From 132023689e6a21da37ef0eca6fe789b3a077b329 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Sat, 6 Sep 2025 19:48:00 -0600 Subject: [PATCH] jdk: implement EdDSA/XDH; RAW key wrap/unwrap helper (PKCS#8 only); wire provider; use base unwrapSubjectPublicKeyInfo(Set) + ObjectIdentifier OIDs --- .../jvmMain/kotlin/JdkCryptographyProvider.kt | 2 + .../src/jvmMain/kotlin/algorithms/JdkEdDSA.kt | 109 ++++++++++++++++ .../src/jvmMain/kotlin/algorithms/JdkXDH.kt | 117 ++++++++++++++++++ .../kotlin/algorithms/KeyInfoUnwrap.kt | 19 +++ 4 files changed, 247 insertions(+) create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt create mode 100644 cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt index 5d978df3..8adc7eec 100644 --- a/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/JdkCryptographyProvider.kt @@ -131,7 +131,9 @@ internal class JdkCryptographyProvider(provider: Provider?) : CryptographyProvid RSA.PKCS1 -> JdkRsaPkcs1(state) RSA.RAW -> JdkRsaRaw(state) ECDSA -> JdkEcdsa(state) + EdDSA -> JdkEdDSA(state) ECDH -> JdkEcdh(state) + XDH -> JdkXDH(state) PBKDF2 -> JdkPbkdf2(state) HKDF -> JdkHkdf(state, this) else -> null diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt new file mode 100644 index 00000000..49c81466 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkEdDSA.kt @@ -0,0 +1,109 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.jdk.* +import dev.whyoleg.cryptography.providers.jdk.materials.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.jdk.operations.* +import dev.whyoleg.cryptography.serialization.pem.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* + +internal class JdkEdDSA(private val state: JdkCryptographyState) : EdDSA { + private fun curveName(curve: EdDSA.Curve): String = when (curve) { + EdDSA.Curve.Ed25519 -> "Ed25519" + EdDSA.Curve.Ed448 -> "Ed448" + } + private fun oid(curve: EdDSA.Curve): ObjectIdentifier = when (curve) { + EdDSA.Curve.Ed25519 -> ObjectIdentifier.Ed25519 + EdDSA.Curve.Ed448 -> ObjectIdentifier.Ed448 + } + + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : JdkPublicKeyDecoder(state, curveName(curve)) { + override fun JPublicKey.convert(): EdDSA.PublicKey = EdDsaPublicKey(state, this) + + override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { + EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") + EdDSA.PublicKey.Format.RAW -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) + EdDSA.PublicKey.Format.DER -> decodeFromDer(bytes) + EdDSA.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) + } + } + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder = + object : JdkPrivateKeyDecoder(state, curveName(curve)) { + override fun JPrivateKey.convert(): EdDSA.PrivateKey = EdDsaPrivateKey(state, this) + + override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { + EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") + EdDSA.PrivateKey.Format.RAW -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) + EdDSA.PrivateKey.Format.DER -> decodeFromDer(bytes) + EdDSA.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) + } + } + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator = object : JdkKeyPairGenerator(state, curveName(curve)) { + override fun JKeyPairGenerator.init() { + // no additional init required + } + + override fun JKeyPair.convert(): EdDSA.KeyPair = EdDsaKeyPair( + EdDsaPublicKey(state, public), + EdDsaPrivateKey(state, private), + ) + } + + private class EdDsaKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdDsaPublicKey( + private val state: JdkCryptographyState, + private val key: JPublicKey, + ) : EdDSA.PublicKey, JdkEncodableKey(key) { + override fun signatureVerifier(): SignatureVerifier { + return JdkSignatureVerifier(state, key, "EdDSA", null) + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.JWK -> error("JWK is not supported") + EdDSA.PublicKey.Format.RAW -> { + val der = encodeToDer() + unwrapSubjectPublicKeyInfo(setOf(ObjectIdentifier.Ed25519, ObjectIdentifier.Ed448), der) + } + EdDSA.PublicKey.Format.DER -> encodeToDer() + EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) + } + } + + private class EdDsaPrivateKey( + private val state: JdkCryptographyState, + private val key: JPrivateKey, + ) : EdDSA.PrivateKey, JdkEncodableKey(key) { + override fun signatureGenerator(): SignatureGenerator { + return JdkSignatureGenerator(state, key, "EdDSA", null) + } + + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.JWK -> error("JWK is not supported") + EdDSA.PrivateKey.Format.RAW -> { + val der = encodeToDer() + KeyInfoUnwrap.unwrapPkcs8ForOids(der, listOf(ObjectIdentifier.Ed25519, ObjectIdentifier.Ed448)) + } + EdDSA.PrivateKey.Format.DER -> encodeToDer() + EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) + } + } +} diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt new file mode 100644 index 00000000..a05c7d37 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/JdkXDH.kt @@ -0,0 +1,117 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.jdk.* +import dev.whyoleg.cryptography.providers.jdk.materials.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.jdk.operations.* +import dev.whyoleg.cryptography.serialization.pem.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* + +internal class JdkXDH(private val state: JdkCryptographyState) : XDH { + private fun curveName(curve: XDH.Curve): String = when (curve) { + XDH.Curve.X25519 -> "X25519" + XDH.Curve.X448 -> "X448" + } + private fun oid(curve: XDH.Curve): ObjectIdentifier = when (curve) { + XDH.Curve.X25519 -> ObjectIdentifier.X25519 + XDH.Curve.X448 -> ObjectIdentifier.X448 + } + + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : JdkPublicKeyDecoder(state, curveName(curve)) { + override fun JPublicKey.convert(): XDH.PublicKey = XdhPublicKey(state, this) + + override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { + XDH.PublicKey.Format.JWK -> error("JWK is not supported") + XDH.PublicKey.Format.RAW -> decodeFromDer( + wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes) + ) + XDH.PublicKey.Format.DER -> decodeFromDer(bytes) + XDH.PublicKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PublicKey, bytes)) + } + } + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder = + object : JdkPrivateKeyDecoder(state, curveName(curve)) { + override fun JPrivateKey.convert(): XDH.PrivateKey = XdhPrivateKey(state, this) + + override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { + XDH.PrivateKey.Format.JWK -> error("JWK is not supported") + XDH.PrivateKey.Format.RAW -> decodeFromDer( + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(oid(curve)), + bytes + ) + ) + XDH.PrivateKey.Format.DER -> decodeFromDer(bytes) + XDH.PrivateKey.Format.PEM -> decodeFromDer(unwrapPem(PemLabel.PrivateKey, bytes)) + } + } + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator = object : JdkKeyPairGenerator(state, curveName(curve)) { + override fun JKeyPairGenerator.init() { + // no additional init required + } + + override fun JKeyPair.convert(): XDH.KeyPair = XdhKeyPair( + XdhPublicKey(state, public), + XdhPrivateKey(state, private), + ) + } + + private class XdhKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XdhPublicKey( + private val state: JdkCryptographyState, + val key: JPublicKey, + ) : XDH.PublicKey, JdkEncodableKey(key), SharedSecretGenerator { + private val keyAgreement = state.keyAgreement("XDH") + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { + check(other is XdhPrivateKey) { "Only key produced by JDK provider is supported" } + return keyAgreement.doAgreement(state, other.key, key) + } + + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.JWK -> error("JWK is not supported") + XDH.PublicKey.Format.RAW -> { + val der = encodeToDer() + unwrapSubjectPublicKeyInfo(setOf(ObjectIdentifier.X25519, ObjectIdentifier.X448), der) + } + XDH.PublicKey.Format.DER -> encodeToDer() + XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, encodeToDer()) + } + } + + private class XdhPrivateKey( + private val state: JdkCryptographyState, + val key: JPrivateKey, + ) : XDH.PrivateKey, JdkEncodableKey(key), SharedSecretGenerator { + private val keyAgreement = state.keyAgreement("XDH") + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + check(other is XdhPublicKey) { "Only key produced by JDK provider is supported" } + return keyAgreement.doAgreement(state, key, other.key) + } + + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.JWK -> error("JWK is not supported") + XDH.PrivateKey.Format.RAW -> { + val der = encodeToDer() + KeyInfoUnwrap.unwrapPkcs8ForOids(der, listOf(ObjectIdentifier.X25519, ObjectIdentifier.X448)) + } + XDH.PrivateKey.Format.DER -> encodeToDer() + XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, encodeToDer()) + } + } +} + + diff --git a/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt new file mode 100644 index 00000000..14202ff0 --- /dev/null +++ b/cryptography-providers/jdk/src/jvmMain/kotlin/algorithms/KeyInfoUnwrap.kt @@ -0,0 +1,19 @@ +package dev.whyoleg.cryptography.providers.jdk.algorithms + +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.providers.base.materials.* + +internal object KeyInfoUnwrap { + fun unwrapPkcs8ForOids(der: ByteArray, oids: List): ByteArray { + var lastError: Throwable? = null + for (oid in oids) { + try { + return unwrapPrivateKeyInfo(oid, der) + } catch (t: Throwable) { + lastError = t + } + } + throw lastError ?: IllegalArgumentException("No OID matched for PKCS#8 unwrap") + } +}