Skip to content

Commit 6afa6af

Browse files
committed
openssl3: implement EdDSA/XDH; key agreement refactor; nullable-hash signatures; wire provider
1 parent 1d0f98f commit 6afa6af

File tree

6 files changed

+449
-10
lines changed

6 files changed

+449
-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+
}

0 commit comments

Comments
 (0)