diff --git a/Sources/JWTKit/ECDSA/ECDSA.swift b/Sources/JWTKit/ECDSA/ECDSA.swift index c1a0d3d4..a4b5035f 100644 --- a/Sources/JWTKit/ECDSA/ECDSA.swift +++ b/Sources/JWTKit/ECDSA/ECDSA.swift @@ -7,7 +7,7 @@ public enum ECDSA: Sendable {} public protocol ECDSAKey: Sendable { associatedtype Curve: ECDSACurveType - + var curve: ECDSACurve { get } var parameters: ECDSAParameters? { get } } diff --git a/Sources/JWTKit/ECDSA/ECDSACurveType.swift b/Sources/JWTKit/ECDSA/ECDSACurveType.swift index af794927..9ddafd43 100644 --- a/Sources/JWTKit/ECDSA/ECDSACurveType.swift +++ b/Sources/JWTKit/ECDSA/ECDSACurveType.swift @@ -19,6 +19,8 @@ public protocol ECDSACurveType: Sendable { associatedtype Signature: ECDSASignature associatedtype PrivateKey: ECDSAPrivateKey + associatedtype SigningAlgorithm: ECDSASigningAlgorithm + static var curve: ECDSACurve { get } static var byteRanges: (x: Range, y: Range) { get } } diff --git a/Sources/JWTKit/ECDSA/ECDSASigner.swift b/Sources/JWTKit/ECDSA/ECDSASigner.swift index 82be7b71..8d52c26b 100644 --- a/Sources/JWTKit/ECDSA/ECDSASigner.swift +++ b/Sources/JWTKit/ECDSA/ECDSASigner.swift @@ -3,10 +3,10 @@ import Foundation struct ECDSASigner: JWTAlgorithm, CryptoSigner { let privateKey: ECDSA.PrivateKey? let publicKey: ECDSA.PublicKey - let algorithm: DigestAlgorithm - public let name: String + let algorithm: DigestAlgorithm = Key.Curve.SigningAlgorithm.digestAlgorithm + let name: String = Key.Curve.SigningAlgorithm.name - init(key: Key, algorithm: DigestAlgorithm, name: String) { + init(key: Key) { switch key { case let privateKey as ECDSA.PrivateKey: self.privateKey = privateKey @@ -18,8 +18,6 @@ struct ECDSASigner: JWTAlgorithm, CryptoSigner { // This should never happen fatalError("Unexpected key type: \(type(of: key))") } - self.algorithm = algorithm - self.name = name } func sign(_ plaintext: some DataProtocol) throws -> [UInt8] { diff --git a/Sources/JWTKit/ECDSA/ECDSASigningAlgorithm.swift b/Sources/JWTKit/ECDSA/ECDSASigningAlgorithm.swift new file mode 100644 index 00000000..b81959cc --- /dev/null +++ b/Sources/JWTKit/ECDSA/ECDSASigningAlgorithm.swift @@ -0,0 +1,4 @@ +public protocol ECDSASigningAlgorithm { + static var name: String { get } + static var digestAlgorithm: DigestAlgorithm { get } +} diff --git a/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift b/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift index c9eae097..a96ff17d 100644 --- a/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift +++ b/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift @@ -1,88 +1,16 @@ import Crypto public extension JWTKeyCollection { - /// Adds an ES256 key to the collection. - /// - /// This method configures and adds an ES256 (ECDSA using P-256 and SHA-256) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addES256(key: myES256Key) - /// ``` - /// - /// - Parameters: - /// - key: The ``ES256Key`` to be used for signing. This key should be securely stored and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, this identifier will be used in the JWT `kid` - /// header field to identify the key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), which allows for method chaining. - @discardableResult - func addES256( - key: Key, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self - where Key.Curve == P256 - { - add(.init( - algorithm: ECDSASigner(key: key, algorithm: .sha256, name: "ES256"), - parser: parser, - serializer: serializer - ), for: kid) - } - - /// Adds an ES384 key to the collection. - /// - /// This method configures and adds an ES384(ECDSA using P-384 and SHA-384) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addES384(key: myES384Key) - /// ``` - /// - /// - Parameters: - /// - key: The ``ES384Key`` to be used for signing. This key should be securely stored and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, this identifier will be used in the JWT `kid` - /// header field to identify the key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), which allows for method chaining. - @discardableResult - func addES384( - key: Key, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self - where Key.Curve == P384 - { - add(.init( - algorithm: ECDSASigner(key: key, algorithm: .sha384, name: "ES384"), - parser: parser, - serializer: serializer - ), for: kid) - } - - /// Adds an ES512 key to the collection. - /// - /// This method configures and adds an ES512 (ECDSA using P-521 and SHA-512) key to the collection. + /// Adds an ECDSA key to the collection. /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addES512(key: myES512Key) + /// .addECDSA(key: myECDSAKey) /// ``` /// /// - Parameters: - /// - key: The ``ES512Key`` to be used for signing. This key should be securely stored and not exposed. + /// - key: The ``ECDSAKey`` to be used for signing. This key should be securely stored and not exposed. /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, this identifier will be used in the JWT `kid` /// header field to identify the key. /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. @@ -91,16 +19,14 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), which allows for method chaining. @discardableResult - func addES512( - key: Key, + func addECDSA( + key: some ECDSAKey, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self - where Key.Curve == P521 - { + ) -> Self { add(.init( - algorithm: ECDSASigner(key: key, algorithm: .sha512, name: "ES512"), + algorithm: ECDSASigner(key: key), parser: parser, serializer: serializer ), for: kid) diff --git a/Sources/JWTKit/ECDSA/P256+CurveType.swift b/Sources/JWTKit/ECDSA/P256+CurveType.swift index 28ae9fb4..b15436ac 100644 --- a/Sources/JWTKit/ECDSA/P256+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P256+CurveType.swift @@ -16,6 +16,11 @@ extension P256: ECDSACurveType, @unchecked Sendable { /// - The X coordinate spans bytes 1 through 32 (byte 0 is for the prefix). /// - The Y coordinate spans bytes 33 through 64. public static let byteRanges: (x: Range, y: Range) = (1 ..< 33, 33 ..< 65) + + public struct SigningAlgorithm: ECDSASigningAlgorithm { + public static let name = "ES256" + public static let digestAlgorithm: DigestAlgorithm = .sha256 + } } // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable diff --git a/Sources/JWTKit/ECDSA/P384+CurveType.swift b/Sources/JWTKit/ECDSA/P384+CurveType.swift index a1797a97..ce589908 100644 --- a/Sources/JWTKit/ECDSA/P384+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P384+CurveType.swift @@ -16,6 +16,11 @@ extension P384: ECDSACurveType, @unchecked Sendable { /// - The X coordinate spans bytes 1 through 48. /// - The Y coordinate spans bytes 49 through 96. public static let byteRanges: (x: Range, y: Range) = (1 ..< 49, 49 ..< 97) + + public enum SigningAlgorithm: ECDSASigningAlgorithm { + public static let name = "ES384" + public static let digestAlgorithm: DigestAlgorithm = .sha384 + } } // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable diff --git a/Sources/JWTKit/ECDSA/P521+CurveType.swift b/Sources/JWTKit/ECDSA/P521+CurveType.swift index 0433e899..b814be62 100644 --- a/Sources/JWTKit/ECDSA/P521+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P521+CurveType.swift @@ -17,6 +17,11 @@ extension P521: ECDSACurveType, @unchecked Sendable { /// - The X coordinate spans bytes 1 through 66. /// - The Y coordinate spans bytes 67 through 132. public static let byteRanges: (x: Range, y: Range) = (1 ..< 67, 67 ..< 133) + + public enum SigningAlgorithm: ECDSASigningAlgorithm { + public static let name = "ES512" + public static let digestAlgorithm: DigestAlgorithm = .sha512 + } } // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable diff --git a/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift b/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift index 927f1c2e..31e9d083 100644 --- a/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift +++ b/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift @@ -2,111 +2,12 @@ import Crypto import Foundation public extension JWTKeyCollection { - // MARK: 256 - - /// Adds an HS256 key to the collection. - /// - /// This method configures and adds an HS256 (HMAC with SHA-256) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS256(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The secret key as a `String` used for HMAC signing. This key should be kept confidential - /// and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS256( - key: String, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - addHS256(key: [UInt8](key.utf8), kid: kid, parser: parser, serializer: serializer) - } - - /// Adds an HS256 key to the collection. - /// - /// This method configures and adds an HS256 (HMAC with SHA-256) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS256(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The secret key as data conforming to `DataProtocol` used for HMAC signing. This key should be kept confidential - /// and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS256( - key: some DataProtocol, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - let symmetricKey = SymmetricKey(data: key.copyBytes()) - return addHS256(key: symmetricKey, kid: kid, parser: parser, serializer: serializer) - } - - /// Adds an HS256 key to the collection. - /// - /// This method configures and adds an HS256 (HMAC with SHA-256) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS256(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The `SymmetricKey` used for HMAC signing. This key should be kept confidential - /// and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS256( - key: SymmetricKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init(algorithm: HMACSigner(key: key, name: "HS256"), parser: parser, serializer: serializer), for: kid) - } -} - -public extension JWTKeyCollection { - // MARK: 384 - - /// Adds an HS384 key to the collection. - /// - /// This method configures and adds an HS384 (HMAC with SHA-384) key to the collection. + /// Adds an HMAC key to the collection. /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addHS384(key: "mySecretKey") + /// .addHMAC(key: "mySecretKey") /// ``` /// /// - Parameters: @@ -120,23 +21,28 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addHS384( + func addHMAC( key: String, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - addHS384(key: [UInt8](key.utf8), kid: kid, parser: parser, serializer: serializer) + addHMAC( + key: [UInt8](key.utf8), + digestAlgorithm: digestAlgorithm, + kid: kid, + parser: parser, + serializer: serializer + ) } - /// Adds an HS384 key to the collection. - /// - /// This method configures and adds an HS384 (HMAC with SHA-384) key to the collection. + /// Adds an HMAC key to the collection. /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addHS384(key: "mySecretKey") + /// .addHMAC(key: "mySecretKey".bytes) /// ``` /// /// - Parameters: @@ -150,53 +56,23 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addHS384( + func addHMAC( key: some DataProtocol, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - let symmetricKey = SymmetricKey(data: key.copyBytes()) - return addHS384(key: symmetricKey, kid: kid, parser: parser, serializer: serializer) + addHMAC( + key: SymmetricKey(data: key.copyBytes()), + digestAlgorithm: digestAlgorithm, + kid: kid, + parser: parser, + serializer: serializer + ) } - /// Adds an HS384 key to the collection. - /// - /// This method configures and adds an HS384 (HMAC with SHA-384) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS384(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The `SymmetricKey` used for HMAC signing. This key should be kept confidential - /// and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS384( - key: SymmetricKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init(algorithm: HMACSigner(key: key, name: "HS384"), parser: parser, serializer: serializer), for: kid) - } -} - -public extension JWTKeyCollection { - // MARK: 512 - - /// Adds an HS512 key to the collection. - /// - /// This method configures and adds an HS512 (HMAC with SHA-512) key to the collection. + /// Adds an HMAC key to the collection. /// /// Example Usage: /// ``` @@ -205,7 +81,7 @@ public extension JWTKeyCollection { /// ``` /// /// - Parameters: - /// - key: The secret key as a `String` used for HMAC signing. This key should be kept confidential + /// - key: The `SymmetricKey` used for HMAC signing. This key should be kept confidential /// and secure, as it can be used for both signing and verification. /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field /// to identify this key. @@ -215,71 +91,20 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addHS512( - key: String, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - addHS512(key: [UInt8](key.utf8), kid: kid, parser: parser, serializer: serializer) - } - - /// Adds an HS512 key to the collection. - /// - /// This method configures and adds an HS512 (HMAC with SHA-512) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS256(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The secret key as data conforming to `DataProtocol` used for HMAC signing. This key should be kept confidential and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS512( - key: some DataProtocol, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - let symmetricKey = SymmetricKey(data: key.copyBytes()) - return addHS512(key: symmetricKey, kid: kid, parser: parser, serializer: serializer) - } - - /// Adds an HS512 key to the collection. - /// - /// This method configures and adds an HS512 (HMAC with SHA-512) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addHS256(key: "mySecretKey") - /// ``` - /// - /// - Parameters: - /// - key: The `SymmetricKey` used for HMAC signing. This key should be kept confidential and secure, as it can be used for both signing and verification. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If given, it is used in the JWT `kid` header field - /// to identify this key. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder``, used for encoding JWTs. - /// If `nil`, a default encoder is employed. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder``, used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addHS512( + func addHMAC( key: SymmetricKey, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init(algorithm: HMACSigner(key: key, name: "HS512"), parser: parser, serializer: serializer), for: kid) + switch digestAlgorithm.backing { + case .sha256: + add(.init(algorithm: HMACSigner(key: key, name: "HS256"), parser: parser, serializer: serializer), for: kid) + case .sha384: + add(.init(algorithm: HMACSigner(key: key, name: "HS384"), parser: parser, serializer: serializer), for: kid) + case .sha512: + add(.init(algorithm: HMACSigner(key: key, name: "HS512"), parser: parser, serializer: serializer), for: kid) + } } } diff --git a/Sources/JWTKit/JWK/JWKSigner.swift b/Sources/JWTKit/JWK/JWKSigner.swift index f2934eec..6980db9a 100644 --- a/Sources/JWTKit/JWK/JWKSigner.swift +++ b/Sources/JWTKit/JWK/JWKSigner.swift @@ -1,6 +1,6 @@ struct JWKSigner: Sendable { let jwk: JWK - + let parser: any JWTParser let serializer: any JWTSerializer @@ -68,46 +68,22 @@ struct JWKSigner: Sendable { switch algorithm { case .es256: if let privateExponent = self.jwk.privateExponent { - return try .init(algorithm: ECDSASigner( - key: ES256PrivateKey(key: privateExponent), - algorithm: .sha256, - name: "ES256" - )) + return try .init(algorithm: ECDSASigner(key: ES256PrivateKey(key: privateExponent))) } else { - return try .init(algorithm: ECDSASigner( - key: ES256PublicKey(parameters: (x, y)), - algorithm: .sha256, - name: "ES256" - )) + return try .init(algorithm: ECDSASigner(key: ES256PublicKey(parameters: (x, y)))) } case .es384: if let privateExponent = self.jwk.privateExponent { - return try .init(algorithm: ECDSASigner( - key: ES384PrivateKey(key: privateExponent), - algorithm: .sha384, - name: "ES384" - )) + return try .init(algorithm: ECDSASigner(key: ES384PrivateKey(key: privateExponent))) } else { - return try .init(algorithm: ECDSASigner( - key: ES384PublicKey(parameters: (x, y)), - algorithm: .sha384, - name: "ES384" - )) + return try .init(algorithm: ECDSASigner(key: ES384PublicKey(parameters: (x, y)))) } case .es512: if let privateExponent = self.jwk.privateExponent { - return try .init(algorithm: ECDSASigner( - key: ES512PrivateKey(key: privateExponent), - algorithm: .sha512, - name: "ES512" - )) + return try .init(algorithm: ECDSASigner(key: ES512PrivateKey(key: privateExponent))) } else { - return try .init(algorithm: ECDSASigner( - key: ES512PublicKey(parameters: (x, y)), - algorithm: .sha512, - name: "ES512" - )) + return try .init(algorithm: ECDSASigner(key: ES512PublicKey(parameters: (x, y)))) } default: return nil diff --git a/Sources/JWTKit/JWTError.swift b/Sources/JWTKit/JWTError.swift index 3a3bc73b..6800ae35 100644 --- a/Sources/JWTKit/JWTError.swift +++ b/Sources/JWTKit/JWTError.swift @@ -7,7 +7,7 @@ public struct JWTError: Error, Sendable { case claimVerificationFailure case signingAlgorithmFailure case malformedToken - case signatureVerificationFailed + case signatureVerifictionFailed case missingKIDHeader case unknownKID case invalidJWK @@ -16,6 +16,7 @@ public struct JWTError: Error, Sendable { case missingX5CHeader case invalidX5CChain case invalidHeaderField + case unsupportedCurve case generic } @@ -27,7 +28,7 @@ public struct JWTError: Error, Sendable { public static let claimVerificationFailure = Self(.claimVerificationFailure) public static let signingAlgorithmFailure = Self(.signingAlgorithmFailure) - public static let signatureVerificationFailed = Self(.signatureVerificationFailed) + public static let signatureVerificationFailed = Self(.signatureVerifictionFailed) public static let missingKIDHeader = Self(.missingKIDHeader) public static let malformedToken = Self(.malformedToken) public static let unknownKID = Self(.unknownKID) @@ -37,6 +38,7 @@ public struct JWTError: Error, Sendable { public static let missingX5CHeader = Self(.missingX5CHeader) public static let invalidX5CChain = Self(.invalidX5CChain) public static let invalidHeaderField = Self(.invalidHeaderField) + public static let unsupportedCurve = Self(.unsupportedCurve) public static let generic = Self(.generic) public var description: String { @@ -52,6 +54,7 @@ public struct JWTError: Error, Sendable { fileprivate let kid: JWKIdentifier? fileprivate let identifier: String? fileprivate let failedClaim: (any JWTClaim)? + fileprivate var curve: (any ECDSACurveType)? init( errorType: ErrorType, @@ -60,7 +63,8 @@ public struct JWTError: Error, Sendable { underlying: Error? = nil, kid: JWKIdentifier? = nil, identifier: String? = nil, - failedClaim: (any JWTClaim)? = nil + failedClaim: (any JWTClaim)? = nil, + curve: (any ECDSACurveType)? = nil ) { self.errorType = errorType self.name = name @@ -69,6 +73,7 @@ public struct JWTError: Error, Sendable { self.kid = kid self.identifier = identifier self.failedClaim = failedClaim + self.curve = curve } } @@ -81,6 +86,7 @@ public struct JWTError: Error, Sendable { public var kid: JWKIdentifier? { backing.kid } public var identifier: String? { backing.identifier } public var failedClaim: (any JWTClaim)? { backing.failedClaim } + public var curve: (any ECDSACurveType)? { backing.curve } private init(backing: Backing) { self.backing = backing @@ -128,6 +134,10 @@ public struct JWTError: Error, Sendable { .init(backing: .init(errorType: .invalidHeaderField, reason: reason)) } + public static func unsupportedCurve(curve: any ECDSACurveType) -> Self { + .init(backing: .init(errorType: .unsupportedCurve, curve: curve)) + } + public static func generic(identifier: String, reason: String) -> Self { .init(backing: .init(errorType: .generic, reason: reason)) } diff --git a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift index 151f1659..071a3897 100644 --- a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift +++ b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift @@ -1,156 +1,60 @@ import _CryptoExtras public extension JWTKeyCollection { - /// Adds an RS256 key to the collection. + /// Adds an RSA key to the collection. /// - /// This method configures and adds an RS256 (RSA Signature with SHA-256) key to the collection. + /// This method configures and adds an RSA key to the collection. The key is used for signing JWTs /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addRS256(key: myRSAKey) + /// .addRSA(key: myRSAKey) /// ``` /// /// - Parameters: /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. + /// in the JWT `kid` header field. /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. + /// If `nil`, a default encoder is used. /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. + /// If `nil`, a default decoder is used. + /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addRS256( + func addRSA( key: some RSAKey, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha256, name: "RS256", padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) - } + let name = switch digestAlgorithm.backing { + case .sha256: + "RS256" + case .sha384: + "RS384" + case .sha512: + "RS512" + } - /// Adds an RS384 key to the collection. - /// - /// This method configures and adds an RS384 (RSA Signature with SHA-384) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = try await JWTKeyCollection() - /// .addRS384(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addRS384( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha384, name: "RS384", padding: .insecurePKCS1v1_5), + return add(.init( + algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .insecurePKCS1v1_5), parser: parser, serializer: serializer ), for: kid) } - /// Adds an RS512 key to the collection. + /// Adds a PSS key to the collection. /// - /// This method configures and adds an RS512 (RSA Signature with SHA-512) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = try await JWTKeyCollection() - /// .addRS512(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addRS512( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha512, name: "RS512", padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) - } - - // MARK: PSS - - /// Adds a PS256 key to the collection. - /// - /// This method configures and adds a PS256 (RSA PSS Signature with SHA-256) key to the collection. PS256 - /// uses RSASSA-PSS with SHA-256 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS256. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addPS256(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addPS256( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha256, name: "PS256", padding: .PSS), - parser: parser, - serializer: serializer - ), - for: kid) - } - - /// Adds a PS384 key to the collection. - /// - /// This method configures and adds a PS256 (RSA PSS Signature with SHA-384) key to the collection. PS384 - /// uses RSASSA-PSS with SHA-384 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS384. + /// This method configures and adds a PSS (RSA PSS Signature) key to the collection. PSS + /// uses RSASSA-PSS for the RSA signature, which is considered more secure than PKCS#1 v1.5 + /// padding used in RSA. /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addPS384(key: myRSAKey) + /// .addPSS(key: myRSAKey) /// ``` /// /// - Parameters: @@ -163,50 +67,24 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addPS384( + func addPSS( key: some RSAKey, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha384, name: "PS384", padding: .PSS), - parser: parser, - serializer: serializer - ), - for: kid) - } + let name = switch digestAlgorithm.backing { + case .sha256: + "PS256" + case .sha384: + "PS384" + case .sha512: + "PS512" + } - /// Adds a PS512 key to the collection. - /// - /// This method configures and adds a PS512 (RSA PSS Signature with SHA-512) key to the collection. PS512 - /// uses RSASSA-PSS with SHA-512 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS512. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addPS512(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addPS512( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha512, name: "PS512", padding: .PSS), + return add(.init( + algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .PSS), parser: parser, serializer: serializer ), diff --git a/Sources/JWTKit/Utilities/CryptoSigner.swift b/Sources/JWTKit/Utilities/CryptoSigner.swift index 06b277ca..ee696484 100644 --- a/Sources/JWTKit/Utilities/CryptoSigner.swift +++ b/Sources/JWTKit/Utilities/CryptoSigner.swift @@ -1,10 +1,18 @@ import Crypto import Foundation -enum DigestAlgorithm { - case sha256 - case sha384 - case sha512 +public struct DigestAlgorithm: Sendable, Equatable { + enum Backing { + case sha256 + case sha384 + case sha512 + } + + let backing: Backing + + public static let sha256 = Self(backing: .sha256) + public static let sha384 = Self(backing: .sha384) + public static let sha512 = Self(backing: .sha512) } protocol CryptoSigner: Sendable { @@ -21,7 +29,7 @@ private enum CryptoError: Error { extension CryptoSigner { func digest(_ plaintext: some DataProtocol) throws -> any Digest { - switch algorithm { + switch algorithm.backing { case .sha256: SHA256.hash(data: plaintext) case .sha384: diff --git a/Sources/JWTKit/X5C/X5CVerifier.swift b/Sources/JWTKit/X5C/X5CVerifier.swift index e5ae34f0..4a830ba0 100644 --- a/Sources/JWTKit/X5C/X5CVerifier.swift +++ b/Sources/JWTKit/X5C/X5CVerifier.swift @@ -193,7 +193,7 @@ public struct X5CVerifier: Sendable { // Assuming the chain is valid, verify the token was signed by the valid certificate let ecdsaKey = try ES256PublicKey(certificate: certificates[0].serializeAsPEM().pemString) - let signer = JWTSigner(algorithm: ECDSASigner(key: ecdsaKey, algorithm: .sha256, name: headerAlg), parser: parser) + let signer = JWTSigner(algorithm: ECDSASigner(key: ecdsaKey), parser: parser) return try await signer.verify(token) } diff --git a/Tests/JWTKitTests/ClaimTests.swift b/Tests/JWTKitTests/ClaimTests.swift index 1a7f0299..550079eb 100644 --- a/Tests/JWTKitTests/ClaimTests.swift +++ b/Tests/JWTKitTests/ClaimTests.swift @@ -68,11 +68,11 @@ final class ClaimTests: XCTestCase { XCTAssertEqual((jwtError.failedClaim as? AudienceClaim)?.value, [id1.uuidString, id2.uuidString]) } } - + func testExpirationEncoding() async throws { let exp = ExpirationClaim(value: Date(timeIntervalSince1970: 2_000_000_000)) let parser = DefaultJWTParser() - let keyCollection = await JWTKeyCollection().addHS256(key: "secret".bytes, parser: parser) + let keyCollection = await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256, parser: parser) let jwt = try await keyCollection.sign(ExpirationPayload(exp: exp)) let parsed = try parser.parse(jwt.bytes, as: ExpirationPayload.self) let header = parsed.header diff --git a/Tests/JWTKitTests/ECDSATests.swift b/Tests/JWTKitTests/ECDSATests.swift index 9cedcf73..cf6dbcf8 100644 --- a/Tests/JWTKitTests/ECDSATests.swift +++ b/Tests/JWTKitTests/ECDSATests.swift @@ -25,7 +25,9 @@ final class ECDSATests: XCTestCase { func testSigningWithPublicKey() async throws { let key = try ES256PrivateKey(pem: ecdsaPrivateKey) let publicKey = try ES256PublicKey(pem: ecdsaPublicKey) - let keys = await JWTKeyCollection().addES256(key: key, kid: "private").addES256(key: publicKey, kid: "public") + let keys = await JWTKeyCollection() + .addECDSA(key: key, kid: "private") + .addECDSA(key: publicKey, kid: "public") let payload = TestPayload( sub: "vapor", @@ -51,15 +53,15 @@ final class ECDSATests: XCTestCase { ) let keyCollection = JWTKeyCollection() let key = ES256PrivateKey() - await keyCollection.addES256(key: key) + await keyCollection.addECDSA(key: key) let token = try await keyCollection.sign(payload) try await XCTAssertEqualAsync(await keyCollection.verify(token, as: TestPayload.self), payload) } func testECDSAPublicPrivate() async throws { let keys = try await JWTKeyCollection() - .addES256(key: ES256PublicKey(pem: ecdsaPublicKey), kid: "public") - .addES256(key: ES256PrivateKey(pem: ecdsaPrivateKey), kid: "private") + .addECDSA(key: ES256PublicKey(pem: ecdsaPublicKey), kid: "public") + .addECDSA(key: ES256PrivateKey(pem: ecdsaPrivateKey), kid: "private") let payload = TestPayload( sub: "vapor", @@ -90,7 +92,7 @@ final class ECDSATests: XCTestCase { // sign jwt let key = try ES384PrivateKey(key: privateKey) - let keys = await JWTKeyCollection().addES384(key: key, kid: "vapor") + let keys = await JWTKeyCollection().addECDSA(key: key, kid: "vapor") let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") @@ -129,7 +131,7 @@ final class ECDSATests: XCTestCase { // sign jwt let key = try ES384PrivateKey(key: privateKey) - let keys = await JWTKeyCollection().addES384(key: key, kid: "vapor") + let keys = await JWTKeyCollection().addECDSA(key: key, kid: "vapor") let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") @@ -168,7 +170,7 @@ final class ECDSATests: XCTestCase { // sign jwt let key = try ES384PrivateKey(key: privateKey) - let keys = await JWTKeyCollection().addES384(key: key, kid: "vapor") + let keys = await JWTKeyCollection().addECDSA(key: key, kid: "vapor") let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") @@ -205,7 +207,7 @@ final class ECDSATests: XCTestCase { } } - let keys = await JWTKeyCollection().addES256(key: ES256PrivateKey(), kid: "vapor") + let keys = await JWTKeyCollection().addECDSA(key: ES256PrivateKey(), kid: "vapor") do { let token = try await keys.sign(Payload(foo: "qux")) @@ -236,12 +238,12 @@ final class ECDSATests: XCTestCase { let message = "test".bytes let ec = ES256PrivateKey() - let keys = await JWTKeyCollection().addES256(key: ec, kid: "initial") + let keys = await JWTKeyCollection().addECDSA(key: ec, kid: "initial") let signature = try await keys.getKey(for: "initial").sign(message) let params = ec.parameters! - try await keys.addES256(key: ES256PublicKey(parameters: params), kid: "params") + try await keys.addECDSA(key: ES256PublicKey(parameters: params), kid: "params") try await XCTAssertTrueAsync(try await keys.getKey(for: "params").verify(signature, signs: message)) XCTAssertEqual(ec.curve, .p256) } @@ -250,12 +252,12 @@ final class ECDSATests: XCTestCase { let message = "test".bytes let ec = ES384PrivateKey() - let keys = await JWTKeyCollection().addES384(key: ec, kid: "initial") + let keys = await JWTKeyCollection().addECDSA(key: ec, kid: "initial") let signature = try await keys.getKey(for: "initial").sign(message) let params = ec.parameters! - try await keys.addES384(key: ES384PublicKey(parameters: params), kid: "params") + try await keys.addECDSA(key: ES384PublicKey(parameters: params), kid: "params") try await XCTAssertTrueAsync(try await keys.getKey(for: "params").verify(signature, signs: message)) XCTAssertEqual(ec.curve, .p384) } @@ -264,12 +266,12 @@ final class ECDSATests: XCTestCase { let message = "test".bytes let ec = ES512PrivateKey() - let keys = await JWTKeyCollection().addES512(key: ec, kid: "initial") + let keys = await JWTKeyCollection().addECDSA(key: ec, kid: "initial") let signature = try await keys.getKey(for: "initial").sign(message) let params = ec.parameters! - try await keys.addES512(key: ES512PublicKey(parameters: params), kid: "params") + try await keys.addECDSA(key: ES512PublicKey(parameters: params), kid: "params") try await XCTAssertTrueAsync(try await keys.getKey(for: "params").verify(signature, signs: message)) XCTAssertEqual(ec.curve, .p521) } diff --git a/Tests/JWTKitTests/JWTKitTests.swift b/Tests/JWTKitTests/JWTKitTests.swift index 9ea4094e..a1c0c182 100644 --- a/Tests/JWTKitTests/JWTKitTests.swift +++ b/Tests/JWTKitTests/JWTKitTests.swift @@ -36,7 +36,7 @@ class JWTKitTests: XCTestCase { } let keyCollection = await JWTKeyCollection() - .addHS256(key: "secret") + .addHMAC(key: "secret", digestAlgorithm: .sha256) do { let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo" @@ -57,7 +57,7 @@ class JWTKitTests: XCTestCase { func testParse() async throws { let data = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6OTk5OTk5OTk5OTk5fQ.Ks7KcdjrlUTYaSNeAO5SzBla_sFCHkUh4vvJYn6q29U" - let test = try await JWTKeyCollection().addHS256(key: "secret".bytes) + let test = try await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) .verify(data, as: TestPayload.self) XCTAssertEqual(test.name, "John Doe") @@ -69,7 +69,7 @@ class JWTKitTests: XCTestCase { let data = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MX0.-x_DAYIg4R4R9oZssqgWyJP_oWO1ESj8DgKrGCk7i5o" do { - _ = try await JWTKeyCollection().addHS256(key: "secret".bytes) + _ = try await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) .verify(data, as: TestPayload.self) } catch let error as JWTError { XCTAssertEqual(error.errorType, .claimVerificationFailure) @@ -81,14 +81,14 @@ class JWTKitTests: XCTestCase { func testExpirationDecoding() async throws { let data = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwMDAwMDAwMDB9.JgCO_GqUQnbS0z2hCxJLE9Tpt5SMoZObHBxzGBWuTYQ" - let test = try await JWTKeyCollection().addHS256(key: "secret".bytes) + let test = try await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) .verify(data, as: ExpirationPayload.self) XCTAssertEqual(test.exp.value, Date(timeIntervalSince1970: 2_000_000_000)) } func testSigners() async throws { let data = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImZvbyJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6OTk5OTk5OTk5OTk5OTl9.Gf7leJ8i30LmMI7GBTpWDMXV60y1wkTOCOBudP9v9ms" - let keyCollection = await JWTKeyCollection().addHS256(key: "bar".bytes, kid: "foo") + let keyCollection = await JWTKeyCollection().addHMAC(key: "bar".bytes, digestAlgorithm: .sha256, kid: "foo") let payload = try await keyCollection.verify(data, as: TestPayload.self) XCTAssertEqual(payload.name, "John Doe") } @@ -139,7 +139,8 @@ class JWTKitTests: XCTestCase { } // create public key signer (verifier) - let keyCollection = try await JWTKeyCollection().addES256(key: ES256PublicKey(pem: publicKey.bytes)) + let keyCollection = try await JWTKeyCollection() + .addECDSA(key: ES256PublicKey(pem: publicKey.bytes)) // decode jwt and test payload contents let jwt = try await keyCollection.verify(token, as: JWTioPayload.self) @@ -242,7 +243,7 @@ class JWTKitTests: XCTestCase { } } - let keyCollection = await JWTKeyCollection().addES256(key: ES256PrivateKey()) + let keyCollection = await JWTKeyCollection().addECDSA(key: ES256PrivateKey()) do { let token = try await keyCollection.sign(Payload(foo: "qux")) _ = try await keyCollection.verify(token, as: Payload.self) @@ -263,11 +264,15 @@ class JWTKitTests: XCTestCase { let privateExponent = "awDmF9aqLqokmXjiydda8mKboArWwP2Ih7K3Ad3Og_u9nUp2gZrXiCMxGGSQiN5Jg3yiW_ffNYaHfyfRWKyQ_g31n4UfPLmPtw6iL3V9GChV5ZDRE9HpxE88U8r1h__xFFrrdnBeWKW8NldI70jg7vY6uiRae4uuXCfSbs4iAUxmRVKWCnV7JE6sObQKUV_EJkBcyND5Y97xsmWD0nPmXCnloQ84gF-eTErJoZBvQhJ4BhmBeUlREHmDKssaxVOCK4l335DKHD1vbuPk9e49M71BK7r2y4Atqk3TEetnwzMs3u-L9RqHaGIBw5u324uGweY7QeD7HFdAUtpjOq_MQQ" // sign jwt - let keyCollection = try await JWTKeyCollection().addRS256(key: Insecure.RSA.PrivateKey( - modulus: modulus, - exponent: exponent, - privateExponent: privateExponent - ), kid: "vapor") + let keyCollection = try await JWTKeyCollection().addRSA( + key: Insecure.RSA.PrivateKey( + modulus: modulus, + exponent: exponent, + privateExponent: privateExponent + ), + digestAlgorithm: .sha256, + kid: "vapor" + ) struct Foo: JWTPayload { var bar: Int func verify(using _: some JWTAlgorithm) throws {} @@ -300,7 +305,7 @@ class JWTKitTests: XCTestCase { func testFirebaseJWTAndCertificate() async throws { let payload = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PublicKey(certificatePEM: firebaseCert)) + .addRSA(key: Insecure.RSA.PublicKey(certificatePEM: firebaseCert), digestAlgorithm: .sha256) .verify(firebaseJWT, as: FirebasePayload.self) XCTAssertEqual(payload.userID, "y8wiKThXGKM88xxrQWDZzKnBuqv2") } @@ -355,7 +360,7 @@ class JWTKitTests: XCTestCase { struct CustomParser: JWTParser { var jsonDecoder: JWTJSONDecoder = .defaultForJWT - func parse(_ token: some DataProtocol, as: Payload.Type) throws -> (header: JWTHeader, payload: Payload, signature: Data) where Payload: JWTPayload { + func parse(_ token: some DataProtocol, as _: Payload.Type) throws -> (header: JWTHeader, payload: Payload, signature: Data) where Payload: JWTPayload { let (encodedHeader, encodedPayload, encodedSignature) = try getTokenParts(token) let header = try jsonDecoder.decode(JWTHeader.self, from: .init(encodedHeader.base64URLDecodedBytes())) @@ -373,7 +378,7 @@ class JWTKitTests: XCTestCase { } let keyCollection = await JWTKeyCollection() - .addHS256(key: "secret", parser: CustomParser(), serializer: CustomSerializer()) + .addHMAC(key: "secret", digestAlgorithm: .sha256, parser: CustomParser(), serializer: CustomSerializer()) let payload = TestPayload(sub: "vapor", name: "Foo", admin: false, exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000))) @@ -385,7 +390,7 @@ class JWTKitTests: XCTestCase { func testJWKEncoding() async throws { let jwkIdentifier = JWKIdentifier(string: "vapor") let data = try JSONEncoder().encode(jwkIdentifier) - let string = String(decoding: data, as: UTF8.self) + let string = String(data: data, encoding: .utf8)! XCTAssertEqual(string, "\"vapor\"") } @@ -415,7 +420,7 @@ class JWTKitTests: XCTestCase { } func testCustomHeaderFields() async throws { - let keyCollection = await JWTKeyCollection().addHS256(key: "secret".bytes) + let keyCollection = await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) let payload = TestPayload( sub: "vapor", @@ -445,12 +450,12 @@ class JWTKitTests: XCTestCase { let jsonDecoder = JSONDecoder() XCTAssertEqual( try jsonDecoder.decode([String: JWTHeaderField].self, from: encodedHeader), - try jsonDecoder.decode([String: JWTHeaderField].self, from: Data(jsonFields.utf8)) + try jsonDecoder.decode([String: JWTHeaderField].self, from: jsonFields.data(using: .utf8)!) ) } func testSampleOpenbankingHeader() async throws { - let keyCollection = await JWTKeyCollection().addHS256(key: "secret".bytes) + let keyCollection = await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) // https://openbanking.atlassian.net/wiki/spaces/DZ/pages/937656404/Read+Write+Data+API+Specification+-+v3.1 let customFields: JWTHeader = [ @@ -490,8 +495,8 @@ class JWTKitTests: XCTestCase { let key = ES256PrivateKey() let keyCollection = await JWTKeyCollection() - .addES256(key: key, kid: "private") - .addES256(key: key.publicKey, kid: "public") + .addECDSA(key: key, kid: "private") + .addECDSA(key: key.publicKey, kid: "public") let payload = TestPayload( sub: "vapor", name: "Foo", @@ -509,8 +514,7 @@ class JWTKitTests: XCTestCase { } func testCustomObjectHeader() async throws { - let keyCollection = await JWTKeyCollection().addHS256(key: "secret".bytes) - + let keyCollection = await JWTKeyCollection().addHMAC(key: "secret".bytes, digestAlgorithm: .sha256) let customFields: JWTHeader = [ "kid": "some-kid", "foo": ["bar": "baz"], @@ -541,7 +545,7 @@ struct LocalePayload: Codable { extension LocalePayload { static func from(_ string: String) throws -> LocalePayload { - let data = Data(string.utf8) + let data = string.data(using: .utf8)! return try JSONDecoder().decode(LocalePayload.self, from: data) } } diff --git a/Tests/JWTKitTests/PSSTests.swift b/Tests/JWTKitTests/PSSTests.swift index fe28ea31..6153d015 100644 --- a/Tests/JWTKitTests/PSSTests.swift +++ b/Tests/JWTKitTests/PSSTests.swift @@ -4,14 +4,14 @@ import XCTest final class PSSTests: XCTestCase { func testPSSDocs() async throws { await XCTAssertNoThrowAsync( - try await JWTKeyCollection().addPS256(key: Insecure.RSA.PublicKey(pem: publicKey)) + try await JWTKeyCollection().addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) ) } func testSigning() async throws { let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -26,7 +26,7 @@ final class PSSTests: XCTestCase { func testSigningWithPublic() async throws { let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -52,8 +52,8 @@ final class PSSTests: XCTestCase { } let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") do { let token = try await keyCollection.sign(Payload(foo: "qux"), header: ["kid": "private"]) @@ -92,7 +92,7 @@ final class PSSTests: XCTestCase { let token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MjAwMDAwMDAwMH0.dCaprjSiEw1w_cS2JzjWlp1mxdF9MV86VMylKiEZf6gM8NZhNo3hgnI3Gg7G_WL_bSzys9Z0QtNpWZeW1Mooa29qDqZolQLKbzyjiIMDFBslz_Hei-tI5318UdFLKIlMT0VyDThwFjyPCiVEvOkKokWSXXGZCHArGXouTWvaTND9C0gOMwSkE8cHU7e0u-_pDEfdv9MRQiGy1Wj-9T_ZN6a0g8yFMQcOU6voo-WSY-m98oylYOifiOighitlD0xNScDnxBH5Qp7yyU81m-s2-xoYVQJhGduvi8mxbo_bU48WIJfmdAYX3aAUh_xpvgcd55bdeMT55G_qnkDBDSLvbQ" let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = try await keyCollection.verify(token, as: TestPayload.self) XCTAssertEqual(payload.sub.value, "1234567890") diff --git a/Tests/JWTKitTests/RSATests.swift b/Tests/JWTKitTests/RSATests.swift index d05cdd8f..e524f856 100644 --- a/Tests/JWTKitTests/RSATests.swift +++ b/Tests/JWTKitTests/RSATests.swift @@ -11,7 +11,7 @@ final class RSATests: XCTestCase { } func testRSADocs() async throws { - await XCTAssertNoThrowAsync(try await JWTKeyCollection().addRS256(key: Insecure.RSA.PublicKey(pem: publicKey))) + await XCTAssertNoThrowAsync(try await JWTKeyCollection().addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256)) } func testPrivateKeyInitialization() throws { @@ -38,8 +38,8 @@ final class RSATests: XCTestCase { func testSigning() async throws { let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -54,7 +54,7 @@ final class RSATests: XCTestCase { func testSigningWithPublic() async throws { let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -74,8 +74,8 @@ final class RSATests: XCTestCase { let privateKey = try Insecure.RSA.PrivateKey(modulus: modulus, exponent: publicExponent, privateExponent: privateExponent) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -136,8 +136,8 @@ final class RSATests: XCTestCase { exp: .init(value: .distantFuture) ) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: certPrivateKey), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(certificatePEM: cert), kid: "cert") + .addRSA(key: Insecure.RSA.PrivateKey(pem: certPrivateKey), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(certificatePEM: cert), digestAlgorithm: .sha256, kid: "cert") let jwt = try await keyCollection.sign(test, kid: "private") let payload = try await keyCollection.verify(jwt, as: TestPayload.self) @@ -145,7 +145,7 @@ final class RSATests: XCTestCase { } func testKeySizeTooSmall() async throws { - await XCTAssertThrowsErrorAsync(try await JWTKeyCollection().addRS256(key: Insecure.RSA.PrivateKey(pem: _512BytesKey))) + await XCTAssertThrowsErrorAsync(try await JWTKeyCollection().addRSA(key: Insecure.RSA.PrivateKey(pem: _512BytesKey), digestAlgorithm: .sha256)) } func testRS256Verification() async throws { @@ -159,8 +159,8 @@ final class RSATests: XCTestCase { exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey2), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey2), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey2), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey2), digestAlgorithm: .sha256, kid: "public") let payload = try await keyCollection.verify(token, as: TestPayload.self) XCTAssertEqual(payload, testPayload) diff --git a/Tests/JWTKitTests/VendorTokenTests.swift b/Tests/JWTKitTests/VendorTokenTests.swift index 0c1c953c..333fd087 100644 --- a/Tests/JWTKitTests/VendorTokenTests.swift +++ b/Tests/JWTKitTests/VendorTokenTests.swift @@ -23,7 +23,7 @@ final class VendorTokenTests: XCTestCase { nonce: "nonceValue" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertNoThrowAsync(try await collection.verify(jwt, as: GoogleIdentityToken.self)) @@ -50,7 +50,7 @@ final class VendorTokenTests: XCTestCase { nonce: "nonceValue" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertThrowsErrorAsync(try await collection.verify(jwt, as: GoogleIdentityToken.self)) { error in @@ -83,7 +83,7 @@ final class VendorTokenTests: XCTestCase { nonce: "nonceValue" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertThrowsErrorAsync(try await collection.verify(jwt, as: GoogleIdentityToken.self)) { error in @@ -111,7 +111,7 @@ final class VendorTokenTests: XCTestCase { realUserStatus: .likelyReal ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertNoThrowAsync(try await collection.verify(jwt, as: AppleIdentityToken.self)) @@ -133,7 +133,7 @@ final class VendorTokenTests: XCTestCase { realUserStatus: .likelyReal ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertThrowsErrorAsync(try await collection.verify(jwt, as: AppleIdentityToken.self)) { error in @@ -169,7 +169,7 @@ final class VendorTokenTests: XCTestCase { version: "2.0" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertNoThrowAsync(try await collection.verify(jwt, as: MicrosoftIdentityToken.self)) @@ -197,7 +197,7 @@ final class VendorTokenTests: XCTestCase { version: "2.0" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertThrowsErrorAsync(try await collection.verify(jwt, as: MicrosoftIdentityToken.self)) { error in @@ -231,7 +231,7 @@ final class VendorTokenTests: XCTestCase { version: "2.0" ) - let collection = await JWTKeyCollection().addHS256(key: "secret") + let collection = await JWTKeyCollection().addHMAC(key: "secret", digestAlgorithm: .sha256) let jwt = try await collection.sign(token) await XCTAssertThrowsErrorAsync(try await collection.verify(jwt, as: MicrosoftIdentityToken.self)) { error in diff --git a/Tests/JWTKitTests/X5CTests.swift b/Tests/JWTKitTests/X5CTests.swift index 3559a304..d536e019 100644 --- a/Tests/JWTKitTests/X5CTests.swift +++ b/Tests/JWTKitTests/X5CTests.swift @@ -207,7 +207,7 @@ final class X5CTests: XCTestCase { } func testSigningWithX5CChain() async throws { - let keyCollection = try await JWTKeyCollection().addES256(key: ES256PrivateKey(pem: x5cLeafCertKey)) + let keyCollection = try await JWTKeyCollection().addECDSA(key: ES256PrivateKey(pem: x5cLeafCertKey)) let payload = TestPayload( sub: "vapor", @@ -227,7 +227,7 @@ final class X5CTests: XCTestCase { } func testSigningWithInvalidX5CChain() async throws { - let keyCollection = try await JWTKeyCollection().addES256(key: ES256PrivateKey(pem: x5cLeafCertKey)) + let keyCollection = try await JWTKeyCollection().addECDSA(key: ES256PrivateKey(pem: x5cLeafCertKey)) let payload = TestPayload( sub: "vapor",