Skip to content

Commit 8dafe0f

Browse files
extras: Provide initializers for RSA keys from RSA numbers as bytes (apple#247)
Co-authored-by: Cory Benfield <[email protected]>
1 parent e2dd5a5 commit 8dafe0f

File tree

8 files changed

+251
-102
lines changed

8 files changed

+251
-102
lines changed

Sources/_CryptoExtras/RSA/RSA+BlindSigning.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ extension _RSA.BlindSigning {
8383
}
8484
}
8585

86+
/// Construct a RSA public key with the specified parameters.
87+
public init(n: some ContiguousBytes, e: some ContiguousBytes, parameters: Parameters) throws {
88+
self.backing = try BackingPublicKey(n: n, e: e)
89+
self.parameters = parameters
90+
}
91+
8692
public var pkcs1DERRepresentation: Data {
8793
self.backing.pkcs1DERRepresentation
8894
}
@@ -171,6 +177,12 @@ extension _RSA.BlindSigning {
171177
}
172178
}
173179

180+
/// Construct an RSA private key with the specified parameters.
181+
public init(n: some ContiguousBytes, e: some ContiguousBytes, d: some ContiguousBytes, p: some ContiguousBytes, q: some ContiguousBytes, parameters: Parameters) throws {
182+
self.backing = try BackingPrivateKey(n: n, e: e, d: d, p: p, q: q)
183+
self.parameters = parameters
184+
}
185+
174186
/// Randomly generate a new RSA private key of a given size.
175187
///
176188
/// This constructor will refuse to generate keys smaller than 2048 bits. Callers that want to enforce minimum

Sources/_CryptoExtras/RSA/RSA.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ extension _RSA.Signing {
9898
}
9999
}
100100

101+
/// Construct an RSA public key with the specified parameters.
102+
///
103+
/// Only the BoringSSL backend provides APIs to create a key from its parameters so we first create a BoringSSL
104+
/// key, and then pass it to the platform-specific initializer that accepts a BoringSSL key.
105+
///
106+
/// On Darwin platforms, this will serialize it to PEM format, and then construct a platform-specific key from
107+
/// the PEM representation.
108+
public init(n: some ContiguousBytes, e: some ContiguousBytes) throws {
109+
self.backing = try BackingPublicKey(BoringSSLRSAPublicKey(n: n, e: e))
110+
}
111+
101112
public var pkcs1DERRepresentation: Data {
102113
self.backing.pkcs1DERRepresentation
103114
}
@@ -178,6 +189,17 @@ extension _RSA.Signing {
178189
}
179190
}
180191

192+
/// Construct an RSA private key with the specified parameters.
193+
///
194+
/// Only the BoringSSL backend provides APIs to create a key from its parameters so we first create a BoringSSL
195+
/// key, and then pass it to the platform-specific initializer that accepts a BoringSSL key.
196+
///
197+
/// On Darwin platforms, this will serialize it to DER format, and then construct a platform-specific key from
198+
/// the DER representation.
199+
public init(n: some ContiguousBytes, e: some ContiguousBytes, d: some ContiguousBytes, p: some ContiguousBytes, q: some ContiguousBytes) throws {
200+
self.backing = try BackingPrivateKey(BoringSSLRSAPrivateKey(n: n, e: e, d: d, p: p, q: q))
201+
}
202+
181203
/// Randomly generate a new RSA private key of a given size.
182204
///
183205
/// This constructor will refuse to generate keys smaller than 2048 bits. Callers that want to enforce minimum
@@ -444,6 +466,17 @@ extension _RSA.Encryption {
444466
guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize }
445467
}
446468

469+
/// Construct an RSA public key with the specified parameters.
470+
///
471+
/// Only the BoringSSL backend provides APIs to create a key from its parameters so we first create a BoringSSL
472+
/// key, and then pass it to the platform-specific initializer that accepts a BoringSSL key.
473+
///
474+
/// On Darwin platforms, this will serialize it to DER format, and then construct a platform-specific key from
475+
/// the DER representation.
476+
public init(n: some ContiguousBytes, e: some ContiguousBytes) throws {
477+
self.backing = try BackingPublicKey(BoringSSLRSAPublicKey(n: n, e: e))
478+
}
479+
447480
public var pkcs1DERRepresentation: Data { self.backing.pkcs1DERRepresentation }
448481
public var pkcs1PEMRepresentation: String { self.backing.pkcs1PEMRepresentation }
449482
public var derRepresentation: Data { self.backing.derRepresentation }
@@ -494,6 +527,18 @@ extension _RSA.Encryption {
494527
guard self.keySizeInBits >= 1024, self.keySizeInBits % 8 == 0 else { throw CryptoKitError.incorrectParameterSize }
495528
}
496529

530+
531+
/// Construct an RSA private key with the specified parameters.
532+
///
533+
/// Only the BoringSSL backend provides APIs to create a key from its parameters so we first create a BoringSSL
534+
/// key, and then pass it to the platform-specific initializer that accepts a BoringSSL key.
535+
///
536+
/// On Darwin platforms, this will serialize it to PEM format, and then construct a platform-specific key from
537+
/// the PEM representation.
538+
public init(n: some ContiguousBytes, e: some ContiguousBytes, d: some ContiguousBytes, p: some ContiguousBytes, q: some ContiguousBytes) throws {
539+
self.backing = try BackingPrivateKey(BoringSSLRSAPrivateKey(n: n, e: e, d: d, p: p, q: q))
540+
}
541+
497542
/// Randomly generate a new RSA private key of a given size.
498543
///
499544
/// This constructor will refuse to generate keys smaller than 2048 bits. Callers that want to enforce minimum

Sources/_CryptoExtras/RSA/RSA_boring.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ internal struct BoringSSLRSAPublicKey: Sendable {
2929
self.backing = try Backing(derRepresentation: derRepresentation)
3030
}
3131

32+
init(n: some ContiguousBytes, e: some ContiguousBytes) throws {
33+
self.backing = try Backing(n: n, e: e)
34+
}
35+
36+
init(_ other: BoringSSLRSAPublicKey) throws {
37+
self = other
38+
}
39+
3240
var pkcs1DERRepresentation: Data {
3341
self.backing.pkcs1DERRepresentation
3442
}
@@ -66,6 +74,14 @@ internal struct BoringSSLRSAPrivateKey: Sendable {
6674
self.backing = try Backing(derRepresentation: derRepresentation)
6775
}
6876

77+
init(n: some ContiguousBytes, e: some ContiguousBytes, d: some ContiguousBytes, p: some ContiguousBytes, q: some ContiguousBytes) throws {
78+
self.backing = try Backing(n: n, e: e, d: d, p: p, q: q)
79+
}
80+
81+
init(_ other: BoringSSLRSAPrivateKey) throws {
82+
self = other
83+
}
84+
6985
init(keySize: _RSA.Signing.KeySize) throws {
7086
self.backing = try Backing(keySize: keySize)
7187
}
@@ -222,6 +238,20 @@ extension BoringSSLRSAPublicKey {
222238
}
223239
}
224240

241+
fileprivate init(n: some ContiguousBytes, e: some ContiguousBytes) throws {
242+
self.pointer = CCryptoBoringSSL_EVP_PKEY_new()
243+
let n = try ArbitraryPrecisionInteger(bytes: n)
244+
let e = try ArbitraryPrecisionInteger(bytes: e)
245+
246+
// Create BoringSSL RSA key.
247+
guard let rsaPtr = n.withUnsafeBignumPointer({ n in
248+
e.withUnsafeBignumPointer { e in
249+
CCryptoBoringSSL_RSA_new_public_key(n, e)
250+
}
251+
}) else { throw CryptoKitError.internalBoringSSLError() }
252+
CCryptoBoringSSL_EVP_PKEY_assign_RSA(self.pointer, rsaPtr)
253+
}
254+
225255
fileprivate var pkcs1DERRepresentation: Data {
226256
return BIOHelper.withWritableMemoryBIO { bio in
227257
let rsaPublicKey = CCryptoBoringSSL_EVP_PKEY_get0_RSA(self.pointer)
@@ -486,6 +516,43 @@ extension BoringSSLRSAPrivateKey {
486516
CCryptoBoringSSL_EVP_PKEY_assign_RSA(self.pointer, rsaPrivateKey)
487517
}
488518

519+
520+
fileprivate init(n: some ContiguousBytes, e: some ContiguousBytes, d: some ContiguousBytes, p: some ContiguousBytes, q: some ContiguousBytes) throws {
521+
self.pointer = CCryptoBoringSSL_EVP_PKEY_new()
522+
let n = try ArbitraryPrecisionInteger(bytes: n)
523+
let e = try ArbitraryPrecisionInteger(bytes: e)
524+
let d = try ArbitraryPrecisionInteger(bytes: d)
525+
let p = try ArbitraryPrecisionInteger(bytes: p)
526+
let q = try ArbitraryPrecisionInteger(bytes: q)
527+
528+
// Compute the CRT params.
529+
let dp = try FiniteFieldArithmeticContext(fieldSize: p - 1).residue(d)
530+
let dq = try FiniteFieldArithmeticContext(fieldSize: q - 1).residue(d)
531+
guard let qi = try FiniteFieldArithmeticContext(fieldSize: p).inverse(q) else {
532+
throw CryptoKitError.internalBoringSSLError()
533+
}
534+
535+
// Create BoringSSL RSA key.
536+
guard let rsaPtr = n.withUnsafeBignumPointer({ n in
537+
e.withUnsafeBignumPointer { e in
538+
d.withUnsafeBignumPointer { d in
539+
p.withUnsafeBignumPointer { p in
540+
q.withUnsafeBignumPointer { q in
541+
dp.withUnsafeBignumPointer { dp in
542+
dq.withUnsafeBignumPointer { dq in
543+
qi.withUnsafeBignumPointer { qi in
544+
CCryptoBoringSSL_RSA_new_private_key(n, e, d, p, q, dp, dq, qi)
545+
}
546+
}
547+
}
548+
}
549+
}
550+
}
551+
}
552+
}) else { throw CryptoKitError.internalBoringSSLError() }
553+
CCryptoBoringSSL_EVP_PKEY_assign_RSA(self.pointer, rsaPtr)
554+
}
555+
489556
private static func pkcs8DERPrivateKey<Bytes: ContiguousBytes>(_ derRepresentation: Bytes) -> OpaquePointer? {
490557
return derRepresentation.withUnsafeBytes { derPtr in
491558
return BIOHelper.withReadOnlyMemoryBIO(wrapping: derPtr) { bio in

Sources/_CryptoExtras/RSA/RSA_security.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ internal struct SecurityRSAPublicKey: @unchecked Sendable {
4343
self.backing = unwrappedKey
4444
}
4545

46+
init(_ boringSSLKey: BoringSSLRSAPublicKey) throws {
47+
try self.init(derRepresentation: boringSSLKey.derRepresentation)
48+
}
49+
4650
var pkcs1DERRepresentation: Data {
4751
var error: Unmanaged<CFError>? = nil
4852
let representation = SecKeyCopyExternalRepresentation(self.backing, &error)
@@ -135,6 +139,10 @@ internal struct SecurityRSAPrivateKey: @unchecked Sendable {
135139
self.backing = unwrappedKey
136140
}
137141

142+
init(_ boringSSLKey: BoringSSLRSAPrivateKey) throws {
143+
try self.init(derRepresentation: boringSSLKey.derRepresentation)
144+
}
145+
138146
var derRepresentation: Data {
139147
var error: Unmanaged<CFError>? = nil
140148
let representation = SecKeyCopyExternalRepresentation(self.backing, &error)

Sources/_CryptoExtras/Util/BoringSSLHelpers.swift

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -92,86 +92,3 @@ extension FixedWidthInteger {
9292
return try block(&bn)
9393
}
9494
}
95-
96-
extension _RSA.BlindSigning.PublicKey {
97-
/// Construct a platform-specific RSA public key with the specified parameters.
98-
///
99-
/// This constructor is used in tests in cases where test vectors provide the key information this way.
100-
///
101-
/// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL
102-
/// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation.
103-
internal init(nHexString: String, eHexString: String, parameters: Parameters) throws {
104-
let n = try ArbitraryPrecisionInteger(hexString: nHexString)
105-
let e = try ArbitraryPrecisionInteger(hexString: eHexString)
106-
107-
// Create BoringSSL RSA key.
108-
guard let rsaPtr = n.withUnsafeBignumPointer({ n in
109-
e.withUnsafeBignumPointer { e in
110-
CCryptoBoringSSL_RSA_new_public_key(n, e)
111-
}
112-
}) else { throw CryptoKitError.internalBoringSSLError() }
113-
defer { CCryptoBoringSSL_RSA_free(rsaPtr) }
114-
115-
// Get PEM representation for key.
116-
let pemRepresentation = BIOHelper.withWritableMemoryBIO { bio in
117-
precondition(CCryptoBoringSSL_PEM_write_bio_RSAPublicKey(bio, rsaPtr) == 1)
118-
return try! String(copyingUTF8MemoryBIO: bio)
119-
}
120-
121-
// Create a key (which might be backed by Security framework) from PEM representation.
122-
try self.init(pemRepresentation: pemRepresentation, parameters: parameters)
123-
}
124-
}
125-
126-
127-
extension _RSA.BlindSigning.PrivateKey {
128-
/// Construct a platform-specific RSA private key with the specified parameters.
129-
///
130-
/// This constructor is used in tests in cases where test vectors provide the key information this way.
131-
///
132-
/// Only the BoringSSL backend provides APIs to create the key from its parameters so we first create a BoringSSL
133-
/// key, serialize it to PEM format, and then construct a platform specific key from the PEM representation.
134-
internal init(nHexString: String, eHexString: String, dHexString: String, pHexString: String, qHexString: String, parameters: Parameters) throws {
135-
let n = try ArbitraryPrecisionInteger(hexString: nHexString)
136-
let e = try ArbitraryPrecisionInteger(hexString: eHexString)
137-
let d = try ArbitraryPrecisionInteger(hexString: dHexString)
138-
let p = try ArbitraryPrecisionInteger(hexString: pHexString)
139-
let q = try ArbitraryPrecisionInteger(hexString: qHexString)
140-
141-
// Compute the CRT params.
142-
let dp = try FiniteFieldArithmeticContext(fieldSize: p - 1).residue(d)
143-
let dq = try FiniteFieldArithmeticContext(fieldSize: q - 1).residue(d)
144-
guard let qi = try FiniteFieldArithmeticContext(fieldSize: p).inverse(q) else {
145-
throw CryptoKitError.internalBoringSSLError()
146-
}
147-
148-
// Create BoringSSL RSA key.
149-
guard let rsaPtr = n.withUnsafeBignumPointer({ n in
150-
e.withUnsafeBignumPointer { e in
151-
d.withUnsafeBignumPointer { d in
152-
p.withUnsafeBignumPointer { p in
153-
q.withUnsafeBignumPointer { q in
154-
dp.withUnsafeBignumPointer { dp in
155-
dq.withUnsafeBignumPointer { dq in
156-
qi.withUnsafeBignumPointer { qi in
157-
CCryptoBoringSSL_RSA_new_private_key(n, e, d, p, q, dp, dq, qi)
158-
}
159-
}
160-
}
161-
}
162-
}
163-
}
164-
}
165-
}) else { throw CryptoKitError.internalBoringSSLError() }
166-
defer { CCryptoBoringSSL_RSA_free(rsaPtr) }
167-
168-
// Get PEM representation for key.
169-
let pemRepresentation = BIOHelper.withWritableMemoryBIO { bio in
170-
precondition(CCryptoBoringSSL_PEM_write_bio_RSAPrivateKey(bio, rsaPtr, nil, nil, 0, nil, nil) == 1)
171-
return try! String(copyingUTF8MemoryBIO: bio)
172-
}
173-
174-
// Create a key (which might be backed by Security framework) from PEM representation.
175-
try self.init(pemRepresentation: pemRepresentation, parameters: parameters)
176-
}
177-
}

0 commit comments

Comments
 (0)