Skip to content

Commit 2ec52ab

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

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
@@ -84,7 +84,9 @@ let package = Package(
8484
.library(name: "CCryptoBoringSSL", type: .static, targets: ["CCryptoBoringSSL"]),
8585
MANGLE_END */
8686
],
87-
dependencies: [],
87+
dependencies: [
88+
.package(url: "https://github.com/apple/swift-asn1.git", .upToNextMajor(from: "1.2.0"))
89+
],
8890
targets: [
8991
.target(
9092
name: "CCryptoBoringSSL",
@@ -141,7 +143,8 @@ let package = Package(
141143
"CCryptoBoringSSL",
142144
"CCryptoBoringSSLShims",
143145
"CryptoBoringWrapper",
144-
"Crypto"
146+
"Crypto",
147+
.product(name: "SwiftASN1", package: "swift-asn1")
145148
],
146149
exclude: privacyManifestExclude + [
147150
"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
@@ -57,34 +58,34 @@ extension _RSA.Signing {
5758
///
5859
/// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate
5960
/// for their use-case.
61+
/// Parameters from RSA PSS keys will be stripped.
6062
public init(pemRepresentation: String) throws {
61-
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)
63+
let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes
6264

63-
guard self.keySizeInBits >= 2048 else {
64-
throw CryptoKitError.incorrectParameterSize
65-
}
65+
try self.init(derRepresentation: derBytes)
6666
}
6767

6868
/// Construct an RSA public key from a PEM representation.
6969
///
7070
/// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate
7171
/// for their use-case.
72+
/// Parameters from RSA PSS keys will be stripped.
7273
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
7374
public init(unsafePEMRepresentation pemRepresentation: String) throws {
74-
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)
75-
76-
guard self.keySizeInBits >= 1024 else {
77-
throw CryptoKitError.incorrectParameterSize
78-
}
75+
let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes
7976

77+
try self.init(unsafeDERRepresentation: derBytes)
8078
}
8179

8280
/// Construct an RSA public key from a DER representation.
8381
///
8482
/// This constructor supports key sizes of 2048 bits or more. Users should validate that key sizes are appropriate
8583
/// for their use-case.
84+
/// Parameters from RSA PSS keys will be stripped.
8685
public init<Bytes: DataProtocol>(derRepresentation: Bytes) throws {
87-
self.backing = try BackingPublicKey(derRepresentation: derRepresentation)
86+
let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation))
87+
88+
self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)
8889

8990
guard self.keySizeInBits >= 2048 else {
9091
throw CryptoKitError.incorrectParameterSize
@@ -95,9 +96,12 @@ extension _RSA.Signing {
9596
///
9697
/// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate
9798
/// for their use-case.
99+
/// Parameters from RSA PSS keys will be stripped.
98100
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
99101
public init<Bytes: DataProtocol>(unsafeDERRepresentation derRepresentation: Bytes) throws {
100-
self.backing = try BackingPublicKey(derRepresentation: derRepresentation)
102+
let sanitizedDer = try SubjectPublicKeyInfo.stripRsaPssParameters(derEncoded: [UInt8](derRepresentation))
103+
104+
self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)
101105

102106
guard self.keySizeInBits >= 1024 else {
103107
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)