Skip to content

Commit bf5414c

Browse files
committed
openssl3: implement EdDSA/XDH; key agreement refactor; nullable-hash signatures; wire provider; use AccumulatingSignFunction/VerifyFunction for one-shot
1 parent 1d0f98f commit bf5414c

File tree

6 files changed

+388
-10
lines changed

6 files changed

+388
-10
lines changed

cryptography-providers/openssl3/api/src/commonMain/kotlin/Openssl3CryptographyProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ internal object Openssl3CryptographyProvider : CryptographyProvider() {
3838
AES.GCM -> Openssl3AesGcm
3939
ECDSA -> Openssl3Ecdsa
4040
ECDH -> Openssl3Ecdh
41+
EdDSA -> Openssl3EdDSA
42+
XDH -> Openssl3XDH
4143
RSA.PSS -> Openssl3RsaPss
4244
RSA.PKCS1 -> Openssl3RsaPkcs1
4345
RSA.OAEP -> Openssl3RsaOaep
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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.openssl3.algorithms
6+
7+
import dev.whyoleg.cryptography.algorithms.*
8+
import dev.whyoleg.cryptography.materials.key.*
9+
import dev.whyoleg.cryptography.operations.*
10+
import dev.whyoleg.cryptography.providers.base.*
11+
import dev.whyoleg.cryptography.providers.openssl3.internal.*
12+
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
13+
import dev.whyoleg.cryptography.providers.openssl3.materials.*
14+
import kotlinx.cinterop.*
15+
import platform.posix.*
16+
import kotlin.experimental.*
17+
import dev.whyoleg.cryptography.serialization.asn1.modules.*
18+
import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier
19+
import dev.whyoleg.cryptography.providers.base.materials.*
20+
import dev.whyoleg.cryptography.providers.openssl3.operations.Openssl3DigestSignatureGenerator
21+
import dev.whyoleg.cryptography.providers.openssl3.operations.Openssl3DigestSignatureVerifier
22+
23+
internal object Openssl3EdDSA : EdDSA {
24+
private fun algorithmName(curve: EdDSA.Curve): String = when (curve) {
25+
EdDSA.Curve.Ed25519 -> "ED25519"
26+
EdDSA.Curve.Ed448 -> "ED448"
27+
}
28+
private fun oid(curve: EdDSA.Curve): ObjectIdentifier = when (curve) {
29+
EdDSA.Curve.Ed25519 -> EdwardsOids.Ed25519
30+
EdDSA.Curve.Ed448 -> EdwardsOids.Ed448
31+
}
32+
33+
override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder<EdDSA.PublicKey.Format, EdDSA.PublicKey> =
34+
object : Openssl3PublicKeyDecoder<EdDSA.PublicKey.Format, EdDSA.PublicKey>(algorithmName(curve)) {
35+
override fun inputType(format: EdDSA.PublicKey.Format): String = when (format) {
36+
EdDSA.PublicKey.Format.DER -> "DER"
37+
EdDSA.PublicKey.Format.PEM -> "PEM"
38+
EdDSA.PublicKey.Format.JWK,
39+
EdDSA.PublicKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking")
40+
}
41+
42+
override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) {
43+
EdDSA.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking(
44+
EdDSA.PublicKey.Format.DER,
45+
wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes)
46+
)
47+
else -> super.decodeFromByteArrayBlocking(format, bytes)
48+
}
49+
50+
override fun wrapKey(key: CPointer<EVP_PKEY>): EdDSA.PublicKey = EdDsaPublicKey(key, curve)
51+
}
52+
53+
override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder<EdDSA.PrivateKey.Format, EdDSA.PrivateKey> =
54+
object : Openssl3PrivateKeyDecoder<EdDSA.PrivateKey.Format, EdDSA.PrivateKey>(algorithmName(curve)) {
55+
override fun inputType(format: EdDSA.PrivateKey.Format): String = when (format) {
56+
EdDSA.PrivateKey.Format.DER -> "DER"
57+
EdDSA.PrivateKey.Format.PEM -> "PEM"
58+
EdDSA.PrivateKey.Format.JWK,
59+
EdDSA.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking")
60+
}
61+
62+
override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) {
63+
EdDSA.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking(
64+
EdDSA.PrivateKey.Format.DER,
65+
wrapPrivateKeyInfo(0, UnknownKeyAlgorithmIdentifier(oid(curve)), bytes)
66+
)
67+
else -> super.decodeFromByteArrayBlocking(format, bytes)
68+
}
69+
70+
override fun wrapKey(key: CPointer<EVP_PKEY>): EdDSA.PrivateKey = EdDsaPrivateKey(key, curve)
71+
}
72+
73+
override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator<EdDSA.KeyPair> =
74+
object : Openssl3KeyPairGenerator<EdDSA.KeyPair>(algorithmName(curve)) {
75+
override fun MemScope.createParams(): CValuesRef<OSSL_PARAM>? = null
76+
override fun wrapKeyPair(keyPair: CPointer<EVP_PKEY>): EdDSA.KeyPair = EdDsaKeyPair(
77+
publicKey = EdDsaPublicKey(keyPair, curve),
78+
privateKey = EdDsaPrivateKey(keyPair, curve)
79+
)
80+
}
81+
82+
private class EdDsaKeyPair(
83+
override val publicKey: EdDSA.PublicKey,
84+
override val privateKey: EdDSA.PrivateKey,
85+
) : EdDSA.KeyPair
86+
87+
private class EdDsaPublicKey(
88+
key: CPointer<EVP_PKEY>,
89+
private val curve: EdDSA.Curve,
90+
) : EdDSA.PublicKey, Openssl3PublicKeyEncodable<EdDSA.PublicKey.Format>(key) {
91+
override fun outputType(format: EdDSA.PublicKey.Format): String = when (format) {
92+
EdDSA.PublicKey.Format.DER -> "DER"
93+
EdDSA.PublicKey.Format.PEM -> "PEM"
94+
EdDSA.PublicKey.Format.JWK,
95+
EdDSA.PublicKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking")
96+
}
97+
98+
override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) {
99+
EdDSA.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo(
100+
oid(curve),
101+
super.encodeToByteArrayBlocking(EdDSA.PublicKey.Format.DER)
102+
)
103+
else -> super.encodeToByteArrayBlocking(format)
104+
}
105+
106+
override fun signatureVerifier(): SignatureVerifier = EdDsaSignatureVerifier(key)
107+
}
108+
109+
private class EdDsaPrivateKey(
110+
key: CPointer<EVP_PKEY>,
111+
private val curve: EdDSA.Curve,
112+
) : EdDSA.PrivateKey, Openssl3PrivateKeyEncodable<EdDSA.PrivateKey.Format>(key) {
113+
override fun outputType(format: EdDSA.PrivateKey.Format): String = when (format) {
114+
EdDSA.PrivateKey.Format.DER -> "DER"
115+
EdDSA.PrivateKey.Format.PEM -> "PEM"
116+
EdDSA.PrivateKey.Format.JWK,
117+
EdDSA.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking")
118+
}
119+
120+
override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) {
121+
EdDSA.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo(
122+
oid(curve),
123+
super.encodeToByteArrayBlocking(EdDSA.PrivateKey.Format.DER)
124+
)
125+
else -> super.encodeToByteArrayBlocking(format)
126+
}
127+
128+
override fun signatureGenerator(): SignatureGenerator = EdDsaSignatureGenerator(key)
129+
}
130+
}
131+
132+
133+
private class EdDsaSignatureGenerator(
134+
private val privateKey: CPointer<EVP_PKEY>,
135+
) : Openssl3DigestSignatureGenerator(privateKey, hashAlgorithm = null) {
136+
override fun MemScope.createParams(): CValuesRef<OSSL_PARAM>? = null
137+
}
138+
139+
private class EdDsaSignatureVerifier(
140+
private val publicKey: CPointer<EVP_PKEY>,
141+
) : Openssl3DigestSignatureVerifier(publicKey, hashAlgorithm = null) {
142+
override fun MemScope.createParams(): CValuesRef<OSSL_PARAM>? = null
143+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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.openssl3.algorithms
6+
7+
import dev.whyoleg.cryptography.algorithms.*
8+
import dev.whyoleg.cryptography.materials.key.*
9+
import dev.whyoleg.cryptography.operations.*
10+
import dev.whyoleg.cryptography.providers.base.*
11+
import dev.whyoleg.cryptography.providers.openssl3.internal.*
12+
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
13+
import dev.whyoleg.cryptography.providers.openssl3.materials.*
14+
import kotlinx.cinterop.*
15+
import platform.posix.*
16+
import kotlin.experimental.*
17+
import dev.whyoleg.cryptography.providers.openssl3.operations.*
18+
import dev.whyoleg.cryptography.serialization.asn1.modules.*
19+
import dev.whyoleg.cryptography.serialization.asn1.ObjectIdentifier
20+
import dev.whyoleg.cryptography.providers.base.materials.*
21+
22+
internal object Openssl3XDH : XDH {
23+
private fun algorithmName(curve: XDH.Curve): String = when (curve) {
24+
XDH.Curve.X25519 -> "X25519"
25+
XDH.Curve.X448 -> "X448"
26+
}
27+
private fun oid(curve: XDH.Curve): ObjectIdentifier = when (curve) {
28+
XDH.Curve.X25519 -> MontgomeryOids.X25519
29+
XDH.Curve.X448 -> MontgomeryOids.X448
30+
}
31+
32+
override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder<XDH.PublicKey.Format, XDH.PublicKey> =
33+
object : Openssl3PublicKeyDecoder<XDH.PublicKey.Format, XDH.PublicKey>(algorithmName(curve)) {
34+
override fun inputType(format: XDH.PublicKey.Format): String = when (format) {
35+
XDH.PublicKey.Format.DER -> "DER"
36+
XDH.PublicKey.Format.PEM -> "PEM"
37+
XDH.PublicKey.Format.JWK,
38+
XDH.PublicKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking")
39+
}
40+
41+
override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) {
42+
XDH.PublicKey.Format.RAW -> super.decodeFromByteArrayBlocking(
43+
XDH.PublicKey.Format.DER,
44+
wrapSubjectPublicKeyInfo(UnknownKeyAlgorithmIdentifier(oid(curve)), bytes)
45+
)
46+
else -> super.decodeFromByteArrayBlocking(format, bytes)
47+
}
48+
49+
override fun wrapKey(key: CPointer<EVP_PKEY>): XDH.PublicKey = XdhPublicKey(key, curve)
50+
}
51+
52+
override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder<XDH.PrivateKey.Format, XDH.PrivateKey> =
53+
object : Openssl3PrivateKeyDecoder<XDH.PrivateKey.Format, XDH.PrivateKey>(algorithmName(curve)) {
54+
override fun inputType(format: XDH.PrivateKey.Format): String = when (format) {
55+
XDH.PrivateKey.Format.DER -> "DER"
56+
XDH.PrivateKey.Format.PEM -> "PEM"
57+
XDH.PrivateKey.Format.JWK,
58+
XDH.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in decodeFromBlocking")
59+
}
60+
61+
override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) {
62+
XDH.PrivateKey.Format.RAW -> super.decodeFromByteArrayBlocking(
63+
XDH.PrivateKey.Format.DER,
64+
wrapPrivateKeyInfo(0, UnknownKeyAlgorithmIdentifier(oid(curve)), bytes)
65+
)
66+
else -> super.decodeFromByteArrayBlocking(format, bytes)
67+
}
68+
69+
override fun wrapKey(key: CPointer<EVP_PKEY>): XDH.PrivateKey = XdhPrivateKey(key, curve)
70+
}
71+
72+
override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator<XDH.KeyPair> =
73+
object : Openssl3KeyPairGenerator<XDH.KeyPair>(algorithmName(curve)) {
74+
override fun MemScope.createParams(): CValuesRef<OSSL_PARAM>? = null
75+
override fun wrapKeyPair(keyPair: CPointer<EVP_PKEY>): XDH.KeyPair = XdhKeyPair(
76+
publicKey = XdhPublicKey(keyPair, curve),
77+
privateKey = XdhPrivateKey(keyPair, curve)
78+
)
79+
}
80+
81+
private class XdhKeyPair(
82+
override val publicKey: XDH.PublicKey,
83+
override val privateKey: XDH.PrivateKey,
84+
) : XDH.KeyPair
85+
86+
private class XdhPublicKey(
87+
key: CPointer<EVP_PKEY>,
88+
private val curve: XDH.Curve,
89+
) : XDH.PublicKey, Openssl3PublicKeyEncodable<XDH.PublicKey.Format>(key), SharedSecretGenerator<XDH.PrivateKey> {
90+
override fun outputType(format: XDH.PublicKey.Format): String = when (format) {
91+
XDH.PublicKey.Format.DER -> "DER"
92+
XDH.PublicKey.Format.PEM -> "PEM"
93+
XDH.PublicKey.Format.JWK,
94+
XDH.PublicKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking")
95+
}
96+
97+
override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) {
98+
XDH.PublicKey.Format.RAW -> unwrapSubjectPublicKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PublicKey.Format.DER))
99+
else -> super.encodeToByteArrayBlocking(format)
100+
}
101+
102+
override fun sharedSecretGenerator(): SharedSecretGenerator<XDH.PrivateKey> = this
103+
override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray {
104+
check(other is XdhPrivateKey)
105+
return deriveSharedSecret(publicKey = key, privateKey = other.key)
106+
}
107+
}
108+
109+
private class XdhPrivateKey(
110+
key: CPointer<EVP_PKEY>,
111+
private val curve: XDH.Curve,
112+
) : XDH.PrivateKey, Openssl3PrivateKeyEncodable<XDH.PrivateKey.Format>(key), SharedSecretGenerator<XDH.PublicKey> {
113+
override fun outputType(format: XDH.PrivateKey.Format): String = when (format) {
114+
XDH.PrivateKey.Format.DER -> "DER"
115+
XDH.PrivateKey.Format.PEM -> "PEM"
116+
XDH.PrivateKey.Format.JWK,
117+
XDH.PrivateKey.Format.RAW -> error("should not be called: handled explicitly in encodeToBlocking")
118+
}
119+
120+
override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) {
121+
XDH.PrivateKey.Format.RAW -> unwrapPrivateKeyInfo(oid(curve), super.encodeToByteArrayBlocking(XDH.PrivateKey.Format.DER))
122+
else -> super.encodeToByteArrayBlocking(format)
123+
}
124+
125+
override fun sharedSecretGenerator(): SharedSecretGenerator<XDH.PublicKey> = this
126+
override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray {
127+
check(other is XdhPublicKey)
128+
return deriveSharedSecret(publicKey = other.key, privateKey = key)
129+
}
130+
}
131+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.openssl3.operations
6+
7+
import dev.whyoleg.cryptography.providers.openssl3.internal.*
8+
import dev.whyoleg.cryptography.providers.base.*
9+
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
10+
import kotlinx.cinterop.*
11+
import platform.posix.*
12+
import kotlin.experimental.*
13+
14+
@OptIn(UnsafeNumber::class)
15+
internal fun deriveSharedSecret(
16+
publicKey: CPointer<EVP_PKEY>,
17+
privateKey: CPointer<EVP_PKEY>,
18+
): ByteArray = memScoped {
19+
val context = checkError(EVP_PKEY_CTX_new_from_pkey(null, privateKey, null))
20+
try {
21+
checkError(EVP_PKEY_derive_init(context))
22+
checkError(EVP_PKEY_derive_set_peer(context, publicKey))
23+
val secretSize = alloc<size_tVar>()
24+
checkError(EVP_PKEY_derive(context, null, secretSize.ptr))
25+
val secret = ByteArray(secretSize.value.toInt())
26+
checkError(EVP_PKEY_derive(context, secret.refToU(0), secretSize.ptr))
27+
secret
28+
} finally {
29+
EVP_PKEY_CTX_free(context)
30+
}
31+
}

cryptography-providers/openssl3/api/src/commonMain/kotlin/operations/Openssl3DigestSignatureGenerator.kt

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,57 @@ import dev.whyoleg.cryptography.operations.*
88
import dev.whyoleg.cryptography.providers.base.*
99
import dev.whyoleg.cryptography.providers.openssl3.internal.*
1010
import dev.whyoleg.cryptography.providers.openssl3.internal.cinterop.*
11+
import dev.whyoleg.cryptography.providers.base.operations.*
1112
import kotlinx.cinterop.*
1213
import platform.posix.*
1314
import kotlin.experimental.*
1415

1516
internal abstract class Openssl3DigestSignatureGenerator(
1617
private val privateKey: CPointer<EVP_PKEY>,
17-
private val hashAlgorithm: String,
18+
// when null, performs one-shot signing (e.g., EdDSA)
19+
private val hashAlgorithm: String?,
1820
) : SignatureGenerator {
1921
@OptIn(ExperimentalNativeApi::class)
2022
private val cleaner = privateKey.upRef().cleaner()
2123

2224
protected abstract fun MemScope.createParams(): CValuesRef<OSSL_PARAM>?
2325

24-
override fun createSignFunction(): SignFunction {
25-
return Openssl3DigestSignFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free))
26+
override fun createSignFunction(): SignFunction = when (hashAlgorithm) {
27+
null -> AccumulatingSignFunction { data ->
28+
memScoped {
29+
val ctx = checkError(EVP_MD_CTX_new())
30+
try {
31+
checkError(
32+
EVP_DigestSignInit_ex(
33+
ctx = ctx,
34+
pctx = null,
35+
mdname = null, // one-shot mode
36+
libctx = null,
37+
props = null,
38+
pkey = privateKey,
39+
params = createParams()
40+
)
41+
)
42+
val siglen = alloc<size_tVar>()
43+
data.usePinned { pin ->
44+
checkError(EVP_DigestSign(ctx, null, siglen.ptr, pin.safeAddressOfU(0), data.size.convert()))
45+
val out = ByteArray(siglen.value.convert())
46+
out.usePinned { outPin ->
47+
checkError(EVP_DigestSign(ctx, outPin.safeAddressOfU(0), siglen.ptr, pin.safeAddressOfU(0), data.size.convert()))
48+
}
49+
out.ensureSizeExactly(siglen.value.convert())
50+
out
51+
}
52+
} finally {
53+
EVP_MD_CTX_free(ctx)
54+
}
55+
}
56+
}
57+
else -> StreamingSignFunction(Resource(checkError(EVP_MD_CTX_new()), ::EVP_MD_CTX_free))
2658
}
2759

2860
// inner class to have a reference to class with cleaner
29-
private inner class Openssl3DigestSignFunction(
61+
private inner class StreamingSignFunction(
3062
private val context: Resource<CPointer<EVP_MD_CTX>>,
3163
) : SignFunction, SafeCloseable(SafeCloseAction(context, AutoCloseable::close)) {
3264
init {
@@ -66,7 +98,7 @@ internal abstract class Openssl3DigestSignatureGenerator(
6698
EVP_DigestSignInit_ex(
6799
ctx = context,
68100
pctx = null,
69-
mdname = hashAlgorithm,
101+
mdname = hashAlgorithm!!,
70102
libctx = null,
71103
props = null,
72104
pkey = privateKey,
@@ -75,4 +107,6 @@ internal abstract class Openssl3DigestSignatureGenerator(
75107
)
76108
}
77109
}
110+
111+
// One-shot path now handled by AccumulatingSignFunction in createSignFunction
78112
}

0 commit comments

Comments
 (0)