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

[117] Allow JWK parameters of type [String] #120

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
722937b
Implement decoding of String and [String] parameters
dlggr Oct 24, 2018
0478b7c
Adapt tests
dlggr Oct 24, 2018
9d98e85
Add new test case
dlggr Oct 24, 2018
32c1e11
Remove key type computed property
dlggr Oct 24, 2018
0bcdd26
Update tests
dlggr Oct 24, 2018
daedd4b
Fix long lines
dlggr Oct 24, 2018
c27f7fa
Update readme
dlggr Oct 24, 2018
82ca0c2
Move parameter getter extension
dlggr Oct 24, 2018
9a6eaa7
Add doc comments to param getters
dlggr Oct 24, 2018
2cf0c6a
Remove old array parameter test
dlggr Oct 24, 2018
3e7ea46
Merge branch 'master' into feature/string-array-jwk-parameters
Nov 7, 2018
2d75cc2
Merge branch 'master' into feature/string-array-jwk-parameters
Dec 13, 2018
bf2539b
Use Codable for JWKPArameterType
daniel-moh Jan 22, 2019
763c67f
Use JWKPArameterType instsead of Any for subscript
daniel-moh Jan 22, 2019
5ad7f1a
Merge branch 'master' into feature/string-array-jwk-parameters
daniel-moh Jan 22, 2019
f006882
Adapt EC keys for String arary parameters
daniel-moh Jan 22, 2019
fcee76c
Adapt tests
daniel-moh Jan 22, 2019
da05a0c
Add symmetric key parsing tests
daniel-moh Jan 22, 2019
df830e1
Merge branch 'master' into feature/string-array-jwk-parameters
Jan 31, 2019
99b7b0a
Add rsa tests to test target
daniel-moh Feb 20, 2019
3044537
Switch on parameter type in a single loop
daniel-moh Feb 20, 2019
575d31a
Add single loop decoding for ec private key as well
daniel-moh Feb 20, 2019
fe2dfe3
Add single loop decoding for rsa private key as well
daniel-moh Feb 20, 2019
3ae5424
Merge branch 'master' into feature/string-array-jwk-parameters
Feb 20, 2019
b75f698
Resolve conflicts
daniel-moh May 27, 2019
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
4 changes: 4 additions & 0 deletions JOSESwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
6505236E1FB4940100E0B1B1 /* AESEncrypter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6505236D1FB4940100E0B1B1 /* AESEncrypter.swift */; };
650523701FB494BE00E0B1B1 /* AESDecrypter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6505236F1FB494BE00E0B1B1 /* AESDecrypter.swift */; };
6506D9E920F4CA2000F34DD8 /* SymmetricKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6506D9E820F4CA2000F34DD8 /* SymmetricKeyTests.swift */; };
6508625B2180D9A00052F9CA /* JWKParameterTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6508625A2180D9A00052F9CA /* JWKParameterTypeTests.swift */; };
65125A321FBF85FA007CF3AE /* JWSDeserializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65125A311FBF85FA007CF3AE /* JWSDeserializationTests.swift */; };
6514ADC92031DD15008A4DD3 /* ASN1DEREncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514ADC82031DD15008A4DD3 /* ASN1DEREncoding.swift */; };
6514ADCB2031DD27008A4DD3 /* ASN1DEREncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514ADCA2031DD27008A4DD3 /* ASN1DEREncodingTests.swift */; };
Expand Down Expand Up @@ -100,6 +101,7 @@
6505236D1FB4940100E0B1B1 /* AESEncrypter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESEncrypter.swift; sourceTree = "<group>"; };
6505236F1FB494BE00E0B1B1 /* AESDecrypter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESDecrypter.swift; sourceTree = "<group>"; };
6506D9E820F4CA2000F34DD8 /* SymmetricKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymmetricKeyTests.swift; sourceTree = "<group>"; };
6508625A2180D9A00052F9CA /* JWKParameterTypeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKParameterTypeTests.swift; sourceTree = "<group>"; };
65125A311FBF85FA007CF3AE /* JWSDeserializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWSDeserializationTests.swift; sourceTree = "<group>"; };
6514ADC82031DD15008A4DD3 /* ASN1DEREncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASN1DEREncoding.swift; sourceTree = "<group>"; };
6514ADCA2031DD27008A4DD3 /* ASN1DEREncodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASN1DEREncodingTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -312,6 +314,7 @@
65A103A0202B03BB00D22BF5 /* ASN1DERParsingTests.swift */,
65A103A2202B0CDF00D22BF5 /* DataRSAPublicKeyTests.swift */,
6546D605203580C6007217FB /* JWKRSADecodingTests.swift */,
6508625A2180D9A00052F9CA /* JWKParameterTypeTests.swift */,
65826AB320286B3A00AFFC46 /* JWKRSAEncodingTests.swift */,
65E733D41FEC031B0009EAC6 /* JWKRSAKeysTests.swift */,
6536560A2035DC3900A3AC3B /* JWKSetCodingTests.swift */,
Expand Down Expand Up @@ -559,6 +562,7 @@
C8F096501FC56B25000BEE4D /* RSAEncrypterTests.swift in Sources */,
65125A321FBF85FA007CF3AE /* JWSDeserializationTests.swift in Sources */,
65A103A3202B0CDF00D22BF5 /* DataRSAPublicKeyTests.swift in Sources */,
6508625B2180D9A00052F9CA /* JWKParameterTypeTests.swift in Sources */,
C81DD9261FD6F07F00026024 /* (null) in Sources */,
C84BDE171FAB1CB60002B5D0 /* RSASignerTests.swift in Sources */,
C8EE14541FAC797500A616E4 /* RSAVerifierTests.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions JOSESwift/Sources/JWK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ public protocol JWK: Codable {
/// The parameters of the JWK representing the properties of the key(s), including the value(s).
/// Check [RFC 7517, Section 4](https://tools.ietf.org/html/rfc7517#section-4) and
/// [RFC 7518, Section 6](https://tools.ietf.org/html/rfc7518#section-6) for possible parameters.
var parameters: [String: String] { get }
var parameters: [String: JWKParameterType] { get }

/// Accesses the specified parameter.
/// The parameters of the JWK representing the properties of the key(s), including the value(s).
/// Check [RFC 7517, Section 4](https://tools.ietf.org/html/rfc7517#section-4) and
/// [RFC 7518, Section 6](https://tools.ietf.org/html/rfc7518#section-6) for possible parameters.
///
/// - Parameter parameter: The desired parameter.
subscript(parameter: String) -> String? { get }
subscript(parameter: String) -> Any? { get }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use Any? here instead of JWKParameterType?? Are there non [String]? or String? return types?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right! I'll need to investigate what I did exactly back then but yeah I think you're right. Will update.

The whole parameter stuff in JWK and in the JOSE header is not exactly nice. We'd love to update all of that some time. 🙏


/// Initializes a JWK from given JSON data.
///
Expand Down
58 changes: 57 additions & 1 deletion JOSESwift/Sources/JWKExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Foundation
// MARK: Subscript

public extension JWK {
subscript(parameter: String) -> String? {
subscript(parameter: String) -> Any? {
return parameters[parameter]
}
}
Expand All @@ -46,3 +46,59 @@ public extension JWK {
return try? JSONEncoder().encode(self)
}
}

// MARK: Parameter getters

extension JWK {
/// The public key use parameter identifies the intended use of a public key.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.2).
var keyUse: String? {
return parameters[JWKParameter.keyUse.rawValue] as? String
}

/// The key operations parameter identifies the operation(s) for which the key is intended to be used.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.3).
var keyOperations: [String]? {
return parameters[JWKParameter.keyOperations.rawValue] as? [String]
}

/// The algorithm parameter identifies the algorithm intended for use with the key.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.4).
var algorithm: String? {
return parameters[JWKParameter.algorithm.rawValue] as? String
}

/// The key identifier parameter is used to match a specific key.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.5).
var keyIdentifier: String? {
return parameters[JWKParameter.keyIdentifier.rawValue] as? String
}

/// The X.509 URL parameter is a URI that refers to a resource for an X.509 public key certificate
/// or certificate chain.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.6).
var X509URL: String? {
return parameters[JWKParameter.X509URL.rawValue] as? String
}

/// The X.509 certificate chain parameter contains a chain of one or more PKIX certificates.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.7).
var X509CertificateChain: [String]? {
return parameters[JWKParameter.X509CertificateChain.rawValue] as? [String]
}

/// The X.509 certificate SHA-1 thumbprint parameter is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
/// of the DER encoding of an X.509 certificate.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.8).
var X509CertificateSHA1Thumbprint: String? {
return parameters[JWKParameter.X509CertificateSHA1Thumbprint.rawValue] as? String
}

/// The X.509 certificate SHA-256 thumbprint parameter is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
/// of the DER encoding of an X.509 certificate.
/// See [RFC-7517](https://tools.ietf.org/html/rfc7517#section-4.9).
var X509CertificateSHA256Thumbprint: String? {
return parameters[JWKParameter.X509CertificateSHA256Thumbprint.rawValue] as? String
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Files should have a single trailing newline.
trailing_newline JWKExtensions.swift:104

19 changes: 15 additions & 4 deletions JOSESwift/Sources/JWKParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@

import Foundation

// Common protocol for all types that can be used as JWK parameters.
public protocol JWKParameterType: Encodable, Decodable { }

extension String: JWKParameterType { }
extension Array: JWKParameterType where Element == String { }

/// Possible common JWK parameters.
/// See [RFC-7517, Section 4](https://tools.ietf.org/html/rfc7517#section-4) for details.
public enum JWKParameter: String, CodingKey {
Expand All @@ -36,10 +42,15 @@ public enum JWKParameter: String, CodingKey {
case X509CertificateSHA1Thumbprint = "x5t"
case X509CertificateSHA256Thumbprint = "x5t#S256"

static let nonStringParameters: [JWKParameter] = [
.keyOperations,
.X509CertificateChain
]
var type: Codable.Type {
switch self {
case .keyType, .keyUse, .algorithm, .keyIdentifier,
.X509URL, .X509CertificateSHA1Thumbprint, .X509CertificateSHA256Thumbprint:
return String.self
case .keyOperations, .X509CertificateChain:
return [String].self
}
}
}

/// RSA specific JWK parameters.
Expand Down
38 changes: 30 additions & 8 deletions JOSESwift/Sources/RSAKeyCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ extension RSAPublicKey: Encodable {
// Other common parameters are optional.
for parameter in parameters {
// Only encode known parameters.
if let key = JWKParameter(rawValue: parameter.key) {
try commonParameters.encode(parameter.value, forKey: key)
guard let key = JWKParameter(rawValue: parameter.key) else {
continue
}

if let value = parameter.value as? String {
try commonParameters.encode(value, forKey: key)
} else if let value = parameter.value as? [String] {
try commonParameters.encode(value, forKey: key)
}
}

Expand All @@ -61,11 +67,16 @@ extension RSAPublicKey: Decodable {
}

// Other common parameters are optional.
var parameters: [String: String] = [:]
for key in commonParameters.allKeys where !JWKParameter.nonStringParameters.contains(key) {
var parameters: [String: JWKParameterType] = [:]

for key in commonParameters.allKeys where key.type == String.self {
parameters[key.rawValue] = try commonParameters.decode(String.self, forKey: key)
}

for key in commonParameters.allKeys where key.type == [String].self {
parameters[key.rawValue] = try commonParameters.decode([String].self, forKey: key)
}

// RSA public key specific parameters.
let rsaParameters = try decoder.container(keyedBy: RSAParameter.self)
let modulus = try rsaParameters.decode(String.self, forKey: .modulus)
Expand All @@ -89,8 +100,14 @@ extension RSAPrivateKey: Encodable {
// Other common parameters are optional.
for parameter in parameters {
// Only encode known parameters.
if let key = JWKParameter(rawValue: parameter.key) {
try commonParameters.encode(parameter.value, forKey: key)
guard let key = JWKParameter(rawValue: parameter.key) else {
continue
}

if let value = parameter.value as? String {
try commonParameters.encode(value, forKey: key)
} else if let value = parameter.value as? [String] {
try commonParameters.encode(value, forKey: key)
}
}

Expand Down Expand Up @@ -118,11 +135,16 @@ extension RSAPrivateKey: Decodable {
}

// Other common parameters are optional.
var parameters: [String: String] = [:]
for key in commonParameters.allKeys where !JWKParameter.nonStringParameters.contains(key) {
var parameters: [String: JWKParameterType] = [:]

for key in commonParameters.allKeys where key.type == String.self {
parameters[key.rawValue] = try commonParameters.decode(String.self, forKey: key)
}

for key in commonParameters.allKeys where key.type == [String].self {
parameters[key.rawValue] = try commonParameters.decode([String].self, forKey: key)
}

// RSA private key specific parameters.
let rsaParameters = try decoder.container(keyedBy: RSAParameter.self)
let modulus = try rsaParameters.decode(String.self, forKey: .modulus)
Expand Down
14 changes: 8 additions & 6 deletions JOSESwift/Sources/RSAKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public struct RSAPublicKey: JWK {
public let keyType: JWKKeyType

/// The JWK parameters.
public let parameters: [String: String]
public let parameters: [String: JWKParameterType]

/// The modulus value for the RSA public key.
public let modulus: String
Expand All @@ -113,7 +113,7 @@ public struct RSAPublicKey: JWK {
/// - exponent: The public exponent value for the RSA public key in `base64urlUInt` encoding
/// as specified in [RFC-7518, Section 2](https://tools.ietf.org/html/rfc7518#section-2).
/// - parameters: Additional JWK parameters.
public init(modulus: String, exponent: String, additionalParameters parameters: [String: String] = [:]) {
public init(modulus: String, exponent: String, additionalParameters parameters: [String: JWKParameterType] = [:]) {
self.keyType = .RSA
self.modulus = modulus
self.exponent = exponent
Expand All @@ -134,7 +134,8 @@ public struct RSAPublicKey: JWK {
/// - publicKey: The public key that the resulting JWK should represent.
/// - parameters: Any additional parameters to be contained in the JWK.
/// - Throws: A `JOSESwiftError` indicating any errors.
public init(publicKey: ExpressibleAsRSAPublicKeyComponents, additionalParameters parameters: [String: String] = [:]) throws {
public init(publicKey: ExpressibleAsRSAPublicKeyComponents,
additionalParameters parameters: [String: JWKParameterType] = [:]) throws {
guard let components = try? publicKey.rsaPublicKeyComponents() else {
throw JOSESwiftError.couldNotConstructJWK
}
Expand Down Expand Up @@ -184,7 +185,7 @@ public struct RSAPrivateKey: JWK {
public let keyType: JWKKeyType

/// The JWK parameters.
public let parameters: [String: String]
public let parameters: [String: JWKParameterType]

/// The modulus value for the RSA private key.
public let modulus: String
Expand All @@ -205,7 +206,7 @@ public struct RSAPrivateKey: JWK {
// - privateExponent: The private exponent value for the RSA private key in `base64urlUInt` encoding
/// as specified in [RFC-7518, Section 2](https://tools.ietf.org/html/rfc7518#section-2).
/// - parameters: Additional JWK parameters.
public init(modulus: String, exponent: String, privateExponent: String, additionalParameters parameters: [String: String] = [:]) {
public init(modulus: String, exponent: String, privateExponent: String, additionalParameters parameters: [String: JWKParameterType] = [:]) {
self.keyType = .RSA
self.modulus = modulus
self.exponent = exponent
Expand All @@ -228,7 +229,8 @@ public struct RSAPrivateKey: JWK {
/// - privateKey: The private key that the resulting JWK should represent.
/// - parameters: Any additional parameters to be contained in the JWK.
/// - Throws: A `JOSESwiftError` indicating any errors.
public init(privateKey: ExpressibleAsRSAPrivateKeyComponents, additionalParameters parameters: [String: String] = [:]) throws {
public init(privateKey: ExpressibleAsRSAPrivateKeyComponents,
additionalParameters parameters: [String: JWKParameterType] = [:]) throws {
guard let (modulus, exponent, privateExponent) = try? privateKey.rsaPrivateKeyComponents() else {
throw JOSESwiftError.couldNotConstructJWK
}
Expand Down
21 changes: 16 additions & 5 deletions JOSESwift/Sources/SymmetricKeyCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ extension SymmetricKey: Encodable {
// Other common parameters are optional.
for parameter in parameters {
// Only encode known parameters.
if let key = JWKParameter(rawValue: parameter.key) {
try commonParameters.encode(parameter.value, forKey: key)
guard let key = JWKParameter(rawValue: parameter.key) else {
continue
}

if let value = parameter.value as? String {
try commonParameters.encode(value, forKey: key)
} else if let value = parameter.value as? [String] {
try commonParameters.encode(value, forKey: key)
}
}

Expand All @@ -60,12 +66,17 @@ extension SymmetricKey: Decodable {
}

// Other common parameters are optional.
var parameters: [String: String] = [:]
for key in commonParameters.allKeys {
var parameters: [String: JWKParameterType] = [:]

for key in commonParameters.allKeys where key.type == String.self {
parameters[key.rawValue] = try commonParameters.decode(String.self, forKey: key)
}

// RSA public key specific parameters.
for key in commonParameters.allKeys where key.type == [String].self {
parameters[key.rawValue] = try commonParameters.decode([String].self, forKey: key)
}

// Symmetric public key specific parameters.
let symmetricKeyParameters = try decoder.container(keyedBy: SymmetricKeyParameter.self)
let key = try symmetricKeyParameters.decode(String.self, forKey: .key)

Expand Down
6 changes: 3 additions & 3 deletions JOSESwift/Sources/SymmetricKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public struct SymmetricKey: JWK {
public let keyType: JWKKeyType

/// The JWK parameters.
public let parameters: [String: String]
public let parameters: [String: JWKParameterType]

/// The symmetric key represented as
/// base64url encoding of the octet sequence containing the key data.
Expand All @@ -68,7 +68,7 @@ public struct SymmetricKey: JWK {
/// - Parameters:
/// - key: The octet sequence containing the key data.
/// - parameters: Additional JWK parameters.
public init(key: Data, additionalParameters parameters: [String: String] = [:]) {
public init(key: Data, additionalParameters parameters: [String: JWKParameterType] = [:]) {
self.keyType = .OCT
self.key = key.base64URLEncodedString()

Expand All @@ -87,7 +87,7 @@ public struct SymmetricKey: JWK {
/// - key: The symmetirc key that the resulting JWK should represent.
/// - parameters: Any additional parameters to be contained in the JWK.
/// - Throws: A `JOSESwiftError` indicating any errors.
public init(key: ExpressibleAsSymmetricKeyComponents, additionalParameters parameters: [String: String] = [:]) throws {
public init(key: ExpressibleAsSymmetricKeyComponents, additionalParameters parameters: [String: JWKParameterType] = [:]) throws {
guard let components = try? key.symmetricKeyComponents() else {
throw JOSESwiftError.couldNotConstructJWK
}
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,6 @@ let publicKey: SecKey = try! jwk.converted(to: SecKey.self)

More details about decoding RSA public keys can be found [in the wiki](../../wiki/jwk).

:warning: We currently ignore the key parameters [`"key_ops"`](https://tools.ietf.org/html/rfc7517#section-4.3) and [`"x5c"`](https://tools.ietf.org/html/rfc7517#section-4.7) when decoding. This is due to a bug in our decoding implementation. See [#117](https://github.com/airsidemobile/JOSESwift/issues/117) for details.

## Security

JOSESwift uses the [iOS Security framework](https://developer.apple.com/documentation/security) and [Apple’s CommonCrypto](https://opensource.apple.com//source/CommonCrypto/) for cryptography.
Expand Down
Loading