From 71a055584afbff3ee650a0d2a03db59a28bbab0d Mon Sep 17 00:00:00 2001 From: Amir Abbas Mousavian Date: Fri, 19 Apr 2024 02:06:50 +0330 Subject: [PATCH] fix: SPKI and PKCS8 encoding errors fix: Changeable RSA thumbprint by changing exponent fix: Crash when parsing EC keys using SecKey chore: Update docker file to use Swift 5.10 --- Dockerfile | 2 +- Sources/JWSETKit/Base/EncryptedData.swift | 23 ++++++++++++++++ .../Certificate/JSONWebCertificate.swift | 2 +- .../Certificate/SecCertificate.swift | 2 +- .../Certificate/X509Certificate.swift | 2 +- Sources/JWSETKit/Cryptography/EC/JWK-EC.swift | 2 +- .../JWSETKit/Cryptography/KeyExporter.swift | 23 +++++++++------- Sources/JWSETKit/Cryptography/Keys.swift | 27 ++++++++++++------- .../JWSETKit/Cryptography/RSA/SecKey.swift | 5 +--- .../Cryptography/ThumbprintTests.swift | 24 ++++++++++++++--- 10 files changed, 80 insertions(+), 32 deletions(-) diff --git a/Dockerfile b/Dockerfile index da218f5..4ed20d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM swift:5.9 +FROM swift:5.10 # Create app directory RUN mkdir -p /usr/src/app diff --git a/Sources/JWSETKit/Base/EncryptedData.swift b/Sources/JWSETKit/Base/EncryptedData.swift index 47be05f..a20cfa4 100644 --- a/Sources/JWSETKit/Base/EncryptedData.swift +++ b/Sources/JWSETKit/Base/EncryptedData.swift @@ -78,6 +78,15 @@ public struct SealedData: DataProtocol, BidirectionalCollection, Hashable, Senda self.tag = sealedBox.tag } + /// Creates a sealed box from the given ChaChaPoly sealed box. + /// - Parameters: + /// - sealedBox: Container for your data. + public init(_ sealedBox: ChaChaPoly.SealedBox) { + self.nonce = Data(sealedBox.nonce) + self.ciphertext = sealedBox.ciphertext + self.tag = sealedBox.tag + } + /// Creates a sealed box from the given AES sealed box. /// /// - Parameters: @@ -109,3 +118,17 @@ extension AES.GCM.SealedBox { ) } } + +extension ChaChaPoly.SealedBox { + /// Creates a ChaChaPoly sealed box from the given sealed box. + /// + /// - Parameters: + /// - sealedBox: Container for your data. + public init(_ sealedData: SealedData) throws { + self = try .init( + nonce: .init(data: sealedData.nonce), + ciphertext: sealedData.ciphertext, + tag: sealedData.tag + ) + } +} diff --git a/Sources/JWSETKit/Cryptography/Certificate/JSONWebCertificate.swift b/Sources/JWSETKit/Cryptography/Certificate/JSONWebCertificate.swift index 1d50adf..ce9cf06 100644 --- a/Sources/JWSETKit/Cryptography/Certificate/JSONWebCertificate.swift +++ b/Sources/JWSETKit/Cryptography/Certificate/JSONWebCertificate.swift @@ -44,7 +44,7 @@ public struct JSONWebCertificateChain: MutableJSONWebKey, JSONWebValidatingKey, try leaf.verifySignature(signature, for: data, using: algorithm) } - public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction { + public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction { try leaf.thumbprint(format: format, using: hashFunction) } } diff --git a/Sources/JWSETKit/Cryptography/Certificate/SecCertificate.swift b/Sources/JWSETKit/Cryptography/Certificate/SecCertificate.swift index 42a382a..2663c64 100644 --- a/Sources/JWSETKit/Cryptography/Certificate/SecCertificate.swift +++ b/Sources/JWSETKit/Cryptography/Certificate/SecCertificate.swift @@ -41,7 +41,7 @@ extension SecCertificate: JSONWebValidatingKey { } } - public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction { + public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction { try publicKey.thumbprint(format: format, using: hashFunction) } } diff --git a/Sources/JWSETKit/Cryptography/Certificate/X509Certificate.swift b/Sources/JWSETKit/Cryptography/Certificate/X509Certificate.swift index 551880f..a37b87d 100644 --- a/Sources/JWSETKit/Cryptography/Certificate/X509Certificate.swift +++ b/Sources/JWSETKit/Cryptography/Certificate/X509Certificate.swift @@ -79,7 +79,7 @@ extension Certificate.PublicKey: JSONWebValidatingKey { throw JSONWebKeyError.unknownKeyType } - public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction { + public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction { try jsonWebKey().thumbprint(format: format, using: hashFunction) } } diff --git a/Sources/JWSETKit/Cryptography/EC/JWK-EC.swift b/Sources/JWSETKit/Cryptography/EC/JWK-EC.swift index ac6c2b8..13edfc4 100644 --- a/Sources/JWSETKit/Cryptography/EC/JWK-EC.swift +++ b/Sources/JWSETKit/Cryptography/EC/JWK-EC.swift @@ -206,7 +206,7 @@ enum ECHelper { } return stride(from: 0, to: data.count, by: keyLength / 8).map { - data[$0 ..< min($0 + keyLength / 8, data.count)] + data.dropFirst($0).prefix(keyLength / 8) } } diff --git a/Sources/JWSETKit/Cryptography/KeyExporter.swift b/Sources/JWSETKit/Cryptography/KeyExporter.swift index 24696f6..b26a369 100644 --- a/Sources/JWSETKit/Cryptography/KeyExporter.swift +++ b/Sources/JWSETKit/Cryptography/KeyExporter.swift @@ -137,15 +137,6 @@ extension PKCS8PrivateKey: DERKeyContainer { var algorithmIdentifier: RFC5480AlgorithmIdentifier { algorithm } - - init(pkcs1: Data) { - self.init( - algorithm: .init(algorithm: .AlgorithmIdentifier.rsaEncryption, parameters: nil), - privateKey: [UInt8](pkcs1), - publicKey: [] - ) - privateKey.publicKey = nil - } } extension DERKeyContainer { @@ -276,6 +267,8 @@ struct RFC5480AlgorithmIdentifier: DERImplicitlyTaggable, Hashable { try coder.serialize(self.algorithm) if let parameters = self.parameters { try coder.serialize(parameters) + } else { + try coder.serialize(ASN1Null()) } } } @@ -298,6 +291,11 @@ extension RFC5480AlgorithmIdentifier { algorithm: .AlgorithmIdentifier.idEcPublicKey, parameters: try! .init(erasing: ASN1ObjectIdentifier.NamedCurves.secp521r1) ) + + static let rsaEncryption = RFC5480AlgorithmIdentifier( + algorithm: .AlgorithmIdentifier.rsaEncryption, + parameters: nil + ) } struct SEC1PrivateKey: DERImplicitlyTaggable, PEMRepresentable { @@ -391,7 +389,7 @@ struct PKCS8PrivateKey: DERImplicitlyTaggable { var algorithm: RFC5480AlgorithmIdentifier - var privateKey: SEC1PrivateKey + var privateKey: any DERSerializable init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws { self = try DER.sequence(rootNode, identifier: identifier) { nodes in @@ -428,6 +426,11 @@ struct PKCS8PrivateKey: DERImplicitlyTaggable { // safe enough to do: it certainly avoids the possibility of disagreeing on what it is! self.privateKey = SEC1PrivateKey(privateKey: privateKey, algorithm: nil, publicKey: publicKey) } + + init(pkcs1: [UInt8]) throws { + self.algorithm = .rsaEncryption + self.privateKey = try ASN1Any(derEncoded: pkcs1[...]) + } func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws { try coder.appendConstructedNode(identifier: identifier) { coder in diff --git a/Sources/JWSETKit/Cryptography/Keys.swift b/Sources/JWSETKit/Cryptography/Keys.swift index d347e29..04214bd 100644 --- a/Sources/JWSETKit/Cryptography/Keys.swift +++ b/Sources/JWSETKit/Cryptography/Keys.swift @@ -49,7 +49,6 @@ public protocol JSONWebKey: Codable, Hashable { /// /// - Returns: A new instance of thumbprint digest. func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction - } @_documentation(visibility: private) @@ -105,6 +104,16 @@ public protocol MutableJSONWebKey: JSONWebKey { var storage: JSONWebValueStorage { get set } } +extension JSONWebValueStorage { + fileprivate func normalizedField(_ key: String, blockSize _: Int? = nil) -> Self { + var copy = self + if let data = self[key] as Data? { + copy[key] = data.drop(while: { $0 == 0 }) + } + return copy + } +} + extension JSONWebKey { /// Creates a new JWK using json data. @available(*, deprecated, message: "Use JSONDecoder instead.") @@ -142,11 +151,7 @@ extension JSONWebKey { } func checkRequiredFields(_ fields: KeyPath...) throws { - for field in fields { - if self[keyPath: field] == nil { - throw JSONWebKeyError.keyNotFound - } - } + try checkRequiredFields(fields) } func checkRequiredFields(_ fields: [KeyPath]) throws { @@ -157,7 +162,7 @@ extension JSONWebKey { } } - func jwkThumbprint(using hashFunction: H.Type) throws -> H.Digest where H : HashFunction { + func jwkThumbprint(using _: H.Type) throws -> H.Digest where H: HashFunction { let thumbprintKeys: Set = [ // Algorithm-specific keys "kty", "crv", @@ -166,16 +171,18 @@ extension JSONWebKey { // EC/OKP keys "x", "y", "d", // Symmetric keys - "k" + "k", ] - let thumbprintStorage = storage.filter(thumbprintKeys.contains) + let thumbprintStorage = storage + .filter(thumbprintKeys.contains) + .normalizedField("e") let encoder = JSONEncoder() encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes] let data = try encoder.encode(thumbprintStorage) return H.hash(data: data) } - public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction { + public func thumbprint(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction { switch format { case .spki: guard let self = self as? (any JSONWebKeyExportable) else { diff --git a/Sources/JWSETKit/Cryptography/RSA/SecKey.swift b/Sources/JWSETKit/Cryptography/RSA/SecKey.swift index 6e0fa26..22691cd 100644 --- a/Sources/JWSETKit/Cryptography/RSA/SecKey.swift +++ b/Sources/JWSETKit/Cryptography/RSA/SecKey.swift @@ -215,9 +215,6 @@ extension JSONWebSigningKey where Self: SecKey { secKeyType = kSecAttrKeyTypeRSA case .ellipticCurve: secKeyType = kSecAttrKeyTypeECSECPrimeRandom - if derRepresentation.count.isMultiple(of: 2) { - derRepresentation.insert(0x04, at: 0) - } default: throw JSONWebKeyError.unknownKeyType } @@ -313,7 +310,7 @@ extension SecKey: JSONWebKeyExportable { case (.spki, .rsa, false): return try SubjectPublicKeyInfo(pkcs1: externalRepresentation).derRepresentation case (.pkcs8, .rsa, true): - return try PKCS8PrivateKey(pkcs1: externalRepresentation).derRepresentation + return try PKCS8PrivateKey(pkcs1: [UInt8](externalRepresentation)).derRepresentation case (.jwk, _, _): return try jwkRepresentation default: diff --git a/Tests/JWSETKitTests/Cryptography/ThumbprintTests.swift b/Tests/JWSETKitTests/Cryptography/ThumbprintTests.swift index a5c03df..a836e54 100644 --- a/Tests/JWSETKitTests/Cryptography/ThumbprintTests.swift +++ b/Tests/JWSETKitTests/Cryptography/ThumbprintTests.swift @@ -1,6 +1,6 @@ // -// File.swift -// +// ThumbprintTests.swift +// // // Created by Amir Abbas Mousavian on 4/18/24. // @@ -12,6 +12,9 @@ import CryptoKit #else import Crypto #endif +#if canImport(CommonCrypto) +import CommonCrypto +#endif final class ThumbprintTests: XCTestCase { let keyData: Data = .init(""" @@ -35,9 +38,24 @@ final class ThumbprintTests: XCTestCase { XCTAssertEqual(thumbprint.data, Data(urlBase64Encoded: "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs")) } + func testJWKAmbiguousThumbprint() throws { + var key = try JSONWebRSAPublicKey(importing: keyData, format: .jwk) + key.exponent = Data([0x00, 0x01, 0x00, 0x01]) + let thumbprint = try key.thumbprint(format: .jwk, using: SHA256.self) + XCTAssertEqual(thumbprint.data, Data(urlBase64Encoded: "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs")) + } + func testSPKIThumbprint() throws { let key = try JSONWebRSAPublicKey(importing: keyData, format: .jwk) let thumbprint = try key.thumbprint(format: .spki, using: SHA256.self) - XCTAssertEqual(thumbprint.data, Data(urlBase64Encoded: "HDoH_pBCw1_TM0QPO5q74tZfDFsYFTyw4pknhCU2HP8")) + XCTAssertEqual(thumbprint.data, Data(urlBase64Encoded: "rTIyDPbFltiEsFOBulc6uo3dV0m03o9KI6efmondrrI")) + } + +#if canImport(CommonCrypto) + func testECDSAThumbprint() throws { + let secECKey = try SecKey(algorithm: .ecdsaSignatureP256SHA256) + let ecKey = try P256.Signing.PrivateKey(derRepresentation: secECKey.exportKey(format: .pkcs8)) + try XCTAssertEqual(ecKey.derRepresentation, secECKey.exportKey(format: .pkcs8)) } +#endif }