-
Notifications
You must be signed in to change notification settings - Fork 101
Expand file tree
/
Copy pathMLDSA.swift
More file actions
187 lines (174 loc) · 6.86 KB
/
Copy pathMLDSA.swift
File metadata and controls
187 lines (174 loc) · 6.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2026 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SwiftASN1
@preconcurrency import Crypto
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
/// The ML-DSA parameter sets this library can name.
///
/// This type deliberately stores no key material and names no swift-crypto ML-DSA type,
/// so it can be declared without an availability annotation: enum cases can't carry
/// `@available`, so keeping this payload free of macOS-26-only types is what lets
/// certificates *signed* with ML-DSA be parsed and re-serialized unconditionally.
@usableFromInline
enum MLDSAVariant: Hashable, Sendable {
case mldsa65
case mldsa87
}
/// The raw bytes of an ML-DSA public key, plus its parameter set.
///
/// Reconstructing a swift-crypto ML-DSA public key from its raw representation costs ~0.4%
/// of a verification (measured in the design doc), so the key is stored as validated bytes
/// and rebuilt on each use. The payload is plain data so the enum case that carries it can
/// be declared without an availability annotation (macOS-26-only types can't appear in an
/// unannotated enum payload); only the *initializers* need swift-crypto's ML-DSA API.
@usableFromInline
struct MLDSAPublicKeyBytes: Hashable, Sendable {
@usableFromInline
var variant: MLDSAVariant
@usableFromInline
var bytes: Data
/// Validates and stores SPKI subjectPublicKey bytes for the given parameter set.
///
/// Throws ``CertificateError/unsupportedPublicKeyAlgorithm(reason:)`` at runtime on
/// Darwin platforms older than macOS 26 (where CryptoKit has no ML-DSA).
@usableFromInline
init(spkiBytes: ArraySlice<UInt8>, variant: MLDSAVariant) throws {
guard #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
else {
throw CertificateError.unsupportedPublicKeyAlgorithm(
reason: "ML-DSA requires macOS 26, iOS 26, watchOS 26, tvOS 26, or visionOS 26"
)
}
// Let swift-crypto validate the encoding; we store the validated raw bytes.
switch variant {
case .mldsa65:
self.bytes = try MLDSA65.PublicKey(rawRepresentation: spkiBytes).rawRepresentation
case .mldsa87:
self.bytes = try MLDSA87.PublicKey(rawRepresentation: spkiBytes).rawRepresentation
}
self.variant = variant
}
}
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
extension MLDSAPublicKeyBytes {
@usableFromInline
func isValidSignature<Bytes: DataProtocol>(
_ signature: Certificate.Signature,
for bytes: Bytes,
signatureAlgorithm: Certificate.SignatureAlgorithm
) -> Bool {
guard case .mldsa(let signatureVariant, let rawSignature) = signature.backing,
signatureVariant == self.variant
else {
// Signature mismatch, including an ML-DSA signature under the other parameter set.
return false
}
return self.isValidSignature(rawSignature, for: bytes, signatureAlgorithm: signatureAlgorithm)
}
@usableFromInline
func isValidSignature<SignatureBytes: DataProtocol, Bytes: DataProtocol>(
_ signature: SignatureBytes,
for bytes: Bytes,
signatureAlgorithm: Certificate.SignatureAlgorithm
) -> Bool {
switch (self.variant, signatureAlgorithm) {
case (.mldsa65, .mldsa65), (.mldsa87, .mldsa87):
break
default:
return false
}
guard #available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
else {
return false
}
switch self.variant {
case .mldsa65:
guard let key = try? MLDSA65.PublicKey(rawRepresentation: self.bytes) else {
return false
}
return key.isValidSignature(signature, for: bytes)
case .mldsa87:
guard let key = try? MLDSA87.PublicKey(rawRepresentation: self.bytes) else {
return false
}
return key.isValidSignature(signature, for: bytes)
}
}
}
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
extension MLDSAPublicKeyBytes {
@usableFromInline
init(_ mldsa65: MLDSA65.PublicKey) {
self.variant = .mldsa65
self.bytes = mldsa65.rawRepresentation
}
@usableFromInline
init(_ mldsa87: MLDSA87.PublicKey) {
self.variant = .mldsa87
self.bytes = mldsa87.rawRepresentation
}
}
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
extension Certificate.PublicKey {
/// Construct a public key wrapping an ML-DSA-65 public key.
/// - Parameter mldsa65: The ML-DSA-65 public key to wrap.
@inlinable
public init(_ mldsa65: MLDSA65.PublicKey) {
self.init(backing: .mldsa(MLDSAPublicKeyBytes(mldsa65)))
}
/// Construct a public key wrapping an ML-DSA-87 public key.
/// - Parameter mldsa87: The ML-DSA-87 public key to wrap.
@inlinable
public init(_ mldsa87: MLDSA87.PublicKey) {
self.init(backing: .mldsa(MLDSAPublicKeyBytes(mldsa87)))
}
}
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
extension MLDSA65.PublicKey {
/// Create an ML-DSA-65 public key from a given ``Certificate/PublicKey-swift.struct``.
///
/// Fails if the key is not an ML-DSA-65 key.
///
/// - Parameters:
/// - key: The key to unwrap.
public init?(_ key: Certificate.PublicKey) {
guard case .mldsa(let backing) = key.backing, backing.variant == .mldsa65,
let key = try? MLDSA65.PublicKey(rawRepresentation: backing.bytes)
else {
return nil
}
self = key
}
}
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, macCatalyst 26.0, visionOS 26.0, *)
extension MLDSA87.PublicKey {
/// Create an ML-DSA-87 public key from a given ``Certificate/PublicKey-swift.struct``.
///
/// Fails if the key is not an ML-DSA-87 key.
///
/// - Parameters:
/// - key: The key to unwrap.
public init?(_ key: Certificate.PublicKey) {
guard case .mldsa(let backing) = key.backing, backing.variant == .mldsa87,
let key = try? MLDSA87.PublicKey(rawRepresentation: backing.bytes)
else {
return nil
}
self = key
}
}