Skip to content

Commit c2aa3e7

Browse files
committed
Extract keys serialization into provider-base and support EC SEC1 private key format in CryptoKit
1 parent b2420af commit c2aa3e7

File tree

17 files changed

+222
-235
lines changed

17 files changed

+222
-235
lines changed

cryptography-providers-tests-api/src/commonMain/kotlin/support.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ fun AlgorithmTestScope<*>.supportsDigest(digest: CryptographyAlgorithmId<Digest>
3838

3939
fun AlgorithmTestScope<*>.supportsKeyFormat(format: KeyFormat): Boolean = supports {
4040
when {
41-
provider.isCryptoKit && (format == EC.PrivateKey.Format.DER.SEC1 || format == EC.PrivateKey.Format.PEM.SEC1) -> "TODO"
4241
// only WebCrypto supports JWK for now
4342
format.name == "JWK" &&
4443
!provider.isWebCrypto -> "JWK key format"

cryptography-providers/apple/src/commonMain/kotlin/algorithms/SecEcdsa.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2024-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.whyoleg.cryptography.providers.apple.algorithms
@@ -11,6 +11,7 @@ import dev.whyoleg.cryptography.materials.key.*
1111
import dev.whyoleg.cryptography.operations.*
1212
import dev.whyoleg.cryptography.providers.apple.internal.*
1313
import dev.whyoleg.cryptography.providers.base.*
14+
import dev.whyoleg.cryptography.providers.base.materials.*
1415
import dev.whyoleg.cryptography.serialization.asn1.*
1516
import dev.whyoleg.cryptography.serialization.asn1.modules.*
1617
import dev.whyoleg.cryptography.serialization.pem.*

cryptography-providers/apple/src/commonMain/kotlin/algorithms/SecRsa.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright (c) 2024-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package dev.whyoleg.cryptography.providers.apple.algorithms
@@ -9,6 +9,7 @@ import dev.whyoleg.cryptography.algorithms.*
99
import dev.whyoleg.cryptography.bigint.*
1010
import dev.whyoleg.cryptography.materials.key.*
1111
import dev.whyoleg.cryptography.providers.apple.internal.*
12+
import dev.whyoleg.cryptography.providers.base.materials.*
1213
import dev.whyoleg.cryptography.serialization.asn1.*
1314
import dev.whyoleg.cryptography.serialization.asn1.modules.*
1415
import dev.whyoleg.cryptography.serialization.pem.*
@@ -36,8 +37,8 @@ internal abstract class SecRsa<PublicK : RSA.PublicKey, PrivateK : RSA.PrivateKe
3637
RSA.PublicKey.Format.JWK -> error("$format is not supported")
3738
RSA.PublicKey.Format.DER.PKCS1 -> bytes
3839
RSA.PublicKey.Format.PEM.PKCS1 -> unwrapPem(PemLabel.RsaPublicKey, bytes)
39-
RSA.PublicKey.Format.DER -> unwrapPublicKey(ObjectIdentifier.RSA, bytes)
40-
RSA.PublicKey.Format.PEM -> unwrapPublicKey(ObjectIdentifier.RSA, unwrapPem(PemLabel.PublicKey, bytes))
40+
RSA.PublicKey.Format.DER -> unwrapSubjectPublicKeyInfo(ObjectIdentifier.RSA, bytes)
41+
RSA.PublicKey.Format.PEM -> unwrapSubjectPublicKeyInfo(ObjectIdentifier.RSA, unwrapPem(PemLabel.PublicKey, bytes))
4142
}
4243

4344
val secKey = CFMutableDictionary(2) {
@@ -61,8 +62,8 @@ internal abstract class SecRsa<PublicK : RSA.PublicKey, PrivateK : RSA.PrivateKe
6162
RSA.PrivateKey.Format.JWK -> error("$format is not supported")
6263
RSA.PrivateKey.Format.DER.PKCS1 -> bytes
6364
RSA.PrivateKey.Format.PEM.PKCS1 -> unwrapPem(PemLabel.RsaPrivateKey, bytes)
64-
RSA.PrivateKey.Format.DER -> unwrapPrivateKey(ObjectIdentifier.RSA, bytes)
65-
RSA.PrivateKey.Format.PEM -> unwrapPrivateKey(ObjectIdentifier.RSA, unwrapPem(PemLabel.PrivateKey, bytes))
65+
RSA.PrivateKey.Format.DER -> unwrapPrivateKeyInfo(ObjectIdentifier.RSA, bytes)
66+
RSA.PrivateKey.Format.PEM -> unwrapPrivateKeyInfo(ObjectIdentifier.RSA, unwrapPem(PemLabel.PrivateKey, bytes))
6667
}
6768

6869
val secKey = CFMutableDictionary(2) {
@@ -117,8 +118,8 @@ internal abstract class SecRsa<PublicK : RSA.PublicKey, PrivateK : RSA.PrivateKe
117118
RSA.PublicKey.Format.JWK -> error("$format is not supported")
118119
RSA.PublicKey.Format.DER.PKCS1 -> pkcs1Key
119120
RSA.PublicKey.Format.PEM.PKCS1 -> wrapPem(PemLabel.RsaPublicKey, pkcs1Key)
120-
RSA.PublicKey.Format.DER -> wrapPublicKey(RsaKeyAlgorithmIdentifier, pkcs1Key)
121-
RSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, wrapPublicKey(RsaKeyAlgorithmIdentifier, pkcs1Key))
121+
RSA.PublicKey.Format.DER -> wrapSubjectPublicKeyInfo(RsaKeyAlgorithmIdentifier, pkcs1Key)
122+
RSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, wrapSubjectPublicKeyInfo(RsaKeyAlgorithmIdentifier, pkcs1Key))
122123
}
123124
}
124125
}
@@ -136,8 +137,8 @@ internal abstract class SecRsa<PublicK : RSA.PublicKey, PrivateK : RSA.PrivateKe
136137
RSA.PrivateKey.Format.JWK -> error("$format is not supported")
137138
RSA.PrivateKey.Format.DER.PKCS1 -> pkcs1Key
138139
RSA.PrivateKey.Format.PEM.PKCS1 -> wrapPem(PemLabel.RsaPrivateKey, pkcs1Key)
139-
RSA.PrivateKey.Format.DER -> wrapPrivateKey(0, RsaKeyAlgorithmIdentifier, pkcs1Key)
140-
RSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, wrapPrivateKey(0, RsaKeyAlgorithmIdentifier, pkcs1Key))
140+
RSA.PrivateKey.Format.DER -> wrapPrivateKeyInfo(0, RsaKeyAlgorithmIdentifier, pkcs1Key)
141+
RSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, wrapPrivateKeyInfo(0, RsaKeyAlgorithmIdentifier, pkcs1Key))
141142
}
142143
}
143144
}

cryptography-providers/apple/src/commonMain/kotlin/internal/keys.kt

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,11 @@
55
package dev.whyoleg.cryptography.providers.apple.internal
66

77
import dev.whyoleg.cryptography.providers.base.*
8-
import dev.whyoleg.cryptography.serialization.asn1.*
9-
import dev.whyoleg.cryptography.serialization.asn1.modules.*
10-
import dev.whyoleg.cryptography.serialization.pem.*
118
import kotlinx.cinterop.*
129
import platform.CoreFoundation.*
1310
import platform.Foundation.*
1411
import platform.Security.*
1512

16-
internal fun unwrapPem(label: PemLabel, key: ByteArray): ByteArray =
17-
Pem.decode(key).ensurePemLabel(label).bytes
18-
19-
internal fun wrapPem(label: PemLabel, key: ByteArray): ByteArray = Pem.encodeToByteArray(PemContent(label, key))
20-
21-
internal fun unwrapPublicKey(algorithm: ObjectIdentifier, key: ByteArray): ByteArray =
22-
Der.decodeFromByteArray(SubjectPublicKeyInfo.serializer(), key).also {
23-
check(it.algorithm.algorithm == algorithm) { "Expected algorithm '${algorithm.value}', received: '${it.algorithm.algorithm}'" }
24-
}.subjectPublicKey.byteArray
25-
26-
internal fun wrapPublicKey(identifier: KeyAlgorithmIdentifier, key: ByteArray): ByteArray = Der.encodeToByteArray(
27-
SubjectPublicKeyInfo.serializer(),
28-
SubjectPublicKeyInfo(identifier, BitArray(0, key))
29-
)
30-
31-
internal fun unwrapPrivateKey(algorithm: ObjectIdentifier, key: ByteArray): ByteArray =
32-
Der.decodeFromByteArray(PrivateKeyInfo.serializer(), key).also {
33-
check(it.privateKeyAlgorithm.algorithm == algorithm)
34-
}.privateKey
35-
36-
internal fun wrapPrivateKey(version: Int, identifier: KeyAlgorithmIdentifier, key: ByteArray): ByteArray = Der.encodeToByteArray(
37-
PrivateKeyInfo.serializer(),
38-
PrivateKeyInfo(version, identifier, key)
39-
)
40-
4113
internal fun decodeSecKey(input: ByteArray, attributes: CFMutableDictionaryRef?): SecKeyRef = memScoped {
4214
val error = alloc<CFErrorRefVar>()
4315
input.useNSData {

cryptography-providers/base/api/cryptography-provider-base.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ public abstract class dev/whyoleg/cryptography/providers/base/algorithms/BaseHkd
5858
public fun secretDerivation-nkIq3jI (Ldev/whyoleg/cryptography/CryptographyAlgorithmId;I[B[B)Ldev/whyoleg/cryptography/operations/SecretDerivation;
5959
}
6060

61+
public final class dev/whyoleg/cryptography/providers/base/algorithms/EcKt {
62+
public static final fun convertEcPrivateKeyFromPkcs8ToSec1 ([B)[B
63+
public static final fun convertEcPrivateKeyFromSec1ToPkcs8 ([B)[B
64+
}
65+
66+
public final class dev/whyoleg/cryptography/providers/base/materials/KeysKt {
67+
public static final fun unwrapPem-unSj4pc (Ljava/lang/String;[B)[B
68+
public static final fun unwrapPrivateKeyInfo-4RESAxk (Ljava/lang/String;[B)[B
69+
public static final fun unwrapSubjectPublicKeyInfo-4RESAxk (Ljava/lang/String;[B)[B
70+
public static final fun wrapPem-unSj4pc (Ljava/lang/String;[B)[B
71+
public static final fun wrapPrivateKeyInfo (ILdev/whyoleg/cryptography/serialization/asn1/modules/KeyAlgorithmIdentifier;[B)[B
72+
public static final fun wrapSubjectPublicKeyInfo (Ldev/whyoleg/cryptography/serialization/asn1/modules/KeyAlgorithmIdentifier;[B)[B
73+
}
74+
6175
public final class dev/whyoleg/cryptography/providers/base/operations/AccumulatingCipherFunction : dev/whyoleg/cryptography/providers/base/operations/BaseCipherFunction {
6276
public fun <init> (Lkotlin/jvm/functions/Function1;)V
6377
public fun close ()V

cryptography-providers/base/api/cryptography-provider-base.klib.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ final val dev.whyoleg.cryptography.providers.base/EmptyByteArray // dev.whyoleg.
138138
final fun <get-EmptyByteArray>(): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base/EmptyByteArray.<get-EmptyByteArray>|<get-EmptyByteArray>(){}[0]
139139

140140
final fun (kotlin/ByteArray).dev.whyoleg.cryptography.providers.base/ensureSizeExactly(kotlin/Int): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base/ensureSizeExactly|[email protected](kotlin.Int){}[0]
141+
final fun dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromPkcs8ToSec1(kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromPkcs8ToSec1|convertEcPrivateKeyFromPkcs8ToSec1(kotlin.ByteArray){}[0]
142+
final fun dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromSec1ToPkcs8(kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.algorithms/convertEcPrivateKeyFromSec1ToPkcs8|convertEcPrivateKeyFromSec1ToPkcs8(kotlin.ByteArray){}[0]
143+
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapPem(dev.whyoleg.cryptography.serialization.pem/PemLabel, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapPem|unwrapPem(dev.whyoleg.cryptography.serialization.pem.PemLabel;kotlin.ByteArray){}[0]
144+
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapPrivateKeyInfo(dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapPrivateKeyInfo|unwrapPrivateKeyInfo(dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier;kotlin.ByteArray){}[0]
145+
final fun dev.whyoleg.cryptography.providers.base.materials/unwrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1/ObjectIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/unwrapSubjectPublicKeyInfo|unwrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier;kotlin.ByteArray){}[0]
146+
final fun dev.whyoleg.cryptography.providers.base.materials/wrapPem(dev.whyoleg.cryptography.serialization.pem/PemLabel, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/wrapPem|wrapPem(dev.whyoleg.cryptography.serialization.pem.PemLabel;kotlin.ByteArray){}[0]
147+
final fun dev.whyoleg.cryptography.providers.base.materials/wrapPrivateKeyInfo(kotlin/Int, dev.whyoleg.cryptography.serialization.asn1.modules/KeyAlgorithmIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/wrapPrivateKeyInfo|wrapPrivateKeyInfo(kotlin.Int;dev.whyoleg.cryptography.serialization.asn1.modules.KeyAlgorithmIdentifier;kotlin.ByteArray){}[0]
148+
final fun dev.whyoleg.cryptography.providers.base.materials/wrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1.modules/KeyAlgorithmIdentifier, kotlin/ByteArray): kotlin/ByteArray // dev.whyoleg.cryptography.providers.base.materials/wrapSubjectPublicKeyInfo|wrapSubjectPublicKeyInfo(dev.whyoleg.cryptography.serialization.asn1.modules.KeyAlgorithmIdentifier;kotlin.ByteArray){}[0]
141149
final fun dev.whyoleg.cryptography.providers.base/checkBounds(kotlin/Int, kotlin/Int, kotlin/Int) // dev.whyoleg.cryptography.providers.base/checkBounds|checkBounds(kotlin.Int;kotlin.Int;kotlin.Int){}[0]
142150

143151
// Targets: [native]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.base.algorithms
6+
7+
import dev.whyoleg.cryptography.*
8+
import dev.whyoleg.cryptography.serialization.asn1.*
9+
import dev.whyoleg.cryptography.serialization.asn1.modules.*
10+
11+
@CryptographyProviderApi
12+
public fun convertEcPrivateKeyFromPkcs8ToSec1(input: ByteArray): ByteArray {
13+
val privateKeyInfo = Der.decodeFromByteArray(PrivateKeyInfo.serializer(), input)
14+
15+
val privateKeyAlgorithm = privateKeyInfo.privateKeyAlgorithm
16+
check(privateKeyAlgorithm is EcKeyAlgorithmIdentifier) {
17+
"Expected algorithm '${ObjectIdentifier.EC}', received: '${privateKeyAlgorithm.algorithm}'"
18+
}
19+
// the produced key could not contain parameters in underlying EcPrivateKey,
20+
// but they are available in `privateKeyAlgorithm`
21+
val ecPrivateKey = Der.decodeFromByteArray(EcPrivateKey.serializer(), privateKeyInfo.privateKey)
22+
if (ecPrivateKey.parameters != null) return privateKeyInfo.privateKey
23+
24+
val enhancedEcPrivateKey = EcPrivateKey(
25+
version = ecPrivateKey.version,
26+
privateKey = ecPrivateKey.privateKey,
27+
parameters = privateKeyAlgorithm.parameters,
28+
publicKey = ecPrivateKey.publicKey
29+
)
30+
return Der.encodeToByteArray(EcPrivateKey.serializer(), enhancedEcPrivateKey)
31+
}
32+
33+
@CryptographyProviderApi
34+
public fun convertEcPrivateKeyFromSec1ToPkcs8(input: ByteArray): ByteArray {
35+
val ecPrivateKey = Der.decodeFromByteArray(EcPrivateKey.serializer(), input)
36+
37+
checkNotNull(ecPrivateKey.parameters) { "EC Parameters are not present in the key" }
38+
39+
val privateKeyInfo = PrivateKeyInfo(
40+
version = 0,
41+
privateKeyAlgorithm = EcKeyAlgorithmIdentifier(ecPrivateKey.parameters),
42+
privateKey = input
43+
)
44+
return Der.encodeToByteArray(PrivateKeyInfo.serializer(), privateKeyInfo)
45+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.whyoleg.cryptography.providers.base.materials
6+
7+
import dev.whyoleg.cryptography.*
8+
import dev.whyoleg.cryptography.serialization.asn1.*
9+
import dev.whyoleg.cryptography.serialization.asn1.modules.*
10+
import dev.whyoleg.cryptography.serialization.pem.*
11+
12+
@CryptographyProviderApi
13+
public fun unwrapPem(label: PemLabel, key: ByteArray): ByteArray {
14+
return Pem.decode(key).ensurePemLabel(label).bytes
15+
}
16+
17+
@CryptographyProviderApi
18+
public fun wrapPem(label: PemLabel, key: ByteArray): ByteArray {
19+
return Pem.encodeToByteArray(PemContent(label, key))
20+
}
21+
22+
@CryptographyProviderApi
23+
public fun unwrapSubjectPublicKeyInfo(algorithm: ObjectIdentifier, key: ByteArray): ByteArray {
24+
return Der.decodeFromByteArray(SubjectPublicKeyInfo.serializer(), key).also {
25+
check(it.algorithm.algorithm == algorithm) { "Expected algorithm '${algorithm.value}', received: '${it.algorithm.algorithm}'" }
26+
}.subjectPublicKey.byteArray
27+
}
28+
29+
@CryptographyProviderApi
30+
public fun wrapSubjectPublicKeyInfo(identifier: KeyAlgorithmIdentifier, key: ByteArray): ByteArray {
31+
return Der.encodeToByteArray(
32+
SubjectPublicKeyInfo.serializer(),
33+
SubjectPublicKeyInfo(identifier, BitArray(0, key))
34+
)
35+
}
36+
37+
@CryptographyProviderApi
38+
public fun unwrapPrivateKeyInfo(algorithm: ObjectIdentifier, key: ByteArray): ByteArray {
39+
return Der.decodeFromByteArray(PrivateKeyInfo.serializer(), key).also {
40+
check(it.privateKeyAlgorithm.algorithm == algorithm) { "Expected algorithm '${algorithm.value}', received: '${it.privateKeyAlgorithm.algorithm}'" }
41+
}.privateKey
42+
}
43+
44+
@CryptographyProviderApi
45+
public fun wrapPrivateKeyInfo(version: Int, identifier: KeyAlgorithmIdentifier, key: ByteArray): ByteArray {
46+
return Der.encodeToByteArray(
47+
PrivateKeyInfo.serializer(),
48+
PrivateKeyInfo(version, identifier, key)
49+
)
50+
}

0 commit comments

Comments
 (0)