Skip to content

Commit

Permalink
Update RSA key
Browse files Browse the repository at this point in the history
  • Loading branch information
ptoffy committed Apr 15, 2024
1 parent e61abc1 commit 8a55868
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 208 deletions.
219 changes: 36 additions & 183 deletions Sources/JWTKit/RSA/JWTKeyCollection+RSA.swift
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
),
Expand Down
18 changes: 11 additions & 7 deletions Tests/JWTKitTests/JWTKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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(
Expand Down
14 changes: 7 additions & 7 deletions Tests/JWTKitTests/PSSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"])
Expand Down Expand Up @@ -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")
Expand Down
Loading

0 comments on commit 8a55868

Please sign in to comment.