Skip to content

Commit 23b6a15

Browse files
authored
Expose supported signature algorithms for private keys (#281)
### Motivation: When loading a private key from disk as a `Certificate.PrivateKey` there is no straight forward way to query the supported signature algorithms. For signature algorithm negotiation, the raw values defined in RFC are useful as well. ### Modifications: Add a new property to query the supported signature algorithms for a given key. Add a new property that translates a signature algorithm to the respective value defined in RFC 8446. ### Results: Working with certificate private keys and signature algorithms gets easier.
1 parent c399f90 commit 23b6a15

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

Sources/X509/CertificatePrivateKey.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,36 @@ extension Certificate.PrivateKey {
378378
}
379379
}
380380
}
381+
382+
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
383+
extension Certificate.PrivateKey {
384+
/// Return a list of all supported signature types for this private key. The ordering is not a comment on the
385+
/// preference or security of the contained algorithms.
386+
@inlinable
387+
public var supportedSignatureAlgorithms: [Certificate.SignatureAlgorithm] {
388+
switch backing {
389+
case .p256, .p384, .p521:
390+
return [.ecdsaWithSHA512, .ecdsaWithSHA384, .ecdsaWithSHA256]
391+
case .rsa:
392+
return [
393+
.sha512WithRSAEncryption, .sha384WithRSAEncryption, .sha256WithRSAEncryption, .sha1WithRSAEncryption,
394+
]
395+
#if canImport(Darwin)
396+
case .secureEnclaveP256:
397+
return [.ecdsaWithSHA512, .ecdsaWithSHA384, .ecdsaWithSHA256]
398+
case .secKey(let key):
399+
switch key.type {
400+
case .RSA:
401+
return [
402+
.sha512WithRSAEncryption, .sha384WithRSAEncryption, .sha256WithRSAEncryption,
403+
.sha1WithRSAEncryption,
404+
]
405+
case .ECDSA:
406+
return [.ecdsaWithSHA512, .ecdsaWithSHA384, .ecdsaWithSHA256]
407+
}
408+
#endif
409+
case .ed25519:
410+
return [.ed25519]
411+
}
412+
}
413+
}

Sources/X509/SignatureAlgorithm.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,64 @@ extension AlgorithmIdentifier {
152152
}
153153
}
154154
}
155+
156+
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
157+
extension Certificate.SignatureAlgorithm {
158+
/// Map a signature algorithm to the signature scheme value defined in RFC 8446 for TLS 1.3.
159+
public var rfc8446SignatureSchemeValue: UInt16 {
160+
get throws {
161+
switch self {
162+
case .ecdsaWithSHA256:
163+
return 0x0403
164+
case .ecdsaWithSHA384:
165+
return 0x0503
166+
case .ecdsaWithSHA512:
167+
return 0x0603
168+
case .sha1WithRSAEncryption:
169+
return 0x0201
170+
case .sha256WithRSAEncryption:
171+
return 0x0401
172+
case .sha384WithRSAEncryption:
173+
return 0x0501
174+
case .sha512WithRSAEncryption:
175+
return 0x0601
176+
case .ed25519:
177+
return 0x0807
178+
default:
179+
throw CertificateError.unsupportedSignatureAlgorithm(
180+
reason: "SignatureAlgorithm(\(self)) has an unsupprted value"
181+
)
182+
}
183+
}
184+
}
185+
}
186+
187+
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *)
188+
extension Certificate.SignatureAlgorithm {
189+
/// Initialize a signature algorithm from a raw value as defined in RFC8446.
190+
///
191+
/// Returns: The signature algorithm matching the value for the algorithms supported by Swift Certificates and
192+
/// nil otherwise.
193+
public init?(rfc8446SignatureSchemeValue value: UInt16) {
194+
switch value {
195+
case 0x0403:
196+
self = .ecdsaWithSHA256
197+
case 0x0503:
198+
self = .ecdsaWithSHA384
199+
case 0x0603:
200+
self = .ecdsaWithSHA512
201+
case 0x0201:
202+
self = .sha1WithRSAEncryption
203+
case 0x0401:
204+
self = .sha256WithRSAEncryption
205+
case 0x0501:
206+
self = .sha384WithRSAEncryption
207+
case 0x0601:
208+
self = .sha512WithRSAEncryption
209+
case 0x0807:
210+
self = .ed25519
211+
default:
212+
return nil
213+
}
214+
}
215+
}

Tests/X509Tests/SignatureTests.swift

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,68 @@ final class SignatureTests: XCTestCase {
5050
)
5151
#endif
5252

53+
// RFC8446 defines the following values for signature schemes for TLS 1.3 (signature algorithms before).
54+
// Swift Certificates only supports a handful of them.
55+
//
56+
// enum {
57+
// /* RSASSA-PKCS1-v1_5 algorithms */
58+
// rsa_pkcs1_sha256(0x0401),
59+
// rsa_pkcs1_sha384(0x0501),
60+
// rsa_pkcs1_sha512(0x0601),
61+
//
62+
// /* ECDSA algorithms */
63+
// ecdsa_secp256r1_sha256(0x0403),
64+
// ecdsa_secp384r1_sha384(0x0503),
65+
// ecdsa_secp521r1_sha512(0x0603),
66+
//
67+
// /* RSASSA-PSS algorithms with public key OID rsaEncryption */
68+
// rsa_pss_rsae_sha256(0x0804),
69+
// rsa_pss_rsae_sha384(0x0805),
70+
// rsa_pss_rsae_sha512(0x0806),
71+
//
72+
// /* EdDSA algorithms */
73+
// ed25519(0x0807),
74+
// ed448(0x0808),
75+
//
76+
// /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */
77+
// rsa_pss_pss_sha256(0x0809),
78+
// rsa_pss_pss_sha384(0x080a),
79+
// rsa_pss_pss_sha512(0x080b),
80+
//
81+
// /* Legacy algorithms */
82+
// rsa_pkcs1_sha1(0x0201),
83+
// ecdsa_sha1(0x0203),
84+
//
85+
// /* Reserved Code Points */
86+
// private_use(0xFE00..0xFFFF),
87+
// (0xFFFF)
88+
// } SignatureScheme;
89+
static let supportedRFC8446SignatureAlgorithms: [UInt16: Certificate.SignatureAlgorithm] = [
90+
// RSASSA-PKCS1-v1_5 algorithms
91+
0x0401: Certificate.SignatureAlgorithm.sha256WithRSAEncryption,
92+
0x0501: Certificate.SignatureAlgorithm.sha384WithRSAEncryption,
93+
0x0601: Certificate.SignatureAlgorithm.sha512WithRSAEncryption,
94+
95+
// RSASSA-PSS algorithms with public key OID rsaEncryption
96+
// Currently not supported
97+
98+
// ECDSA algorithms
99+
0x0403: Certificate.SignatureAlgorithm.ecdsaWithSHA256,
100+
0x0503: Certificate.SignatureAlgorithm.ecdsaWithSHA384,
101+
0x0603: Certificate.SignatureAlgorithm.ecdsaWithSHA512,
102+
103+
// EdDSA algorithms
104+
0x0807: Certificate.SignatureAlgorithm.ed25519,
105+
// ed448 is currenlty not supported
106+
107+
// RSASSA-PSS algorithms with public key OID RSASSA-PSS
108+
// Currently not supported
109+
110+
// Legacy algorithms
111+
0x0201: Certificate.SignatureAlgorithm.sha1WithRSAEncryption,
112+
// ecdsa_sha1 is currenlty not supported
113+
]
114+
53115
func testRSASignatureBytes() throws {
54116
let input = Array("Hello World".utf8)
55117
let privateKey = Certificate.PrivateKey(Self.rsaKey)
@@ -941,4 +1003,122 @@ final class SignatureTests: XCTestCase {
9411003
0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
9421004
])
9431005
}
1006+
1007+
func testSignatureAlgorithmTranslatesToCorrectRFC8446Value() throws {
1008+
for (value, algorithm) in SignatureTests.supportedRFC8446SignatureAlgorithms {
1009+
XCTAssertEqual(value, try! algorithm.rfc8446SignatureSchemeValue)
1010+
}
1011+
}
1012+
1013+
func testSignatureAlgorithmInitializedWithRFC8446Value() {
1014+
let supportedValues = Set(SignatureTests.supportedRFC8446SignatureAlgorithms.keys)
1015+
for value in 0...UInt16.max {
1016+
if supportedValues.contains(value) {
1017+
let algo = Certificate.SignatureAlgorithm(rfc8446SignatureSchemeValue: value)
1018+
XCTAssertNotNil(algo)
1019+
XCTAssertEqual(algo, SignatureTests.supportedRFC8446SignatureAlgorithms[value])
1020+
} else {
1021+
XCTAssertNil(Certificate.SignatureAlgorithm(rfc8446SignatureSchemeValue: value))
1022+
}
1023+
}
1024+
1025+
}
1026+
1027+
func testMapPrivateKeyToSupportedSignatureAlgorithmRSA() throws {
1028+
XCTAssertEqual(
1029+
Set(Certificate.PrivateKey(Self.rsaKey).supportedSignatureAlgorithms),
1030+
Set([.sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption, .sha1WithRSAEncryption])
1031+
)
1032+
}
1033+
1034+
func testMapPrivateKeyToSupportedSignatureAlgorithmECDSA() throws {
1035+
XCTAssertEqual(
1036+
Set(Certificate.PrivateKey(Self.p256Key).supportedSignatureAlgorithms),
1037+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1038+
)
1039+
XCTAssertEqual(
1040+
Set(Certificate.PrivateKey(Self.p384Key).supportedSignatureAlgorithms),
1041+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1042+
)
1043+
XCTAssertEqual(
1044+
Set(Certificate.PrivateKey(Self.p521Key).supportedSignatureAlgorithms),
1045+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1046+
)
1047+
}
1048+
1049+
func testMapPrivateKeyToSupportedSignatureAlgorithmEdDSA() throws {
1050+
XCTAssertEqual(Set(Certificate.PrivateKey(Self.ed25519Key).supportedSignatureAlgorithms), Set([.ed25519]))
1051+
}
1052+
1053+
#if canImport(Darwin)
1054+
func testMapPrivateKeyToSupportedSignatureAlgorithmSecureEncalve() throws {
1055+
guard let secureEnclaveP256 = Self.secureEnclaveP256 else {
1056+
throw XCTSkip("No SEP")
1057+
}
1058+
XCTAssertEqual(
1059+
Set(Certificate.PrivateKey(secureEnclaveP256).supportedSignatureAlgorithms),
1060+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1061+
)
1062+
}
1063+
1064+
func testMapPrivateKeyToSupportedSignatureAlgorithmSecKeyRSA() async throws {
1065+
guard let secKeyRSA = Self.secKeyRSA else {
1066+
throw XCTSkip("Key Error")
1067+
}
1068+
XCTAssertEqual(
1069+
Set(try Certificate.PrivateKey(secKeyRSA).supportedSignatureAlgorithms),
1070+
Set([.sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption, .sha1WithRSAEncryption])
1071+
)
1072+
}
1073+
1074+
func testMapPrivateKeyToSupportedSignatureAlgorithmSecKeyEC256() async throws {
1075+
guard let secKeyEC256 = Self.secKeyEC256 else {
1076+
throw XCTSkip("Key Error")
1077+
}
1078+
XCTAssertEqual(
1079+
Set(try Certificate.PrivateKey(secKeyEC256).supportedSignatureAlgorithms),
1080+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1081+
)
1082+
}
1083+
1084+
func testMapPrivateKeyToSupportedSignatureAlgorithmSecKeyEC2384() async throws {
1085+
guard let secKeyEC384 = Self.secKeyEC384 else {
1086+
throw XCTSkip("Key Error")
1087+
}
1088+
XCTAssertEqual(
1089+
Set(try Certificate.PrivateKey(secKeyEC384).supportedSignatureAlgorithms),
1090+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1091+
)
1092+
}
1093+
1094+
func testMapPrivateKeyToSupportedSignatureAlgorithmSecKeyEC521() async throws {
1095+
guard let secKeyEC521 = Self.secKeyEC521 else {
1096+
throw XCTSkip("Key Error")
1097+
}
1098+
XCTAssertEqual(
1099+
Set(try Certificate.PrivateKey(secKeyEC521).supportedSignatureAlgorithms),
1100+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1101+
)
1102+
}
1103+
1104+
func testMapPrivateKeyToSupportedSignatureAlgorithmsecKeyEnclaveEC256() async throws {
1105+
guard let secKeyEnclaveEC256 = Self.secKeyEnclaveEC256 else {
1106+
throw XCTSkip("Key Error")
1107+
}
1108+
XCTAssertEqual(
1109+
Set(try Certificate.PrivateKey(secKeyEnclaveEC256).supportedSignatureAlgorithms),
1110+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1111+
)
1112+
}
1113+
1114+
func testMapPrivateKeyToSupportedSignatureAlgorithmsecKeyEnclaveEC384() async throws {
1115+
guard let secKeyEnclaveEC384 = Self.secKeyEnclaveEC384 else {
1116+
throw XCTSkip("Key Error")
1117+
}
1118+
XCTAssertEqual(
1119+
Set(try Certificate.PrivateKey(secKeyEnclaveEC384).supportedSignatureAlgorithms),
1120+
Set([.ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512])
1121+
)
1122+
}
1123+
#endif
9441124
}

0 commit comments

Comments
 (0)