Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parsed JWK header parameter #240

Merged
7 commits merged into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions JOSESwift/Sources/JOSEHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
34 changes: 33 additions & 1 deletion JOSESwift/Sources/JWEHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
34 changes: 33 additions & 1 deletion JOSESwift/Sources/JWSHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions Tests/JWEHeaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<CFError>?

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)
}
}
72 changes: 72 additions & 0 deletions Tests/JWSHeaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<CFError>?

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<CFError>?

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)
}
}