Skip to content

Commit b639b5b

Browse files
Support loading RSAPSS public keys with parameters (#268)
* Support loading RSAPSS public keys with parameters Parameters are stripped and the key is treated as a regular public key. * address review + reset algorithm as well * run update_cmakelists.sh * attempt to fix the cmake build * include FetchContent * early return for non-PSS keys * simplify code
1 parent ffca28b commit b639b5b

File tree

6 files changed

+206
-14
lines changed

6 files changed

+206
-14
lines changed

CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,15 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
4848
find_package(Foundation CONFIG)
4949
endif()
5050

51+
include(FetchContent)
52+
find_package(SwiftASN1 CONFIG)
53+
if(NOT SwiftASN1_FOUND)
54+
message("-- Vending swift-asn1")
55+
FetchContent_Declare(ASN1
56+
GIT_REPOSITORY https://github.com/apple/swift-asn1
57+
GIT_TAG 1.1.0)
58+
FetchContent_MakeAvailable(ASN1)
59+
endif()
60+
5161
add_subdirectory(Sources)
5262
add_subdirectory(cmake/modules)

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", 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/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ add_library(_CryptoExtras
2626
"Util/PEMDocument.swift"
2727
"Util/RandomBytes.swift"
2828
"Util/Shared/ArbitraryPrecisionInteger_boring.swift"
29-
"Util/Shared/FiniteFieldArithmeticContext_boring.swift")
29+
"Util/Shared/FiniteFieldArithmeticContext_boring.swift"
30+
"Util/SubjectPublicKeyInfo.swift")
3031

3132
target_include_directories(_CryptoExtras PRIVATE
3233
$<TARGET_PROPERTY:CCryptoBoringSSL,INCLUDE_DIRECTORIES>

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: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftCrypto open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftASN1
16+
17+
struct SubjectPublicKeyInfo: DERImplicitlyTaggable, Hashable {
18+
static var defaultIdentifier: ASN1Identifier {
19+
.sequence
20+
}
21+
22+
var algorithmIdentifier: RFC5480AlgorithmIdentifier
23+
24+
var key: ASN1BitString
25+
26+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
27+
// The SPKI block looks like this:
28+
//
29+
// SubjectPublicKeyInfo ::= SEQUENCE {
30+
// algorithm AlgorithmIdentifier,
31+
// subjectPublicKey BIT STRING
32+
// }
33+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
34+
let algorithmIdentifier = try RFC5480AlgorithmIdentifier(derEncoded: &nodes)
35+
let key = try ASN1BitString(derEncoded: &nodes)
36+
37+
return SubjectPublicKeyInfo(algorithmIdentifier: algorithmIdentifier, key: key)
38+
}
39+
}
40+
41+
private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1BitString) {
42+
self.algorithmIdentifier = algorithmIdentifier
43+
self.key = key
44+
}
45+
46+
internal init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: [UInt8]) {
47+
self.algorithmIdentifier = algorithmIdentifier
48+
self.key = ASN1BitString(bytes: key[...])
49+
}
50+
51+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
52+
try coder.appendConstructedNode(identifier: identifier) { coder in
53+
try coder.serialize(self.algorithmIdentifier)
54+
try coder.serialize(self.key)
55+
}
56+
}
57+
}
58+
59+
struct RFC5480AlgorithmIdentifier: DERImplicitlyTaggable, Hashable {
60+
static var defaultIdentifier: ASN1Identifier {
61+
.sequence
62+
}
63+
64+
var algorithm: ASN1ObjectIdentifier
65+
66+
var parameters: ASN1Any?
67+
68+
init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any?) {
69+
self.algorithm = algorithm
70+
self.parameters = parameters
71+
}
72+
73+
init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
74+
// The AlgorithmIdentifier block looks like this.
75+
//
76+
// AlgorithmIdentifier ::= SEQUENCE {
77+
// algorithm OBJECT IDENTIFIER,
78+
// parameters ANY DEFINED BY algorithm OPTIONAL
79+
// }
80+
//
81+
// ECParameters ::= CHOICE {
82+
// namedCurve OBJECT IDENTIFIER
83+
// -- implicitCurve NULL
84+
// -- specifiedCurve SpecifiedECDomain
85+
// }
86+
//
87+
// We don't bother with helpers: we just try to decode it directly.
88+
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
89+
let algorithmOID = try ASN1ObjectIdentifier(derEncoded: &nodes)
90+
91+
let parameters = nodes.next().map { ASN1Any(derEncoded: $0) }
92+
93+
return .init(algorithm: algorithmOID, parameters: parameters)
94+
}
95+
}
96+
97+
func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
98+
try coder.appendConstructedNode(identifier: identifier) { coder in
99+
try coder.serialize(self.algorithm)
100+
if let parameters = self.parameters {
101+
try coder.serialize(parameters)
102+
}
103+
}
104+
}
105+
}
106+
107+
extension SubjectPublicKeyInfo {
108+
static func stripRsaPssParameters(derEncoded: [UInt8]) throws -> [UInt8] {
109+
guard var spki = try? SubjectPublicKeyInfo(derEncoded: derEncoded),
110+
spki.algorithmIdentifier.algorithm == .AlgorithmIdentifier.rsaPSS
111+
else {
112+
// If it's neither a SPKI nor a PSS key, we don't have to modify it.
113+
return derEncoded
114+
}
115+
116+
spki.algorithmIdentifier.algorithm = .AlgorithmIdentifier.rsaEncryption
117+
spki.algorithmIdentifier.parameters = try ASN1Any(erasing: ASN1Null())
118+
119+
var serializer = DER.Serializer()
120+
try serializer.serialize(spki)
121+
122+
return serializer.serializedBytes
123+
}
124+
}

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)