diff --git a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift index d21824a6..9cdb8117 100644 --- a/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift +++ b/Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift @@ -1,181 +1,60 @@ import _CryptoExtras public extension JWTKeyCollection { - @discardableResult - func addRSA( - key: some RSAKey, - digestAlgorithm: DigestAlgorithm, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - let name = switch digestAlgorithm { - 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) - } - - /// Adds an RS256 key to the collection. + /// Adds an RSA key to the collection. /// - /// This method configures and adds an RS256 (RSA Signature with SHA-256) key to the collection. + /// This method configures and adds an RSA key to the collection. The key is used for signing JWTs /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addRS256(key: myRSAKey) + /// .addRSA(key: myRSAKey) /// ``` /// /// - Parameters: /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. + /// in the JWT `kid` header field. /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. + /// If `nil`, a default encoder is used. /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. + /// If `nil`, a default decoder is used. + /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addRS256( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha256, name: "RS256", padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) - } - - /// Adds an RS384 key to the collection. - /// - /// This method configures and adds an RS384 (RSA Signature with SHA-384) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = try await JWTKeyCollection() - /// .addRS384(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addRS384( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha384, name: "RS384", padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) - } - - /// Adds an RS512 key to the collection. - /// - /// This method configures and adds an RS512 (RSA Signature with SHA-512) key to the collection. - /// - /// Example Usage: - /// ``` - /// let collection = try await JWTKeyCollection() - /// .addRS512(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addRS512( + func addRSA( key: some RSAKey, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha512, name: "RS512", padding: .insecurePKCS1v1_5), - parser: parser, - serializer: serializer - ), - for: kid) - } - - // MARK: PSS + let name = switch digestAlgorithm { + case .sha256: + "RS256" + case .sha384: + "RS384" + case .sha512: + "RS512" + } - /// Adds a PS256 key to the collection. - /// - /// This method configures and adds a PS256 (RSA PSS Signature with SHA-256) key to the collection. PS256 - /// uses RSASSA-PSS with SHA-256 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS256. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addPS256(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addPS256( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha256, name: "PS256", padding: .PSS), + return add(.init( + algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .insecurePKCS1v1_5), parser: parser, serializer: serializer ), for: kid) } - /// Adds a PS384 key to the collection. + /// Adds a PSS key to the collection. /// - /// This method configures and adds a PS256 (RSA PSS Signature with SHA-384) key to the collection. PS384 - /// uses RSASSA-PSS with SHA-384 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS384. + /// This method configures and adds a PSS (RSA PSS Signature) key to the collection. PSS + /// uses RSASSA-PSS for the RSA signature, which is considered more secure than PKCS#1 v1.5 + /// padding used in RSA. /// /// Example Usage: /// ``` /// let collection = await JWTKeyCollection() - /// .addPS384(key: myRSAKey) + /// .addPSS(key: myRSAKey) /// ``` /// /// - Parameters: @@ -188,50 +67,24 @@ public extension JWTKeyCollection { /// If `nil`, a default decoder is used. /// - Returns: The same instance of the collection (`Self`), enabling method chaining. @discardableResult - func addPS384( + func addPSS( key: some RSAKey, + digestAlgorithm: DigestAlgorithm, kid: JWKIdentifier? = nil, parser: some JWTParser = DefaultJWTParser(), serializer: some JWTSerializer = DefaultJWTSerializer() ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha384, name: "PS384", padding: .PSS), - parser: parser, - serializer: serializer - ), - for: kid) - } + let name = switch digestAlgorithm { + case .sha256: + "PS256" + case .sha384: + "PS384" + case .sha512: + "PS512" + } - /// Adds a PS512 key to the collection. - /// - /// This method configures and adds a PS512 (RSA PSS Signature with SHA-512) key to the collection. PS512 - /// uses RSASSA-PSS with SHA-512 for the RSA signature, which is considered more secure than PKCS#1 v1.5 - /// padding used in RS512. - /// - /// Example Usage: - /// ``` - /// let collection = await JWTKeyCollection() - /// .addPS512(key: myRSAKey) - /// ``` - /// - /// - Parameters: - /// - key: The ``RSAKey`` to use for signing. This key should be kept secure and not exposed. - /// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, it will be used to identify this key - /// in the JWT `kid` header field. - /// - jsonEncoder: An optional custom JSON encoder conforming to ``JWTJSONEncoder`` used for encoding JWTs. - /// If `nil`, a default encoder is used. - /// - jsonDecoder: An optional custom JSON decoder conforming to ``JWTJSONDecoder`` used for decoding JWTs. - /// If `nil`, a default decoder is used. - /// - Returns: The same instance of the collection (`Self`), enabling method chaining. - @discardableResult - func addPS512( - key: some RSAKey, - kid: JWKIdentifier? = nil, - parser: some JWTParser = DefaultJWTParser(), - serializer: some JWTSerializer = DefaultJWTSerializer() - ) -> Self { - add(.init( - algorithm: RSASigner(key: key, algorithm: .sha512, name: "PS512", padding: .PSS), + return add(.init( + algorithm: RSASigner(key: key, algorithm: digestAlgorithm, name: name, padding: .PSS), parser: parser, serializer: serializer ), diff --git a/Tests/JWTKitTests/JWTKitTests.swift b/Tests/JWTKitTests/JWTKitTests.swift index 9ff028ba..df7cbfab 100644 --- a/Tests/JWTKitTests/JWTKitTests.swift +++ b/Tests/JWTKitTests/JWTKitTests.swift @@ -264,11 +264,15 @@ class JWTKitTests: XCTestCase { let privateExponent = "awDmF9aqLqokmXjiydda8mKboArWwP2Ih7K3Ad3Og_u9nUp2gZrXiCMxGGSQiN5Jg3yiW_ffNYaHfyfRWKyQ_g31n4UfPLmPtw6iL3V9GChV5ZDRE9HpxE88U8r1h__xFFrrdnBeWKW8NldI70jg7vY6uiRae4uuXCfSbs4iAUxmRVKWCnV7JE6sObQKUV_EJkBcyND5Y97xsmWD0nPmXCnloQ84gF-eTErJoZBvQhJ4BhmBeUlREHmDKssaxVOCK4l335DKHD1vbuPk9e49M71BK7r2y4Atqk3TEetnwzMs3u-L9RqHaGIBw5u324uGweY7QeD7HFdAUtpjOq_MQQ" // sign jwt - let keyCollection = try await JWTKeyCollection().addRS256(key: Insecure.RSA.PrivateKey( - modulus: modulus, - exponent: exponent, - privateExponent: privateExponent - ), kid: "vapor") + let keyCollection = try await JWTKeyCollection().addRSA( + key: Insecure.RSA.PrivateKey( + modulus: modulus, + exponent: exponent, + privateExponent: privateExponent + ), + digestAlgorithm: .sha256, + kid: "vapor" + ) struct Foo: JWTPayload { var bar: Int func verify(using _: JWTAlgorithm) throws {} @@ -301,7 +305,7 @@ class JWTKitTests: XCTestCase { func testFirebaseJWTAndCertificate() async throws { let payload = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PublicKey(certificatePEM: firebaseCert)) + .addRSA(key: Insecure.RSA.PublicKey(certificatePEM: firebaseCert), digestAlgorithm: .sha256) .verify(firebaseJWT, as: FirebasePayload.self) XCTAssertEqual(payload.userID, "y8wiKThXGKM88xxrQWDZzKnBuqv2") } @@ -490,7 +494,7 @@ class JWTKitTests: XCTestCase { func testSigningWithKidInHeader() async throws { let key = ES256PrivateKey() - let keyCollection = try await JWTKeyCollection() + let keyCollection = await JWTKeyCollection() .addECDSA(key: key, kid: "private") .addECDSA(key: key.publicKey, kid: "public") let payload = TestPayload( diff --git a/Tests/JWTKitTests/PSSTests.swift b/Tests/JWTKitTests/PSSTests.swift index 9b9d3c9a..82f7368f 100644 --- a/Tests/JWTKitTests/PSSTests.swift +++ b/Tests/JWTKitTests/PSSTests.swift @@ -4,14 +4,14 @@ import XCTest final class PSSTests: XCTestCase { func testPSSDocs() async throws { await XCTAssertNoThrowAsync( - try await JWTKeyCollection().addPS256(key: Insecure.RSA.PublicKey(pem: publicKey)) + try await JWTKeyCollection().addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256) ) } func testSigning() async throws { let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -26,7 +26,7 @@ final class PSSTests: XCTestCase { func testSigningWithPublic() async throws { let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -52,8 +52,8 @@ final class PSSTests: XCTestCase { } let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") do { let token = try await keyCollection.sign(Payload(foo: "qux"), header: ["kid": "private"]) @@ -92,7 +92,7 @@ final class PSSTests: XCTestCase { let token = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MjAwMDAwMDAwMH0.dCaprjSiEw1w_cS2JzjWlp1mxdF9MV86VMylKiEZf6gM8NZhNo3hgnI3Gg7G_WL_bSzys9Z0QtNpWZeW1Mooa29qDqZolQLKbzyjiIMDFBslz_Hei-tI5318UdFLKIlMT0VyDThwFjyPCiVEvOkKokWSXXGZCHArGXouTWvaTND9C0gOMwSkE8cHU7e0u-_pDEfdv9MRQiGy1Wj-9T_ZN6a0g8yFMQcOU6voo-WSY-m98oylYOifiOighitlD0xNScDnxBH5Qp7yyU81m-s2-xoYVQJhGduvi8mxbo_bU48WIJfmdAYX3aAUh_xpvgcd55bdeMT55G_qnkDBDSLvbQ" let keyCollection = try await JWTKeyCollection() - .addPS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addPSS(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = try await keyCollection.verify(token, as: TestPayload.self) XCTAssertEqual(payload.sub.value, "1234567890") diff --git a/Tests/JWTKitTests/RSATests.swift b/Tests/JWTKitTests/RSATests.swift index d05cdd8f..e524f856 100644 --- a/Tests/JWTKitTests/RSATests.swift +++ b/Tests/JWTKitTests/RSATests.swift @@ -11,7 +11,7 @@ final class RSATests: XCTestCase { } func testRSADocs() async throws { - await XCTAssertNoThrowAsync(try await JWTKeyCollection().addRS256(key: Insecure.RSA.PublicKey(pem: publicKey))) + await XCTAssertNoThrowAsync(try await JWTKeyCollection().addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256)) } func testPrivateKeyInitialization() throws { @@ -38,8 +38,8 @@ final class RSATests: XCTestCase { func testSigning() async throws { let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -54,7 +54,7 @@ final class RSATests: XCTestCase { func testSigningWithPublic() async throws { let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey), kid: "public") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -74,8 +74,8 @@ final class RSATests: XCTestCase { let privateKey = try Insecure.RSA.PrivateKey(modulus: modulus, exponent: publicExponent, privateExponent: privateExponent) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey.pemRepresentation), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: privateKey.publicKey.pemRepresentation), digestAlgorithm: .sha256, kid: "public") let payload = TestPayload( sub: "vapor", @@ -136,8 +136,8 @@ final class RSATests: XCTestCase { exp: .init(value: .distantFuture) ) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: certPrivateKey), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(certificatePEM: cert), kid: "cert") + .addRSA(key: Insecure.RSA.PrivateKey(pem: certPrivateKey), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(certificatePEM: cert), digestAlgorithm: .sha256, kid: "cert") let jwt = try await keyCollection.sign(test, kid: "private") let payload = try await keyCollection.verify(jwt, as: TestPayload.self) @@ -145,7 +145,7 @@ final class RSATests: XCTestCase { } func testKeySizeTooSmall() async throws { - await XCTAssertThrowsErrorAsync(try await JWTKeyCollection().addRS256(key: Insecure.RSA.PrivateKey(pem: _512BytesKey))) + await XCTAssertThrowsErrorAsync(try await JWTKeyCollection().addRSA(key: Insecure.RSA.PrivateKey(pem: _512BytesKey), digestAlgorithm: .sha256)) } func testRS256Verification() async throws { @@ -159,8 +159,8 @@ final class RSATests: XCTestCase { exp: .init(value: .init(timeIntervalSince1970: 2_000_000_000)) ) let keyCollection = try await JWTKeyCollection() - .addRS256(key: Insecure.RSA.PrivateKey(pem: privateKey2), kid: "private") - .addRS256(key: Insecure.RSA.PublicKey(pem: publicKey2), kid: "public") + .addRSA(key: Insecure.RSA.PrivateKey(pem: privateKey2), digestAlgorithm: .sha256, kid: "private") + .addRSA(key: Insecure.RSA.PublicKey(pem: publicKey2), digestAlgorithm: .sha256, kid: "public") let payload = try await keyCollection.verify(token, as: TestPayload.self) XCTAssertEqual(payload, testPayload)