Skip to content

Commit ccb902a

Browse files
Support loading RSAPSS public keys with parameters
Parameters are stripped and the key is treated as a regular public key.
1 parent e8f2ddd commit ccb902a

File tree

4 files changed

+186
-13
lines changed

4 files changed

+186
-13
lines changed

Package.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ let package = Package(
7272
.library(name: "CCryptoBoringSSL", type: .static, targets: ["CCryptoBoringSSL"]),
7373
MANGLE_END */
7474
],
75-
dependencies: [],
75+
dependencies: [
76+
.package(url: "https://github.com/apple/swift-asn1.git", .upToNextMajor(from: "1.2.0"))
77+
],
7678
targets: [
7779
.target(
7880
name: "CCryptoBoringSSL",
@@ -135,7 +137,8 @@ let package = Package(
135137
"CCryptoBoringSSL",
136138
"CCryptoBoringSSLShims",
137139
"CryptoBoringWrapper",
138-
"Crypto"
140+
"Crypto",
141+
.product(name: "SwiftASN1", package: "swift-asn1")
139142
],
140143
exclude: [
141144
"CMakeLists.txt",

Sources/_CryptoExtras/RSA/RSA.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414
import Foundation
1515
import Crypto
16+
import SwiftASN1
1617

1718
#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
1819
fileprivate typealias BackingPublicKey = SecurityRSAPublicKey
@@ -61,34 +62,34 @@ extension _RSA.Signing {
6162
///
6263
/// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate
6364
/// for their use-case.
65+
/// Parameters from RSA PSS keys will be stripped.
6466
public init(pemRepresentation: String) throws {
65-
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)
67+
let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes
6668

67-
guard self.keySizeInBits >= 2048 else {
68-
throw CryptoKitError.incorrectParameterSize
69-
}
69+
try self.init(derRepresentation: derBytes)
7070
}
7171

7272
/// Construct an RSA public key from a PEM representation.
7373
///
7474
/// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate
7575
/// for their use-case.
76+
/// Parameters from RSA PSS keys will be stripped.
7677
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
7778
public init(unsafePEMRepresentation pemRepresentation: String) throws {
78-
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)
79-
80-
guard self.keySizeInBits >= 1024 else {
81-
throw CryptoKitError.incorrectParameterSize
82-
}
79+
let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes
8380

81+
try self.init(unsafeDERRepresentation: derBytes)
8482
}
8583

8684
/// Construct an RSA public key from a DER representation.
8785
///
8886
/// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate
8987
/// for their use-case.
88+
/// Parameters from RSA PSS keys will be stripped.
9089
public init<Bytes: DataProtocol>(derRepresentation: Bytes) throws {
91-
self.backing = try BackingPublicKey(derRepresentation: derRepresentation)
90+
let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation))
91+
92+
self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)
9293

9394
guard self.keySizeInBits >= 2048 else {
9495
throw CryptoKitError.incorrectParameterSize
@@ -99,9 +100,12 @@ extension _RSA.Signing {
99100
///
100101
/// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate
101102
/// for their use-case.
103+
/// Parameters from RSA PSS keys will be stripped.
102104
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
103105
public init<Bytes: DataProtocol>(unsafeDERRepresentation derRepresentation: Bytes) throws {
104-
self.backing = try BackingPublicKey(derRepresentation: derRepresentation)
106+
let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation))
107+
108+
self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)
105109

106110
guard self.keySizeInBits >= 1024 else {
107111
throw CryptoKitError.incorrectParameterSize
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// SubjectPublicKeyInfo.swift
3+
//
4+
//
5+
// Created by Gautier Delorme on 24/09/2024.
6+
//
7+
8+
import SwiftASN1
9+
10+
struct SubjectPublicKeyInfo: DERImplicitlyTaggable, Hashable {
11+
static var defaultIdentifier: ASN1Identifier {
12+
.sequence
13+
}
14+
15+
var algorithmIdentifier: RFC5480AlgorithmIdentifier
16+
17+
var key: ASN1BitString
18+
19+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
20+
// The SPKI block looks like this:
21+
//
22+
// SubjectPublicKeyInfo ::= SEQUENCE {
23+
// algorithm AlgorithmIdentifier,
24+
// subjectPublicKey BIT STRING
25+
// }
26+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
27+
let algorithmIdentifier = try RFC5480AlgorithmIdentifier(derEncoded: &nodes)
28+
let key = try ASN1BitString(derEncoded: &nodes)
29+
30+
return SubjectPublicKeyInfo(algorithmIdentifier: algorithmIdentifier, key: key)
31+
}
32+
}
33+
34+
private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1BitString) {
35+
self.algorithmIdentifier = algorithmIdentifier
36+
self.key = key
37+
}
38+
39+
internal init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: [UInt8]) {
40+
self.algorithmIdentifier = algorithmIdentifier
41+
self.key = ASN1BitString(bytes: key[...])
42+
}
43+
44+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
45+
try coder.appendConstructedNode(identifier: identifier) { coder in
46+
try coder.serialize(self.algorithmIdentifier)
47+
try coder.serialize(self.key)
48+
}
49+
}
50+
}
51+
52+
struct RFC5480AlgorithmIdentifier: DERImplicitlyTaggable, Hashable {
53+
static var defaultIdentifier: ASN1Identifier {
54+
.sequence
55+
}
56+
57+
var algorithm: ASN1ObjectIdentifier
58+
59+
var parameters: ASN1Any?
60+
61+
init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any?) {
62+
self.algorithm = algorithm
63+
self.parameters = parameters
64+
}
65+
66+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
67+
// The AlgorithmIdentifier block looks like this.
68+
//
69+
// AlgorithmIdentifier ::= SEQUENCE {
70+
// algorithm OBJECT IDENTIFIER,
71+
// parameters ANY DEFINED BY algorithm OPTIONAL
72+
// }
73+
//
74+
// ECParameters ::= CHOICE {
75+
// namedCurve OBJECT IDENTIFIER
76+
// -- implicitCurve NULL
77+
// -- specifiedCurve SpecifiedECDomain
78+
// }
79+
//
80+
// We don't bother with helpers: we just try to decode it directly.
81+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
82+
let algorithmOID = try ASN1ObjectIdentifier(derEncoded: &nodes)
83+
84+
let parameters = nodes.next().map { ASN1Any(derEncoded: $0) }
85+
86+
return .init(algorithm: algorithmOID, parameters: parameters)
87+
}
88+
}
89+
90+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
91+
try coder.appendConstructedNode(identifier: identifier) { coder in
92+
try coder.serialize(self.algorithm)
93+
if let parameters = self.parameters {
94+
try coder.serialize(parameters)
95+
}
96+
}
97+
}
98+
}
99+
100+
extension SubjectPublicKeyInfo {
101+
static func stripRsaPssParameters(derEncoded: [UInt8]) throws -> [UInt8] {
102+
guard var spki = try? SubjectPublicKeyInfo(derEncoded: derEncoded) else {
103+
// If it's not a SPKI then it can't be a PSS key so we just return it.
104+
return derEncoded
105+
}
106+
107+
if spki.algorithmIdentifier.algorithm == .AlgorithmIdentifier.rsaPSS {
108+
spki.algorithmIdentifier.parameters = try ASN1Any(erasing: ASN1Null())
109+
}
110+
111+
var serializer = DER.Serializer()
112+
try serializer.serialize(spki)
113+
114+
return serializer.serializedBytes
115+
}
116+
}

Tests/_CryptoExtrasTests/TestRSASigning.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,56 @@ import Crypto
1717
@testable import _CryptoExtras
1818

1919
final class TestRSASigning: XCTestCase {
20+
21+
func test_rsaPssParameters() throws {
22+
let rsaPssPublicKeyPEM = """
23+
-----BEGIN PUBLIC KEY-----
24+
MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB
25+
CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAvcOaxSJoSiiXIQme6HEF
26+
d0/QHjtk5+U1RbeejxeUR80Q1f8E5v7+uIBEFVbwZpIJZtmSB3bxbS31rOBGVcrI
27+
IAfCnUlq6DK1fEL1fgn61XMiSSyKr75L5ZXv9Rib95h3lrNbhW0DUaXzf61kw3+Z
28+
4KV1btD7C+fdiLzPm18UQv8jJSbCE6hv3MWdkG3NcwgZC+iXwz3DFcsclyYg/+Om
29+
0hx8UJ/34vNpeE+0MHwyl0j/eO7izrzTZnfsm4ZRaU3mw0ORDQmo8MyIDFa55R/v
30+
30otk9y3LFkaeEyl1+7VFjJzoOEtze6VkTEzV8e/BTu4eXlKQ6CEYvHhUkNmHGC+
31+
mwIDAQAB
32+
-----END PUBLIC KEY-----
33+
"""
34+
35+
let rsaPssPublicKeyDER = Data(base64Encoded:
36+
"MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB" +
37+
"CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAxPJvJDGPzb2rBWfE5JCB" +
38+
"p2OAmR46zIbaVjIR1lUabKCdb5CxdnHvQBymp3AlvOGTNzSLxTXOaYn7MzeFvAVI" +
39+
"mpRRzXzalG0ZfM4AkPBtjPz93pPLWEfgk+/i+JLWlWUStUGgGKNbJn4yJ8cJ8n+E" +
40+
"/5+ry+tUYHEJm9A4/HwH4Agg78kPtnEvIvdC/aIw4TEpjZDewVNAEW2rBuQNd01r" +
41+
"fAo2CSzbH76gL02mnLuvh1xyrKz+v9gyo9Taw273KU+83HPs91obgX4WpEfWOnd6" +
42+
"LMJHRZo92FXnW6IHkCdz12khyS1TVIq4ONwjvmS6q3V9UwQg/uuyoSNnRfWXvZXQ" +
43+
"aQIDAQAB"
44+
)!
45+
46+
let rsaPssPublicKey1024PEM = """
47+
-----BEGIN PUBLIC KEY-----
48+
MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI
49+
MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQDGv67JltnwgkFxQOI8YUldC1LG
50+
rCLOpyAN/Vq4WyLQ6TKcPevcYA8XmuXL8tC85rMQQG1GMwMWKcf/kf0NDKblUFjZ
51+
BevUPmQF3Jadsn9ST+RMn8D+kq31Hdc0UG/WjZSpMHTkc8SWIjr2E6DIILn/OA/w
52+
G3jVOeTsEfUeGExhVwIDAQAB
53+
-----END PUBLIC KEY-----
54+
"""
55+
56+
let rsaPssPublicKey1024DER = Data(base64Encoded:
57+
"MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI" +
58+
"MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQC7LZLbFhzOCoTmXEABRsyOkRiB" +
59+
"18XkkJBwTkn2JES1jVZogXtcq5ZV+KmPulOrzLuaC45IliS5OZ1hJuC7m8/devXk" +
60+
"HaNId+y2cZxRYnfNCsEzvTryxt+01VMQJA4VHsdmhJO6TEIUzDIfj3BlahZuoU11" +
61+
"VZ4wgVIpYymQidJigQIDAQAB"
62+
)!
63+
64+
XCTAssertEqual(try _RSA.Signing.PublicKey(pemRepresentation: rsaPssPublicKeyPEM).keySizeInBits, 2048)
65+
XCTAssertEqual(try _RSA.Signing.PublicKey(derRepresentation: rsaPssPublicKeyDER).keySizeInBits, 2048)
66+
XCTAssertEqual(try _RSA.Signing.PublicKey(unsafePEMRepresentation: rsaPssPublicKey1024PEM).keySizeInBits, 1024)
67+
XCTAssertEqual(try _RSA.Signing.PublicKey(unsafeDERRepresentation: rsaPssPublicKey1024DER).keySizeInBits, 1024)
68+
}
69+
2070
func test_wycheproofPKCS1Vectors() throws {
2171
try wycheproofTest(
2272
jsonName: "rsa_signature_test",

0 commit comments

Comments
 (0)