From 09799fc06c0258cd1731498994e5cceedd5f25c7 Mon Sep 17 00:00:00 2001 From: Matthieu Barthelemy Date: Tue, 5 May 2026 18:23:43 +0200 Subject: [PATCH 1/3] Support for RSA JWS --- Sources/JWTKit/X5C/X5CVerifier.swift | 33 ++++++++++++++++++++ Tests/JWTKitTests/X5CTests.swift | 46 ++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/Sources/JWTKit/X5C/X5CVerifier.swift b/Sources/JWTKit/X5C/X5CVerifier.swift index 74b6b6c5..9acae5ee 100644 --- a/Sources/JWTKit/X5C/X5CVerifier.swift +++ b/Sources/JWTKit/X5C/X5CVerifier.swift @@ -221,6 +221,39 @@ extension X5CVerifier { case .eddsa: let eddsaKey = try EdDSA.PublicKey(pem: certificate.publicKey.serializeAsPEM().pemString) return JWTSigner(algorithm: EdDSASigner(key: eddsaKey), parser: parser) + case .rs256, .ps256: + let rsaKey = try Insecure.RSA.PublicKey(pem: certificate.publicKey.serializeAsPEM().pemString) + return JWTSigner( + algorithm: RSASigner( + key: rsaKey, + algorithm: .sha256, + name: "", + padding: alg == .rs256 ? .insecurePKCS1v1_5 : .PSS + ), + parser: parser + ) + case .rs384, .ps384: + let rsaKey = try Insecure.RSA.PublicKey(pem: certificate.publicKey.serializeAsPEM().pemString) + return JWTSigner( + algorithm: RSASigner( + key: rsaKey, + algorithm: .sha384, + name: "", + padding: alg == .rs384 ? .insecurePKCS1v1_5 : .PSS + ), + parser: parser + ) + case .rs512, .ps512: + let rsaKey = try Insecure.RSA.PublicKey(pem: certificate.publicKey.serializeAsPEM().pemString) + return JWTSigner( + algorithm: RSASigner( + key: rsaKey, + algorithm: .sha512, + name: "", + padding: alg == .rs512 ? .insecurePKCS1v1_5 : .PSS + ), + parser: parser + ) default: throw JWTError.unsupportedAlgorithm(alg: alg.rawValue) } diff --git a/Tests/JWTKitTests/X5CTests.swift b/Tests/JWTKitTests/X5CTests.swift index 12e92d49..f9708637 100644 --- a/Tests/JWTKitTests/X5CTests.swift +++ b/Tests/JWTKitTests/X5CTests.swift @@ -278,7 +278,7 @@ struct X5CTests { } } - @Test("Test signing with invalid x5c chain", arguments: [JWK.Algorithm.es256, .es384, .es512]) + @Test("Test signing with invalid x5c chain", arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) func signWithInvalidX5CChain(_ alg: JWK.Algorithm) async throws { let keyCollection = JWTKeyCollection() switch alg { @@ -288,6 +288,21 @@ struct X5CTests { try await keyCollection.add(ecdsa: ES384PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) case .es512: try await keyCollection.add(ecdsa: ES512PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) + case .rs256, .ps256: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha256 + ) + case .rs384, .ps384: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha384 + ) + case .rs512, .ps512: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha512 + ) default: return } @@ -315,7 +330,7 @@ struct X5CTests { } } - @Test("Test signing with ES x5c chain", arguments: [JWK.Algorithm.es256, .es384, .es512]) + @Test("Test signing with ES, RS, PS x5c chains", arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) func signWithESX5CChain(_ alg: JWK.Algorithm) async throws { let keyCollection = JWTKeyCollection() switch alg { @@ -325,6 +340,21 @@ struct X5CTests { try await keyCollection.add(ecdsa: ES384PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) case .es512: try await keyCollection.add(ecdsa: ES512PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) + case .rs256, .ps256: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha256 + ) + case .rs384, .ps384: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha384 + ) + case .rs512, .ps512: + try await keyCollection.add( + rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha512 + ) default: return } @@ -427,6 +457,12 @@ let x5cLeafCertKeys: [JWK.Algorithm: Certificate.PrivateKey] = try! [ .es384: Certificate.PrivateKey(pemEncoded: ES384PrivateKey().pemRepresentation), .es512: Certificate.PrivateKey(pemEncoded: ES512PrivateKey().pemRepresentation), .eddsa: Certificate.PrivateKey(pemEncoded: EdDSA.PrivateKey().pemRepresentation), + .rs256: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), + .ps256: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), + .rs384: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), + .ps384: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), + .rs512: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), + .ps512: Certificate.PrivateKey(pemEncoded: Insecure.RSA.PrivateKey(backing: .init(keySize: .bits2048)).pemRepresentation), ] let x5cCerts: [JWK.Algorithm: [String]] = [ @@ -434,6 +470,12 @@ let x5cCerts: [JWK.Algorithm: [String]] = [ .es384: getChain(alg: .es384), .es512: getChain(alg: .es512), .eddsa: getChain(alg: .eddsa), + .rs256: getChain(alg: .rs256), + .ps256: getChain(alg: .ps256), + .rs384: getChain(alg: .rs384), + .ps384: getChain(alg: .ps384), + .rs512: getChain(alg: .rs512), + .ps512: getChain(alg: .ps512), ] private func getChain(alg: JWK.Algorithm) -> [String] { From 4f3e49ee6729d2b8881a1dba4deb4c7dc5660cdd Mon Sep 17 00:00:00 2001 From: Matthieu Barthelemy Date: Thu, 7 May 2026 16:30:30 +0200 Subject: [PATCH 2/3] Support for RSA JWS --- Tests/JWTKitTests/X5CTests.swift | 44 +++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/Tests/JWTKitTests/X5CTests.swift b/Tests/JWTKitTests/X5CTests.swift index f9708637..e0415386 100644 --- a/Tests/JWTKitTests/X5CTests.swift +++ b/Tests/JWTKitTests/X5CTests.swift @@ -288,21 +288,36 @@ struct X5CTests { try await keyCollection.add(ecdsa: ES384PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) case .es512: try await keyCollection.add(ecdsa: ES512PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) - case .rs256, .ps256: + case .rs256: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha256 ) - case .rs384, .ps384: + case .ps256: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha256 + ) + case .rs384: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha384 ) - case .rs512, .ps512: + case .ps384: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha384 + ) + case .rs512: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha512 ) + case .ps512: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha512 + ) default: return } @@ -331,7 +346,7 @@ struct X5CTests { } @Test("Test signing with ES, RS, PS x5c chains", arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) - func signWithESX5CChain(_ alg: JWK.Algorithm) async throws { + func signWithX5CChain(_ alg: JWK.Algorithm) async throws { let keyCollection = JWTKeyCollection() switch alg { case .es256: @@ -340,21 +355,36 @@ struct X5CTests { try await keyCollection.add(ecdsa: ES384PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) case .es512: try await keyCollection.add(ecdsa: ES512PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString)) - case .rs256, .ps256: + case .rs256: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha256 ) - case .rs384, .ps384: + case .ps256: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha256 + ) + case .rs384: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha384 ) - case .rs512, .ps512: + case .ps384: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha384 + ) + case .rs512: try await keyCollection.add( rsa: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), digestAlgorithm: .sha512 ) + case .ps512: + try await keyCollection.add( + pss: Insecure.RSA.PrivateKey(pem: x5cLeafCertKeys[alg]!.serializeAsPEM().pemString), + digestAlgorithm: .sha512 + ) default: return } From 7e985b2d75991db447ca83a3362dfb0118d48c17 Mon Sep 17 00:00:00 2001 From: Matthieu Barthelemy Date: Thu, 7 May 2026 16:51:55 +0200 Subject: [PATCH 3/3] Swift-format --- Tests/JWTKitTests/X5CTests.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tests/JWTKitTests/X5CTests.swift b/Tests/JWTKitTests/X5CTests.swift index e0415386..2b5e8aed 100644 --- a/Tests/JWTKitTests/X5CTests.swift +++ b/Tests/JWTKitTests/X5CTests.swift @@ -278,7 +278,9 @@ struct X5CTests { } } - @Test("Test signing with invalid x5c chain", arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) + @Test( + "Test signing with invalid x5c chain", + arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) func signWithInvalidX5CChain(_ alg: JWK.Algorithm) async throws { let keyCollection = JWTKeyCollection() switch alg { @@ -345,7 +347,9 @@ struct X5CTests { } } - @Test("Test signing with ES, RS, PS x5c chains", arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) + @Test( + "Test signing with ES, RS, PS x5c chains", + arguments: [JWK.Algorithm.es256, .es384, .es512, .rs256, .ps256, .rs384, .ps384, .rs512, .ps512]) func signWithX5CChain(_ alg: JWK.Algorithm) async throws { let keyCollection = JWTKeyCollection() switch alg {