Skip to content

Commit

Permalink
Add customizable JSON encoders and decoders (#95)
Browse files Browse the repository at this point in the history
* Require SwiftCrypto 2.x, as we should been doing for a long time at this point
* Add fully API-compatible ability to specify custom JSONEncoders and JSONDecoders for both the JWTSigners type and individual JWTSigner instances.
* Enable using alternative JSON encoder/decoder implementations. Provide an integer date encoding/decoding strategy for the Foundation JSON coders (as apparently some JWT implementations insist on non-decimal dates despite the RFC)
* Do a major overhaul of most of the README.
  • Loading branch information
gwynne committed Jul 18, 2023
1 parent 17b02f6 commit 9e929d9
Show file tree
Hide file tree
Showing 15 changed files with 363 additions and 205 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
MANGLE_END */
],
dependencies: [
.package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "3.0.0")
.package(url: "https://github.com/apple/swift-crypto.git", from: "2.0.0")
],
targets: [
.target(name: "CJWTKitBoringSSL"),
Expand Down
214 changes: 99 additions & 115 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Sources/JWTKit/Claims/JWTUnixEpochClaim.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ extension JWTUnixEpochClaim {
/// See `Decodable`.
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
try self.init(value: .init(timeIntervalSince1970: container.decode(Double.self)))
self.init(value: try container.decode(Date.self))
}

/// See `Encodable`.
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value.timeIntervalSince1970)
try container.encode(self.value)
}
}
26 changes: 17 additions & 9 deletions Sources/JWTKit/ECDSA/JWTSigner+ECDSA.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
@_implementationOnly import CJWTKitBoringSSL
import class Foundation.JSONEncoder
import class Foundation.JSONDecoder

extension JWTSigner {
public static func es256(key: ECDSAKey) -> JWTSigner {
return .init(algorithm: ECDSASigner(
public static func es256(key: ECDSAKey) -> JWTSigner { .es256(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func es256(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
algorithm: CJWTKitBoringSSL_EVP_sha256(),
name: "ES256"
))
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func es384(key: ECDSAKey) -> JWTSigner {
return .init(algorithm: ECDSASigner(
public static func es384(key: ECDSAKey) -> JWTSigner { .es384(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func es384(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
algorithm: CJWTKitBoringSSL_EVP_sha384(),
name: "ES384"
))
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func es512(key: ECDSAKey) -> JWTSigner {
return .init(algorithm: ECDSASigner(
public static func es512(key: ECDSAKey) -> JWTSigner { .es512(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func es512(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
algorithm: CJWTKitBoringSSL_EVP_sha512(),
name: "ES512"
))
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}
}
6 changes: 4 additions & 2 deletions Sources/JWTKit/EdDSA/JWTSigner+EdDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import Foundation
import Crypto

extension JWTSigner {
public static func eddsa(_ key: EdDSAKey) -> JWTSigner {
.init(algorithm: EdDSASigner(key: key))
public static func eddsa(_ key: EdDSAKey) -> JWTSigner { .eddsa(key, jsonEncoder: nil, jsonDecoder: nil) }

public static func eddsa(_ key: EdDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: EdDSASigner(key: key), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}
}
49 changes: 31 additions & 18 deletions Sources/JWTKit/HMAC/JWTSigner+HMAC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,65 @@ import Foundation
extension JWTSigner {
// MARK: 256

public static func hs256(key: String) -> JWTSigner {
self.hs256(key: [UInt8](key.utf8))
public static func hs256(key: String) -> JWTSigner { .hs256(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs256<Key: DataProtocol>(key: Key) -> JWTSigner { .hs256(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs256(key: SymmetricKey) -> JWTSigner { .hs256(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func hs256(key: String, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.hs256(key: [UInt8](key.utf8), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs256<Key>(key: Key) -> JWTSigner
public static func hs256<Key>(key: Key, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner
where Key: DataProtocol
{
let symmetricKey = SymmetricKey(data: key.copyBytes())
return JWTSigner.hs256(key: symmetricKey)
return .hs256(key: symmetricKey, jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs256(key: SymmetricKey) -> JWTSigner {
return .init(algorithm: HMACSigner<SHA256>(key: key, name: "HS256"))
public static func hs256(key: SymmetricKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: HMACSigner<SHA256>(key: key, name: "HS256"), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)

}

// MARK: 384

public static func hs384(key: String) -> JWTSigner {
self.hs384(key: [UInt8](key.utf8))
public static func hs384(key: String) -> JWTSigner { .hs384(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs384<Key: DataProtocol>(key: Key) -> JWTSigner { .hs384(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs384(key: SymmetricKey) -> JWTSigner { .hs384(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func hs384(key: String, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.hs384(key: [UInt8](key.utf8), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs384<Key>(key: Key) -> JWTSigner
public static func hs384<Key>(key: Key, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner
where Key: DataProtocol
{
let symmetricKey = SymmetricKey(data: key.copyBytes())
return JWTSigner.hs384(key: symmetricKey)
return .hs384(key: symmetricKey, jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs384(key: SymmetricKey) -> JWTSigner {
return .init(algorithm: HMACSigner<SHA384>(key: key, name: "HS384"))
public static func hs384(key: SymmetricKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: HMACSigner<SHA384>(key: key, name: "HS384"), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

// MARK: 512

public static func hs512(key: String) -> JWTSigner {
self.hs512(key: [UInt8](key.utf8))
public static func hs512(key: String) -> JWTSigner { .hs512(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs512<Key: DataProtocol>(key: Key) -> JWTSigner { .hs512(key: key, jsonEncoder: nil, jsonDecoder: nil) }
public static func hs512(key: SymmetricKey) -> JWTSigner { .hs512(key: key, jsonEncoder: nil, jsonDecoder: nil) }

public static func hs512(key: String, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.hs512(key: [UInt8](key.utf8), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs512<Key>(key: Key) -> JWTSigner
public static func hs512<Key>(key: Key, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner
where Key: DataProtocol
{
let symmetricKey = SymmetricKey(data: key.copyBytes())
return JWTSigner.hs512(key: symmetricKey)
return .hs512(key: symmetricKey, jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

public static func hs512(key: SymmetricKey) -> JWTSigner {
return .init(algorithm: HMACSigner<SHA512>(key: key, name: "HS512"))
public static func hs512(key: SymmetricKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: HMACSigner<SHA512>(key: key, name: "HS512"), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}
}
14 changes: 4 additions & 10 deletions Sources/JWTKit/JWTParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ struct JWTParser {
self.encodedSignature = tokenParts[2]
}

func header() throws -> JWTHeader {
try self.jsonDecoder()
func header(jsonDecoder: any JWTJSONDecoder) throws -> JWTHeader {
try jsonDecoder
.decode(JWTHeader.self, from: .init(self.encodedHeader.base64URLDecodedBytes()))
}

func payload<Payload>(as payload: Payload.Type) throws -> Payload
func payload<Payload>(as payload: Payload.Type, jsonDecoder: any JWTJSONDecoder) throws -> Payload
where Payload: JWTPayload
{
try self.jsonDecoder()
try jsonDecoder
.decode(Payload.self, from: .init(self.encodedPayload.base64URLDecodedBytes()))
}

Expand All @@ -43,10 +43,4 @@ struct JWTParser {
private var message: ArraySlice<UInt8> {
self.encodedHeader + [.period] + self.encodedPayload
}

private func jsonDecoder() -> JSONDecoder {
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .secondsSince1970
return jsonDecoder
}
}
6 changes: 2 additions & 4 deletions Sources/JWTKit/JWTSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ struct JWTSerializer {
using signer: JWTSigner,
typ: String = "JWT",
kid: JWKIdentifier? = nil,
cty: String? = nil
cty: String? = nil,
jsonEncoder: any JWTJSONEncoder
) throws -> String
where Payload: JWTPayload
{
let jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .secondsSince1970

// encode header, copying header struct to mutate alg
var header = JWTHeader()
header.kid = kid
Expand Down
17 changes: 14 additions & 3 deletions Sources/JWTKit/JWTSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ import Foundation
/// A JWT signer.
public final class JWTSigner {
public let algorithm: JWTAlgorithm

internal var jsonEncoder: (any JWTJSONEncoder)?
internal var jsonDecoder: (any JWTJSONDecoder)?

public init(algorithm: JWTAlgorithm) {
self.algorithm = algorithm
self.jsonEncoder = nil
self.jsonDecoder = nil
}

public init(algorithm: JWTAlgorithm, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) {
self.algorithm = algorithm
self.jsonEncoder = jsonEncoder
self.jsonDecoder = jsonDecoder
}

public func sign<Payload>(
Expand All @@ -16,7 +27,7 @@ public final class JWTSigner {
) throws -> String
where Payload: JWTPayload
{
try JWTSerializer().sign(payload, using: self, typ: typ, kid: kid, cty: cty)
try JWTSerializer().sign(payload, using: self, typ: typ, kid: kid, cty: cty, jsonEncoder: self.jsonEncoder ?? .defaultForJWT)
}

public func unverified<Payload>(
Expand All @@ -34,7 +45,7 @@ public final class JWTSigner {
) throws -> Payload
where Message: DataProtocol, Payload: JWTPayload
{
try JWTParser(token: token).payload(as: Payload.self)
try JWTParser(token: token).payload(as: Payload.self, jsonDecoder: self.jsonDecoder ?? .defaultForJWT)
}

public func verify<Payload>(
Expand All @@ -60,7 +71,7 @@ public final class JWTSigner {
where Payload: JWTPayload
{
try parser.verify(using: self)
let payload = try parser.payload(as: Payload.self)
let payload = try parser.payload(as: Payload.self, jsonDecoder: self.jsonDecoder ?? .defaultForJWT)
try payload.verify(using: self)
return payload
}
Expand Down
Loading

0 comments on commit 9e929d9

Please sign in to comment.