From 1a2339c96b4a93a8643ebf84ab7e3a322c68a730 Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Sat, 6 Sep 2025 20:00:32 -0600 Subject: [PATCH 1/2] cryptokit: implement EdDSA/XDH; add SwiftEd; wire provider; decode unwrap-to-RAW+decodeRaw; encode hoists raw; rename *Key; use ObjectIdentifier OIDs --- .../kotlin/CryptoKitCryptographyProvider.kt | 2 + .../kotlin/algorithms/CryptoKitEdDSA.kt | 158 ++++++++++++++++++ .../kotlin/algorithms/CryptoKitXDH.kt | 147 ++++++++++++++++ .../src/commonMain/swift/SwiftEd.swift | 87 ++++++++++ 4 files changed, 394 insertions(+) create mode 100644 cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt create mode 100644 cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt create mode 100644 cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt index a7eeb254..e93ddba6 100644 --- a/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/CryptoKitCryptographyProvider.kt @@ -30,6 +30,8 @@ internal object CryptoKitCryptographyProvider : CryptographyProvider() { AES.GCM -> CryptoKitAesGcm ECDSA -> CryptoKitEcdsa ECDH -> CryptoKitEcdh + EdDSA -> CryptoKitEdDSA + XDH -> CryptoKitXDH else -> null } as A? } diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt new file mode 100644 index 00000000..cd2dab24 --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitEdDSA.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.cryptokit.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.providers.base.operations.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.cinterop.* +import platform.Foundation.* + +internal object CryptoKitEdDSA : EdDSA { + override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: EdDSA.PublicKey.Format, bytes: ByteArray): EdDSA.PublicKey = when (format) { + EdDSA.PublicKey.Format.RAW -> EdPublicKey( + swiftTry { error -> bytes.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } } + ) + EdDSA.PublicKey.Format.DER -> { + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier.Ed25519, bytes) + EdPublicKey(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + EdDSA.PublicKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PublicKey, bytes) + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier.Ed25519, der) + EdPublicKey(swiftTry { error -> raw.useNSData { SwiftEdDsaPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + } + } + + override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: EdDSA.PrivateKey.Format, bytes: ByteArray): EdDSA.PrivateKey = when (format) { + EdDSA.PrivateKey.Format.RAW -> EdPrivateKey( + swiftTry { error -> bytes.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } } + ) + EdDSA.PrivateKey.Format.DER -> { + val raw = unwrapPrivateKeyInfo(ObjectIdentifier.Ed25519, bytes) + EdPrivateKey(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + EdDSA.PrivateKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PrivateKey, bytes) + val raw = unwrapPrivateKeyInfo(ObjectIdentifier.Ed25519, der) + EdPrivateKey(swiftTry { error -> raw.useNSData { SwiftEdDsaPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + } + } + + override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator { + check(curve == EdDSA.Curve.Ed25519) { "CryptoKit supports Ed25519 only" } + return object : KeyGenerator { + override fun generateKeyBlocking(): EdDSA.KeyPair { + val p = SwiftEdDsaPrivateKey.generate() + return EdKeyPair(EdPublicKey(p.publicKey()), EdPrivateKey(p)) + } + } + } + + private class EdKeyPair( + override val publicKey: EdDSA.PublicKey, + override val privateKey: EdDSA.PrivateKey, + ) : EdDSA.KeyPair + + private class EdPublicKey( + val key: SwiftEdDsaPublicKey, + ) : EdDSA.PublicKey { + override fun encodeToByteArrayBlocking(format: EdDSA.PublicKey.Format): ByteArray = when (format) { + EdDSA.PublicKey.Format.RAW -> { + val raw = key.rawRepresentation().toByteArray() + raw + } + EdDSA.PublicKey.Format.DER -> { + val raw = key.rawRepresentation().toByteArray() + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.Ed25519), + raw + ) + } + EdDSA.PublicKey.Format.PEM -> { + val raw = key.rawRepresentation().toByteArray() + wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.Ed25519), + raw + ) + ) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureVerifier(): SignatureVerifier = object : SignatureVerifier { + override fun createVerifyFunction(): VerifyFunction = + AccumulatingVerifyFunction { data, signature, startIndex, endIndex -> + val sig = signature.copyOfRange(startIndex, endIndex) + val ok = data.useNSData { dataNs -> sig.useNSData { sigNs -> + key.verifyWithSignature(sigNs, message = dataNs) + } } as Boolean + ok + } + } + } + + private class EdPrivateKey( + val key: SwiftEdDsaPrivateKey, + ) : EdDSA.PrivateKey { + override fun encodeToByteArrayBlocking(format: EdDSA.PrivateKey.Format): ByteArray = when (format) { + EdDSA.PrivateKey.Format.RAW -> { + val raw = key.rawRepresentation().toByteArray() + raw + } + EdDSA.PrivateKey.Format.DER -> { + val raw = key.rawRepresentation().toByteArray() + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.Ed25519), + raw + ) + } + EdDSA.PrivateKey.Format.PEM -> { + val raw = key.rawRepresentation().toByteArray() + wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.Ed25519), + raw + ) + ) + } + else -> error("$format is not supported by CryptoKit EdDSA") + } + + override fun signatureGenerator(): SignatureGenerator = object : SignatureGenerator { + override fun createSignFunction(): SignFunction = + AccumulatingSignFunction { data -> + swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } }.toByteArray() + } + override fun generateSignatureBlocking(data: ByteArray): ByteArray = + swiftTry { error -> data.useNSData { key.signWithMessage(it, error) } }.toByteArray() + } + } +} diff --git a/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt new file mode 100644 index 00000000..b5db4538 --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/kotlin/algorithms/CryptoKitXDH.kt @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license. + */ + +package dev.whyoleg.cryptography.providers.cryptokit.algorithms + +import dev.whyoleg.cryptography.algorithms.* +import dev.whyoleg.cryptography.materials.key.* +import dev.whyoleg.cryptography.operations.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.* +import dev.whyoleg.cryptography.providers.cryptokit.internal.swiftinterop.* +import dev.whyoleg.cryptography.providers.base.* +import dev.whyoleg.cryptography.providers.base.materials.* +import dev.whyoleg.cryptography.serialization.asn1.* +import dev.whyoleg.cryptography.serialization.asn1.modules.* +import dev.whyoleg.cryptography.serialization.pem.* +import kotlinx.cinterop.* +import platform.Foundation.* + +internal object CryptoKitXDH : XDH { + override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: XDH.PublicKey.Format, bytes: ByteArray): XDH.PublicKey = when (format) { + XDH.PublicKey.Format.RAW -> XPublicKey( + swiftTry { error -> bytes.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } } + ) + XDH.PublicKey.Format.DER -> { + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier.X25519, bytes) + XPublicKey(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + XDH.PublicKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PublicKey, bytes) + val raw = unwrapSubjectPublicKeyInfo(ObjectIdentifier.X25519, der) + XPublicKey(swiftTry { error -> raw.useNSData { SwiftXdhPublicKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit XDH") + } + } + } + + override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyDecoder { + override fun decodeFromByteArrayBlocking(format: XDH.PrivateKey.Format, bytes: ByteArray): XDH.PrivateKey = when (format) { + XDH.PrivateKey.Format.RAW -> XPrivate( + swiftTry { error -> bytes.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } } + ) + XDH.PrivateKey.Format.DER -> { + val raw = unwrapPrivateKeyInfo(ObjectIdentifier.X25519, bytes) + XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + XDH.PrivateKey.Format.PEM -> { + val der = unwrapPem(PemLabel.PrivateKey, bytes) + val raw = unwrapPrivateKeyInfo(ObjectIdentifier.X25519, der) + XPrivate(swiftTry { error -> raw.useNSData { SwiftXdhPrivateKey.decodeRawWithRawRepresentation(it, error) } }) + } + else -> error("$format is not supported by CryptoKit XDH") + } + } + } + + override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator { + check(curve == XDH.Curve.X25519) { "CryptoKit supports X25519 only" } + return object : KeyGenerator { + override fun generateKeyBlocking(): XDH.KeyPair { + val p = SwiftXdhPrivateKey.generate() + return XKeyPair(XPublicKey(p.publicKey()), XPrivateKey(p)) + } + } + } + + private class XKeyPair( + override val publicKey: XDH.PublicKey, + override val privateKey: XDH.PrivateKey, + ) : XDH.KeyPair + + private class XPublicKey( + val key: SwiftXdhPublicKey, + ) : XDH.PublicKey, SharedSecretGenerator { + override fun encodeToByteArrayBlocking(format: XDH.PublicKey.Format): ByteArray = when (format) { + XDH.PublicKey.Format.RAW -> { + val raw = key.rawRepresentation().toByteArray() + raw + } + XDH.PublicKey.Format.DER -> { + val raw = key.rawRepresentation().toByteArray() + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.X25519), + raw + ) + } + XDH.PublicKey.Format.PEM -> { + val raw = key.rawRepresentation().toByteArray() + wrapPem( + PemLabel.PublicKey, + wrapSubjectPublicKeyInfo( + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.X25519), + raw + ) + ) + } + else -> error("$format is not supported by CryptoKit XDH") + } + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray { + require(other is XPrivateKey) + return swiftTry { error -> other.key.deriveSecretWith(key, error) }.toByteArray() + } + } + + private class XPrivateKey( + val key: SwiftXdhPrivateKey, + ) : XDH.PrivateKey, SharedSecretGenerator { + override fun encodeToByteArrayBlocking(format: XDH.PrivateKey.Format): ByteArray = when (format) { + XDH.PrivateKey.Format.RAW -> { + val raw = key.rawRepresentation().toByteArray() + raw + } + XDH.PrivateKey.Format.DER -> { + val raw = key.rawRepresentation().toByteArray() + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.X25519), + raw + ) + } + XDH.PrivateKey.Format.PEM -> { + val raw = key.rawRepresentation().toByteArray() + wrapPem( + PemLabel.PrivateKey, + wrapPrivateKeyInfo( + 0, + UnknownKeyAlgorithmIdentifier(ObjectIdentifier.X25519), + raw + ) + ) + } + else -> error("$format is not supported by CryptoKit XDH") + } + override fun sharedSecretGenerator(): SharedSecretGenerator = this + override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray { + require(other is XPublicKey) + return swiftTry { error -> key.deriveSecretWith(other.key, error) }.toByteArray() + } + } +} diff --git a/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift b/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift new file mode 100644 index 00000000..829e166b --- /dev/null +++ b/cryptography-providers/cryptokit/src/commonMain/swift/SwiftEd.swift @@ -0,0 +1,87 @@ +import CryptoKit +import Foundation + +@objc public class SwiftEdDsaPublicKey: NSObject { + private let key: Curve25519.Signing.PublicKey + + internal init(_ key: Curve25519.Signing.PublicKey) { self.key = key } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftEdDsaPublicKey { + return SwiftEdDsaPublicKey(try Curve25519.Signing.PublicKey(rawRepresentation: raw as Data)) + } + + @objc public func verify( + signature: NSData, + message: NSData + ) -> Bool { + return key.isValidSignature(signature as Data, for: message as Data) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftEdDsaPrivateKey: NSObject { + private let key: Curve25519.Signing.PrivateKey + + private override init() { self.key = Curve25519.Signing.PrivateKey() } + private init(key: Curve25519.Signing.PrivateKey) { self.key = key } + + @objc public static func generate() -> SwiftEdDsaPrivateKey { SwiftEdDsaPrivateKey() } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftEdDsaPrivateKey { + return SwiftEdDsaPrivateKey(key: try Curve25519.Signing.PrivateKey(rawRepresentation: raw as Data)) + } + + @objc public func publicKey() -> SwiftEdDsaPublicKey { SwiftEdDsaPublicKey(key.publicKey) } + + @objc public func sign( + message: NSData + ) throws -> Data { + try key.signature(for: message as Data) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftXdhPublicKey: NSObject { + let key: Curve25519.KeyAgreement.PublicKey + internal init(_ key: Curve25519.KeyAgreement.PublicKey) { self.key = key } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftXdhPublicKey { + return SwiftXdhPublicKey(try Curve25519.KeyAgreement.PublicKey(rawRepresentation: raw as Data)) + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + +@objc public class SwiftXdhPrivateKey: NSObject { + let key: Curve25519.KeyAgreement.PrivateKey + private override init() { self.key = Curve25519.KeyAgreement.PrivateKey() } + private init(_ key: Curve25519.KeyAgreement.PrivateKey) { self.key = key } + + @objc public static func generate() -> SwiftXdhPrivateKey { SwiftXdhPrivateKey() } + + @objc public static func decodeRaw( + rawRepresentation raw: NSData + ) throws -> SwiftXdhPrivateKey { + return SwiftXdhPrivateKey(try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: raw as Data)) + } + + @objc public func publicKey() -> SwiftXdhPublicKey { SwiftXdhPublicKey(key.publicKey) } + + @objc public func deriveSecret( + with publicKey: SwiftXdhPublicKey + ) throws -> Data { + let ss = try key.sharedSecretFromKeyAgreement(with: publicKey.key) + return ss.withUnsafeBytes { Data($0) } + } + + @objc public func rawRepresentation() -> Data { key.rawRepresentation } +} + From 4fed3f4ec72625513990fbceb9295af44f9060cf Mon Sep 17 00:00:00 2001 From: Aria Wisp Date: Sat, 6 Sep 2025 20:39:21 -0600 Subject: [PATCH 2/2] docs(cryptokit): add EdDSA/XDH capability note (Ed25519/X25519 only) --- docs/providers/cryptokit.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/providers/cryptokit.md b/docs/providers/cryptokit.md index 61ba9d2d..38abdda1 100644 --- a/docs/providers/cryptokit.md +++ b/docs/providers/cryptokit.md @@ -8,6 +8,7 @@ For supported targets and algorithms, please consult [Supported primitives secti * KeyFormat: doesn't support `JWK` key format yet * AES.GCM supports only a default tag size of 96 bits +* EdDSA/XDH: supports only Ed25519 and X25519 ## Example