Skip to content

Commit

Permalink
Support loading RSAPSS public keys with parameters (#268)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gautierdelorme authored Oct 3, 2024
1 parent ffca28b commit b639b5b
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 14 deletions.
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,15 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL Darwin)
find_package(Foundation CONFIG)
endif()

include(FetchContent)
find_package(SwiftASN1 CONFIG)
if(NOT SwiftASN1_FOUND)
message("-- Vending swift-asn1")
FetchContent_Declare(ASN1
GIT_REPOSITORY https://github.com/apple/swift-asn1
GIT_TAG 1.1.0)
FetchContent_MakeAvailable(ASN1)
endif()

add_subdirectory(Sources)
add_subdirectory(cmake/modules)
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ let package = Package(
.library(name: "CCryptoBoringSSL", type: .static, targets: ["CCryptoBoringSSL"]),
MANGLE_END */
],
dependencies: [],
dependencies: [
.package(url: "https://github.com/apple/swift-asn1.git", from: "1.2.0")
],
targets: [
.target(
name: "CCryptoBoringSSL",
Expand Down Expand Up @@ -141,7 +143,8 @@ let package = Package(
"CCryptoBoringSSL",
"CCryptoBoringSSLShims",
"CryptoBoringWrapper",
"Crypto"
"Crypto",
.product(name: "SwiftASN1", package: "swift-asn1")
],
exclude: privacyManifestExclude + [
"CMakeLists.txt",
Expand Down
3 changes: 2 additions & 1 deletion Sources/_CryptoExtras/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ add_library(_CryptoExtras
"Util/PEMDocument.swift"
"Util/RandomBytes.swift"
"Util/Shared/ArbitraryPrecisionInteger_boring.swift"
"Util/Shared/FiniteFieldArithmeticContext_boring.swift")
"Util/Shared/FiniteFieldArithmeticContext_boring.swift"
"Util/SubjectPublicKeyInfo.swift")

target_include_directories(_CryptoExtras PRIVATE
$<TARGET_PROPERTY:CCryptoBoringSSL,INCLUDE_DIRECTORIES>
Expand Down
26 changes: 15 additions & 11 deletions Sources/_CryptoExtras/RSA/RSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===//
import Foundation
import Crypto
import SwiftASN1

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

guard self.keySizeInBits >= 2048 else {
throw CryptoKitError.incorrectParameterSize
}
try self.init(derRepresentation: derBytes)
}

/// Construct an RSA public key from a PEM representation.
///
/// This constructor supports key sizes of 1024 bits or more. Users should validate that key sizes are appropriate
/// for their use-case.
/// Parameters from RSA PSS keys will be stripped.
/// - Warning: Key sizes less than 2048 are not recommended and should only be used for compatibility reasons.
public init(unsafePEMRepresentation pemRepresentation: String) throws {
self.backing = try BackingPublicKey(pemRepresentation: pemRepresentation)

guard self.keySizeInBits >= 1024 else {
throw CryptoKitError.incorrectParameterSize
}
let derBytes = try PEMDocument(pemString: pemRepresentation).derBytes

try self.init(unsafeDERRepresentation: derBytes)
}

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

self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)

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

self.backing = try BackingPublicKey(derRepresentation: sanitizedDer)

guard self.keySizeInBits >= 1024 else {
throw CryptoKitError.incorrectParameterSize
Expand Down
124 changes: 124 additions & 0 deletions Sources/_CryptoExtras/Util/SubjectPublicKeyInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2024 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftASN1

struct SubjectPublicKeyInfo: DERImplicitlyTaggable, Hashable {
static var defaultIdentifier: ASN1Identifier {
.sequence
}

var algorithmIdentifier: RFC5480AlgorithmIdentifier

var key: ASN1BitString

init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
// The SPKI block looks like this:
//
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING
// }
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
let algorithmIdentifier = try RFC5480AlgorithmIdentifier(derEncoded: &nodes)
let key = try ASN1BitString(derEncoded: &nodes)

return SubjectPublicKeyInfo(algorithmIdentifier: algorithmIdentifier, key: key)
}
}

private init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: ASN1BitString) {
self.algorithmIdentifier = algorithmIdentifier
self.key = key
}

internal init(algorithmIdentifier: RFC5480AlgorithmIdentifier, key: [UInt8]) {
self.algorithmIdentifier = algorithmIdentifier
self.key = ASN1BitString(bytes: key[...])
}

func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
try coder.appendConstructedNode(identifier: identifier) { coder in
try coder.serialize(self.algorithmIdentifier)
try coder.serialize(self.key)
}
}
}

struct RFC5480AlgorithmIdentifier: DERImplicitlyTaggable, Hashable {
static var defaultIdentifier: ASN1Identifier {
.sequence
}

var algorithm: ASN1ObjectIdentifier

var parameters: ASN1Any?

init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any?) {
self.algorithm = algorithm
self.parameters = parameters
}

init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
// The AlgorithmIdentifier block looks like this.
//
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL
// }
//
// ECParameters ::= CHOICE {
// namedCurve OBJECT IDENTIFIER
// -- implicitCurve NULL
// -- specifiedCurve SpecifiedECDomain
// }
//
// We don't bother with helpers: we just try to decode it directly.
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
let algorithmOID = try ASN1ObjectIdentifier(derEncoded: &nodes)

let parameters = nodes.next().map { ASN1Any(derEncoded: $0) }

return .init(algorithm: algorithmOID, parameters: parameters)
}
}

func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {
try coder.appendConstructedNode(identifier: identifier) { coder in
try coder.serialize(self.algorithm)
if let parameters = self.parameters {
try coder.serialize(parameters)
}
}
}
}

extension SubjectPublicKeyInfo {
static func stripRsaPssParameters(derEncoded: [UInt8]) throws -> [UInt8] {
guard var spki = try? SubjectPublicKeyInfo(derEncoded: derEncoded),
spki.algorithmIdentifier.algorithm == .AlgorithmIdentifier.rsaPSS
else {
// If it's neither a SPKI nor a PSS key, we don't have to modify it.
return derEncoded
}

spki.algorithmIdentifier.algorithm = .AlgorithmIdentifier.rsaEncryption
spki.algorithmIdentifier.parameters = try ASN1Any(erasing: ASN1Null())

var serializer = DER.Serializer()
try serializer.serialize(spki)

return serializer.serializedBytes
}
}
50 changes: 50 additions & 0 deletions Tests/_CryptoExtrasTests/TestRSASigning.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,56 @@ import Crypto
@testable import _CryptoExtras

final class TestRSASigning: XCTestCase {

func test_rsaPssParameters() throws {
let rsaPssPublicKeyPEM = """
-----BEGIN PUBLIC KEY-----
MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB
CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAvcOaxSJoSiiXIQme6HEF
d0/QHjtk5+U1RbeejxeUR80Q1f8E5v7+uIBEFVbwZpIJZtmSB3bxbS31rOBGVcrI
IAfCnUlq6DK1fEL1fgn61XMiSSyKr75L5ZXv9Rib95h3lrNbhW0DUaXzf61kw3+Z
4KV1btD7C+fdiLzPm18UQv8jJSbCE6hv3MWdkG3NcwgZC+iXwz3DFcsclyYg/+Om
0hx8UJ/34vNpeE+0MHwyl0j/eO7izrzTZnfsm4ZRaU3mw0ORDQmo8MyIDFa55R/v
30otk9y3LFkaeEyl1+7VFjJzoOEtze6VkTEzV8e/BTu4eXlKQ6CEYvHhUkNmHGC+
mwIDAQAB
-----END PUBLIC KEY-----
"""

let rsaPssPublicKeyDER = Data(base64Encoded:
"MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEB" +
"CDALBglghkgBZQMEAgGiAwIBIAOCAQ8AMIIBCgKCAQEAxPJvJDGPzb2rBWfE5JCB" +
"p2OAmR46zIbaVjIR1lUabKCdb5CxdnHvQBymp3AlvOGTNzSLxTXOaYn7MzeFvAVI" +
"mpRRzXzalG0ZfM4AkPBtjPz93pPLWEfgk+/i+JLWlWUStUGgGKNbJn4yJ8cJ8n+E" +
"/5+ry+tUYHEJm9A4/HwH4Agg78kPtnEvIvdC/aIw4TEpjZDewVNAEW2rBuQNd01r" +
"fAo2CSzbH76gL02mnLuvh1xyrKz+v9gyo9Taw273KU+83HPs91obgX4WpEfWOnd6" +
"LMJHRZo92FXnW6IHkCdz12khyS1TVIq4ONwjvmS6q3V9UwQg/uuyoSNnRfWXvZXQ" +
"aQIDAQAB"
)!

let rsaPssPublicKey1024PEM = """
-----BEGIN PUBLIC KEY-----
MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI
MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQDGv67JltnwgkFxQOI8YUldC1LG
rCLOpyAN/Vq4WyLQ6TKcPevcYA8XmuXL8tC85rMQQG1GMwMWKcf/kf0NDKblUFjZ
BevUPmQF3Jadsn9ST+RMn8D+kq31Hdc0UG/WjZSpMHTkc8SWIjr2E6DIILn/OA/w
G3jVOeTsEfUeGExhVwIDAQAB
-----END PUBLIC KEY-----
"""

let rsaPssPublicKey1024DER = Data(base64Encoded:
"MIHPMD0GCSqGSIb3DQEBCjAwoA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEI" +
"MAsGCWCGSAFlAwQCAaIDAgEgA4GNADCBiQKBgQC7LZLbFhzOCoTmXEABRsyOkRiB" +
"18XkkJBwTkn2JES1jVZogXtcq5ZV+KmPulOrzLuaC45IliS5OZ1hJuC7m8/devXk" +
"HaNId+y2cZxRYnfNCsEzvTryxt+01VMQJA4VHsdmhJO6TEIUzDIfj3BlahZuoU11" +
"VZ4wgVIpYymQidJigQIDAQAB"
)!

XCTAssertEqual(try _RSA.Signing.PublicKey(pemRepresentation: rsaPssPublicKeyPEM).keySizeInBits, 2048)
XCTAssertEqual(try _RSA.Signing.PublicKey(derRepresentation: rsaPssPublicKeyDER).keySizeInBits, 2048)
XCTAssertEqual(try _RSA.Signing.PublicKey(unsafePEMRepresentation: rsaPssPublicKey1024PEM).keySizeInBits, 1024)
XCTAssertEqual(try _RSA.Signing.PublicKey(unsafeDERRepresentation: rsaPssPublicKey1024DER).keySizeInBits, 1024)
}

func test_wycheproofPKCS1Vectors() throws {
try wycheproofTest(
jsonName: "rsa_signature_test",
Expand Down

0 comments on commit b639b5b

Please sign in to comment.