-
-
Notifications
You must be signed in to change notification settings - Fork 61
Add MLDSA{65,87} support
#229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
0ed9f07
Add `MLDSA{65,87}` support
ptoffy 9915232
Re-format
ptoffy df20a87
Switch to `_@spi(PostQuantum)`
ptoffy c621ea8
More formatting
ptoffy 89f8fd5
Even more formatting
ptoffy aa13636
Merge branch 'main' into mldsa
ptoffy 105e11c
Update MLDSA impl
ptoffy 0f2a8a6
Merge branch 'main' into mldsa
ptoffy 91eda39
Merge branch 'main' into mldsa
ptoffy 04db51a
Fix imports
ptoffy bd5bfe7
Minor fixes
ptoffy 30cd52a
Merge branch 'main' into mldsa
ptoffy 46e1bbd
Update Tests/JWTKitTests/MLDSATests.swift
ptoffy 531ef57
Remove unused error case
ptoffy 88bbf84
Update docs
ptoffy 884d332
Slight improvement
ptoffy 371e7f4
Update Sources/JWTKit/Docs.docc/index.md
ptoffy 35ac5d9
Add snippet end
ptoffy a31e3c2
Actually add the algo to the table
ptoffy 10d40e6
What am I doing
ptoffy 35563d9
Update snippets
ptoffy 998695b
Merge branch 'main' into mldsa
ptoffy 515e83d
Add benchmarks
ptoffy 1e4642c
Fix
ptoffy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| Packages | ||
| .build | ||
| .index-build | ||
| .DS_Store | ||
| *.xcodeproj | ||
| Package.pins | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| extension JWTKeyCollection { | ||
| @_spi(PostQuantum) | ||
| @discardableResult | ||
| public func add( | ||
| mldsa key: some MLDSAKey, | ||
| kid: JWKIdentifier? = nil, | ||
| parser: some JWTParser = DefaultJWTParser(), | ||
| serializer: some JWTSerializer = DefaultJWTSerializer() | ||
| ) -> Self { | ||
| self.add( | ||
| .init(algorithm: MLDSASigner(key: key), parser: parser, serializer: serializer), | ||
| for: kid | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import _CryptoExtras | ||
|
|
||
| #if !canImport(Darwin) | ||
| import FoundationEssentials | ||
| #else | ||
| import Foundation | ||
| #endif | ||
|
|
||
| @_spi(PostQuantum) public enum MLDSA: Sendable {} | ||
|
|
||
| extension MLDSA { | ||
| public struct PublicKey<KeyType>: MLDSAKey where KeyType: MLDSAType { | ||
| public typealias MLDSAType = KeyType | ||
|
|
||
| typealias PublicKey = KeyType.PrivateKey.PublicKey | ||
|
|
||
| let backing: any MLDSAPublicKey | ||
|
|
||
| public init(backing: some MLDSAPublicKey) { | ||
| self.backing = backing | ||
| } | ||
|
|
||
| public init(rawRepresentation: some DataProtocol) throws { | ||
| self.backing = try PublicKey(rawRepresentation: rawRepresentation) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension MLDSA { | ||
| public struct PrivateKey<KeyType>: MLDSAKey where KeyType: MLDSAType { | ||
| public typealias MLDSAType = KeyType | ||
|
|
||
| typealias PrivateKey = KeyType.PrivateKey | ||
|
|
||
| let backing: any MLDSAPrivateKey | ||
|
|
||
| public var publicKey: MLDSA.PublicKey<KeyType> { | ||
| .init(backing: self.backing.publicKey) | ||
| } | ||
|
|
||
| public init(backing: some MLDSAPrivateKey) { | ||
| self.backing = backing | ||
| } | ||
|
|
||
| public init(seedRepresentation: some DataProtocol) throws { | ||
| self.backing = try PrivateKey(seedRepresentation: seedRepresentation) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import _CryptoExtras | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA65.PublicKey: MLDSAPublicKey { | ||
| public typealias MLDSAType = MLDSA65 | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA65.PrivateKey: MLDSAPrivateKey { | ||
| public typealias MLDSAType = MLDSA65 | ||
| } | ||
|
|
||
| @_spi(PostQuantum) public typealias MLDSA65PublicKey = MLDSA.PublicKey<MLDSA65> | ||
| @_spi(PostQuantum) public typealias MLDSA65PrivateKey = MLDSA.PrivateKey<MLDSA65> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import _CryptoExtras | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA87.PublicKey: MLDSAPublicKey { | ||
| public typealias MLDSAType = MLDSA87 | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA87.PrivateKey: MLDSAPrivateKey { | ||
| public typealias MLDSAType = MLDSA87 | ||
| } | ||
|
|
||
| @_spi(PostQuantum) public typealias MLDSA87PublicKey = MLDSA.PublicKey<MLDSA87> | ||
| @_spi(PostQuantum) public typealias MLDSA87PrivateKey = MLDSA.PrivateKey<MLDSA87> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| enum MLDSAError: Error { | ||
| case noPrivateKey | ||
| case noPublicKey | ||
ptoffy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| case failedToSign(Error) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| #if canImport(FoundationEssentials) | ||
| import FoundationEssentials | ||
| #else | ||
| import Foundation | ||
| #endif | ||
|
|
||
| @_spi(PostQuantum) | ||
| public protocol MLDSAKey: Sendable { | ||
| associatedtype MLDSAType: JWTKit.MLDSAType | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| public protocol MLDSAPublicKey: Sendable { | ||
| associatedtype MLDSAType | ||
|
|
||
| init(rawRepresentation: some DataProtocol) throws | ||
| var rawRepresentation: Data { get } | ||
| func isValidSignature<S: DataProtocol, D: DataProtocol>(_ signature: S, for data: D) -> Bool | ||
| func isValidSignature<S: DataProtocol, D: DataProtocol, C: DataProtocol>( | ||
| _ signature: S, for data: D, context: C | ||
| ) -> Bool | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| public protocol MLDSAPrivateKey: Sendable { | ||
| associatedtype MLDSAType | ||
| associatedtype PublicKey: MLDSAPublicKey | ||
|
|
||
| var seedRepresentation: Data { get } | ||
| var publicKey: PublicKey { get } | ||
| init(seedRepresentation: some DataProtocol) throws | ||
| func signature<D: DataProtocol>(for data: D) throws -> Data | ||
| func signature<D: DataProtocol, C: DataProtocol>(for data: D, context: C) throws -> Data | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import _CryptoExtras | ||
|
|
||
| #if canImport(FoundationEssentials) | ||
| import FoundationEssentials | ||
| #else | ||
| import Foundation | ||
| #endif | ||
|
|
||
| struct MLDSASigner<Key: MLDSAKey>: JWTAlgorithm, Sendable { | ||
| let privateKey: MLDSA.PrivateKey<Key.MLDSAType>? | ||
| let publicKey: MLDSA.PublicKey<Key.MLDSAType> | ||
|
|
||
| var name: String = Key.MLDSAType.name | ||
|
|
||
| init(key: Key) { | ||
| switch key { | ||
| case let key as MLDSA.PrivateKey<Key.MLDSAType>: | ||
| self.privateKey = key | ||
| self.publicKey = key.publicKey | ||
| case let key as MLDSA.PublicKey<Key.MLDSAType>: | ||
| self.privateKey = nil | ||
| self.publicKey = key | ||
| default: | ||
| fatalError() | ||
ptoffy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| func sign(_ plaintext: some DataProtocol) throws -> [UInt8] { | ||
| guard let privateKey else { | ||
| throw JWTError.signingAlgorithmFailure(MLDSAError.noPrivateKey) | ||
| } | ||
|
|
||
| let signature: Data | ||
| do { | ||
| signature = try privateKey.backing.signature(for: plaintext) | ||
| } catch { | ||
| throw JWTError.signingAlgorithmFailure(MLDSAError.failedToSign(error)) | ||
| } | ||
|
|
||
| return signature.copyBytes() | ||
ptoffy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| func verify(_ signature: some DataProtocol, signs plaintext: some DataProtocol) throws -> Bool { | ||
| publicKey.backing.isValidSignature(signature, for: plaintext) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import _CryptoExtras | ||
|
|
||
| @_spi(PostQuantum) | ||
| public protocol MLDSAType { | ||
| associatedtype PrivateKey: MLDSAPrivateKey | ||
|
|
||
| static var name: String { get } | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA65: MLDSAType { | ||
| public static var name: String { "ML-DSA-65" } | ||
| } | ||
|
|
||
| @_spi(PostQuantum) | ||
| extension MLDSA87: MLDSAType { | ||
| public static var name: String { "ML-DSA-87" } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import Crypto | ||
| import Foundation | ||
| @_spi(PostQuantum) import JWTKit | ||
| import Testing | ||
|
|
||
| @Suite("MLDSA Tests") | ||
| struct MLDSATests { | ||
| @Test("MLDSA65 Signing") | ||
| func sign65() async throws { | ||
| struct Foo: JWTPayload { | ||
| var bar: Int | ||
| func verify(using _: some JWTAlgorithm) throws {} | ||
| } | ||
|
|
||
| let key = try MLDSA65PrivateKey( | ||
| seedRepresentation: Data(fromHexEncodedString: mldsa65PrivateKeySeedRepresentation)!) | ||
|
|
||
| let keyCollection = JWTKeyCollection() | ||
| await keyCollection.add(mldsa: key) | ||
|
|
||
| let jwt = try await keyCollection.sign(Foo(bar: 42)) | ||
| let verified = try await keyCollection.verify(jwt, as: Foo.self) | ||
|
|
||
| #expect(verified.bar == 42) | ||
| } | ||
|
|
||
| @Test("MLDSA87 Signing") | ||
| func sign87() async throws { | ||
| struct Foo: JWTPayload { | ||
| var bar: Int | ||
| func verify(using _: some JWTAlgorithm) throws {} | ||
| } | ||
|
|
||
| let key = try MLDSA87PrivateKey( | ||
| seedRepresentation: Data(fromHexEncodedString: mldsa65PrivateKeySeedRepresentation)!) | ||
ptoffy marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| let keyCollection = JWTKeyCollection() | ||
| await keyCollection.add(mldsa: key) | ||
|
|
||
| let jwt = try await keyCollection.sign(Foo(bar: 42)) | ||
| let verified = try await keyCollection.verify(jwt, as: Foo.self) | ||
|
|
||
| #expect(verified.bar == 42) | ||
|
|
||
| print(jwt) | ||
| } | ||
| } | ||
|
|
||
| let mldsa65PrivateKeySeedRepresentation = | ||
| "70cefb9aed5b68e018b079da8284b9d5cad5499ed9c265ff73588005d85c225c" | ||
|
|
||
| let mldsa87PrivateKeySeedRepresentation = | ||
| "19e9e5efe0c1549ddb1d72213636d16fe2faeb2428257004ae464094ca536a66" | ||
|
|
||
| extension Data { | ||
| init?(fromHexEncodedString string: String) { | ||
| func decodeNibble(u: UInt8) -> UInt8? { | ||
| switch u { | ||
| case 0x30...0x39: u - 0x30 | ||
| case 0x41...0x46: u - 0x41 + 10 | ||
| case 0x61...0x66: u - 0x61 + 10 | ||
| default: nil | ||
| } | ||
| } | ||
|
|
||
| self.init(capacity: string.utf8.count / 2) | ||
|
|
||
| var iter = string.utf8.makeIterator() | ||
| while let c1 = iter.next() { | ||
| guard | ||
| let val1 = decodeNibble(u: c1), | ||
| let c2 = iter.next(), | ||
| let val2 = decodeNibble(u: c2) | ||
| else { return nil } | ||
| self.append(val1 << 4 + val2) | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.