diff --git a/JOSESwift/Sources/JOSEHeader.swift b/JOSESwift/Sources/JOSEHeader.swift index 11f2ba47..7c21f279 100644 --- a/JOSESwift/Sources/JOSEHeader.swift +++ b/JOSESwift/Sources/JOSEHeader.swift @@ -64,6 +64,7 @@ extension JOSEHeader { public protocol CommonHeaderParameterSpace { var jku: URL? { get set } var jwk: String? { get set } + var jwkTyped: JWK? { get set } var kid: String? { get set } var x5u: URL? { get set } var x5c: [String]? { get set } diff --git a/JOSESwift/Sources/JWEHeader.swift b/JOSESwift/Sources/JWEHeader.swift index 28362ba6..a03b2aaf 100644 --- a/JOSESwift/Sources/JWEHeader.swift +++ b/JOSESwift/Sources/JWEHeader.swift @@ -144,7 +144,7 @@ extension JWEHeader: CommonHeaderParameterSpace { } } - /// The JSON Web key corresponding to the key used to encrypt the JWE. + /// The JSON Web key corresponding to the key used to encrypt the JWE, as a String. public var jwk: String? { set { parameters["jwk"] = newValue @@ -154,6 +154,38 @@ extension JWEHeader: CommonHeaderParameterSpace { } } + /// The JSON Web key corresponding to the key used to encrypt the JWE, as a JWK. + public var jwkTyped: JWK? { + set { + parameters["jwk"] = newValue?.parameters + } + get { + guard let jwkParameters = parameters["jwk"] as? [String: String] else { + return nil + } + + guard + let keyTypeString = jwkParameters[JWKParameter.keyType.rawValue], + let keyType = JWKKeyType(rawValue: keyTypeString) + else { + return nil + } + + guard let json = try? JSONEncoder().encode(jwkParameters) else { + return nil + } + + switch keyType { + case JWKKeyType.EC: + return try? ECPublicKey(data: json) + case JWKKeyType.OCT: + return try? SymmetricKey(data: json) + case JWKKeyType.RSA: + return try? RSAPublicKey(data: json) + } + } + } + /// The Key ID indicates the key which was used to encrypt the JWE. public var kid: String? { set { diff --git a/JOSESwift/Sources/JWSHeader.swift b/JOSESwift/Sources/JWSHeader.swift index 0d3f024d..f02699f9 100644 --- a/JOSESwift/Sources/JWSHeader.swift +++ b/JOSESwift/Sources/JWSHeader.swift @@ -104,7 +104,7 @@ extension JWSHeader: CommonHeaderParameterSpace { } } - /// The JSON Web key corresponding to the key used to digitally sign the JWS. + /// The JSON Web key corresponding to the key used to digitally sign the JWS, as a String. public var jwk: String? { set { parameters["jwk"] = newValue @@ -114,6 +114,38 @@ extension JWSHeader: CommonHeaderParameterSpace { } } + /// The JSON Web key corresponding to the key used to digitally sign the JWS, as a JWK. + public var jwkTyped: JWK? { + set { + parameters["jwk"] = newValue?.parameters + } + get { + guard let jwkParameters = parameters["jwk"] as? [String: String] else { + return nil + } + + guard + let keyTypeString = jwkParameters[JWKParameter.keyType.rawValue], + let keyType = JWKKeyType(rawValue: keyTypeString) + else { + return nil + } + + guard let json = try? JSONEncoder().encode(jwkParameters) else { + return nil + } + + switch keyType { + case JWKKeyType.EC: + return try? ECPublicKey(data: json) + case JWKKeyType.OCT: + return try? SymmetricKey(data: json) + case JWKKeyType.RSA: + return try? RSAPublicKey(data: json) + } + } + } + /// The Key ID indicates the key which was used to secure the JWS. public var kid: String? { set { diff --git a/Tests/JWEHeaderTests.swift b/Tests/JWEHeaderTests.swift index 977d7c3e..1c98fabc 100644 --- a/Tests/JWEHeaderTests.swift +++ b/Tests/JWEHeaderTests.swift @@ -282,4 +282,40 @@ class JWEHeaderTests: XCTestCase { XCTAssertEqual(header1.algorithm, header1.keyManagementAlgorithm) XCTAssertEqual(header2.encryptionAlgorithm, header1.contentEncryptionAlgorithm) } + + func testJwkTypedHeaderParamRSA() throws { + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ] + ] + + var error: Unmanaged? + + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let publicKey = SecKeyCopyPublicKey(privateKey)! + + let jwk = try RSAPublicKey(publicKey: publicKey) + + var header = JWEHeader(keyManagementAlgorithm: .RSA1_5, contentEncryptionAlgorithm: .A256CBCHS512) + + header.jwkTyped = jwk + + // The actual 'jwk' parameter is expected to be a dictionary + let jwkParam = header.parameters["jwk"] as? [String: String] + XCTAssertNotNil(jwkParam) + + let headerJwk = header.jwkTyped as? RSAPublicKey + XCTAssertNotNil(headerJwk) + XCTAssertEqual(jwk.keyType, headerJwk?.keyType) + XCTAssertEqual(jwk.exponent, headerJwk?.exponent) + XCTAssertEqual(jwk.modulus, headerJwk?.modulus) + } } diff --git a/Tests/JWSHeaderTests.swift b/Tests/JWSHeaderTests.swift index b1c093a8..179b2aed 100644 --- a/Tests/JWSHeaderTests.swift +++ b/Tests/JWSHeaderTests.swift @@ -158,4 +158,76 @@ class JWSHeaderTests: XCTestCase { XCTAssertEqual(header.crit, crit) } + func testJwkTypedHeaderParamEC() throws { + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeEC, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 256, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ] + ] + + var error: Unmanaged? + + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let publicKey = SecKeyCopyPublicKey(privateKey)! + + let jwk = try ECPublicKey(publicKey: publicKey) + + var header = JWSHeader(algorithm: .ES256) + + header.jwkTyped = jwk + + // The actual 'jwk' parameter is expected to be a dictionary + let jwkParam = header.parameters["jwk"] as? [String: String] + XCTAssertNotNil(jwkParam) + + let headerJwk = header.jwkTyped as? ECPublicKey + XCTAssertNotNil(headerJwk) + XCTAssertEqual(jwk.keyType, headerJwk?.keyType) + XCTAssertEqual(jwk.crv, headerJwk?.crv) + XCTAssertEqual(jwk.x, headerJwk?.x) + XCTAssertEqual(jwk.y, headerJwk?.y) + } + + func testJwkTypedHeaderParamRSA() throws { + let attributes: [String: Any] = [ + kSecAttrKeyType as String: kSecAttrKeyTypeRSA, + kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits as String: 2048, + kSecPrivateKeyAttrs as String: [ + kSecAttrIsPermanent as String: false + ] + ] + + var error: Unmanaged? + + guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { + print(error!) + return + } + + let publicKey = SecKeyCopyPublicKey(privateKey)! + + let jwk = try RSAPublicKey(publicKey: publicKey) + + var header = JWSHeader(algorithm: .ES256) + + header.jwkTyped = jwk + + // The actual 'jwk' parameter is expected to be a dictionary + let jwkParam = header.parameters["jwk"] as? [String: String] + XCTAssertNotNil(jwkParam) + + let headerJwk = header.jwkTyped as? RSAPublicKey + XCTAssertNotNil(headerJwk) + XCTAssertEqual(jwk.keyType, headerJwk?.keyType) + XCTAssertEqual(jwk.exponent, headerJwk?.exponent) + XCTAssertEqual(jwk.modulus, headerJwk?.modulus) + } }