Skip to content

Commit

Permalink
feat: Normalizing JSONWebContentType (make shortened)
Browse files Browse the repository at this point in the history
  • Loading branch information
amosavian committed Jan 5, 2024
1 parent 05d197a commit 81a862d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 71 deletions.
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 @@ -122,7 +122,7 @@ enum ECHelper {
}

return stride(from: 0, to: data.count, by: keyLength / 8).map {
data[$0..<min($0 + keyLength / 8, data.count)]
data[$0 ..< min($0 + keyLength / 8, data.count)]
}
}

Expand Down
6 changes: 2 additions & 4 deletions Sources/JWSETKit/Cryptography/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public struct JSONWebKeySet: Codable, Hashable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let keys = try container.decode([AnyJSONWebKey].self, forKey: .keys)
self.keys = try keys.map { try $0.specialized() }
self.keys = keys.map { (try? $0.specialized()) ?? $0 }
}

public func encode(to encoder: any Encoder) throws {
Expand Down Expand Up @@ -348,9 +348,7 @@ extension JSONWebKeySet: RandomAccessCollection {
}

public subscript(position: Int) -> any JSONWebKey {
get {
keys[position]
}
keys[position]
}

public subscript(bounds: Range<Int>) -> JSONWebKeySet {
Expand Down
92 changes: 90 additions & 2 deletions Sources/JWSETKit/Entities/JOSE/JOSEHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,109 @@ public struct JOSEHeader: JSONWebContainer {
throw JSONWebValidationError.missingRequiredField(key: "alg")
}
}

private var normalizedStorage: JSONWebValueStorage {
var result = storage
result["typ"] = result["typ"].map(JSONWebContentType.init(rawValue:))?.mimeType
result["cty"] = result["cty"].map(JSONWebContentType.init(rawValue:))?.mimeType
return result
}

public static func == (lhs: JOSEHeader, rhs: JOSEHeader) -> Bool {
lhs.normalizedStorage == rhs.normalizedStorage
}
}

/// Content type of payload in JOSE header..
/// Content type of payload in JOSE header.
///
/// To keep messages compact in common situations, it is RECOMMENDED that
/// producers omit an "application/" prefix of a media type value in a
/// "typ" Header Parameter when no other '/' appears in the media type
/// value. A recipient using the media type value MUST treat it as if
/// "application/" were prepended to any "typ" value not containing a
/// '/'. For instance, a "typ" value of "example" SHOULD be used to
/// represent the "application/example" media type, whereas the media
/// type "application/example;part="1/2"" cannot be shortened to
/// "example;part="1/2"".
public struct JSONWebContentType: StringRepresentable {
public let rawValue: String

/// IANA Media type per RFC-2045.
///
/// As application prefix is optional in JOSE content type, this property returns a valid lowercased MIME type.
public var mimeType: String {
var result = rawValue.lowercased()
if !result.contains("/") {
result = "application/\(result)"
}
return result
}

public init(rawValue: String) {
self.rawValue = rawValue.trimmingCharacters(in: .whitespaces)
self.rawValue = rawValue
.trimmingCharacters(in: .whitespaces)
.replacingOccurrences(of: "application/", with: "", options: [.anchored])
}

public static func == (lhs: JSONWebContentType, rhs: JSONWebContentType) -> Bool {
lhs.mimeType == rhs.mimeType
}

public func hash(into hasher: inout Hasher) {
hasher.combine(mimeType)
}
}

extension JSONWebContentType {
/// Payload contains a JSON with JSON Web Token (JWT) claims.
///
/// JWT values are encoded as a series of` base64url`-encoded values (some of which may be the empty
/// string) separated by period ('.') characters.
public static let jwt: Self = "JWT"

/// Payload contains encrypted data with JSON Web Encryption (JWE) serialization.
public static let jwe: Self = "JWE"

/// JOSE values are encoded as a series of `base64url`-encoded values (some of which may be the empty
/// string), each separated from the next by a single period ('.') character.
///
/// This value indicates that the content is a JWS or JWE using the JWS Compact Serialization
/// or the JWE Compact Serialization.
public static let jose: Self = "JOSE"

/// JOSE values are represented as a JSON Object; UTF-8 encoding SHOULD be employed for the JSON object.
///
/// This value indicates that the content is a JWS or JWE using the JWS JSON Serialization
/// or the JWE JSON Serialization.
public static let joseJSON: Self = "JOSE+JSON"

/// Payload contains a JSON with JSON Web Key (JWK) parameters.
public static let jwk: Self = "jwk+json"

/// Payload contains a JSON with JSON Web Key Set (JWKS) items.
public static let jwks: Self = "jwk-set+json"
}

#if canImport(UniformTypeIdentifiers)
import UniformTypeIdentifiers

extension JSONWebContentType {
/// Initializes content type from Uniform Type Identifier.
///
/// - Parameter utType: A structure that represents a type of data to load, send, or receive.
public init?(_ utType: UTType) {
guard let mimeType = utType.preferredMIMEType else {
return nil
}

self.init(rawValue: mimeType)
}

/// Returns a Uniform Type Identifer corresponding to MIME type.
public var utType: UTType? {
let mimeType = mimeType
let conformingType: UTType = mimeType.hasSuffix("+json") ? .json : .data
return .init(mimeType: mimeType, conformingTo: conformingType)
}
}
#endif
2 changes: 1 addition & 1 deletion Tests/JWSETKitTests/Cryptography/ECTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// ECTests.swift
//
//
//
// Created by Amir Abbas Mousavian on 12/27/23.
//
Expand Down
124 changes: 62 additions & 62 deletions Tests/JWSETKitTests/Cryptography/JWKSetTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// JWKSetTests.swift
//
//
//
// Created by Amir Abbas Mousavian on 12/30/23.
//
Expand All @@ -9,66 +9,66 @@ import XCTest
@testable import JWSETKit

final class JWKSetTests: XCTestCase {
let jwksData: Data = Data("""
{"keys":
[
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"},
{"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx\
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs\
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2\
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI\
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb\
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"},
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
"use":"enc",
"kid":"1"},
{"kty":"RSA",
"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4\
cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst\
n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q\
vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS\
D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw\
0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9\
M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij\
wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d\
_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz\
nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz\
me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
"p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV\
nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV\
WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
"q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum\
qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx\
kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
"dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim\
YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu\
YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
"dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU\
vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9\
GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
"qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg\
UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx\
yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
"alg":"RS256",
"kid":"2011-04-29"}
]
}
""".utf8)
let jwksData: Data = .init("""
{"keys":
[
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"use":"enc",
"kid":"1"},
{"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx\
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs\
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2\
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI\
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb\
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"alg":"RS256",
"kid":"2011-04-29"},
{"kty":"EC",
"crv":"P-256",
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
"use":"enc",
"kid":"1"},
{"kty":"RSA",
"n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4\
cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMst\
n64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2Q\
vzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbIS\
D08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw\
0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB",
"d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9\
M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqij\
wp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d\
_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBz\
nbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFz\
me1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
"p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPV\
nwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqV\
WlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
"q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyum\
qjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgx\
kIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
"dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oim\
YwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_Nmtu\
YZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
"dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUU\
vMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9\
GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
"qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzg\
UIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rx\
yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
"alg":"RS256",
"kid":"2011-04-29"}
]
}
""".utf8)

func testDecode() throws {
let jwks = try JSONDecoder().decode(JSONWebKeySet.self, from: jwksData)
Expand All @@ -92,6 +92,6 @@ yR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",

func testEncode() throws {
let jwks = try JSONDecoder().decode(JSONWebKeySet.self, from: jwksData)
let encoded = try JSONEncoder().encode(jwks)
let _ = try JSONEncoder().encode(jwks)
}
}
2 changes: 1 addition & 1 deletion Tests/JWSETKitTests/Entities/JOSEHeaderJWSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ final class JOSEHeaderJWSTests: XCTestCase {
claims.certificateThumbprint = Data(base64Encoded: "We5K4CMGHXgX4urupYm/Zq2gIhm7d6MdNTEyRu+b6Ck=")
claims.certificateChain = [cert1, cert2, cert3]
claims.type = .jwt
claims.contentType = .init(rawValue: "application/json")
claims.contentType = .init(rawValue: "json")
claims.critical = ["b64"]

let decoder = JSONDecoder()
Expand Down

0 comments on commit 81a862d

Please sign in to comment.