diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81b62534..aa31e72a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,15 @@ on: push: { branches: [ main ] } jobs: + lint: + runs-on: ubuntu-latest + container: swift:jammy + steps: + - name: Check out JWTKit + uses: actions/checkout@v4 + - name: Run format lint check + run: swift format lint --strict --recursive --parallel . + linux-integration: if: ${{ !(github.event.pull_request.draft || false) }} runs-on: ubuntu-latest diff --git a/.swift-format b/.swift-format new file mode 100644 index 00000000..2d09ae36 --- /dev/null +++ b/.swift-format @@ -0,0 +1,70 @@ +{ + "fileScopedDeclarationPrivacy": { + "accessLevel": "private" + }, + "indentation": { + "spaces": 4 + }, + "indentConditionalCompilationBlocks": true, + "indentSwitchCaseLabels": false, + "lineBreakAroundMultilineExpressionChainComponents": false, + "lineBreakBeforeControlFlowKeywords": false, + "lineBreakBeforeEachArgument": false, + "lineBreakBeforeEachGenericRequirement": false, + "lineLength": 140, + "maximumBlankLines": 1, + "multiElementCollectionTrailingCommas": true, + "noAssignmentInExpressions": { + "allowedFunctions": [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether": false, + "respectsExistingLineBreaks": true, + "rules": { + "AllPublicDeclarationsHaveDocumentation": false, + "AlwaysUseLiteralForEmptyCollectionInit": false, + "AlwaysUseLowerCamelCase": true, + "AmbiguousTrailingClosureOverload": true, + "BeginDocumentationCommentWithOneLineSummary": false, + "DoNotUseSemicolons": true, + "DontRepeatTypeInStaticProperties": true, + "FileScopedDeclarationPrivacy": true, + "FullyIndirectEnum": true, + "GroupNumericLiterals": true, + "IdentifiersMustBeASCII": true, + "NeverForceUnwrap": false, + "NeverUseForceTry": false, + "NeverUseImplicitlyUnwrappedOptionals": false, + "NoAccessLevelOnExtensionDeclaration": true, + "NoAssignmentInExpressions": true, + "NoBlockComments": true, + "NoCasesWithOnlyFallthrough": true, + "NoEmptyTrailingClosureParentheses": true, + "NoLabelsInCasePatterns": true, + "NoLeadingUnderscores": false, + "NoParensAroundConditions": true, + "NoPlaygroundLiterals": true, + "NoVoidReturnOnFunctionSignature": true, + "OmitExplicitReturns": false, + "OneCasePerLine": true, + "OneVariableDeclarationPerLine": true, + "OnlyOneTrailingClosureArgument": true, + "OrderedImports": true, + "ReplaceForEachWithForLoop": true, + "ReturnVoidInsteadOfEmptyTuple": true, + "TypeNamesShouldBeCapitalized": true, + "UseEarlyExits": false, + "UseExplicitNilCheckInConditions": true, + "UseLetInEveryBoundCaseVariable": true, + "UseShorthandTypeNames": true, + "UseSingleLinePropertyGetter": true, + "UseSynthesizedInitializer": true, + "UseTripleSlashForDocumentationComments": true, + "UseWhereClausesInForLoops": false, + "ValidateDocumentationComments": false + }, + "spacesAroundRangeFormationOperators": false, + "tabWidth": 8, + "version": 1 +} diff --git a/Snippets/JWKExamples.swift b/Snippets/JWKExamples.swift index d2c40e5e..61cc0ea9 100644 --- a/Snippets/JWKExamples.swift +++ b/Snippets/JWKExamples.swift @@ -4,13 +4,13 @@ import JWTKit let rsaModulus = "..." let json = """ -{ - "keys": [ - {"kty": "RSA", "alg": "RS256", "kid": "a", "n": "\(rsaModulus)", "e": "AQAB"}, - {"kty": "RSA", "alg": "RS512", "kid": "b", "n": "\(rsaModulus)", "e": "AQAB"}, - ] -} -""" + { + "keys": [ + {"kty": "RSA", "alg": "RS256", "kid": "a", "n": "\(rsaModulus)", "e": "AQAB"}, + {"kty": "RSA", "alg": "RS512", "kid": "b", "n": "\(rsaModulus)", "e": "AQAB"}, + ] + } + """ // Create key collection and add JWKS let keys = try await JWTKeyCollection().add(jwksJSON: json) diff --git a/Snippets/JWTKitExamples.swift b/Snippets/JWTKitExamples.swift index d790bd64..32405abe 100644 --- a/Snippets/JWTKitExamples.swift +++ b/Snippets/JWTKitExamples.swift @@ -1,3 +1,4 @@ +import Foundation // snippet.KEY_COLLECTION import JWTKit @@ -51,8 +52,8 @@ do { do { // snippet.VERIFYING let exampleJWT = """ - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo - """ + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo + """ // snippet.VERIFYING_PAYLOAD // Parse the JWT, verifies its signature, and decodes its content @@ -105,8 +106,6 @@ do { // snippet.end } -import Foundation - extension DataProtocol { func base64URLDecodedBytes() -> [UInt8] { let string = String(decoding: self, as: UTF8.self) @@ -142,16 +141,20 @@ struct CustomSerializer: JWTSerializer { 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())) + let header = try jsonDecoder.decode( + JWTHeader.self, from: .init(encodedHeader.base64URLDecodedBytes())) - let payload = if header.b64?.asBool ?? true { - try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload.base64URLDecodedBytes())) - } else { - try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload)) - } + let payload = + if header.b64?.asBool ?? true { + try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload.base64URLDecodedBytes())) + } else { + try self.jsonDecoder.decode(Payload.self, from: .init(encodedPayload)) + } let signature = Data(encodedSignature.base64URLDecodedBytes()) @@ -175,8 +178,10 @@ do { do { // snippet.CUSTOM_ENCODING - let encoder = JSONEncoder(); encoder.dateEncodingStrategy = .iso8601 - let decoder = JSONDecoder(); decoder.dateDecodingStrategy = .iso8601 + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 let parser = DefaultJWTParser(jsonDecoder: decoder) let serializer = DefaultJWTSerializer(jsonEncoder: encoder) diff --git a/Sources/JWTKit/Claims/JWTClaim.swift b/Sources/JWTKit/Claims/JWTClaim.swift index b9fbec4c..f1f27fde 100644 --- a/Sources/JWTKit/Claims/JWTClaim.swift +++ b/Sources/JWTKit/Claims/JWTClaim.swift @@ -12,22 +12,22 @@ public protocol JWTClaim: Codable, Sendable { init(value: Value) } -public extension JWTClaim where Value == String, Self: ExpressibleByStringLiteral { +extension JWTClaim where Value == String, Self: ExpressibleByStringLiteral { /// See `ExpressibleByStringLiteral`. - init(stringLiteral string: String) { + public init(stringLiteral string: String) { self.init(value: string) } } -public extension JWTClaim { +extension JWTClaim { /// See `Decodable`. - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let single = try decoder.singleValueContainer() try self.init(value: single.decode(Value.self)) } /// See `Encodable`. - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var single = encoder.singleValueContainer() try single.encode(value) } diff --git a/Sources/JWTKit/Claims/JWTMultiValueClaim.swift b/Sources/JWTKit/Claims/JWTMultiValueClaim.swift index 557064aa..08f06ce4 100644 --- a/Sources/JWTKit/Claims/JWTMultiValueClaim.swift +++ b/Sources/JWTKit/Claims/JWTMultiValueClaim.swift @@ -4,11 +4,11 @@ public protocol JWTMultiValueClaim: JWTClaim where Value: Collection, Value.Elem init(value: Value.Element) } -public extension JWTMultiValueClaim { +extension JWTMultiValueClaim { /// Single-element initializer. Uses the `CollectionOfOneDecoder` to work /// around the lack of an initializer on the `Collection` protocol. Not /// spectacularly efficient, but it works. - init(value: Value.Element) { + public init(value: Value.Element) { self.init(value: try! CollectionOfOneDecoder.decode(value)) } @@ -39,13 +39,14 @@ public extension JWTMultiValueClaim { /// in a list of more than one. This implementation behaves according to /// the semantics of the particular `Collection` type used as its value; /// `Array` will preserve ordering and duplicates, `Set` will not. - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { try self.init(value: container.decode(Value.Element.self)) - } catch let DecodingError.typeMismatch(type, context) - where type == Value.Element.self && context.codingPath.count == container.codingPath.count + } catch DecodingError.typeMismatch(let type, let context) + where type == Value.Element.self + && context.codingPath.count == container.codingPath.count { // Unfortunately, `typeMismatch()` doesn't let us explicitly look for what type found, // only what type was expected, so we have to match the coding path depth instead. @@ -65,11 +66,11 @@ public extension JWTMultiValueClaim { /// - Warning: If the claim has zero values, this implementation will encode /// an inefficient zero-element representation. See the notes regarding /// this on `init(from decoder:)` above. - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self.value.first { - case let .some(value) where self.value.count == 1: + case .some(let value) where self.value.count == 1: try container.encode(value) default: try container.encode(self.value) @@ -85,7 +86,7 @@ public extension JWTMultiValueClaim { /// `ExpressibleByArrayLiteral`, but what fun would that be? private struct CollectionOfOneDecoder: Decoder, UnkeyedDecodingContainer where T: Collection, T: Codable, T.Element: Codable { static func decode(_ element: T.Element) throws -> T { - return try T(from: self.init(value: element)) + try T(from: self.init(value: element)) } /// The single value we're returning. diff --git a/Sources/JWTKit/Claims/JWTUnixEpochClaim.swift b/Sources/JWTKit/Claims/JWTUnixEpochClaim.swift index 30ea86f2..88281d0c 100644 --- a/Sources/JWTKit/Claims/JWTUnixEpochClaim.swift +++ b/Sources/JWTKit/Claims/JWTUnixEpochClaim.swift @@ -2,15 +2,15 @@ import Foundation public protocol JWTUnixEpochClaim: JWTClaim where Value == Date {} -public extension JWTUnixEpochClaim { +extension JWTUnixEpochClaim { /// See `Decodable`. - init(from decoder: Decoder) throws { + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() try self.init(value: container.decode(Date.self)) } /// See `Encodable`. - func encode(to encoder: Encoder) throws { + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.value) } diff --git a/Sources/JWTKit/Claims/LocaleClaim.swift b/Sources/JWTKit/Claims/LocaleClaim.swift index e358afd1..b5235a8f 100644 --- a/Sources/JWTKit/Claims/LocaleClaim.swift +++ b/Sources/JWTKit/Claims/LocaleClaim.swift @@ -1,8 +1,4 @@ -#if compiler(<6.0) && !canImport(Darwin) - @preconcurrency import Foundation -#else - import Foundation -#endif +import Foundation public struct LocaleClaim: JWTClaim, Equatable, ExpressibleByStringLiteral { /// See ``JWTClaim``. diff --git a/Sources/JWTKit/ECDSA/ECDSA.swift b/Sources/JWTKit/ECDSA/ECDSA.swift index d98b17bc..9af7cf1a 100644 --- a/Sources/JWTKit/ECDSA/ECDSA.swift +++ b/Sources/JWTKit/ECDSA/ECDSA.swift @@ -11,8 +11,8 @@ public protocol ECDSAKey: Sendable { var parameters: ECDSAParameters? { get } } -public extension ECDSA { - struct PublicKey: ECDSAKey, Equatable where Curve: ECDSACurveType { +extension ECDSA { + public struct PublicKey: ECDSAKey, Equatable where Curve: ECDSACurveType { typealias Signature = Curve.Signature typealias PublicKey = Curve.PrivateKey.PublicKey @@ -106,15 +106,15 @@ public extension ECDSA { init(backing: PublicKey) { self.backing = backing } - + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.parameters?.x == rhs.parameters?.x && lhs.parameters?.y == rhs.parameters?.y } } } -public extension ECDSA { - struct PrivateKey: ECDSAKey, Equatable where Curve: ECDSACurveType { +extension ECDSA { + public struct PrivateKey: ECDSAKey, Equatable where Curve: ECDSACurveType { typealias PrivateKey = Curve.PrivateKey typealias Signature = PrivateKey.Signature @@ -189,7 +189,7 @@ public extension ECDSA { public init() { self.backing = PrivateKey() } - + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.parameters?.x == rhs.parameters?.x && lhs.parameters?.y == rhs.parameters?.y } diff --git a/Sources/JWTKit/ECDSA/ECDSASigner.swift b/Sources/JWTKit/ECDSA/ECDSASigner.swift index 8d52c26b..65363ba3 100644 --- a/Sources/JWTKit/ECDSA/ECDSASigner.swift +++ b/Sources/JWTKit/ECDSA/ECDSASigner.swift @@ -29,7 +29,9 @@ struct ECDSASigner: JWTAlgorithm, CryptoSigner { return [UInt8](signature.rawRepresentation) } - public func verify(_ signature: some DataProtocol, signs plaintext: some DataProtocol) throws -> Bool { + public func verify(_ signature: some DataProtocol, signs plaintext: some DataProtocol) throws + -> Bool + { let digest = try self.digest(plaintext) return try publicKey.backing.isValidSignature(signature, for: digest) } diff --git a/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift b/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift index 03706563..1c7d1bb5 100644 --- a/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift +++ b/Sources/JWTKit/ECDSA/JWTKeyCollection+ECDSA.swift @@ -1,6 +1,6 @@ import Crypto -public extension JWTKeyCollection { +extension JWTKeyCollection { /// Adds an ECDSA key to the collection. /// /// Example Usage: @@ -19,16 +19,18 @@ 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 add( + public func add( ecdsa key: some ECDSAKey, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: ECDSASigner(key: key), - parser: parser, - serializer: serializer - ), for: kid) + add( + .init( + 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 d86c78d3..a13f7a86 100644 --- a/Sources/JWTKit/ECDSA/P256+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P256+CurveType.swift @@ -15,7 +15,7 @@ extension P256: ECDSACurveType { /// Thus: /// - 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 static let byteRanges: (x: Range, y: Range) = (1..<33, 33..<65) public struct SigningAlgorithm: ECDSASigningAlgorithm { public static let name = "ES256" @@ -38,18 +38,11 @@ extension P256.Signing.PublicKey: ECDSAPublicKey { } } -#if compiler(<6) // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable -extension P256.Signing.PrivateKey: ECDSAPrivateKey, @unchecked Sendable {} -extension P256.Signing.ECDSASignature: ECDSASignature, @unchecked Sendable {} -extension P256.Signing.PublicKey: @unchecked Sendable {} -extension P256: @unchecked Sendable {} -#else extension P256.Signing.PrivateKey: ECDSAPrivateKey, @unchecked @retroactive Sendable {} extension P256.Signing.ECDSASignature: ECDSASignature, @unchecked @retroactive Sendable {} extension P256.Signing.PublicKey: @unchecked @retroactive Sendable {} extension P256: @unchecked @retroactive Sendable {} -#endif public typealias ES256PublicKey = ECDSA.PublicKey public typealias ES256PrivateKey = ECDSA.PrivateKey diff --git a/Sources/JWTKit/ECDSA/P384+CurveType.swift b/Sources/JWTKit/ECDSA/P384+CurveType.swift index c148e688..275d8435 100644 --- a/Sources/JWTKit/ECDSA/P384+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P384+CurveType.swift @@ -15,7 +15,7 @@ extension P384: ECDSACurveType { /// Thus: /// - 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 static let byteRanges: (x: Range, y: Range) = (1..<49, 49..<97) public enum SigningAlgorithm: ECDSASigningAlgorithm { public static let name = "ES384" @@ -38,18 +38,11 @@ extension P384.Signing.PublicKey: ECDSAPublicKey { } } -#if compiler(<6) // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable -extension P384.Signing.PrivateKey: ECDSAPrivateKey, @unchecked Sendable {} -extension P384.Signing.ECDSASignature: ECDSASignature, @unchecked Sendable {} -extension P384.Signing.PublicKey: @unchecked Sendable {} -extension P384: @unchecked Sendable {} -#else extension P384.Signing.PrivateKey: ECDSAPrivateKey, @unchecked @retroactive Sendable {} extension P384.Signing.ECDSASignature: ECDSASignature, @unchecked @retroactive Sendable {} extension P384.Signing.PublicKey: @unchecked @retroactive Sendable {} extension P384: @unchecked @retroactive Sendable {} -#endif public typealias ES384PublicKey = ECDSA.PublicKey public typealias ES384PrivateKey = ECDSA.PrivateKey diff --git a/Sources/JWTKit/ECDSA/P521+CurveType.swift b/Sources/JWTKit/ECDSA/P521+CurveType.swift index e7e326bc..900189f7 100644 --- a/Sources/JWTKit/ECDSA/P521+CurveType.swift +++ b/Sources/JWTKit/ECDSA/P521+CurveType.swift @@ -16,7 +16,7 @@ extension P521: ECDSACurveType { /// Thus: /// - 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 static let byteRanges: (x: Range, y: Range) = (1..<67, 67..<133) public enum SigningAlgorithm: ECDSASigningAlgorithm { public static let name = "ES512" @@ -39,18 +39,10 @@ extension P521.Signing.PublicKey: ECDSAPublicKey { } } -#if compiler(<6) -// TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable -extension P521.Signing.PrivateKey: ECDSAPrivateKey, @unchecked Sendable {} -extension P521.Signing.ECDSASignature: ECDSASignature, @unchecked Sendable {} -extension P521.Signing.PublicKey: @unchecked Sendable {} -extension P521: @unchecked Sendable {} -#else extension P521.Signing.PrivateKey: ECDSAPrivateKey, @unchecked @retroactive Sendable {} extension P521.Signing.ECDSASignature: ECDSASignature, @unchecked @retroactive Sendable {} extension P521.Signing.PublicKey: @unchecked @retroactive Sendable {} extension P521: @unchecked @retroactive Sendable {} -#endif public typealias ES512PublicKey = ECDSA.PublicKey public typealias ES512PrivateKey = ECDSA.PrivateKey diff --git a/Sources/JWTKit/EdDSA/EdDSA.swift b/Sources/JWTKit/EdDSA/EdDSA.swift index 0be69a26..0fce1d0b 100644 --- a/Sources/JWTKit/EdDSA/EdDSA.swift +++ b/Sources/JWTKit/EdDSA/EdDSA.swift @@ -9,12 +9,12 @@ public enum EdDSA: Sendable {} /// Both ``EdDSA.PublicKey`` and ``EdDSA.PrivateKey`` conform to this protocol. public protocol EdDSAKey: Sendable {} -public extension EdDSA { +extension EdDSA { /// A struct representing a public key used in EdDSA (Edwards-curve Digital Signature Algorithm). /// /// In JWT, EdDSA public keys are represented as a single x-coordinate and are used for verifying signatures. /// Currently, only the ``EdDSACurve/ed25519`` curve is supported. - struct PublicKey: EdDSAKey { + public struct PublicKey: EdDSAKey { let backing: Curve25519.Signing.PublicKey let curve: EdDSACurve @@ -47,10 +47,11 @@ public extension EdDSA { throw EdDSAError.publicKeyMissing } - let key = switch curve.backing { - case .ed25519: - try Curve25519.Signing.PublicKey(rawRepresentation: xData) - } + let key = + switch curve.backing { + case .ed25519: + try Curve25519.Signing.PublicKey(rawRepresentation: xData) + } self.init(backing: key) } @@ -61,12 +62,12 @@ public extension EdDSA { } } -public extension EdDSA { +extension EdDSA { /// A struct representing a private key used in EdDSA (Edwards-curve Digital Signature Algorithm). /// /// In JWT, EdDSA private keys are represented as a pair of x-coordinate and private key (d) and are used for signing. /// Currently, only the ``Curve/ed25519`` curve is supported. - struct PrivateKey: EdDSAKey { + public struct PrivateKey: EdDSAKey { let backing: Curve25519.Signing.PrivateKey let curve: EdDSACurve @@ -79,10 +80,11 @@ public extension EdDSA { /// - Throws: An error if key generation fails. /// - Returns: A new ``EdDSA.PrivateKey`` instance with a freshly generated key pair. public init(curve: EdDSACurve = .ed25519) throws { - let key = switch curve.backing { - case .ed25519: - Curve25519.Signing.PrivateKey() - } + let key = + switch curve.backing { + case .ed25519: + Curve25519.Signing.PrivateKey() + } self.init(backing: key) } @@ -116,10 +118,11 @@ public extension EdDSA { throw EdDSAError.privateKeyMissing } - let key = switch curve.backing { - case .ed25519: - try Curve25519.Signing.PrivateKey(rawRepresentation: dData) - } + let key = + switch curve.backing { + case .ed25519: + try Curve25519.Signing.PrivateKey(rawRepresentation: dData) + } self.init(backing: key) } @@ -134,11 +137,6 @@ public extension EdDSA { } } -#if compiler(<6) // TODO: Remove @unchecked Sendable when Crypto is updated to use Sendable -extension Curve25519.Signing.PublicKey: @unchecked Sendable {} -extension Curve25519.Signing.PrivateKey: @unchecked Sendable {} -#else extension Curve25519.Signing.PublicKey: @unchecked @retroactive Sendable {} extension Curve25519.Signing.PrivateKey: @unchecked @retroactive Sendable {} -#endif diff --git a/Sources/JWTKit/EdDSA/EdDSASigner.swift b/Sources/JWTKit/EdDSA/EdDSASigner.swift index 152dcc57..84cf7499 100644 --- a/Sources/JWTKit/EdDSA/EdDSASigner.swift +++ b/Sources/JWTKit/EdDSA/EdDSASigner.swift @@ -27,18 +27,16 @@ struct EdDSASigner: JWTAlgorithm, Sendable { switch privateKey.curve.backing { case .ed25519: - return try Curve25519.Signing.PrivateKey( - rawRepresentation: privateKey.rawRepresentation - ).signature(for: plaintext).copyBytes() + return try Curve25519.Signing.PrivateKey(rawRepresentation: privateKey.rawRepresentation) + .signature(for: plaintext).copyBytes() } } func verify(_ signature: some DataProtocol, signs plaintext: some DataProtocol) throws -> Bool { switch publicKey.curve.backing { case .ed25519: - return try Curve25519.Signing.PublicKey( - rawRepresentation: publicKey.rawRepresentation - ).isValidSignature(signature, for: plaintext) + try Curve25519.Signing.PublicKey(rawRepresentation: publicKey.rawRepresentation) + .isValidSignature(signature, for: plaintext) } } } diff --git a/Sources/JWTKit/EdDSA/JWTKeyCollection+EdDSA.swift b/Sources/JWTKit/EdDSA/JWTKeyCollection+EdDSA.swift index 85dac905..22e55cce 100644 --- a/Sources/JWTKit/EdDSA/JWTKeyCollection+EdDSA.swift +++ b/Sources/JWTKit/EdDSA/JWTKeyCollection+EdDSA.swift @@ -1,7 +1,7 @@ import Crypto import Foundation -public extension JWTKeyCollection { +extension JWTKeyCollection { /// Adds an EdDSA key to the collection using an ``EdDSAKey``. /// /// This method incorporates an EdDSA (Edwards-curve Digital Signature Algorithm) signer into the collection. @@ -23,16 +23,17 @@ public extension JWTKeyCollection { /// a default decoder is used for decoding JWT payloads. /// - Returns: The same instance of the collection (`Self`), useful for chaining multiple configuration calls. @discardableResult - func add( + public func add( eddsa key: some EdDSAKey, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: EdDSASigner(key: key), - parser: parser, - serializer: serializer - ), for: kid) + add( + .init( + algorithm: EdDSASigner(key: key), + parser: parser, + serializer: serializer + ), for: kid) } } diff --git a/Sources/JWTKit/HMAC/HMACSigner.swift b/Sources/JWTKit/HMAC/HMACSigner.swift index 90011a3f..589783ce 100644 --- a/Sources/JWTKit/HMAC/HMACSigner.swift +++ b/Sources/JWTKit/HMAC/HMACSigner.swift @@ -5,8 +5,21 @@ struct HMACSigner: JWTAlgorithm where SHAType: HashFunction { let key: SymmetricKey let name: String + init(key: SymmetricKey) { + self.key = key + switch SHAType.self { + case is SHA256.Type: + self.name = "HS256" + case is SHA384.Type: + self.name = "HS384" + case is SHA512.Type: + self.name = "HS512" + default: + fatalError("Unsupported hash function: \(SHAType.self)") + } + } + func sign(_ plaintext: some DataProtocol) throws -> [UInt8] { - let authentication = Crypto.HMAC.authenticationCode(for: plaintext, using: self.key) - return Array(authentication) + Array(HMAC.authenticationCode(for: plaintext, using: self.key)) } } diff --git a/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift b/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift index 7a1d7732..b0c4c706 100644 --- a/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift +++ b/Sources/JWTKit/HMAC/JWTKeyCollection+HMAC.swift @@ -1,7 +1,7 @@ import Crypto import Foundation -public extension JWTKeyCollection { +extension JWTKeyCollection { /// Adds an HMAC key to the collection. /// /// Example Usage: @@ -21,20 +21,22 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func add( + public func add( hmac key: HMACKey, digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - switch digestAlgorithm.backing { - case .sha256: - add(.init(algorithm: HMACSigner(key: key.key, name: "HS256"), parser: parser, serializer: serializer), for: kid) - case .sha384: - add(.init(algorithm: HMACSigner(key: key.key, name: "HS384"), parser: parser, serializer: serializer), for: kid) - case .sha512: - add(.init(algorithm: HMACSigner(key: key.key, name: "HS512"), parser: parser, serializer: serializer), for: kid) - } + let signer: any JWTAlgorithm = + switch digestAlgorithm.backing { + case .sha256: + HMACSigner(key: key.key) + case .sha384: + HMACSigner(key: key.key) + case .sha512: + HMACSigner(key: key.key) + } + return add(.init(algorithm: signer, parser: parser, serializer: serializer), for: kid) } } diff --git a/Sources/JWTKit/JWK/JWK.swift b/Sources/JWTKit/JWK/JWK.swift index 6ec1b5c2..849af7bd 100644 --- a/Sources/JWTKit/JWK/JWK.swift +++ b/Sources/JWTKit/JWK/JWK.swift @@ -10,9 +10,9 @@ public struct JWK: Codable, Sendable { public var rawValue: String { switch self.backing { - case let .ecdsa(ecdsaCurve): + case .ecdsa(let ecdsaCurve): ecdsaCurve.rawValue - case let .eddsa(eddsaCurve): + case .eddsa(let eddsaCurve): eddsaCurve.rawValue } } @@ -49,15 +49,16 @@ public struct JWK: Codable, Sendable { } else if let eddsaCurve = try? container.decode(EdDSACurve.self) { self = .eddsa(eddsaCurve) } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Curve type not supported") + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "Curve type not supported") } } public func encode(to encoder: any Encoder) throws { switch self.backing { - case let .ecdsa(ecdsaCurve): + case .ecdsa(let ecdsaCurve): try ecdsaCurve.encode(to: encoder) - case let .eddsa(eddsaCurve): + case .eddsa(let eddsaCurve): try eddsaCurve.encode(to: encoder) } } diff --git a/Sources/JWTKit/JWK/JWKSigner.swift b/Sources/JWTKit/JWK/JWKSigner.swift index 937b825e..6682d332 100644 --- a/Sources/JWTKit/JWK/JWKSigner.swift +++ b/Sources/JWTKit/JWK/JWKSigner.swift @@ -41,35 +41,39 @@ extension JWK { throw JWTError.invalidJWK(reason: "Missing RSA primitives") } - let rsaKey: RSAKey = if let privateExponent = self.privateExponent { - if let prime1, let prime2 { - try Insecure.RSA.PrivateKey( - modulus: modulus, - exponent: exponent, - privateExponent: privateExponent, - prime1: prime1, - prime2: prime2 - ) + let rsaKey: RSAKey = + if let privateExponent = self.privateExponent { + if let prime1, let prime2 { + try Insecure.RSA.PrivateKey( + modulus: modulus, + exponent: exponent, + privateExponent: privateExponent, + prime1: prime1, + prime2: prime2 + ) + } else { + try Insecure.RSA.PrivateKey( + modulus: modulus, + exponent: exponent, + privateExponent: privateExponent + ) + } } else { - try Insecure.RSA.PrivateKey( - modulus: modulus, - exponent: exponent, - privateExponent: privateExponent - ) + try Insecure.RSA.PublicKey(modulus: modulus, exponent: exponent) } - } else { - try Insecure.RSA.PublicKey(modulus: modulus, exponent: exponent) - } let algorithm = alg ?? self.algorithm switch algorithm { case .rs256: - return RSASigner(key: rsaKey, algorithm: .sha256, name: "RS256", padding: .insecurePKCS1v1_5) + return RSASigner( + key: rsaKey, algorithm: .sha256, name: "RS256", padding: .insecurePKCS1v1_5) case .rs384: - return RSASigner(key: rsaKey, algorithm: .sha384, name: "RS384", padding: .insecurePKCS1v1_5) + return RSASigner( + key: rsaKey, algorithm: .sha384, name: "RS384", padding: .insecurePKCS1v1_5) case .rs512: - return RSASigner(key: rsaKey, algorithm: .sha512, name: "RS512", padding: .insecurePKCS1v1_5) + return RSASigner( + key: rsaKey, algorithm: .sha512, name: "RS512", padding: .insecurePKCS1v1_5) case .ps256: return RSASigner(key: rsaKey, algorithm: .sha256, name: "PS256", padding: .PSS) case .ps384: @@ -125,11 +129,11 @@ extension JWK { let algorithm = alg ?? self.algorithm switch (algorithm, self.x, self.privateExponent) { - case let (.eddsa, .some(_), .some(d)): + case (.eddsa, .some(_), .some(let d)): let key = try EdDSA.PrivateKey(d: d, curve: curve) return EdDSASigner(key: key) - case let (.eddsa, .some(x), .none): + case (.eddsa, .some(let x), .none): let key = try EdDSA.PublicKey(x: x, curve: curve) return EdDSASigner(key: key) diff --git a/Sources/JWTKit/JWTError.swift b/Sources/JWTKit/JWTError.swift index 5c7775a4..9c8a8424 100644 --- a/Sources/JWTKit/JWTError.swift +++ b/Sources/JWTKit/JWTError.swift @@ -75,7 +75,7 @@ public struct JWTError: Error, Sendable, Equatable { self.failedClaim = failedClaim self.curve = curve } - + static func == (lhs: JWTError.Backing, rhs: JWTError.Backing) -> Bool { lhs.errorType == rhs.errorType } @@ -147,7 +147,7 @@ public struct JWTError: Error, Sendable, Equatable { public static func generic(identifier: String, reason: String) -> Self { .init(backing: .init(errorType: .generic, reason: reason)) } - + public static func == (lhs: JWTError, rhs: JWTError) -> Bool { lhs.backing == rhs.backing } diff --git a/Sources/JWTKit/JWTHeader+CommonFields.swift b/Sources/JWTKit/JWTHeader+CommonFields.swift index b34995cd..4a87633d 100644 --- a/Sources/JWTKit/JWTHeader+CommonFields.swift +++ b/Sources/JWTKit/JWTHeader+CommonFields.swift @@ -1,37 +1,37 @@ -public extension JWTHeader { +extension JWTHeader { /// The `alg` (Algorithm) Header Parameter identifies the cryptographic algorithm used to secure the JWT. /// Common values include `HS256`, `RS256`, etc. - var alg: String? { + public var alg: String? { get { self[dynamicMember: "alg"]?.asString } set { self[dynamicMember: "alg"] = newValue.map { .string($0) } } } /// The `kid` (Key ID) Header Parameter is a hint indicating which key was used to secure the JWT. /// This parameter allows originators to explicitly signal a change of key to recipients. - var kid: String? { + public var kid: String? { get { self[dynamicMember: "kid"]?.asString } set { self[dynamicMember: "kid"] = newValue.map { .string($0) } } } /// The `typ` (Type) Header Parameter is used to declare the media type of the JWT. /// While optional, it's typically set to `JWT`. - var typ: String? { + public var typ: String? { get { self[dynamicMember: "typ"]?.asString } set { self[dynamicMember: "typ"] = newValue.map { .string($0) } } } /// The `cty` (Content Type) Header Parameter is used to declare the media type of the payload /// when the JWT is nested (e.g., encrypted JWT inside a JWT). - var cty: String? { + public var cty: String? { get { self[dynamicMember: "cty"]?.asString } set { self[dynamicMember: "cty"] = newValue.map { .string($0) } } } /// The `crit` (Critical) Header Parameter indicates that extensions to standard JWT specifications /// are being used and must be understood and processed. - var crit: [String]? { + public var crit: [String]? { get { - if case let .array(array) = self[dynamicMember: "crit"] { + if case .array(let array) = self[dynamicMember: "crit"] { return array.compactMap { $0.asString } } return nil @@ -44,23 +44,23 @@ public extension JWTHeader { /// The `jku` (JWK Set URL) Header Parameter is a URI that refers to a resource for a set of JSON-encoded public keys, /// one of which corresponds to the key used to digitally sign the JWT. - var jku: String? { + public var jku: String? { get { self[dynamicMember: "jku"]?.asString } set { self[dynamicMember: "jku"] = newValue.map { .string($0) } } } /// The `jwk` (JSON Web Key) Header Parameter is a JSON object that represents a cryptographic key. /// This parameter is used to transmit a key to be used in securing the JWT. - var jwk: [String: JWTHeaderField]? { + public var jwk: [String: JWTHeaderField]? { get { self[dynamicMember: "jwk"]?.asObject } set { self[dynamicMember: "jwk"] = newValue.map { .object($0) } } } /// The `x5c` (X.509 Certificate Chain) Header Parameter contains a chain of one or more PKIX certificates. /// Each string in the array is a base64-encoded (Section 4 of [RFC4648] - not base64url-encoded) DER [ITU.X690.1994] PKIX certificate value. - var x5c: [String]? { + public var x5c: [String]? { get { - if case let .array(array) = self[dynamicMember: "x5c"] { + if case .array(let array) = self[dynamicMember: "x5c"] { return array.compactMap { $0.asString } } return nil @@ -73,21 +73,21 @@ public extension JWTHeader { /// The `x5u` (X.509 URL) Header Parameter is a URI that refers to a resource for the X.509 public key certificate /// or certificate chain corresponding to the key used to digitally sign the JWT. - var x5u: String? { + public var x5u: String? { get { self[dynamicMember: "x5u"]?.asString } set { self[dynamicMember: "x5u"] = newValue.map { .string($0) } } } /// The `x5t` (X.509 Certificate SHA-1 Thumbprint) Header Parameter is a base64url-encoded SHA-1 thumbprint /// (a.k.a. digest) of the DER encoding of the X.509 certificate [RFC5280]. - var x5t: String? { + public var x5t: String? { get { self[dynamicMember: "x5t"]?.asString } set { self[dynamicMember: "x5t"] = newValue.map { .string($0) } } } /// The `x5t#S256` (X.509 Certificate SHA-256 Thumbprint) Header Parameter is a base64url-encoded SHA-256 thumbprint /// of the DER encoding of the X.509 certificate [RFC5280]. - var x5tS256: String? { + public var x5tS256: String? { get { self[dynamicMember: "x5t#S256"]?.asString } set { self[dynamicMember: "x5t#S256"] = newValue.map { .string($0) } } } diff --git a/Sources/JWTKit/JWTHeader.swift b/Sources/JWTKit/JWTHeader.swift index 3b7109f8..8f41a8be 100644 --- a/Sources/JWTKit/JWTHeader.swift +++ b/Sources/JWTKit/JWTHeader.swift @@ -32,7 +32,7 @@ extension JWTHeader: ExpressibleByDictionaryLiteral { extension JWTHeader: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try self.fields.forEach { key, value in + for (key, value) in self.fields { try container.encode(value, forKey: .custom(name: key)) } } @@ -51,7 +51,7 @@ extension JWTHeader: Codable { var stringValue: String { switch self { - case let .custom(name): + case .custom(let name): return name } } diff --git a/Sources/JWTKit/JWTHeaderField.swift b/Sources/JWTKit/JWTHeaderField.swift index cfac7c48..d77cd61c 100644 --- a/Sources/JWTKit/JWTHeaderField.swift +++ b/Sources/JWTKit/JWTHeaderField.swift @@ -12,13 +12,22 @@ public indirect enum JWTHeaderField: Hashable, Sendable, Codable { public init(from decoder: any Decoder) throws { let container: any SingleValueDecodingContainer - do { container = try decoder.singleValueContainer() } - catch DecodingError.typeMismatch { self = .null; return } + do { + container = try decoder.singleValueContainer() + } catch DecodingError.typeMismatch { + self = .null + return + } - if container.decodeNil() { self = .null; return } + if container.decodeNil() { + self = .null + return + } - do { self = try .bool(container.decode(Bool.self)); return } - catch DecodingError.typeMismatch {} + do { + self = try .bool(container.decode(Bool.self)) + return + } catch DecodingError.typeMismatch {} // This is a bit of a hack to correctly differentiate between integers and doubles do { @@ -31,71 +40,80 @@ public indirect enum JWTHeaderField: Hashable, Sendable, Codable { return } catch DecodingError.typeMismatch {} - do { self = try .string(container.decode(String.self)); return } - catch DecodingError.typeMismatch {} + do { + self = try .string(container.decode(String.self)) + return + } catch DecodingError.typeMismatch {} - do { self = try .array(container.decode([Self].self)); return } - catch DecodingError.typeMismatch {} + do { + self = try .array(container.decode([Self].self)) + return + } catch DecodingError.typeMismatch {} - do { self = try .object(container.decode([String: Self].self)); return } - catch DecodingError.typeMismatch {} + do { + self = try .object(container.decode([String: Self].self)) + return + } catch DecodingError.typeMismatch {} - throw DecodingError.dataCorruptedError(in: container, debugDescription: "No valid JSON type found.") + throw DecodingError.dataCorruptedError( + in: container, debugDescription: "No valid JSON type found.") } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() switch self { case .null: try container.encodeNil() - case let .bool(value): try container.encode(value) - case let .int(value): try container.encode(value) - case let .decimal(value): try container.encode(value) - case let .string(value): try container.encode(value) - case let .array(value): try container.encode(value) - case let .object(value): try container.encode(value) + case .bool(let value): try container.encode(value) + case .int(let value): try container.encode(value) + case .decimal(let value): try container.encode(value) + case .string(let value): try container.encode(value) + case .array(let value): try container.encode(value) + case .object(let value): try container.encode(value) } } } -public extension JWTHeaderField { - var isNull: Bool { if case .null = self { true } else { false } } - var asBool: Bool? { if case let .bool(b) = self { b } else { nil } } - var asInt: Int? { if case let .int(i) = self { i } else { nil } } - var asDecimal: Double? { if case let .decimal(d) = self { d } else { nil } } - var asString: String? { if case let .string(s) = self { s } else { nil } } - var asArray: [Self]? { if case let .array(a) = self { a } else { nil } } - var asObject: [String: Self]? { if case let .object(o) = self { o } else { nil } } +extension JWTHeaderField { + public var isNull: Bool { if case .null = self { true } else { false } } + public var asBool: Bool? { if case .bool(let b) = self { b } else { nil } } + public var asInt: Int? { if case .int(let i) = self { i } else { nil } } + public var asDecimal: Double? { if case .decimal(let d) = self { d } else { nil } } + public var asString: String? { if case .string(let s) = self { s } else { nil } } + public var asArray: [Self]? { if case .array(let a) = self { a } else { nil } } + public var asObject: [String: Self]? { if case .object(let o) = self { o } else { nil } } } -public extension JWTHeaderField { - func asObject(of _: T.Type) throws -> [String: T] { +extension JWTHeaderField { + public func asObject(of _: T.Type) throws -> [String: T] { guard let object = self.asObject else { throw JWTError.invalidHeaderField(reason: "Element is not an object") } - let values: [String: T]? = switch T.self { - case is Bool.Type: object.compactMapValues { $0.asBool } as? [String: T] - case is Int.Type: object.compactMapValues { $0.asInt } as? [String: T] - case is Double.Type: object.compactMapValues { $0.asDecimal } as? [String: T] - case is String.Type: object.compactMapValues { $0.asString } as? [String: T] - default: nil - } + let values: [String: T]? = + switch T.self { + case is Bool.Type: object.compactMapValues { $0.asBool } as? [String: T] + case is Int.Type: object.compactMapValues { $0.asInt } as? [String: T] + case is Double.Type: object.compactMapValues { $0.asDecimal } as? [String: T] + case is String.Type: object.compactMapValues { $0.asString } as? [String: T] + default: nil + } guard let values, object.count == values.count else { throw JWTError.invalidHeaderField(reason: "Object is not homogeneous") } return values } - func asArray(of _: T.Type) throws -> [T] { + public func asArray(of _: T.Type) throws -> [T] { guard let array = self.asArray else { throw JWTError.invalidHeaderField(reason: "Element is not an array") } - let values: [T]? = switch T.self { - case is Bool.Type: array.compactMap { $0.asBool } as? [T] - case is Int.Type: array.compactMap { $0.asInt } as? [T] - case is Double.Type: array.compactMap { $0.asDecimal } as? [T] - case is String.Type: array.compactMap { $0.asString } as? [T] - default: nil - } + let values: [T]? = + switch T.self { + case is Bool.Type: array.compactMap { $0.asBool } as? [T] + case is Int.Type: array.compactMap { $0.asInt } as? [T] + case is Double.Type: array.compactMap { $0.asDecimal } as? [T] + case is String.Type: array.compactMap { $0.asString } as? [T] + default: nil + } guard let values, array.count == values.count else { throw JWTError.invalidHeaderField(reason: "Array is not homogeneous") } diff --git a/Sources/JWTKit/JWTKeyCollection.swift b/Sources/JWTKit/JWTKeyCollection.swift index 8d3658ab..d68915eb 100644 --- a/Sources/JWTKit/JWTKeyCollection.swift +++ b/Sources/JWTKit/JWTKeyCollection.swift @@ -45,7 +45,8 @@ public actor JWTKeyCollection: Sendable { /// - Returns: Self for chaining. @discardableResult func add(_ signer: JWTSigner, for kid: JWKIdentifier? = nil) -> Self { - let signer = JWTSigner(algorithm: signer.algorithm, parser: signer.parser, serializer: signer.serializer) + let signer = JWTSigner( + algorithm: signer.algorithm, parser: signer.parser, serializer: signer.serializer) if let kid { if self.storage[kid] != nil { @@ -82,7 +83,9 @@ public actor JWTKeyCollection: Sendable { /// - Returns: Self for chaining. @discardableResult public func add(jwks: JWKS) throws -> Self { - try jwks.keys.forEach { try self.add(jwk: $0) } + for jwk in jwks.keys { + try self.add(jwk: jwk) + } return self } @@ -94,14 +97,12 @@ public actor JWTKeyCollection: Sendable { /// - Throws: ``JWTError/invalidJWK`` if the JWK cannot be added due to missing key identifier. /// - Returns: Self for chaining. @discardableResult - public func add( - jwk: JWK, - isDefault: Bool? = nil - ) throws -> Self { + public func add(jwk: JWK, isDefault: Bool? = nil) throws -> Self { guard let kid = jwk.keyIdentifier else { throw JWTError.invalidJWK(reason: "Missing KID") } - let signer = try JWKSigner(jwk: jwk, parser: defaultJWTParser, serializer: defaultJWTSerializer) + let signer = try JWKSigner( + jwk: jwk, parser: defaultJWTParser, serializer: defaultJWTSerializer) self.storage[kid] = .jwk(signer) switch (self.default, isDefault) { @@ -129,14 +130,16 @@ public actor JWTKeyCollection: Sendable { } switch signer { - case let .jwt(jwt): + case .jwt(let jwt): return jwt - case let .jwk(jwk): + case .jwk(let jwk): if let signer = await jwk.signer { return signer } else { guard let alg, let jwkAlg = JWK.Algorithm(rawValue: alg) else { - throw JWTError.generic(identifier: "Algorithm", reason: "Invalid algorithm or unable to create signer with provided algorithm.") + throw JWTError.generic( + identifier: "Algorithm", reason: "Invalid algorithm or unable to create signer with provided algorithm." + ) } return try await jwk.makeSigner(for: jwkAlg) } @@ -149,7 +152,7 @@ public actor JWTKeyCollection: Sendable { /// - alg: An optional algorithm identifier. /// - Returns: A ``JWTKey`` if one is found; otherwise, `nil`. /// - Throws: ``JWTError/generic`` if the algorithm cannot be retrieved. - public func getKey(for kid: JWKIdentifier? = nil, alg: String? = nil) async throws -> JWTAlgorithm { + public func getKey(for kid: JWKIdentifier? = nil, alg: String? = nil) async throws -> any JWTAlgorithm { try await self.getSigner(for: kid, alg: alg).algorithm } @@ -166,8 +169,7 @@ public actor JWTKeyCollection: Sendable { as _: Payload.Type = Payload.self, parser: (any JWTParser)? = nil ) throws -> Payload - where Payload: JWTPayload - { + where Payload: JWTPayload { try self.unverified([UInt8](token.utf8), parser: parser) } @@ -184,8 +186,7 @@ public actor JWTKeyCollection: Sendable { as _: Payload.Type = Payload.self, parser: (any JWTParser)? = nil ) throws -> Payload - where Payload: JWTPayload - { + where Payload: JWTPayload { try (parser ?? self.defaultJWTParser).parse(token, as: Payload.self).payload } @@ -202,8 +203,7 @@ public actor JWTKeyCollection: Sendable { as _: Payload.Type = Payload.self, iteratingKeys: Bool = false ) async throws -> Payload - where Payload: JWTPayload - { + where Payload: JWTPayload { try await self.verify([UInt8](token.utf8), as: Payload.self, iteratingKeys: iteratingKeys) } @@ -220,8 +220,7 @@ public actor JWTKeyCollection: Sendable { as _: Payload.Type = Payload.self, iteratingKeys: Bool = false ) async throws -> Payload - where Payload: JWTPayload - { + where Payload: JWTPayload { let header = try defaultJWTParser.parseHeader(token) let kid = header.kid.flatMap { JWKIdentifier(string: $0) } var signer = try await self.getSigner(for: kid, alg: header.alg) diff --git a/Sources/JWTKit/JWTParser.swift b/Sources/JWTKit/JWTParser.swift index 5f4ff046..54729b8d 100644 --- a/Sources/JWTKit/JWTParser.swift +++ b/Sources/JWTKit/JWTParser.swift @@ -2,12 +2,17 @@ import Foundation public protocol JWTParser: Sendable { var jsonDecoder: JWTJSONDecoder { get set } - 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 } -public extension JWTParser { - func getTokenParts(_ token: some DataProtocol) throws -> (header: ArraySlice, payload: ArraySlice, signature: ArraySlice) { - let tokenParts = token.copyBytes().split(separator: .period, omittingEmptySubsequences: false) +extension JWTParser { + public func getTokenParts(_ token: some DataProtocol) throws -> ( + header: ArraySlice, payload: ArraySlice, signature: ArraySlice + ) { + let tokenParts = token.copyBytes().split( + separator: .period, omittingEmptySubsequences: false) guard tokenParts.count == 3 else { throw JWTError.malformedToken(reason: "Token is not split in 3 parts") @@ -40,12 +45,14 @@ public struct DefaultJWTParser: JWTParser { self.jsonDecoder = jsonDecoder } - public func parse(_ token: some DataProtocol, as: Payload.Type) throws -> (header: JWTHeader, payload: Payload, signature: Data) - where Payload: JWTPayload - { + public 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: JWTHeader, payload: Payload, signature: Data + let header: JWTHeader + let payload: Payload + let signature: Data do { header = try jsonDecoder.decode(JWTHeader.self, from: .init(encodedHeader.base64URLDecodedBytes())) diff --git a/Sources/JWTKit/JWTSigner.swift b/Sources/JWTKit/JWTSigner.swift index 4ed86ddc..db877dfc 100644 --- a/Sources/JWTKit/JWTSigner.swift +++ b/Sources/JWTKit/JWTSigner.swift @@ -7,7 +7,11 @@ final class JWTSigner: Sendable { let parser: any JWTParser let serializer: any JWTSerializer - init(algorithm: JWTAlgorithm, parser: any JWTParser = DefaultJWTParser(), serializer: any JWTSerializer = DefaultJWTSerializer()) { + init( + algorithm: some JWTAlgorithm, + parser: any JWTParser = DefaultJWTParser(), + serializer: any JWTSerializer = DefaultJWTSerializer() + ) { self.algorithm = algorithm self.parser = parser self.serializer = serializer @@ -17,9 +21,7 @@ final class JWTSigner: Sendable { try await serializer.sign(payload, with: header, using: self.algorithm) } - func verify(_ token: some DataProtocol) async throws -> Payload - where Payload: JWTPayload - { + func verify(_ token: some DataProtocol) async throws -> Payload where Payload: JWTPayload { let (encodedHeader, encodedPayload, encodedSignature) = try parser.getTokenParts(token) let data = encodedHeader + [.period] + encodedPayload let signature = encodedSignature.base64URLDecodedBytes() diff --git a/Sources/JWTKit/None/JWTKeyCollection+UnsecuredNone.swift b/Sources/JWTKit/None/JWTKeyCollection+UnsecuredNone.swift index 75e04360..1cb28a8f 100644 --- a/Sources/JWTKit/None/JWTKeyCollection+UnsecuredNone.swift +++ b/Sources/JWTKit/None/JWTKeyCollection+UnsecuredNone.swift @@ -1,4 +1,4 @@ -public extension JWTKeyCollection { +extension JWTKeyCollection { /// Adds a configuration for JWTs without a signature. /// /// This method configures JWT processing to accept tokens with the 'none' algorithm, indicating that the JWT @@ -28,11 +28,13 @@ public extension JWTKeyCollection { /// requirements of your system. It is not recommended for scenarios where data integrity and authentication /// are critical. @discardableResult - func addUnsecuredNone( + public func addUnsecuredNone( kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init(algorithm: UnsecuredNoneSigner(), parser: parser, serializer: serializer), for: kid) + add( + .init(algorithm: UnsecuredNoneSigner(), parser: parser, serializer: serializer), + for: kid) } } diff --git a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift index 1794987c..504152ec 100644 --- a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift +++ b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift @@ -1,6 +1,6 @@ import _CryptoExtras -public extension JWTKeyCollection { +extension JWTKeyCollection { /// Adds an RSA key to the collection. /// /// This method configures and adds an RSA key to the collection. The key is used for signing JWTs @@ -21,28 +21,31 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func add( + public func add( rsa key: some RSAKey, digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - let name = switch digestAlgorithm.backing { - case .sha256: - "RS256" - case .sha384: - "RS384" - case .sha512: - "RS512" - } + let name = + switch digestAlgorithm.backing { + case .sha256: + "RS256" + case .sha384: + "RS384" + case .sha512: + "RS512" + } - return add(.init( - algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) + return add( + .init( + algorithm: RSASigner( + key: key, algorithm: digestAlgorithm, name: name, padding: .insecurePKCS1v1_5), + parser: parser, + serializer: serializer + ), + for: kid) } /// Adds a PSS key to the collection. @@ -67,27 +70,30 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func add( + public func add( pss key: some RSAKey, digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - let name = switch digestAlgorithm.backing { - case .sha256: - "PS256" - case .sha384: - "PS384" - case .sha512: - "PS512" - } + let name = + switch digestAlgorithm.backing { + case .sha256: + "PS256" + case .sha384: + "PS384" + case .sha512: + "PS512" + } - return add(.init( - algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .PSS), - parser: parser, - serializer: serializer - ), - for: kid) + return add( + .init( + algorithm: RSASigner( + key: key, algorithm: digestAlgorithm, name: name, padding: .PSS), + parser: parser, + serializer: serializer + ), + for: kid) } } diff --git a/Sources/JWTKit/RSA/RSA.swift b/Sources/JWTKit/RSA/RSA.swift index 9865ca25..f8db6784 100644 --- a/Sources/JWTKit/RSA/RSA.swift +++ b/Sources/JWTKit/RSA/RSA.swift @@ -1,24 +1,24 @@ -import _CryptoExtras import Crypto import Foundation import X509 +import _CryptoExtras -public extension Insecure { +extension Insecure { /// Namespace encompassing functionality related to the RSA (Rivest–Shamir–Adleman) cryptographic algorithm. /// Relatively to other algorithms such as ECDSA and EdDSA, RSA is considered slow and should be avoided when possible. - enum RSA: Sendable {} + public enum RSA: Sendable {} } /// The `RSAKey` protocol defines the common interface for both public and private RSA keys. /// Implementers of this protocol can represent keys used for cryptographic operations in the RSA algorithm. public protocol RSAKey: Sendable {} -public extension Insecure.RSA { +extension Insecure.RSA { /// A structure representing a public RSA key. /// /// In JWT, RSA public keys are used to verify JWTs. /// They consist of a modulus and an exponent. - struct PublicKey: RSAKey, Equatable { + public struct PublicKey: RSAKey, Equatable { // Exports the current public key as a PEM encoded string. /// /// - Returns: A PEM encoded string representation of the key. @@ -167,19 +167,19 @@ public extension Insecure.RSA { let primitives = try self.backing.getKeyPrimitives() return (modulus: primitives.modulus, publicExponent: primitives.publicExponent) } - + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.derRepresentation == rhs.derRepresentation } } } -public extension Insecure.RSA { +extension Insecure.RSA { /// A structure representing a private RSA key. /// /// In JWT, RSA private keys are used to sign JWTs. /// They consist of a modulus, an exponent, and a private exponent. - struct PrivateKey: RSAKey, Equatable { + public struct PrivateKey: RSAKey, Equatable { /// Exports the current private key as a PEM encoded string. /// /// - Throws: If the key is not a private key. @@ -325,7 +325,7 @@ public extension Insecure.RSA { func signature(for digest: D, padding: _RSA.Signing.Padding) throws -> _RSA.Signing.RSASignature { try self.backing.signature(for: digest, padding: padding) } - + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.derRepresentation == rhs.derRepresentation } diff --git a/Sources/JWTKit/RSA/RSASigner.swift b/Sources/JWTKit/RSA/RSASigner.swift index 8e6c8a3b..250a70d6 100644 --- a/Sources/JWTKit/RSA/RSASigner.swift +++ b/Sources/JWTKit/RSA/RSASigner.swift @@ -1,5 +1,5 @@ -import _CryptoExtras import Foundation +import _CryptoExtras struct RSASigner: JWTAlgorithm, CryptoSigner { let publicKey: Insecure.RSA.PublicKey diff --git a/Sources/JWTKit/Utilities/Base64URL.swift b/Sources/JWTKit/Utilities/Base64URL.swift index 2f57d1de..5aac9a09 100644 --- a/Sources/JWTKit/Utilities/Base64URL.swift +++ b/Sources/JWTKit/Utilities/Base64URL.swift @@ -1,7 +1,7 @@ import Foundation -package extension String { - func base64URLDecodedData() -> Data? { +extension String { + package func base64URLDecodedData() -> Data? { var base64URL = replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") @@ -11,27 +11,29 @@ package extension String { } } -package extension DataProtocol { - func base64URLDecodedBytes() -> [UInt8] { +extension DataProtocol { + package func base64URLDecodedBytes() -> [UInt8] { Data(base64Encoded: Data(copyBytes()).base64URLUnescaped())?.copyBytes() ?? [] } - func base64URLEncodedBytes() -> [UInt8] { + package func base64URLEncodedBytes() -> [UInt8] { Data(copyBytes()).base64EncodedData().base64URLEscaped().copyBytes() } } // MARK: Data Escape -private extension Data { +extension Data { /// Converts base64-url encoded data to a base64 encoded data. /// /// https://tools.ietf.org/html/rfc4648#page-7 - mutating func base64URLUnescape() { + fileprivate mutating func base64URLUnescape() { for idx in self.indices { switch self[idx] { - case 0x2D /* - */: self[idx] = 0x2B /* + */ - case 0x5F /* _ */: self[idx] = 0x2F /* / */ + case 0x2D: // - + self[idx] = 0x2B // + + case 0x5F: // _ + self[idx] = 0x2F // / default: break } } @@ -45,11 +47,13 @@ private extension Data { /// Converts base64 encoded data to a base64-url encoded data. /// /// https://tools.ietf.org/html/rfc4648#page-7 - mutating func base64URLEscape() { + fileprivate mutating func base64URLEscape() { for idx in self.indices { switch self[idx] { - case 0x2B /* + */: self[idx] = 0x2D /* - */ - case 0x2F /* / */: self[idx] = 0x5F /* _ */ + case 0x2B: // + + self[idx] = 0x2D // - + case 0x2F: // / + self[idx] = 0x5F // _ default: break } } @@ -59,7 +63,7 @@ private extension Data { /// Converts base64-url encoded data to a base64 encoded data. /// /// https://tools.ietf.org/html/rfc4648#page-7 - func base64URLUnescaped() -> Data { + fileprivate func base64URLUnescaped() -> Data { var data = self data.base64URLUnescape() return data @@ -68,7 +72,7 @@ private extension Data { /// Converts base64 encoded data to a base64-url encoded data. /// /// https://tools.ietf.org/html/rfc4648#page-7 - func base64URLEscaped() -> Data { + fileprivate func base64URLEscaped() -> Data { var data = self data.base64URLEscape() return data diff --git a/Sources/JWTKit/Utilities/CustomizedJSONCoders.swift b/Sources/JWTKit/Utilities/CustomizedJSONCoders.swift index 1855fb53..6075fc7c 100644 --- a/Sources/JWTKit/Utilities/CustomizedJSONCoders.swift +++ b/Sources/JWTKit/Utilities/CustomizedJSONCoders.swift @@ -8,13 +8,8 @@ public protocol JWTJSONEncoder: Sendable { func encode(_ value: T) throws -> Data } -#if compiler(<6.0) && !canImport(Darwin) - extension JSONDecoder: JWTJSONDecoder, @unchecked Sendable {} - extension JSONEncoder: JWTJSONEncoder, @unchecked Sendable {} -#else - extension JSONDecoder: JWTJSONDecoder {} - extension JSONEncoder: JWTJSONEncoder {} -#endif +extension JSONDecoder: JWTJSONDecoder {} +extension JSONEncoder: JWTJSONEncoder {} extension JSONDecoder.DateDecodingStrategy { public static var integerSecondsSince1970: Self { diff --git a/Sources/JWTKit/Utilities/Utilities.swift b/Sources/JWTKit/Utilities/Utilities.swift index e9ee5505..2fc88eef 100644 --- a/Sources/JWTKit/Utilities/Utilities.swift +++ b/Sources/JWTKit/Utilities/Utilities.swift @@ -1,7 +1,7 @@ import Foundation -public extension DataProtocol { - func copyBytes() -> [UInt8] { +extension DataProtocol { + public func copyBytes() -> [UInt8] { if let array = self.withContiguousStorageIfAvailable({ buffer in [UInt8](buffer) }) { diff --git a/Sources/JWTKit/Vendor/AppleIdentityToken.swift b/Sources/JWTKit/Vendor/AppleIdentityToken.swift index 4352578e..5de588f6 100644 --- a/Sources/JWTKit/Vendor/AppleIdentityToken.swift +++ b/Sources/JWTKit/Vendor/AppleIdentityToken.swift @@ -91,10 +91,10 @@ public struct AppleIdentityToken: JWTPayload { } } -public extension AppleIdentityToken { +extension AppleIdentityToken { /// Taken from https://developer.apple.com/documentation/authenticationservices/asuserdetectionstatus /// With slight modification to make adding new cases non-breaking. - struct UserDetectionStatus: OptionSet, Codable, Sendable { + public struct UserDetectionStatus: OptionSet, Codable, Sendable { /// Used for decoding/encoding private enum Status: Int, Codable { case unsupported @@ -103,7 +103,7 @@ public extension AppleIdentityToken { } /// Not supported on current platform, ignore the value - public static let unsupported = UserDetectionStatus([]) // 0 was giving a warning + public static let unsupported = UserDetectionStatus([]) // 0 was giving a warning /// We could not determine the value. New users in the ecosystem will get this value as well, so you should not block these users, but instead treat them as any new user through standard email sign up flows public static let unknown = UserDetectionStatus(rawValue: 1) diff --git a/Sources/JWTKit/X5C/X5CVerifier.swift b/Sources/JWTKit/X5C/X5CVerifier.swift index 28befd88..1b1489e7 100644 --- a/Sources/JWTKit/X5C/X5CVerifier.swift +++ b/Sources/JWTKit/X5C/X5CVerifier.swift @@ -152,8 +152,7 @@ public struct X5CVerifier: Sendable { // Ensure the algorithm used is ES256, as it's the only supported one (for now) guard let headerAlg = header.alg, headerAlg == "ES256" else { - throw JWTError.invalidX5CChain( - reason: "Unsupported algorithm: \(String(describing: header.alg))") + throw JWTError.invalidX5CChain(reason: "Unsupported algorithm: \(String(describing: header.alg))") } // Ensure the x5c header parameter is present and not empty @@ -199,7 +198,7 @@ public struct X5CVerifier: Sendable { intermediates: untrustedChain ) - if case let .couldNotValidate(failures) = result { + if case .couldNotValidate(let failures) = result { throw JWTError.invalidX5CChain(reason: "\(failures)") } diff --git a/Tests/JWTKitTests/ClaimTests.swift b/Tests/JWTKitTests/ClaimTests.swift index dbc919c7..62611ffc 100644 --- a/Tests/JWTKitTests/ClaimTests.swift +++ b/Tests/JWTKitTests/ClaimTests.swift @@ -1,6 +1,6 @@ +import Foundation import JWTKit import Testing -import Foundation @Suite("Claim Tests") struct ClaimTests { @@ -9,18 +9,18 @@ struct ClaimTests { let payload = #"{"trueStr":"true","trueBool":true,"falseStr":"false","falseBool":false}"# var data = Data(payload.utf8) let decoded = try! JSONDecoder().decode(BoolPayload.self, from: data) - + #expect(decoded.trueStr.value == true) #expect(decoded.trueBool.value == true) #expect(decoded.falseBool.value == false) #expect(decoded.falseStr.value == false) - + data = Data(#"{"bad":"Not boolean"}"#.utf8) #expect(throws: DecodingError.self) { try JSONDecoder().decode(BoolPayload.self, from: data) } } - + @Test("Test Claim with Locale") func localeClaim() async throws { let ptBR = #"{"locale":"pt-BR"}"# @@ -29,8 +29,9 @@ struct ClaimTests { let brazillianPortugese = try LocalePayload.from(ptBR) let nadizaDialectSlovenia = try LocalePayload.from(#"{"locale":"sl-nedis"}"#) let germanSwissPost1996 = try LocalePayload.from(#"{"locale":"de-CH-1996"}"#) - let chineseTraditionalTwoPrivate = try LocalePayload.from(#"{"locale":"zh-Hant-CN-x-private1-private2"}"#) - + let chineseTraditionalTwoPrivate = try LocalePayload.from( + #"{"locale":"zh-Hant-CN-x-private1-private2"}"#) + #expect(plainEnglish.locale.value.identifier == "en") #expect(brazillianPortugese.locale.value.identifier == "pt-BR") #expect(nadizaDialectSlovenia.locale.value.identifier == "sl-nedis") @@ -41,35 +42,36 @@ struct ClaimTests { let string = String(bytes: encoded, encoding: .utf8)! #expect(string == ptBR) } - + @Test("Test Claim with Sindle Audience") func singleAudienceClaim() async throws { let id = UUID() let str = "{\"audience\":\"\(id.uuidString)\"}" let data = Data(str.utf8) let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data) - + #expect(decoded.audience.value == [id.uuidString]) #expect(throws: Never.self) { try decoded.audience.verifyIntendedAudience(includes: id.uuidString) } #expect { try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString) - } throws : { error in + } throws: { error in guard let jwtError = error as? JWTError else { return false } return jwtError.errorType == .claimVerificationFailure - && jwtError.failedClaim is AudienceClaim - && (jwtError.failedClaim as? AudienceClaim)?.value == [id.uuidString] + && jwtError.failedClaim is AudienceClaim + && (jwtError.failedClaim as? AudienceClaim)?.value == [id.uuidString] } } - + @Test("Test Claim with Multiple Audiences") func multipleAudienceClaims() async throws { - let id1 = UUID(), id2 = UUID() + let id1 = UUID() + let id2 = UUID() let str = "{\"audience\":[\"\(id1.uuidString)\", \"\(id2.uuidString)\"]}" let data = Data(str.utf8) let decoded = try! JSONDecoder().decode(AudiencePayload.self, from: data) - + #expect(decoded.audience.value == [id1.uuidString, id2.uuidString]) #expect(throws: Never.self) { try decoded.audience.verifyIntendedAudience(includes: id1.uuidString) @@ -79,23 +81,26 @@ struct ClaimTests { } #expect { try decoded.audience.verifyIntendedAudience(includes: UUID().uuidString) - } throws : { error in + } throws: { error in guard let jwtError = error as? JWTError else { return false } return jwtError.errorType == .claimVerificationFailure - && jwtError.failedClaim is AudienceClaim - && (jwtError.failedClaim as? AudienceClaim)?.value == [id1.uuidString, id2.uuidString] + && jwtError.failedClaim is AudienceClaim + && (jwtError.failedClaim as? AudienceClaim)?.value == [ + id1.uuidString, id2.uuidString, + ] } } - + @Test("Test Expiration Encoding") func expirationEncoding() async throws { let exp = ExpirationClaim(value: Date(timeIntervalSince1970: 2_000_000_000)) let parser = DefaultJWTParser() - let keyCollection = await JWTKeyCollection().add(hmac: .init(from: "secret".bytes), digestAlgorithm: .sha256, parser: parser) + let keyCollection = await JWTKeyCollection() + .add(hmac: .init(from: "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 - + let typ = try #require(header.typ) #expect(typ == "JWT") let alg = try #require(header.alg) diff --git a/Tests/JWTKitTests/ECDSATests.swift b/Tests/JWTKitTests/ECDSATests.swift index 1f50b05e..fb96f4f3 100644 --- a/Tests/JWTKitTests/ECDSATests.swift +++ b/Tests/JWTKitTests/ECDSATests.swift @@ -1,18 +1,18 @@ import Crypto +import Foundation import JWTKit import Testing -import Foundation @Suite("ECDSA Tests") struct ECDSATests { - + @Test("Test ECDSA Docs") func ecdsaDocs() async throws { #expect(throws: Never.self) { try ES256PublicKey(pem: ecdsaPublicKey) } } - + @Test("Test ECDSA from Crypto Key") func ecdsaFromCryptoKey() async throws { let key = try P256.Signing.PublicKey(pemRepresentation: ecdsaPublicKey) @@ -20,7 +20,7 @@ struct ECDSATests { let otherKey = try ES256PublicKey(pem: ecdsaPublicKey) #expect(cryptoKey == otherKey) } - + @Test("Test ECDSA Private from Crypto Key") func ecdsaPrivateFromCryptoKey() async throws { let key = try P256.Signing.PrivateKey(pemRepresentation: ecdsaPrivateKey) @@ -28,7 +28,7 @@ struct ECDSATests { let otherKey = try ES256PrivateKey(pem: ecdsaPrivateKey) #expect(cryptoKey == otherKey) } - + @Test("Test Signing with Public Key") func signingWithPublicKey() async throws { let key = try ES256PrivateKey(pem: ecdsaPrivateKey) @@ -36,14 +36,14 @@ struct ECDSATests { let keys = await JWTKeyCollection() .add(ecdsa: key, kid: "private") .add(ecdsa: publicKey, kid: "public") - + let payload = TestPayload( sub: "vapor", name: "Foo", admin: false, exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) - + await #expect { try await keys.sign(payload, kid: "public") } throws: { error in @@ -53,7 +53,7 @@ struct ECDSATests { return error.errorType == .signingAlgorithmFailure } } - + @Test("Test ECDSA Generate") func ecdsaGenerate() async throws { let payload = TestPayload( @@ -69,13 +69,13 @@ struct ECDSATests { let verifiedPayload = try await keyCollection.verify(token, as: TestPayload.self) #expect(verifiedPayload == payload) } - + @Test("Test ECDSA Public and Private") func ecdsaPublicPrivate() async throws { let keys = try await JWTKeyCollection() .add(ecdsa: ES256PublicKey(pem: ecdsaPublicKey), kid: "public") .add(ecdsa: ES256PrivateKey(pem: ecdsaPrivateKey), kid: "private") - + let payload = TestPayload( sub: "vapor", name: "Foo", @@ -88,104 +88,104 @@ struct ECDSATests { #expect(try await keys.verify(token, as: TestPayload.self) == payload) } } - + @Test("Test Verifying ECDSA Key Using JWK") func verifyingECDSAKeyUsingJWK() async throws { struct Foo: JWTPayload { var bar: Int func verify(using _: some JWTAlgorithm) throws {} } - + // ecdsa key let x = "0tu/H2ShuV8RIgoOxFneTdxmQQYsSk5LdCPuEIBXT+hHd0ufc/OwjEbqilsYnTdm" let y = "RWRZz+tP83N0CGwroGyFVgH3PYAO6Oewpu4Xf6EXCp4+sU8uWegwjd72sBK6axj7" - + let privateKey = "k+1LAHQRSSMcyaouYK0YOzRbUKj6ISnvihO2XdLQZHQgMt9BkuCT0+539FSHmJxg" - + // sign jwt let key = try ES384PrivateKey(key: privateKey) let keys = await JWTKeyCollection().add(ecdsa: key, kid: "vapor") - + let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") - + // verify using jwks without alg let jwksString = """ - { - "keys": [ - { - "kty": "EC", - "use": "sig", - "kid": "vapor", - "x": "\(x)", - "y": "\(y)" - } - ] - } - """ + { + "keys": [ + { + "kty": "EC", + "use": "sig", + "kid": "vapor", + "x": "\(x)", + "y": "\(y)" + } + ] + } + """ try await keys.add(jwksJSON: jwksString) let foo = try await keys.verify(jwt, as: Foo.self) #expect(foo.bar == 42) } - + @Test("Test Verifying ECDSA Key Using JWK Base64URL") func verifyingECDSAKeyUsingJWKBase64URL() async throws { struct Foo: JWTPayload { var bar: Int func verify(using _: some JWTAlgorithm) throws {} } - + // ecdsa key in base64url format let x = "0tu_H2ShuV8RIgoOxFneTdxmQQYsSk5LdCPuEIBXT-hHd0ufc_OwjEbqilsYnTdm" let y = "RWRZz-tP83N0CGwroGyFVgH3PYAO6Oewpu4Xf6EXCp4-sU8uWegwjd72sBK6axj7" - + // private key in base64url format let privateKey = "k-1LAHQRSSMcyaouYK0YOzRbUKj6ISnvihO2XdLQZHQgMt9BkuCT0-539FSHmJxg" - + // sign jwt let key = try ES384PrivateKey(key: privateKey) let keys = await JWTKeyCollection().add(ecdsa: key, kid: "vapor") - + let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") - + // verify using jwks without alg let jwksString = """ - { - "keys": [ - { - "kty": "EC", - "use": "sig", - "kid": "vapor", - "x": "\(x)", - "y": "\(y)" - } - ] - } - """ + { + "keys": [ + { + "kty": "EC", + "use": "sig", + "kid": "vapor", + "x": "\(x)", + "y": "\(y)" + } + ] + } + """ try await keys.add(jwksJSON: jwksString) let foo = try await keys.verify(jwt, as: Foo.self) #expect(foo.bar == 42) } - + @Test("Test Verifying ECDSA Key Using JWK With Mixed Base64 Formats") func verifyingECDSAKeyUsingJWKWithMixedBase64Formats() async throws { struct Foo: JWTPayload { var bar: Int func verify(using _: some JWTAlgorithm) throws {} } - + // ECDSA key in base64url format let x = "0tu_H2ShuV8RIgoOxFneTdxmQQYsSk5LdCPuEIBXT-hHd0ufc_OwjEbqilsYnTdm" let y = "RWRZz-tP83N0CGwroGyFVgH3PYAO6Oewpu4Xf6EXCp4-sU8uWegwjd72sBK6axj7" - + // Private key in base64 format let privateKey = "k+1LAHQRSSMcyaouYK0YOzRbUKj6ISnvihO2XdLQZHQgMt9BkuCT0+539FSHmJxg" - + // Sign JWT let key = try ES384PrivateKey(key: privateKey) let keys = await JWTKeyCollection().add(ecdsa: key, kid: "vapor") - + let jwt = try await keys.sign(Foo(bar: 42), kid: "vapor") - + // Verify using JWK without alg let jwksString = """ { @@ -200,19 +200,19 @@ struct ECDSATests { ] } """ - + try await keys.add(jwksJSON: jwksString) let foo = try await keys.verify(jwt, as: Foo.self) - + #expect(foo.bar == 42) } - + @Test("Test JWT Payload Verification") func jwtPayloadVerification() async throws { struct NotBar: Error { let foo: String } - + struct Payload: JWTPayload { let foo: String func verify(using _: some JWTAlgorithm) throws { @@ -221,118 +221,120 @@ struct ECDSATests { } } } - + let keys = await JWTKeyCollection().add(ecdsa: ES256PrivateKey(), kid: "vapor") - + // First test: "qux" payload should throw NotBar error - await #expect(performing: { - let token = try await keys.sign(Payload(foo: "qux")) - _ = try await keys.verify(token, as: Payload.self) - }, throws: { error in - guard let error = error as? NotBar else { - return false - } - return error.foo == "qux" - }) - + await #expect( + performing: { + let token = try await keys.sign(Payload(foo: "qux")) + _ = try await keys.verify(token, as: Payload.self) + }, + throws: { error in + guard let error = error as? NotBar else { + return false + } + return error.foo == "qux" + }) + // Second test: "bar" payload should pass verification let token = try await keys.sign(Payload(foo: "bar")) let payload = try await keys.verify(token, as: Payload.self) #expect(payload.foo == "bar") } - + @Test("Test Export Public Key as PEM") func exportPublicKeyAsPEM() async throws { let key = try ES256PublicKey(pem: ecdsaPublicKey) let key2 = try ES256PublicKey(pem: key.pemRepresentation) #expect(key == key2) } - + @Test("Test Export Private Key as PEM") func exportPrivateKeyAsPEM() async throws { let key = try ES256PrivateKey(pem: ecdsaPrivateKey) let key2 = try ES256PrivateKey(pem: key.pemRepresentation) #expect(key == key2) } - + @Test("Test Get EC Parameters ES256") func getECParametersES256() async throws { let message = "test".bytes - + // Create ES256 private key let ec = ES256PrivateKey() let keys = await JWTKeyCollection().add(ecdsa: ec, kid: "initial") - + // Sign the message with the initial private key let signature = try await keys.getKey(for: "initial").sign(message) - + // Extract the EC parameters and create a public key from it let params = ec.parameters! try await keys.add(ecdsa: ES256PublicKey(parameters: params), kid: "params") - + // Verify the signature using the public key created from the parameters #expect(try await keys.getKey(for: "params").verify(signature, signs: message)) - + // Ensure the curve is p256 #expect(ec.curve == .p256) } - + @Test("Test Get EC Parameters ES384") func getECParametersES384() async throws { let message = "test".bytes - + // Create ES384 private key let ec = ES384PrivateKey() let keys = await JWTKeyCollection().add(ecdsa: ec, kid: "initial") - + // Sign the message with the initial private key let signature = try await keys.getKey(for: "initial").sign(message) - + // Extract the EC parameters and create a public key from it let params = ec.parameters! try await keys.add(ecdsa: ES384PublicKey(parameters: params), kid: "params") - + // Verify the signature using the public key created from the parameters #expect(try await keys.getKey(for: "params").verify(signature, signs: message)) - + // Ensure the curve is p384 #expect(ec.curve == .p384) } - + @Test("Test Get EC Parameters ES512") func getECParametersES512() async throws { let message = "test".bytes - + // Create ES512 private key let ec = ES512PrivateKey() let keys = await JWTKeyCollection().add(ecdsa: ec, kid: "initial") - + // Sign the message with the initial private key let signature = try await keys.getKey(for: "initial").sign(message) - + // Extract the EC parameters and create a public key from it let params = ec.parameters! try await keys.add(ecdsa: ES512PublicKey(parameters: params), kid: "params") - + // Verify the signature using the public key created from the parameters #expect(try await keys.getKey(for: "params").verify(signature, signs: message)) - + // Ensure the curve is p521 #expect(ec.curve == .p521) } } let ecdsaPrivateKey = """ ------BEGIN PRIVATE KEY----- -MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2sD+kukkA8GZUpmm -jRa4fJ9Xa/JnIG4Hpi7tNO66+OGgCgYIKoZIzj0DAQehRANCAATZp0yt0btpR9kf -ntp4oUUzTV0+eTELXxJxFvhnqmgwGAm1iVW132XLrdRG/ntlbQ1yzUuJkHtYBNve -y+77Vzsd ------END PRIVATE KEY----- -""" + -----BEGIN PRIVATE KEY----- + MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2sD+kukkA8GZUpmm + jRa4fJ9Xa/JnIG4Hpi7tNO66+OGgCgYIKoZIzj0DAQehRANCAATZp0yt0btpR9kf + ntp4oUUzTV0+eTELXxJxFvhnqmgwGAm1iVW132XLrdRG/ntlbQ1yzUuJkHtYBNve + y+77Vzsd + -----END PRIVATE KEY----- + """ let ecdsaPublicKey = """ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx -C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ== ------END PUBLIC KEY----- -""" + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx + C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ== + -----END PUBLIC KEY----- + """ diff --git a/Tests/JWTKitTests/EdDSATests.swift b/Tests/JWTKitTests/EdDSATests.swift index c5a7a746..db4578ed 100644 --- a/Tests/JWTKitTests/EdDSATests.swift +++ b/Tests/JWTKitTests/EdDSATests.swift @@ -33,7 +33,7 @@ struct EdDSATests { admin: false, exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) - + for _ in 0..<1000 { let token = try await keys.sign(payload, kid: "private") // test public signer decoding @@ -61,19 +61,19 @@ struct EdDSATests { // verify using jwks let jwksString = """ - { - "keys": [ - { - "kty": "OKP", - "crv": "Ed25519", - "use": "sig", - "kid": "vapor", - "x": "\(x)", - "d": "\(d)" - } - ] - } - """ + { + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "use": "sig", + "kid": "vapor", + "x": "\(x)", + "d": "\(d)" + } + ] + } + """ try await keyCollection.add(jwksJSON: jwksString) let foo = try await keyCollection.verify(jwt, as: Foo.self) @@ -98,19 +98,19 @@ struct EdDSATests { // verify using jwks let jwksString = """ - { - "keys": [ - { - "kty": "OKP", - "crv": "Ed25519", - "use": "sig", - "kid": "vapor", - "x": "\(x)", - "d": "\(d)" - } - ] - } - """ + { + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "use": "sig", + "kid": "vapor", + "x": "\(x)", + "d": "\(d)" + } + ] + } + """ try await keyCollection.add(jwksJSON: jwksString) let foo = try await keyCollection.verify(jwt, as: Foo.self) @@ -136,19 +136,19 @@ struct EdDSATests { // verify using jwks let jwksString = """ - { - "keys": [ - { - "kty": "OKP", - "crv": "Ed25519", - "use": "sig", - "kid": "vapor", - "x": "\(x)", - "d": "\(d)" - } - ] - } - """ + { + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "use": "sig", + "kid": "vapor", + "x": "\(x)", + "d": "\(d)" + } + ] + } + """ try await keyCollection.add(jwksJSON: jwksString) let foo = try await keyCollection.verify(jwt, as: Foo.self) diff --git a/Tests/JWTKitTests/JWTKitTests.swift b/Tests/JWTKitTests/JWTKitTests.swift index 54d29918..745d023b 100644 --- a/Tests/JWTKitTests/JWTKitTests.swift +++ b/Tests/JWTKitTests/JWTKitTests.swift @@ -332,9 +332,7 @@ struct JWTKitTests { @Test("Test Firebase JWT and Certificate") func addFirebaseJWTAndCertificate() async throws { let payload = try await JWTKeyCollection() - .add( - rsa: Insecure.RSA.PublicKey(certificatePEM: firebaseCert), digestAlgorithm: .sha256 - ) + .add(rsa: Insecure.RSA.PublicKey(certificatePEM: firebaseCert), digestAlgorithm: .sha256) .verify(firebaseJWT, as: FirebasePayload.self) #expect(payload.userID == "y8wiKThXGKM88xxrQWDZzKnBuqv2") } diff --git a/Tests/JWTKitTests/PSSTests.swift b/Tests/JWTKitTests/PSSTests.swift index 991d1f75..c52890c3 100644 --- a/Tests/JWTKitTests/PSSTests.swift +++ b/Tests/JWTKitTests/PSSTests.swift @@ -1,6 +1,6 @@ +import Foundation import JWTKit import Testing -import Foundation @Suite("PSS Tests") struct PSSTests { @@ -8,7 +8,8 @@ struct PSSTests { @Test("Test PSS Docs") func pssDocs() async throws { await #expect(throws: Never.self) { - try await JWTKeyCollection().add(pss: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) + try await JWTKeyCollection().add( + pss: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) } } @@ -16,7 +17,6 @@ struct PSSTests { func signing() async throws { let keyCollection = try await JWTKeyCollection() .add(pss: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") - .add(pss: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -64,18 +64,20 @@ struct PSSTests { let keyCollection = try await JWTKeyCollection() .add(pss: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") - .add(pss: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") // Case where foo is not "bar" - await #expect(performing: { - let token = try await keyCollection.sign(Payload(foo: "qux"), header: ["kid": "private"]) - _ = try await keyCollection.verify(token, as: Payload.self) - }, throws: { error in - guard let notBarError = error as? NotBar else { - return false - } - return notBarError.foo == "qux" - }) + await #expect( + performing: { + let token = try await keyCollection.sign( + Payload(foo: "qux"), header: ["kid": "private"]) + _ = try await keyCollection.verify(token, as: Payload.self) + }, + throws: { error in + guard let notBarError = error as? NotBar else { + return false + } + return notBarError.foo == "qux" + }) // Case where foo is "bar" let token = try await keyCollection.sign(Payload(foo: "bar")) @@ -100,14 +102,16 @@ struct PSSTests { @Test("Test Export Public Key When Key is Private") func exportPublicKeyWhenKeyIsPrivate() async throws { let privateKey = try Insecure.RSA.PrivateKey(pem: privateKey) - let publicKeyFromPrivate = try Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation) + let publicKeyFromPrivate = try Insecure.RSA.PublicKey( + pem: privateKey.publicKey.pemRepresentation) let publicKey = try Insecure.RSA.PublicKey(pem: publicKey) #expect(publicKeyFromPrivate == publicKey) } @Test("Test PS256 in JWT") func ps256InJWT() async throws { - let token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MjAwMDAwMDAwMH0.dCaprjSiEw1w_cS2JzjWlp1mxdF9MV86VMylKiEZf6gM8NZhNo3hgnI3Gg7G_WL_bSzys9Z0QtNpWZeW1Mooa29qDqZolQLKbzyjiIMDFBslz_Hei-tI5318UdFLKIlMT0VyDThwFjyPCiVEvOkKokWSXXGZCHArGXouTWvaTND9C0gOMwSkE8cHU7e0u-_pDEfdv9MRQiGy1Wj-9T_ZN6a0g8yFMQcOU6voo-WSY-m98oylYOifiOighitlD0xNScDnxBH5Qp7yyU81m-s2-xoYVQJhGduvi8mxbo_bU48WIJfmdAYX3aAUh_xpvgcd55bdeMT55G_qnkDBDSLvbQ" + 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() .add(pss: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") @@ -122,39 +126,39 @@ struct PSSTests { @Test("Test PS256 in JWK") func ps256InJWK() async throws { let publicJWK = """ - { - "kty": "RSA", - "kid": "public", - "use": "sig", - "alg": "PS256", - "n": "\(modulus)", - "e": "\(publicExponent)" - } - """ - + { + "kty": "RSA", + "kid": "public", + "use": "sig", + "alg": "PS256", + "n": "\(modulus)", + "e": "\(publicExponent)" + } + """ + let privateJWK = """ - { - "kty": "RSA", - "kid": "private", - "use": "sig", - "alg": "PS256", - "n": "\(modulus)", - "e": "\(publicExponent)", - "d": "\(privateExponent)" - } - """ - + { + "kty": "RSA", + "kid": "private", + "use": "sig", + "alg": "PS256", + "n": "\(modulus)", + "e": "\(publicExponent)", + "d": "\(privateExponent)" + } + """ + let keyCollection = try await JWTKeyCollection() .add(jwk: .init(json: publicJWK)) .add(jwk: .init(json: privateJWK)) - + let payload = TestPayload( sub: "vapor", name: "Foo", admin: true, exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) - + let token = try await keyCollection.sign(payload, kid: "private") let verifiedPayload = try await keyCollection.verify(token, as: TestPayload.self) #expect(verifiedPayload == payload) @@ -162,56 +166,56 @@ struct PSSTests { // openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private_key.pem let privateKey = """ - -----BEGIN PRIVATE KEY----- - MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaCfo4dC9OBEl1 - 497MgHOq6OL2Amz7Jcn3xuNZjyPcfzneSVA+2SmNdxx37xrqNf+ej17MpqQSlaiS - 0X9WkD+tFO7VvkiSfPbNcIbMXauoVQioOX5i2yga27Dx9ywyfF7OIjz1ighgvw1T - dLqzDqd3uT19BSX00TpEZA+3rMKoOaEPuHLSDep7IWZhwa8cm3b3aHSlyjSlt6Gh - rJv4u852rDeeT4iIfCsbstUK+Ag4IdlN2Fmuv4jlZ+uamcZJjBZ6C3+ql9ykdhuJ - tTy+qvFstbQm7pYHjJLGt7t5EHkqZBmWXPJOft/8wJf0t7Joj9kbx7wxKjM37Jvm - Q2lZu5KJAgMBAAECggEAQksa+iLenPfxWaBJKb/6h8qUqwWeO3Qm+NEK1WdqKqJC - mGz68SFq5awmf2NTNQsqSOYxCWiKYkkwdIdfAzUvgmDo7Opot0q6uO29xcRmdRqr - kCK2RvtExlJYU7pptgyajKJlk9LlCiYPKSSqmRcscbUyRlTp4fQN3JMnxIfAer8v - dKGYVvZ3FXWcFS7c/ogDlAkatFmy1J7fJpAitH1BALEC2j3uB9+AZ1ILTcGWrj4i - VaLuPto8ySlGsGoq2a5uMMZ9l8+AEIvDSdvPOpe6uzFqLDNDsb02YFm44Q/zrfr1 - Tsg3PIRH0dKi2kMVkUabZg0Ius8j0LtS/4DhgDqFBQKBgQDQlVuBRqnt6H5VKkY7 - jqVvackUBeKeY9tAzErHoLc4UKr32A/gPA341YTQTG8Jx2AmII9HfWv8Bk3E9Txm - YFZRyFNzsup9MVWAJzjmmBelbA+sAnFbpvuHKwEw6Mm3B68g113PNFKZFKtv8Ioi - 5oxLdAj02CA5b7H3rrgcwcGWxwKBgQC9Dl8c8VtPJDv/7+f8RNLERPWyR+oQKywr - xyp4VKRRVOcMt6M7cB0yL1uMl7dhI4lI6ZUGA5z7Tz/pAHdGvAHJ9zqbhGWgAC0f - 9UcKAUyIx2Ja1QMfxjR7rbhBLswezBXChFTBMo57Cl6BdMV2/5Dy0u+X9hknpXlo - QoJwyhP8LwKBgQDOSekmAe0uDjJjqFutq3aSqdzkoK1wWPIPM/0BUkHiwGVWmamZ - 68slvoaMPAvVcAn3q1wJKFIT/2gK0z/ZQI4edDGUy+59wrz88c2kwechA668P+48 - 5vj8xdt3s8NL8Z2SrW1p8CWAoKCtJQh5W+qE9U2mWdoE9CLfAz2zsyzzIQKBgD27 - q6MvzLkTA+SW2hGuB4S/X9tPUEbnUg0Zg+y29tD4AFpOvKZz/ZSdki9eeyrlB7cf - TuIf2+rT/fJ/jHM0gQEKEcEmgmi0pgeBeCj0M6GWOa+fTt3ZQtn/5+Kg/VYxHgne - XC6Z65yRzjpHfxNUcGhaKJJecehYSESbMyzRT6VRAoGBAIcrVYjy0losUQx1jGR7 - XrUn2I12rLjiNmaxub78SJxWRd0TXywSh/CPdX42xs9ku2Rur0Ar+NweEb7YQjZX - c9YrKwe4kFm7wawf9Sy+9ZvkSqNJ6TNgsw9W+0wRfPg20XuIva0GkNjIZOuPIZuh - 6Bk7+8zqXKzCGABDnrK4h9Ss - -----END PRIVATE KEY----- - """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaCfo4dC9OBEl1 + 497MgHOq6OL2Amz7Jcn3xuNZjyPcfzneSVA+2SmNdxx37xrqNf+ej17MpqQSlaiS + 0X9WkD+tFO7VvkiSfPbNcIbMXauoVQioOX5i2yga27Dx9ywyfF7OIjz1ighgvw1T + dLqzDqd3uT19BSX00TpEZA+3rMKoOaEPuHLSDep7IWZhwa8cm3b3aHSlyjSlt6Gh + rJv4u852rDeeT4iIfCsbstUK+Ag4IdlN2Fmuv4jlZ+uamcZJjBZ6C3+ql9ykdhuJ + tTy+qvFstbQm7pYHjJLGt7t5EHkqZBmWXPJOft/8wJf0t7Joj9kbx7wxKjM37Jvm + Q2lZu5KJAgMBAAECggEAQksa+iLenPfxWaBJKb/6h8qUqwWeO3Qm+NEK1WdqKqJC + mGz68SFq5awmf2NTNQsqSOYxCWiKYkkwdIdfAzUvgmDo7Opot0q6uO29xcRmdRqr + kCK2RvtExlJYU7pptgyajKJlk9LlCiYPKSSqmRcscbUyRlTp4fQN3JMnxIfAer8v + dKGYVvZ3FXWcFS7c/ogDlAkatFmy1J7fJpAitH1BALEC2j3uB9+AZ1ILTcGWrj4i + VaLuPto8ySlGsGoq2a5uMMZ9l8+AEIvDSdvPOpe6uzFqLDNDsb02YFm44Q/zrfr1 + Tsg3PIRH0dKi2kMVkUabZg0Ius8j0LtS/4DhgDqFBQKBgQDQlVuBRqnt6H5VKkY7 + jqVvackUBeKeY9tAzErHoLc4UKr32A/gPA341YTQTG8Jx2AmII9HfWv8Bk3E9Txm + YFZRyFNzsup9MVWAJzjmmBelbA+sAnFbpvuHKwEw6Mm3B68g113PNFKZFKtv8Ioi + 5oxLdAj02CA5b7H3rrgcwcGWxwKBgQC9Dl8c8VtPJDv/7+f8RNLERPWyR+oQKywr + xyp4VKRRVOcMt6M7cB0yL1uMl7dhI4lI6ZUGA5z7Tz/pAHdGvAHJ9zqbhGWgAC0f + 9UcKAUyIx2Ja1QMfxjR7rbhBLswezBXChFTBMo57Cl6BdMV2/5Dy0u+X9hknpXlo + QoJwyhP8LwKBgQDOSekmAe0uDjJjqFutq3aSqdzkoK1wWPIPM/0BUkHiwGVWmamZ + 68slvoaMPAvVcAn3q1wJKFIT/2gK0z/ZQI4edDGUy+59wrz88c2kwechA668P+48 + 5vj8xdt3s8NL8Z2SrW1p8CWAoKCtJQh5W+qE9U2mWdoE9CLfAz2zsyzzIQKBgD27 + q6MvzLkTA+SW2hGuB4S/X9tPUEbnUg0Zg+y29tD4AFpOvKZz/ZSdki9eeyrlB7cf + TuIf2+rT/fJ/jHM0gQEKEcEmgmi0pgeBeCj0M6GWOa+fTt3ZQtn/5+Kg/VYxHgne + XC6Z65yRzjpHfxNUcGhaKJJecehYSESbMyzRT6VRAoGBAIcrVYjy0losUQx1jGR7 + XrUn2I12rLjiNmaxub78SJxWRd0TXywSh/CPdX42xs9ku2Rur0Ar+NweEb7YQjZX + c9YrKwe4kFm7wawf9Sy+9ZvkSqNJ6TNgsw9W+0wRfPg20XuIva0GkNjIZOuPIZuh + 6Bk7+8zqXKzCGABDnrK4h9Ss + -----END PRIVATE KEY----- + """ // openssl rsa -pubout -in private_key.pem -out public_key.pem let publicKey = """ - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmgn6OHQvTgRJdePezIBz - quji9gJs+yXJ98bjWY8j3H853klQPtkpjXccd+8a6jX/no9ezKakEpWoktF/VpA/ - rRTu1b5Iknz2zXCGzF2rqFUIqDl+YtsoGtuw8fcsMnxeziI89YoIYL8NU3S6sw6n - d7k9fQUl9NE6RGQPt6zCqDmhD7hy0g3qeyFmYcGvHJt292h0pco0pbehoayb+LvO - dqw3nk+IiHwrG7LVCvgIOCHZTdhZrr+I5WfrmpnGSYwWegt/qpfcpHYbibU8vqrx - bLW0Ju6WB4ySxre7eRB5KmQZllzyTn7f/MCX9LeyaI/ZG8e8MSozN+yb5kNpWbuS - iQIDAQAB - -----END PUBLIC KEY----- - """ + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmgn6OHQvTgRJdePezIBz + quji9gJs+yXJ98bjWY8j3H853klQPtkpjXccd+8a6jX/no9ezKakEpWoktF/VpA/ + rRTu1b5Iknz2zXCGzF2rqFUIqDl+YtsoGtuw8fcsMnxeziI89YoIYL8NU3S6sw6n + d7k9fQUl9NE6RGQPt6zCqDmhD7hy0g3qeyFmYcGvHJt292h0pco0pbehoayb+LvO + dqw3nk+IiHwrG7LVCvgIOCHZTdhZrr+I5WfrmpnGSYwWegt/qpfcpHYbibU8vqrx + bLW0Ju6WB4ySxre7eRB5KmQZllzyTn7f/MCX9LeyaI/ZG8e8MSozN+yb5kNpWbuS + iQIDAQAB + -----END PUBLIC KEY----- + """ let modulus = """ - gWu7yhI35FScdKARYboJoAm-T7yJfJ9JTvAok_RKOJYcL8oLIRSeLqQX83PPZiWdKTdXaiGWntpDu6vW7VAb-HWPF6tNYSLKDSmR3sEu2488ibWijZtNTCKOSb_1iAKAI5BJ80LTqyQtqaKzT0XUBtMsde8vX1nKI05UxujfTX3kqUtkZgLv1Yk1ZDpUoLOWUTtCm68zpjtBrPiN8bU2jqCGFyMyyXys31xFRzz4MyJ5tREHkQCzx0g7AvW0ge_sBTPQ2U6NSkcZvQyDbfDv27cMUHij1Sjx16SY9a2naTuOgamjtUzyClPLVpchX-McNyS0tjdxWY_yRL9MYuw4AQ - """ + gWu7yhI35FScdKARYboJoAm-T7yJfJ9JTvAok_RKOJYcL8oLIRSeLqQX83PPZiWdKTdXaiGWntpDu6vW7VAb-HWPF6tNYSLKDSmR3sEu2488ibWijZtNTCKOSb_1iAKAI5BJ80LTqyQtqaKzT0XUBtMsde8vX1nKI05UxujfTX3kqUtkZgLv1Yk1ZDpUoLOWUTtCm68zpjtBrPiN8bU2jqCGFyMyyXys31xFRzz4MyJ5tREHkQCzx0g7AvW0ge_sBTPQ2U6NSkcZvQyDbfDv27cMUHij1Sjx16SY9a2naTuOgamjtUzyClPLVpchX-McNyS0tjdxWY_yRL9MYuw4AQ + """ let publicExponent = "AQAB" let privateExponent = """ - L4z0tz7QWE0aGuOA32YqCSnrSYKdBTPFDILCdfHonzfP7WMPibz4jWxu_FzNk9s4Dh-uN2lV3NGW10pAsnqffD89LtYanRjaIdHnLW_PFo5fEL2yltK7qMB9hO1JegppKCfoc79W4-dr-4qy1Op0B3npOP-DaUYlNamfDmIbQW32UKeJzdGIn-_ryrBT7hQW6_uHLS2VFPPk0rNkPPKZYoNaqGnJ0eaFFF-dFwiThXIpPz--dxTAL8xYf275rjG8C9lh6awOfJSIdXMVuQITWf62E0mSQPR2-219bShMKriDYcYLbT3BJEgOkRBBHGuHo9R5TN298anxZqV1u5jtUQ - """ + L4z0tz7QWE0aGuOA32YqCSnrSYKdBTPFDILCdfHonzfP7WMPibz4jWxu_FzNk9s4Dh-uN2lV3NGW10pAsnqffD89LtYanRjaIdHnLW_PFo5fEL2yltK7qMB9hO1JegppKCfoc79W4-dr-4qy1Op0B3npOP-DaUYlNamfDmIbQW32UKeJzdGIn-_ryrBT7hQW6_uHLS2VFPPk0rNkPPKZYoNaqGnJ0eaFFF-dFwiThXIpPz--dxTAL8xYf275rjG8C9lh6awOfJSIdXMVuQITWf62E0mSQPR2-219bShMKriDYcYLbT3BJEgOkRBBHGuHo9R5TN298anxZqV1u5jtUQ + """ } diff --git a/Tests/JWTKitTests/RSATests.swift b/Tests/JWTKitTests/RSATests.swift index 08413dad..11dd4125 100644 --- a/Tests/JWTKitTests/RSATests.swift +++ b/Tests/JWTKitTests/RSATests.swift @@ -7,16 +7,14 @@ struct RSATests { @Test("Test RSA docs") func rsaDocs() async throws { await #expect(throws: Never.self) { - try await JWTKeyCollection().add( - rsa: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) + try await JWTKeyCollection().add(rsa: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) } } @Test("Test private key init") func privateKeyInit() async throws { #expect(throws: Never.self) { - try Insecure.RSA.PrivateKey( - modulus: modulus, exponent: publicExponent, privateExponent: privateExponent) + try Insecure.RSA.PrivateKey(modulus: modulus, exponent: publicExponent, privateExponent: privateExponent) } } @@ -55,10 +53,8 @@ struct RSATests { @Test("Test Signing with Private Key") func sign() async throws { - let keyCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, - kid: "private" - ) + let keyCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") let payload = TestPayload( sub: "vapor", @@ -74,9 +70,8 @@ struct RSATests { @Test("Test Signing with Public Key Should Fail") func signWithPublic() async throws { - let keyCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public" - ) + let keyCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -96,10 +91,8 @@ struct RSATests { modulus: modulus, exponent: publicExponent, privateExponent: privateExponent ) - let keyCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), - digestAlgorithm: .sha256, kid: "private" - ) + let keyCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), digestAlgorithm: .sha256, kid: "private") let payload = TestPayload( sub: "vapor", @@ -120,10 +113,8 @@ struct RSATests { prime1: prime1, prime2: prime2 ) - let keyCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), - digestAlgorithm: .sha256, kid: "private" - ) + let keyCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), digestAlgorithm: .sha256, kid: "private") let payload = TestPayload( sub: "vapor", @@ -139,9 +130,7 @@ struct RSATests { @Test("Test get public key primitives") func getPublicKeyPrimitives() async throws { - let publicKey = try Insecure.RSA.PublicKey( - modulus: modulus, exponent: publicExponent - ) + let publicKey = try Insecure.RSA.PublicKey(modulus: modulus, exponent: publicExponent) let (keyModulus, keyExponent) = try publicKey.getKeyPrimitives() #expect(keyModulus == modulus.base64URLDecodedData()) #expect(keyExponent == publicExponent.base64URLDecodedData()) @@ -155,17 +144,13 @@ struct RSATests { admin: true, exp: .init(value: .distantFuture) ) - let signerCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PrivateKey(pem: certPrivateKey), digestAlgorithm: .sha256, - kid: "private" - ) + let signerCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PrivateKey(pem: certPrivateKey), digestAlgorithm: .sha256, kid: "private") let jwt = try await signerCollection.sign(test, kid: "private") - let verifierCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PublicKey(certificatePEM: cert), digestAlgorithm: .sha256, - kid: "cert" - ) + let verifierCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PublicKey(certificatePEM: cert), digestAlgorithm: .sha256, kid: "cert") let payload = try await verifierCollection.verify(jwt, as: TestPayload.self) #expect(payload == test) @@ -174,9 +159,7 @@ struct RSATests { @Test("Test adding a too small key") func addTooSmallKey() async throws { await #expect(throws: (any Error).self) { - try await JWTKeyCollection().add( - rsa: Insecure.RSA.PrivateKey(pem: _512BytesKey), digestAlgorithm: .sha256 - ) + try await JWTKeyCollection().add(rsa: Insecure.RSA.PrivateKey(pem: _512BytesKey), digestAlgorithm: .sha256) } } @@ -191,10 +174,8 @@ struct RSATests { admin: true, exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) - let keyCollection = try await JWTKeyCollection().add( - rsa: Insecure.RSA.PublicKey(pem: publicKey2), digestAlgorithm: .sha256, - kid: "public" - ) + let keyCollection = try await JWTKeyCollection() + .add(rsa: Insecure.RSA.PublicKey(pem: publicKey2), digestAlgorithm: .sha256, kid: "public") let payload = try await keyCollection.verify(token, as: TestPayload.self) #expect(payload == testPayload) @@ -217,18 +198,14 @@ struct RSATests { @Test("Test exporting public key from private key") func exportPublicKeyWhenKeyIsPrivate() async throws { let privateKey = try Insecure.RSA.PrivateKey(pem: privateKey) - let publicKeyFromPrivate = try Insecure.RSA.PublicKey( - pem: privateKey.publicKey.pemRepresentation - ) + let publicKeyFromPrivate = try Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation) let publicKey = try Insecure.RSA.PublicKey(pem: publicKey) #expect(publicKeyFromPrivate == publicKey) } @Test("Test exporting raw built private key as PEM") func exportKeyAsPEMWhenRawBuilt() async throws { - let key = try Insecure.RSA.PrivateKey( - modulus: modulus, exponent: publicExponent, privateExponent: privateExponent - ) + let key = try Insecure.RSA.PrivateKey(modulus: modulus, exponent: publicExponent, privateExponent: privateExponent) let key2 = try Insecure.RSA.PrivateKey(pem: key.pemRepresentation) #expect(key == key2) } diff --git a/Tests/JWTKitTests/Types/LocalePayload.swift b/Tests/JWTKitTests/Types/LocalePayload.swift index 5d6bc22d..1fec3715 100644 --- a/Tests/JWTKitTests/Types/LocalePayload.swift +++ b/Tests/JWTKitTests/Types/LocalePayload.swift @@ -1,5 +1,5 @@ -import JWTKit import Foundation +import JWTKit struct LocalePayload: Codable { var locale: LocaleClaim diff --git a/Tests/JWTKitTests/VendorTokenTests.swift b/Tests/JWTKitTests/VendorTokenTests.swift index dd607ebb..92a7ca1f 100644 --- a/Tests/JWTKitTests/VendorTokenTests.swift +++ b/Tests/JWTKitTests/VendorTokenTests.swift @@ -211,9 +211,7 @@ struct VendorTokenTests { let jwt = try await collection.sign(token) await #expect( - throws: JWTError.claimVerificationFailure( - failedClaim: token.issuer, reason: "Token not provided by Microsoft" - ) + throws: JWTError.claimVerificationFailure(failedClaim: token.issuer, reason: "Token not provided by Microsoft") ) { try await collection.verify(jwt, as: MicrosoftIdentityToken.self) } diff --git a/Tests/JWTKitTests/X5CTests.swift b/Tests/JWTKitTests/X5CTests.swift index 855b8a50..ece30f00 100644 --- a/Tests/JWTKitTests/X5CTests.swift +++ b/Tests/JWTKitTests/X5CTests.swift @@ -65,8 +65,7 @@ struct X5CTests { /// Should fail validation. @Test("Test missing intermediate certificate") func verifyMissingIntermediate() async throws { - await #expect(throws: (any Error).self, "Missing intermediate cert should throw an error.") - { + await #expect(throws: (any Error).self, "Missing intermediate cert should throw an error.") { try await check(token: missingIntermediateToken) } } @@ -106,9 +105,7 @@ struct X5CTests { /// Should fail validation. @Test("Test missing leaf and intermediate certificates") func verifyMissingLeafAndIntermediate() async throws { - await #expect( - throws: (any Error).self, "Missing leaf/intermediate cert should throw an error." - ) { + await #expect(throws: (any Error).self, "Missing leaf/intermediate cert should throw an error.") { try await check(token: missingLeafAndIntermediateToken) } } @@ -118,9 +115,7 @@ struct X5CTests { /// Should fail validation. @Test("Test missing intermediate and root certificates") func verifyMissingIntermediateAndRoot() async throws { - await #expect( - throws: (any Error).self, "Missing intermediate/root cert should throw an error." - ) { + await #expect(throws: (any Error).self, "Missing intermediate/root cert should throw an error.") { try await check(token: missingIntermediateAndRootToken) } } @@ -189,8 +184,7 @@ struct X5CTests { var payload: StoreKitPayload? do { - payload = try await verifier.verifyJWS( - token, as: StoreKitPayload.self, jsonDecoder: jsonDecoder) + payload = try await verifier.verifyJWS(token, as: StoreKitPayload.self, jsonDecoder: jsonDecoder) } catch { Issue.record("Failed with error: \(error.localizedDescription)") } @@ -218,12 +212,11 @@ struct X5CTests { let result = try await verifier.verifyChain( certificates: [leaf, intermediate], policy: { - RFC5280Policy( - validationTime: Date(timeIntervalSince1970: TimeInterval(1_681_312_846))) + RFC5280Policy(validationTime: Date(timeIntervalSince1970: TimeInterval(1_681_312_846))) }) switch result { - case let .couldNotValidate(failures): + case .couldNotValidate(let failures): Issue.record("Failed to validate: \(failures)") case .validCertificate: break @@ -237,8 +230,7 @@ struct X5CTests { let result = try await verifier.verifyChain( certificates: [leaf, intermediate], policy: { - RFC5280Policy( - validationTime: Date(timeIntervalSince1970: TimeInterval(2_280_946_846))) + RFC5280Policy(validationTime: Date(timeIntervalSince1970: TimeInterval(2_280_946_846))) }) switch result { @@ -312,9 +304,7 @@ struct X5CTests { pemLines.append("-----BEGIN CERTIFICATE-----") while encoded.count > 0 { - let prefixIndex = - encoded.index(encoded.startIndex, offsetBy: 64, limitedBy: encoded.endIndex) - ?? encoded.endIndex + let prefixIndex = encoded.index(encoded.startIndex, offsetBy: 64, limitedBy: encoded.endIndex) ?? encoded.endIndex pemLines.append(encoded[..