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

Add JWK key representation #216

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Tests/LinuxMain.swift
.benchmarkBaselines/
Benchmarks/.benchmarkBaselines/
x5c_test_certs
.swift-format
35 changes: 35 additions & 0 deletions Sources/JWTKit/ECDSA/ECDSA+JWKRepresentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Crypto

extension ECDSA.PublicKey: JWKRepresentable {
public func toJWKRepresentation(
keyIdentifier: JWKIdentifier? = nil,
use: JWK.Usage? = nil,
keyOperations: [JWK.KeyOperation]? = nil,
x509URL: String? = nil,
x509CertificateChain: [String]? = nil,
x509SHA1Thumbprint: String? = nil,
x509SHA256Thumbprint: String? = nil
) -> JWK {
let algorithm: JWK.Algorithm =
switch self.curve {
case .p256: .es256
case .p384: .es384
case .p521: .es512
default: fatalError("Unsupported curve")
}
return .init(
keyType: .ecdsa,
algorithm: algorithm,
keyIdentifier: keyIdentifier,
use: use,
keyOperations: keyOperations,
x509URL: x509URL,
x509CertificateChain: x509CertificateChain,
x509CertificateSHA1Thumbprint: x509SHA1Thumbprint,
x509CertificateSHA256Thumbprint: x509SHA256Thumbprint,
x: self.coordinates.x,
y: self.coordinates.y,
curve: .ecdsa(self.curve)
)
}
}
44 changes: 41 additions & 3 deletions Sources/JWTKit/ECDSA/ECDSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ public protocol ECDSAKey: Sendable {
associatedtype Curve: ECDSACurveType

var curve: ECDSACurve { get }

@available(*, deprecated, renamed: "coordinates")
var parameters: ECDSAParameters? { get }
var coordinates: ECDSACoordinates { get }
}

extension ECDSA {
Expand All @@ -23,12 +26,20 @@ extension ECDSA {

public private(set) var curve: ECDSACurve = Curve.curve

@available(*, deprecated, renamed: "coordinates")
public var parameters: ECDSAParameters? {
// 0x04 || x || y
let x = self.backing.x963Representation[Curve.byteRanges.x].base64EncodedString()
let y = self.backing.x963Representation[Curve.byteRanges.y].base64EncodedString()
return (x, y)
}

public var coordinates: ECDSACoordinates {
// 0x04 || x || y
let x = self.backing.x963Representation[Curve.byteRanges.x].base64EncodedString()
let y = self.backing.x963Representation[Curve.byteRanges.y].base64EncodedString()
return (x, y)
}

var backing: PublicKey

Expand Down Expand Up @@ -87,7 +98,7 @@ extension ECDSA {
try self.init(pem: String(decoding: data, as: UTF8.self))
}

/// Initializes a new ``ECDSA.PublicKey` with ECDSA parameters.
/// 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.
Expand All @@ -98,6 +109,7 @@ extension ECDSA {
///
/// - 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.
@available(*, deprecated, renamed: "init(coordinates:)")
public init(parameters: ECDSAParameters) throws {
guard
let x = parameters.x.base64URLDecodedData(),
Expand All @@ -107,13 +119,34 @@ extension ECDSA {
}
self.backing = try PublicKey(x963Representation: [0x04] + x + y)
}

/// Initializes a new ``ECDSA.PublicKey`` with ECDSA coordinates.
///
/// - Parameters:
/// - parameters: The ``ECDSACoordinates`` 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 ``ECDSACoordinates`` 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(coordinates: ECDSACoordinates) throws {
guard
let x = coordinates.x.base64URLDecodedData(),
let y = coordinates.y.base64URLDecodedData()
else {
throw JWTError.generic(identifier: "ecCoordinates", reason: "Unable to interpret x or y as base64 encoded data")
}
self.backing = try PublicKey(x963Representation: [0x04] + x + y)
}

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
lhs.coordinates.x == rhs.coordinates.x && lhs.coordinates.y == rhs.coordinates.y
}
}
}
Expand All @@ -125,9 +158,14 @@ extension ECDSA {

public private(set) var curve: ECDSACurve = Curve.curve

@available(*, deprecated, renamed: "coordinates")
public var parameters: ECDSAParameters? {
self.publicKey.parameters
}

public var coordinates: ECDSACoordinates {
self.publicKey.coordinates
}

var backing: PrivateKey

Expand Down Expand Up @@ -196,7 +234,7 @@ extension ECDSA {
}

public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.parameters?.x == rhs.parameters?.x && lhs.parameters?.y == rhs.parameters?.y
lhs.coordinates.x == rhs.coordinates.x && lhs.coordinates.y == rhs.coordinates.y
}
}
}
5 changes: 4 additions & 1 deletion Sources/JWTKit/ECDSA/ECDSAKeyTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import X509
import Foundation
#endif

@available(*, deprecated, renamed: "ECDSACoordinates")
public typealias ECDSAParameters = (x: String, y: String)

/// A typealias representing the parameters of an ECDSA (Elliptic Curve Digital Signature Algorithm) key.
///
/// This tuple consists of two strings representing the x and y coordinates on the elliptic curve.
Expand All @@ -19,7 +22,7 @@ import X509
/// - Parameters:
/// - x: A `String` representing the x-coordinate on the elliptic curve.
/// - y: A `String` representing the y-coordinate on the elliptic curve.
public typealias ECDSAParameters = (x: String, y: String)
public typealias ECDSACoordinates = (x: String, y: String)

public protocol ECDSASignature: Sendable {
var rawRepresentation: Data { get set }
Expand Down
27 changes: 27 additions & 0 deletions Sources/JWTKit/EdDSA/EdDSA+JWKRepresentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Crypto

extension EdDSA.PublicKey: JWKRepresentable {
public func toJWKRepresentation(
keyIdentifier: JWKIdentifier? = nil,
use: JWK.Usage? = nil,
keyOperations: [JWK.KeyOperation]? = nil,
x509URL: String? = nil,
x509CertificateChain: [String]? = nil,
x509SHA1Thumbprint: String? = nil,
x509SHA256Thumbprint: String? = nil
) -> JWK {
.init(
keyType: .octetKeyPair,
algorithm: .eddsa,
keyIdentifier: keyIdentifier,
use: use,
keyOperations: keyOperations,
x509URL: x509URL,
x509CertificateChain: x509CertificateChain,
x509CertificateSHA1Thumbprint: x509SHA1Thumbprint,
x509CertificateSHA256Thumbprint: x509SHA256Thumbprint,
x: self.rawRepresentation.base64EncodedString(),
curve: .eddsa(self.curve)
)
}
}
114 changes: 114 additions & 0 deletions Sources/JWTKit/JWK/JWK+Parameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
extension JWK {
/// Supported `kty` key types.
public struct KeyType: Codable, RawRepresentable, Equatable, Sendable {
enum Backing: String, Codable {
case rsa = "RSA"
case ecdsa = "EC"
case octetKeyPair = "OKP"
}

let backing: Backing

public var rawValue: String { self.backing.rawValue }

public static let rsa = Self(backing: .rsa)
public static let ecdsa = Self(backing: .ecdsa)
public static let octetKeyPair = Self(backing: .octetKeyPair)

init(backing: Backing) {
self.backing = backing
}

public init?(rawValue: String) {
guard let backing = Backing(rawValue: rawValue) else {
return nil
}
self.init(backing: backing)
}
}
}

extension JWK {
/// Intended use of the public key.
/// https://datatracker.ietf.org/doc/html/rfc7517#section-4.2
public enum Usage: String, Codable, Sendable {
case signature
case encryption

enum CodingKeys: String, CodingKey {
case signature = "sig"
case encryption = "enc"
}
}
}

extension JWK {
/// Operations that the key is intended to be used for.
/// https://datatracker.ietf.org/doc/html/rfc7517#section-4.3
public enum KeyOperation: String, Codable, Sendable {
case sign
case verify
case encrypt
case decrypt
case wrapKey
case unwrapKey
case deriveKey
case deriveBits
}
}

extension JWK {
/// The cryptographic algorithm family used with the key.
/// https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
public struct Algorithm: Codable, RawRepresentable, Equatable, Sendable {
enum Backing: String, Codable {
case rs256 = "RS256"
case rs384 = "RS384"
case rs512 = "RS512"
case ps256 = "PS256"
case ps384 = "PS384"
case ps512 = "PS512"
case es256 = "ES256"
case es384 = "ES384"
case es512 = "ES512"
case eddsa = "EdDSA"
}

let backing: Backing

public var rawValue: String { self.backing.rawValue }

/// RSA with SHA256
public static let rs256 = Self(backing: .rs256)
/// RSA with SHA384
public static let rs384 = Self(backing: .rs384)
/// RSA with SHA512
public static let rs512 = Self(backing: .rs512)
/// RSA-PSS with SHA256
public static let ps256 = Self(backing: .ps256)
/// RSA-PSS with SHA384
public static let ps384 = Self(backing: .ps384)
/// RSA-PSS with SHA512
public static let ps512 = Self(backing: .ps512)
/// EC with SHA256
public static let es256 = Self(backing: .es256)
/// EC with SHA384
public static let es384 = Self(backing: .es384)
/// EC with SHA512
public static let es512 = Self(backing: .es512)
/// EdDSA
public static let eddsa = Self(backing: .eddsa)

init(backing: Backing) {
self.backing = backing
}

public init?(rawValue: String) {
guard let backing = Backing(rawValue: rawValue) else {
return nil
}
self.init(backing: backing)
}
}
}

Loading
Loading