Skip to content

Commit

Permalink
fix: SPKI and PKCS8 encoding errors
Browse files Browse the repository at this point in the history
fix: Changeable RSA thumbprint by changing exponent
fix: Crash when parsing EC keys using SecKey
chore: Update docker file to use Swift 5.10
  • Loading branch information
amosavian committed Apr 18, 2024
1 parent 71d9d05 commit 71a0555
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM swift:5.9
FROM swift:5.10

# Create app directory
RUN mkdir -p /usr/src/app
Expand Down
23 changes: 23 additions & 0 deletions Sources/JWSETKit/Base/EncryptedData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public struct JSONWebCertificateChain: MutableJSONWebKey, JSONWebValidatingKey,
try leaf.verifySignature(signature, for: data, using: algorithm)
}

public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction {
public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction {
try leaf.thumbprint(format: format, using: hashFunction)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extension SecCertificate: JSONWebValidatingKey {
}
}

public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction {
public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction {
try publicKey.thumbprint(format: format, using: hashFunction)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ extension Certificate.PublicKey: JSONWebValidatingKey {
throw JSONWebKeyError.unknownKeyType
}

public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction {
public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction {
try jsonWebKey().thumbprint(format: format, using: hashFunction)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/JWSETKit/Cryptography/EC/JWK-EC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
23 changes: 13 additions & 10 deletions Sources/JWSETKit/Cryptography/KeyExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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())
}
}
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 17 additions & 10 deletions Sources/JWSETKit/Cryptography/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public protocol JSONWebKey: Codable, Hashable {
///
/// - Returns: A new instance of thumbprint digest.
func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction

}

@_documentation(visibility: private)
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -142,11 +151,7 @@ extension JSONWebKey {
}

func checkRequiredFields<T>(_ fields: KeyPath<Self, T?>...) throws {
for field in fields {
if self[keyPath: field] == nil {
throw JSONWebKeyError.keyNotFound
}
}
try checkRequiredFields(fields)
}

func checkRequiredFields<T>(_ fields: [KeyPath<Self, T?>]) throws {
Expand All @@ -157,7 +162,7 @@ extension JSONWebKey {
}
}

func jwkThumbprint<H>(using hashFunction: H.Type) throws -> H.Digest where H : HashFunction {
func jwkThumbprint<H>(using _: H.Type) throws -> H.Digest where H: HashFunction {
let thumbprintKeys: Set<String> = [
// Algorithm-specific keys
"kty", "crv",
Expand All @@ -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<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H : HashFunction {
public func thumbprint<H>(format: JSONWebKeyFormat, using hashFunction: H.Type) throws -> H.Digest where H: HashFunction {
switch format {
case .spki:
guard let self = self as? (any JSONWebKeyExportable) else {
Expand Down
5 changes: 1 addition & 4 deletions Sources/JWSETKit/Cryptography/RSA/SecKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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:
Expand Down
24 changes: 21 additions & 3 deletions Tests/JWSETKitTests/Cryptography/ThumbprintTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
// ThumbprintTests.swift
//
//
// Created by Amir Abbas Mousavian on 4/18/24.
//
Expand All @@ -12,6 +12,9 @@ import CryptoKit
#else
import Crypto
#endif
#if canImport(CommonCrypto)
import CommonCrypto
#endif

final class ThumbprintTests: XCTestCase {
let keyData: Data = .init("""
Expand All @@ -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
}

0 comments on commit 71a0555

Please sign in to comment.