Skip to content

Commit f21bed3

Browse files
committed
[JDK] Implement API to get public key from private key for RSA and EC
1 parent e49e234 commit f21bed3

File tree

11 files changed

+350
-422
lines changed

11 files changed

+350
-422
lines changed

cryptography-providers/jdk/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ kotlin {
2727
jvmMain.dependencies {
2828
api(projects.cryptographyCore)
2929
implementation(projects.cryptographyProviderBase)
30+
31+
// should be used carefully, for specific cases
32+
compileOnly(libs.bouncycastle)
3033
}
3134
}
3235
}

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEc.kt

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ import java.math.*
1616
import java.security.interfaces.*
1717
import java.security.spec.*
1818

19-
internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP : EC.KeyPair<PublicK, PrivateK>>(
19+
internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey<PublicK>, KP : EC.KeyPair<PublicK, PrivateK>>(
2020
protected val state: JdkCryptographyState,
2121
) : EC<PublicK, PrivateK, KP> {
22+
protected abstract val wrapPublicKey: (JPublicKey) -> PublicK
23+
protected abstract val wrapPrivateKey: (JPrivateKey, PublicK?) -> PrivateK
24+
protected abstract val wrapKeyPair: (PublicK, PrivateK) -> KP
25+
2226
private fun algorithmParameters(spec: AlgorithmParameterSpec): JAlgorithmParameters {
2327
return state.algorithmParameters("EC").also { it.init(spec) }
2428
}
2529

26-
protected abstract fun JPublicKey.convert(): PublicK
27-
protected abstract fun JPrivateKey.convert(): PrivateK
28-
protected abstract fun JKeyPair.convert(): KP
29-
3030
final override fun publicKeyDecoder(curve: EC.Curve): KeyDecoder<EC.PublicKey.Format, PublicK> {
3131
return EcPublicKeyDecoder(algorithmParameters(ECGenParameterSpec(curve.jdkName)).curveName())
3232
}
@@ -54,19 +54,30 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
5454
initialize(keyGenParameters, state.secureRandom)
5555
}
5656

57-
override fun JKeyPair.convert(): KP = with(this@JdkEc) { convert() }
57+
override fun JKeyPair.convert(): KP {
58+
val publicKey = wrapPublicKey(public)
59+
val privateKey = wrapPrivateKey(private, publicKey)
60+
return wrapKeyPair(publicKey, privateKey)
61+
}
5862
}
5963

6064
private inner class EcPublicKeyDecoder(
6165
private val curveName: String,
6266
) : JdkPublicKeyDecoder<EC.PublicKey.Format, PublicK>(state, "EC") {
67+
68+
fun fromPrivateKey(privateKey: ECPrivateKey): PublicK = decode(
69+
BouncyCastleBridge.derivePublicKey(privateKey, curveName) ?: error(
70+
"Getting public key from private key for EC is not supported in JDK without BouncyCastle APIs"
71+
)
72+
)
73+
6374
override fun JPublicKey.convert(): PublicK {
6475
check(this is ECPublicKey)
6576

6677
val keyCurve = algorithmParameters(params).curveName()
6778
check(curveName == keyCurve) { "Key curve $keyCurve is not equal to expected curve $curveName" }
6879

69-
return with(this@JdkEc) { convert() }
80+
return wrapPublicKey(this)
7081
}
7182

7283
override fun decodeFromByteArrayBlocking(format: EC.PublicKey.Format, bytes: ByteArray): PublicK = when (format) {
@@ -98,7 +109,7 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
98109
val keyCurve = algorithmParameters(params).curveName()
99110
check(curveName == keyCurve) { "Key curve $keyCurve is not equal to expected curve $curveName" }
100111

101-
return with(this@JdkEc) { convert() }
112+
return wrapPrivateKey(this, null)
102113
}
103114

104115
override fun decodeFromByteArrayBlocking(format: EC.PrivateKey.Format, bytes: ByteArray): PrivateK = when (format) {
@@ -116,7 +127,7 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
116127
}
117128

118129
protected abstract class BaseEcPublicKey(
119-
private val key: JPublicKey,
130+
val key: JPublicKey,
120131
) : EC.PublicKey, JdkEncodableKey<EC.PublicKey.Format>(key) {
121132
final override fun encodeToByteArrayBlocking(format: EC.PublicKey.Format): ByteArray = when (format) {
122133
EC.PublicKey.Format.JWK -> error("$format is not supported")
@@ -150,9 +161,19 @@ internal sealed class JdkEc<PublicK : EC.PublicKey, PrivateK : EC.PrivateKey, KP
150161

151162
}
152163

153-
protected abstract class BaseEcPrivateKey(
154-
private val key: JPrivateKey,
155-
) : EC.PrivateKey, JdkEncodableKey<EC.PrivateKey.Format>(key) {
164+
protected abstract inner class BaseEcPrivateKey(
165+
val key: JPrivateKey,
166+
private var publicKey: PublicK?,
167+
) : EC.PrivateKey<PublicK>, JdkEncodableKey<EC.PrivateKey.Format>(key) {
168+
override fun getPublicKeyBlocking(): PublicK {
169+
if (publicKey == null) {
170+
key as ECPrivateKey
171+
val curveName = algorithmParameters(key.params).curveName()
172+
publicKey = EcPublicKeyDecoder(curveName).fromPrivateKey(key)
173+
}
174+
return publicKey!!
175+
}
176+
156177
final override fun encodeToByteArrayBlocking(format: EC.PrivateKey.Format): ByteArray = when (format) {
157178
EC.PrivateKey.Format.JWK -> error("$format is not supported")
158179
EC.PrivateKey.Format.DER -> encodeToDer()

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEcdh.kt

Lines changed: 10 additions & 11 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.jdk.algorithms
@@ -10,18 +10,17 @@ import dev.whyoleg.cryptography.providers.jdk.*
1010
import dev.whyoleg.cryptography.providers.jdk.operations.*
1111

1212
internal class JdkEcdh(state: JdkCryptographyState) : JdkEc<ECDH.PublicKey, ECDH.PrivateKey, ECDH.KeyPair>(state), ECDH {
13-
override fun JPublicKey.convert(): ECDH.PublicKey = EcdhPublicKey(state, this)
14-
override fun JPrivateKey.convert(): ECDH.PrivateKey = EcdhPrivateKey(state, this)
15-
override fun JKeyPair.convert(): ECDH.KeyPair = EcdhKeyPair(public.convert(), private.convert())
13+
override val wrapPublicKey: (JPublicKey) -> ECDH.PublicKey = ::EcdhPublicKey
14+
override val wrapPrivateKey: (JPrivateKey, ECDH.PublicKey?) -> ECDH.PrivateKey = ::EcdhPrivateKey
15+
override val wrapKeyPair: (ECDH.PublicKey, ECDH.PrivateKey) -> ECDH.KeyPair = ::EcdhKeyPair
1616

1717
private class EcdhKeyPair(
1818
override val publicKey: ECDH.PublicKey,
1919
override val privateKey: ECDH.PrivateKey,
2020
) : ECDH.KeyPair
2121

22-
private class EcdhPublicKey(
23-
private val state: JdkCryptographyState,
24-
val key: JPublicKey,
22+
private inner class EcdhPublicKey(
23+
key: JPublicKey,
2524
) : ECDH.PublicKey, BaseEcPublicKey(key), SharedSecretGenerator<ECDH.PrivateKey> {
2625
private val keyAgreement = state.keyAgreement("ECDH")
2726
override fun sharedSecretGenerator(): SharedSecretGenerator<ECDH.PrivateKey> = this
@@ -32,10 +31,10 @@ internal class JdkEcdh(state: JdkCryptographyState) : JdkEc<ECDH.PublicKey, ECDH
3231
}
3332
}
3433

35-
private class EcdhPrivateKey(
36-
private val state: JdkCryptographyState,
37-
val key: JPrivateKey,
38-
) : ECDH.PrivateKey, BaseEcPrivateKey(key), SharedSecretGenerator<ECDH.PublicKey> {
34+
private inner class EcdhPrivateKey(
35+
key: JPrivateKey,
36+
publicKey: ECDH.PublicKey?,
37+
) : ECDH.PrivateKey, BaseEcPrivateKey(key, publicKey), SharedSecretGenerator<ECDH.PublicKey> {
3938
private val keyAgreement = state.keyAgreement("ECDH")
4039
override fun sharedSecretGenerator(): SharedSecretGenerator<ECDH.PublicKey> = this
4140
override fun generateSharedSecretToByteArrayBlocking(other: ECDH.PublicKey): ByteArray {

cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEcdsa.kt

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

55
package dev.whyoleg.cryptography.providers.jdk.algorithms
@@ -17,18 +17,17 @@ import dev.whyoleg.cryptography.serialization.asn1.modules.*
1717
import java.security.interfaces.*
1818

1919
internal class JdkEcdsa(state: JdkCryptographyState) : JdkEc<ECDSA.PublicKey, ECDSA.PrivateKey, ECDSA.KeyPair>(state), ECDSA {
20-
override fun JPublicKey.convert(): ECDSA.PublicKey = EcdsaPublicKey(state, this)
21-
override fun JPrivateKey.convert(): ECDSA.PrivateKey = EcdsaPrivateKey(state, this)
22-
override fun JKeyPair.convert(): ECDSA.KeyPair = EcdsaKeyPair(public.convert(), private.convert())
20+
override val wrapPublicKey: (JPublicKey) -> ECDSA.PublicKey = ::EcdsaPublicKey
21+
override val wrapPrivateKey: (JPrivateKey, ECDSA.PublicKey?) -> ECDSA.PrivateKey = ::EcdsaPrivateKey
22+
override val wrapKeyPair: (ECDSA.PublicKey, ECDSA.PrivateKey) -> ECDSA.KeyPair = ::EcdsaKeyPair
2323

2424
private class EcdsaKeyPair(
2525
override val publicKey: ECDSA.PublicKey,
2626
override val privateKey: ECDSA.PrivateKey,
2727
) : ECDSA.KeyPair
2828

29-
private class EcdsaPublicKey(
30-
private val state: JdkCryptographyState,
31-
private val key: JPublicKey,
29+
private inner class EcdsaPublicKey(
30+
key: JPublicKey,
3231
) : ECDSA.PublicKey, BaseEcPublicKey(key) {
3332
override fun signatureVerifier(digest: CryptographyAlgorithmId<Digest>, format: ECDSA.SignatureFormat): SignatureVerifier {
3433
val verifier = JdkSignatureVerifier(state, key, digest.hashAlgorithmName() + "withECDSA", null)
@@ -39,10 +38,10 @@ internal class JdkEcdsa(state: JdkCryptographyState) : JdkEc<ECDSA.PublicKey, EC
3938
}
4039
}
4140

42-
private class EcdsaPrivateKey(
43-
private val state: JdkCryptographyState,
44-
private val key: JPrivateKey,
45-
) : ECDSA.PrivateKey, BaseEcPrivateKey(key) {
41+
private inner class EcdsaPrivateKey(
42+
key: JPrivateKey,
43+
publicKey: ECDSA.PublicKey?,
44+
) : ECDSA.PrivateKey, BaseEcPrivateKey(key, publicKey) {
4645
override fun signatureGenerator(digest: CryptographyAlgorithmId<Digest>, format: ECDSA.SignatureFormat): SignatureGenerator {
4746
val generator = JdkSignatureGenerator(state, key, digest.hashAlgorithmName() + "withECDSA", null)
4847
return when (format) {

0 commit comments

Comments
 (0)