Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 4 to Version 5 Migration Deprecations #221

Open
wants to merge 1 commit into
base: v4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions Sources/JWTKit/ECDSA/ECDSAKey.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
@_implementationOnly import CJWTKitBoringSSL
import Crypto

public final class ECDSAKey: OpenSSLKey {

Expand All @@ -11,6 +12,7 @@ public final class ECDSAKey: OpenSSLKey {
case ed448 = "Ed448"
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PrivateKey(), ES384PrivateKey(), or ES512PrivateKey() instead.")
public static func generate(curve: Curve = .p521) throws -> ECDSAKey {
guard let c = CJWTKitBoringSSL_EC_KEY_new_by_curve_name(curve.cName) else {
throw JWTError.signingAlgorithmFailure(ECDSAError.newKeyByCurveFailure)
Expand All @@ -34,6 +36,7 @@ public final class ECDSAKey: OpenSSLKey {
///
/// - parameters:
/// - pem: Contents of pem file.
@available(*, deprecated, message: "Unavailable in v5. Please use ES256PublicKey(certificate:), ES384PublicKey(certificate:), or ES512PublicKey(certificate:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func certificate(pem string: String) throws -> ECDSAKey {
try self.certificate(pem: [UInt8](string.utf8))
}
Expand All @@ -52,6 +55,7 @@ public final class ECDSAKey: OpenSSLKey {
///
/// - parameters:
/// - pem: Contents of pem file.
@available(*, deprecated, message: "Unavailable in v5. Please use ES256PublicKey(certificate:), ES384PublicKey(certificate:), or ES512PublicKey(certificate:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func certificate<Data>(pem data: Data) throws -> ECDSAKey
where Data: DataProtocol
{
Expand All @@ -68,10 +72,12 @@ public final class ECDSAKey: OpenSSLKey {
return self.init(c)
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PublicKey(pem:), ES384PublicKey(pem:), or ES512PublicKey(pem:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func `public`(pem string: String) throws -> ECDSAKey {
try .public(pem: [UInt8](string.utf8))
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PublicKey(pem:), ES384PublicKey(pem:), or ES512PublicKey(pem:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func `public`<Data>(pem data: Data) throws -> ECDSAKey
where Data: DataProtocol
{
Expand All @@ -81,10 +87,12 @@ public final class ECDSAKey: OpenSSLKey {
return self.init(c)
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PrivateKey(pem:), ES384PrivateKey(pem:), or ES512PrivateKey(pem:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func `private`(pem string: String) throws -> ECDSAKey {
try .private(pem: [UInt8](string.utf8))
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PrivateKey(pem:), ES384PrivateKey(pem:), or ES512PrivateKey(pem:) instead. Note that more interfaces for importing keys is available once you update fully to v5.")
public static func `private`<Data>(pem data: Data) throws -> ECDSAKey
where Data: DataProtocol
{
Expand All @@ -100,6 +108,7 @@ public final class ECDSAKey: OpenSSLKey {
self.c = c
}

@available(*, deprecated, message: "Unavailable in v5. Please use ES256PublicKey(parameters:), ES384PublicKey(parameters:), or ES512PublicKey(parameters:) instead. Note that more interfaces for importing private keys is available once you update fully to v5.")
public convenience init(parameters: Parameters, curve: Curve = .p521, privateKey: String? = nil) throws {
guard let c = CJWTKitBoringSSL_EC_KEY_new_by_curve_name(curve.cName) else {
throw JWTError.signingAlgorithmFailure(ECDSAError.newKeyByCurveFailure)
Expand Down Expand Up @@ -190,3 +199,218 @@ extension ECDSAKey.Curve {
}
}
}

#if compiler(>=6)
public protocol ECDSACurveType: Sendable {
static var curve: ECDSAKey.Curve { get }
}

extension P256: ECDSACurveType, @unchecked @retroactive Sendable {
static public var curve: ECDSAKey.Curve { .p256 }
}

extension P384: ECDSACurveType, @unchecked @retroactive Sendable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will need versions for Swift 5 and Swift 6

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it wouldn’t, because this version of the library would always be compiled with the Swift 5 language mode. Once v5 is used, then none of this is included.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nvm, forgot retroactive didn’t exist pre swift 6 compiler, will update

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm now wondering if its worth adding Sendable conformances at all, since the rest of the library didn't support it yet at all

static public var curve: ECDSAKey.Curve { .p384 }
}

extension P521: ECDSACurveType, @unchecked @retroactive Sendable {
static public var curve: ECDSAKey.Curve { .p521 }
}
#else
public protocol ECDSACurveType {
static var curve: ECDSAKey.Curve { get }
}

extension P256: ECDSACurveType {
static public var curve: ECDSAKey.Curve { .p256 }
}

extension P384: ECDSACurveType {
static public var curve: ECDSAKey.Curve { .p384 }
}

extension P521: ECDSACurveType {
static public var curve: ECDSAKey.Curve { .p521 }
}
#endif

public typealias ES256PublicKey = ECDSA.PublicKey<P256>
public typealias ES256PrivateKey = ECDSA.PrivateKey<P256>

public typealias ES384PublicKey = ECDSA.PublicKey<P384>
public typealias ES384PrivateKey = ECDSA.PrivateKey<P384>

public typealias ES512PublicKey = ECDSA.PublicKey<P521>
public typealias ES512PrivateKey = ECDSA.PrivateKey<P521>

public enum ECDSA {
/// ECDSA.PublicKey was introduced in v5 and replaces ``ECDSAKey``.
///
/// - Note: Please migrate over to ``ECDSA/PublicKey`` before updating to v5, though if you plan on remaining on v4, ``ECDSAKey`` can continue to be used.
public struct PublicKey<Curve: ECDSACurveType> {
let key: ECDSAKey
init(key: ECDSAKey) { self.key = key }

public var curve: ECDSAKey.Curve? { key.curve }
public var parameters: ECDSAKey.Parameters? { key.parameters }

/// Creates an ``ECDSA.PublicKey`` instance from a PEM encoded certificate string.
///
/// - Parameter pem: The PEM encoded certificate string.
/// - Throws: If there is a problem parsing the certificate or deriving the public key.
/// - Returns: A new ``ECDSAKey`` instance with the public key from the certificate.
public init(certificate pem: String) throws {
key = try ECDSAKey.certificate(pem: pem)
}

/// Creates an ``ECDSA.PublicKey`` instance from a PEM encoded certificate data.
///
/// - Parameter pem: The PEM encoded certificate data.
/// - Throws: If there is a problem parsing the certificate or deriving the public key.
/// - Returns: A new ``ECDSA.PublicKey`` instance with the public key from the certificate.
public init<Data: DataProtocol>(certificate pem: Data) throws {
key = try ECDSAKey.certificate(pem: pem)
}

/// Creates an ``ECDSA.PublicKey`` instance from a PEM encoded public key string.
///
/// - Parameter pem: The PEM encoded public key string.
/// - Throws: If there is a problem parsing the public key.
/// - Returns: A new ``ECDSA.PublicKey`` instance with the public key from the certificate.
public init(pem string: String) throws {
key = try ECDSAKey.public(pem: string)
}

/// Creates an ``ECDSA.PublicKey`` instance from a PEM encoded public key data.
///
/// - Parameter pem: The PEM encoded public key data.
/// - Throws: If there is a problem parsing the public key.
/// - Returns: A new ``ECDSA.PublicKey`` instance with the public key from the certificate.
public init<Data: DataProtocol>(pem data: Data) throws {
key = try ECDSAKey.public(pem: data)
}

/// Initializes a new ``ECDSA.PublicKey` with ECDSA parameters.
///
/// - Parameters:
/// - parameters: The ``ECDSAParameters`` tuple containing the x and y coordinates of the public key. These coordinates should be base64 URL encoded strings.
///
/// - Throws:
/// - ``JWTError/generic`` with the identifier `ecCoordinates` if the x and y coordinates from `parameters` cannot be interpreted as base64 encoded data.
/// - ``JWTError/generic`` with the identifier `ecPrivateKey` if the provided `privateKey` is non-nil but cannot be interpreted as a valid `PrivateKey`.
///
/// - Note:
/// The ``ECDSAParameters`` tuple is assumed to have x and y properties that are base64 URL encoded strings representing the respective coordinates of an ECDSA public key.
public init(parameters: ECDSAKey.Parameters) throws {
key = try ECDSAKey(parameters: parameters, curve: Curve.curve, privateKey: nil)
}
}

/// ECDSA.PrivateKey was introduced in v5 and replaces ``ECDSAKey``.
///
/// - Note: Please migrate over to ``ECDSA/PrivateKey`` before updating to v5, though if you plan on remaining on v4, ``ECDSAKey`` can continue to be used.
public struct PrivateKey<Curve: ECDSACurveType> {
let key: ECDSAKey
init(key: ECDSAKey) { self.key = key }

public var curve: ECDSAKey.Curve? { key.curve }
public var parameters: ECDSAKey.Parameters? { key.parameters }

/// Creates an ``ECDSA.PrivateKey`` instance from a PEM encoded private key string.
///
/// - Parameter pem: The PEM encoded private key string.
/// - Throws: If there is a problem parsing the private key.
/// - Returns: A new ``ECDSA.PrivateKey`` instance with the private key.
public init(pem string: String) throws {
key = try ECDSAKey.public(pem: string)
}

/// Creates an ``ECDSA.PrivateKey`` instance from a PEM encoded private key data.
///
/// - Parameter pem: The PEM encoded private key data.
/// - Throws: If there is a problem parsing the private key.
/// - Returns: A new ``ECDSA.PrivateKey`` instance with the private key.
public init<Data: DataProtocol>(pem data: Data) throws {
key = try ECDSAKey.public(pem: data)
}

/// Generates a new ECDSA key.
///
/// - Returns: A new ``ECDSA.PrivateKey`` instance with the generated key.
public init() {
key = try! ECDSAKey.generate(curve: Curve.curve)
}
}
}

extension ECDSA.PublicKey<P256> {
public init(backing: Curve.Signing.PublicKey) throws {
let representation = backing.rawRepresentation
try self.init(parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
))
}
}

extension ECDSA.PublicKey<P384> {
public init(backing: Curve.Signing.PublicKey) throws {
let representation = backing.rawRepresentation
try self.init(parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
))
}
}

extension ECDSA.PublicKey where Curve == P521 {
public init(backing: Curve.Signing.PublicKey) throws {
let representation = backing.rawRepresentation
try self.init(parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
))
}
}

extension ECDSA.PrivateKey<P256> {
public init(backing: Curve.Signing.PrivateKey) throws {
let representation = backing.publicKey.rawRepresentation
try self.init(key: ECDSAKey(
parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
),
curve: Curve.curve,
privateKey: backing.rawRepresentation.base64URLEncodedString()
))
}
}

extension ECDSA.PrivateKey<P384> {
public init(backing: Curve.Signing.PrivateKey) throws {
let representation = backing.publicKey.rawRepresentation
try self.init(key: ECDSAKey(
parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
),
curve: Curve.curve,
privateKey: backing.rawRepresentation.base64URLEncodedString()
))
}
}

extension ECDSA.PrivateKey<P521> {
public init(backing: Curve.Signing.PrivateKey) throws {
let representation = backing.publicKey.rawRepresentation
try self.init(key: ECDSAKey(
parameters: ECDSAKey.Parameters(
x: representation.prefix(representation.count/2).base64URLEncodedString(),
y: representation.suffix(representation.count/2).base64URLEncodedString()
),
curve: Curve.curve,
privateKey: backing.rawRepresentation.base64URLEncodedString()
))
}
}
70 changes: 70 additions & 0 deletions Sources/JWTKit/ECDSA/JWTSigner+ECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import class Foundation.JSONEncoder
import class Foundation.JSONDecoder

extension JWTSigner {
@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es256(key: ECDSAKey) -> JWTSigner { .es256(key: key, jsonEncoder: nil, jsonDecoder: nil) }

@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es256(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
Expand All @@ -13,8 +15,10 @@ extension JWTSigner {
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es384(key: ECDSAKey) -> JWTSigner { .es384(key: key, jsonEncoder: nil, jsonDecoder: nil) }

@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es384(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
Expand All @@ -23,8 +27,10 @@ extension JWTSigner {
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}

@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es512(key: ECDSAKey) -> JWTSigner { .es512(key: key, jsonEncoder: nil, jsonDecoder: nil) }

@available(*, deprecated, message: "Unavailable in v5. Please use JWTKeyCollection.add(ecdsa:kid:) instead.")
public static func es512(key: ECDSAKey, jsonEncoder: (any JWTJSONEncoder)?, jsonDecoder: (any JWTJSONDecoder)?) -> JWTSigner {
.init(algorithm: ECDSASigner(
key: key,
Expand All @@ -33,3 +39,67 @@ extension JWTSigner {
), jsonEncoder: jsonEncoder, jsonDecoder: jsonDecoder)
}
}

extension JWTKeyCollection {
/// Adds an ECDSA key to the collection.
///
/// Example Usage:
/// ```
/// let collection = await JWTKeyCollection()
/// .addECDSA(key: myECDSAKey)
/// ```
///
/// - Parameters:
/// - key: The ``ECDSAKey`` to be used for signing. This key should be securely stored and not exposed.
/// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, this identifier will be used in the JWT `kid`
/// header field to identify the key.
/// - Returns: The same instance of the collection (`Self`), which allows for method chaining.
@discardableResult
public func add<T>(
ecdsa key: ECDSA.PublicKey<T>,
kid: JWKIdentifier? = nil
) -> Self {
switch key.curve {
case .p256:
try signers.use(.es256(key: key.key), kid: kid)
case .p384:
try signers.use(.es384(key: key.key), kid: kid)
case .p521:
try signers.use(.es512(key: key.key), kid: kid)
case .ed25519, .ed448, .none:
fatalError("Unsupported ECDSA key curve: \(key.curve?.rawValue ?? ".none")")
}
return self
}

/// Adds an ECDSA key to the collection.
///
/// Example Usage:
/// ```
/// let collection = await JWTKeyCollection()
/// .addECDSA(key: myECDSAKey)
/// ```
///
/// - Parameters:
/// - key: The ``ECDSAKey`` to be used for signing. This key should be securely stored and not exposed.
/// - kid: An optional ``JWKIdentifier`` (Key ID). If provided, this identifier will be used in the JWT `kid`
/// header field to identify the key.
/// - Returns: The same instance of the collection (`Self`), which allows for method chaining.
@discardableResult
public func add<T>(
ecdsa key: ECDSA.PrivateKey<T>,
kid: JWKIdentifier? = nil
) -> Self {
switch key.curve {
case .p256:
try signers.use(.es256(key: key.key), kid: kid)
case .p384:
try signers.use(.es384(key: key.key), kid: kid)
case .p521:
try signers.use(.es512(key: key.key), kid: kid)
case .ed25519, .ed448, .none:
fatalError("Unsupported ECDSA key curve: \(key.curve?.rawValue ?? ".none")")
}
return self
}
}
7 changes: 7 additions & 0 deletions Sources/JWTKit/JWTParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ struct JWTParser {
.decode(Payload.self, from: .init(self.encodedPayload.base64URLDecodedBytes()))
}

func payload<Payload>(as payload: Payload.Type, jsonDecoder: any JWTJSONDecoder) throws -> Payload
where Payload: AsyncJWTPayload
{
try jsonDecoder
.decode(Payload.self, from: .init(self.encodedPayload.base64URLDecodedBytes()))
}
Comment on lines +33 to +38
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the above variant to support the new protocol.


func verify(using signer: JWTSigner) throws {
guard try signer.algorithm.verify(self.signature, signs: self.message) else {
throw JWTError.signatureVerifictionFailed
Expand Down
Loading
Loading