Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
InMemoryKeyManager implementation (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
amika-sq authored Jan 3, 2024
1 parent 68fd968 commit 2721361
Show file tree
Hide file tree
Showing 12 changed files with 483 additions and 270 deletions.
94 changes: 93 additions & 1 deletion Sources/tbDEX/crypto/Crypto.swift
Original file line number Diff line number Diff line change
@@ -1 +1,93 @@
public enum Crypto {}
import Foundation

enum CryptoError: Error {
case illegalArgument(description: String)
}

enum Crypto {

/// Generates a private key using the specified algorithm and curve, utilizing the appropriate `KeyGenerator`.
/// - Parameters:
/// - algorithm: The JWA algorithm identifier.
/// - curve: The elliptic curve. Null for algorithms that do not use elliptic curves.
/// - Returns: The generated private key as a JWK object.
static func generatePrivateKey(algorithm: Jwk.Algorithm, curve: Jwk.Curve? = nil) throws -> Jwk {
let keyGenerator = try getKeyGenerator(algorithm: algorithm, curve: curve)
return try keyGenerator.generatePrivateKey()
}

/// Computes a public key from the given private key, utilizing relevant `KeyGenerator`.
/// - Parameter privateKey: The private key used to compute the public key.
/// - Returns: The computed public key as a JWK object.
static func computePublicKey(privateKey: Jwk) throws -> Jwk {
let keyGenerator = try getKeyGenerator(algorithm: privateKey.algorithm, curve: privateKey.curve)
return try keyGenerator.computePublicKey(privateKey: privateKey)
}

/// Signs a payload using a private key.
/// - Parameters:
/// - privateKey: The JWK private key to be used for generating the signature.
/// - payload: The data to be signed.
/// - Returns: The digital signature as a byte array.
static func sign<D>(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol {
let signer = try getSigner(algorithm: privateKey.algorithm, curve: privateKey.curve)
return try signer.sign(privateKey: privateKey, payload: payload)
}

/// Verifies a signature against a signed payload using a public key.
///
/// - Parameters:
/// - publicKey: The JWK public key to be used for verifying the signature.
/// - signature: The signature that will be verified.
/// - signedPayload: The data that was signed.
/// - algorithm: The algorithm used for signing/verification, only used if not provided in the JWK.
/// - Returns: Boolean indicating if the publicKey and signature are valid for the given payload.
static func verify<S, D>(
publicKey: Jwk,
signature: S,
signedPayload: D,
algorithm: Jwk.Algorithm? = nil
) throws -> Bool where S: DataProtocol, D: DataProtocol {
let algorithm = publicKey.algorithm ?? algorithm
let verifier = try getVerifier(algorithm: algorithm, curve: publicKey.curve)
return try verifier.verify(publicKey: publicKey, signature: signature, signedPayload: signedPayload)
}

/// Converts a `Jwk` public key into its byte array representation.
/// - Parameter publicKey: `Jwk` object representing the public key to be converted.
/// - Returns: Data representing the byte-level information of the provided public key
static func publicKeyToBytes(publicKey: Jwk) throws -> Data {
let keyGenerator = try getKeyGenerator(algorithm: publicKey.algorithm, curve: publicKey.curve)
return try keyGenerator.publicKeyToBytes(publicKey)
}

// MARK: Private

/// Retrieves a `KeyGenerator` based on the provided algorithm and curve.
/// - Parameters:
/// - algorithm: The cryptographic algorithm to find a key generator for.
/// - curve: The cryptographic curve to find a key generator for.
/// - Returns: The corresponding `KeyGenerator`.
private static func getKeyGenerator(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> KeyGenerator {
switch (algorithm, curve) {
case (nil, .secp256k1),
(Secp256k1.shared.algorithm, nil),
(Secp256k1.shared.algorithm, .secp256k1):
return Secp256k1.shared
case (Ed25519.shared.algorithm, .ed25519):
return Ed25519.shared
default:
throw CryptoError.illegalArgument(
description: "Algorithm \(algorithm?.rawValue ?? "nil") not supported"
)
}
}

private static func getSigner(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> Signer {
return try getKeyGenerator(algorithm: algorithm, curve: curve) as! Signer
}

private static func getVerifier(algorithm: Jwk.Algorithm?, curve: Jwk.Curve? = nil) throws -> Signer {
return try getSigner(algorithm: algorithm, curve: curve)
}
}
129 changes: 74 additions & 55 deletions Sources/tbDEX/crypto/Ed25519.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,41 @@ import Foundation
///
/// This class uses Apple's CryptoKit, specifically `Curve25519.Signing`, for it's cryptographic operations:
/// https://developer.apple.com/documentation/cryptokit/curve25519/signing
public enum Ed25519: KeyGenerator, Signer {
class Ed25519 {

static let keyType: KeyType = .ed25519
/// Shared static instance
static let shared = Ed25519()

// MARK: - Public Functions
/// Private initializer to prevent instantiation
private init() {}
}

enum Ed25519Error: Error {
/// The privateJwk provided did not have the appropriate parameters set on it
case invalidPrivateJwk
/// The publicJwk provided did not have the appropriate parameters set on it
case invalidPublicJwk
}

// MARK: - KeyGenerator

extension Ed25519: KeyGenerator {

var algorithm: Jwk.Algorithm {
.eddsa
}

var keyType: Jwk.KeyType {
.octetKeyPair
}

/// Generates an Ed25519 private key in JSON Web Key (JWK) format.
public static func generatePrivateKey() throws -> Jwk {
func generatePrivateKey() throws -> Jwk {
return try generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey())
}

/// Derives the public key in JSON Web Key (JWK) format from a given Ed25519 private key in JWK format.
public static func computePublicKey(privateKey: Jwk) throws -> Jwk {
func computePublicKey(privateKey: Jwk) throws -> Jwk {
guard let d = privateKey.d else {
throw Ed25519Error.invalidPrivateJwk
}
Expand All @@ -27,38 +49,69 @@ public enum Ed25519: KeyGenerator, Signer {
return try generatePublicJwk(publicKey: privateKey.publicKey)
}

/// Converts a private key from JSON Web Key (JWK) format to a raw bytes.
func privateKeyToBytes(_ privateKey: Jwk) throws -> Data {
guard let d = privateKey.d else {
throw Ed25519Error.invalidPrivateJwk
}

return try d.decodeBase64Url()
}

/// Converts a public key from JSON Web Key (JWK) format to a raw bytes.
func publicKeyToBytes(_ publicKey: Jwk) throws -> Data {
guard let x = publicKey.x else {
throw Ed25519Error.invalidPublicJwk
}

return try x.decodeBase64Url()
}

/// Converts raw private key in bytes to its corresponding JSON Web Key (JWK) format.
public static func bytesToPrivateKey(_ bytes: Data) throws -> Jwk {
func bytesToPrivateKey(_ bytes: Data) throws -> Jwk {
return try generatePrivateJwk(
privateKey: try Curve25519.Signing.PrivateKey(rawRepresentation: bytes)
)
}

/// Converts a raw public key in bytes to its corresponding JSON Web Key (JWK) format.
public static func bytesToPublicKey(_ bytes: Data) throws -> Jwk {
func bytesToPublicKey(_ bytes: Data) throws -> Jwk {
return try generatePublicJwk(
publicKey: try Curve25519.Signing.PublicKey(rawRepresentation: bytes)
)
}

/// Converts a private key from JSON Web Key (JWK) format to a raw bytes.
public static func privateKeyToBytes(_ privateKey: Jwk) throws -> Data {
guard let d = privateKey.d else {
throw Ed25519Error.invalidPrivateJwk
}
// MARK: Private Functions

return try d.decodeBase64Url()
private func generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey) throws -> Jwk {
var jwk = Jwk(
keyType: .octetKeyPair,
curve: .ed25519,
d: privateKey.rawRepresentation.base64UrlEncodedString(),
x: privateKey.publicKey.rawRepresentation.base64UrlEncodedString()
)

jwk.keyIdentifier = try jwk.thumbprint()

return jwk
}

/// Converts a public key from JSON Web Key (JWK) format to a raw bytes.
public static func publicKeyToBytes(_ publicKey: Jwk) throws -> Data {
guard let x = publicKey.x else {
throw Ed25519Error.invalidPublicJwk
}
private func generatePublicJwk(publicKey: Curve25519.Signing.PublicKey) throws -> Jwk {
var jwk = Jwk(
keyType: .octetKeyPair,
curve: .ed25519,
x: publicKey.rawRepresentation.base64UrlEncodedString()
)

return try x.decodeBase64Url()
jwk.keyIdentifier = try jwk.thumbprint()

return jwk
}
}

// MARK: - Signer

extension Ed25519: Signer {
/// Generates an RFC8032-compliant EdDSA signature of given data using an Ed25519 private key in JSON Web Key
/// (JWK) format.
///
Expand All @@ -69,7 +122,7 @@ public enum Ed25519: KeyGenerator, Signer {
/// See
/// [Apple's documentation](https://developer.apple.com/documentation/cryptokit/curve25519/signing/privatekey/signature(for:))
/// for more information
public static func sign<D>(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol {
func sign<D>(privateKey: Jwk, payload: D) throws -> Data where D: DataProtocol {
guard let d = privateKey.d else {
throw Ed25519Error.invalidPrivateJwk
}
Expand All @@ -80,7 +133,7 @@ public enum Ed25519: KeyGenerator, Signer {

/// Verifies an RFC8032-compliant EdDSA signature against given data using an Ed25519 public key in JSON Web Key
/// (JWK) format.
public static func verify<S, D>(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool
func verify<S, D>(publicKey: Jwk, signature: S, signedPayload: D) throws -> Bool
where S: DataProtocol, D: DataProtocol {
guard let x = publicKey.x else {
throw Ed25519Error.invalidPublicJwk
Expand All @@ -89,38 +142,4 @@ public enum Ed25519: KeyGenerator, Signer {
let publicKey = try Curve25519.Signing.PublicKey(rawRepresentation: try x.decodeBase64Url())
return publicKey.isValidSignature(signature, for: signedPayload)
}

// MARK: - Private Functions

private static func generatePrivateJwk(privateKey: Curve25519.Signing.PrivateKey) throws -> Jwk {
var jwk = Jwk(
keyType: .octetKeyPair,
curve: .ed25519,
d: privateKey.rawRepresentation.base64UrlEncodedString(),
x: privateKey.publicKey.rawRepresentation.base64UrlEncodedString()
)

jwk.keyIdentifier = try jwk.thumbprint()

return jwk
}

private static func generatePublicJwk(publicKey: Curve25519.Signing.PublicKey) throws -> Jwk {
var jwk = Jwk(
keyType: .octetKeyPair,
curve: .ed25519,
x: publicKey.rawRepresentation.base64UrlEncodedString()
)

jwk.keyIdentifier = try jwk.thumbprint()

return jwk
}
}

public enum Ed25519Error: Error {
/// The privateJwk provided did not have the appropriate parameters set on it
case invalidPrivateJwk
/// The publicJwk provided did not have the appropriate parameters set on it
case invalidPublicJwk
}
49 changes: 49 additions & 0 deletions Sources/tbDEX/crypto/InMemoryKeyManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Foundation

class InMemoryKeyManager {

/// Backing in-memory store to store generated keys.
private var keyStore = [String: Jwk]()

}

// MARK: - KeyManager

extension InMemoryKeyManager: KeyManager {

func generatePrivateKey(algorithm: Jwk.Algorithm, curve: Jwk.Curve? = nil) throws -> String {
let jwk = try Crypto.generatePrivateKey(algorithm: algorithm, curve: curve)
let alias = try getDeterministicAlias(key: jwk)
keyStore[alias] = jwk

return alias
}

func getPublicKey(keyAlias: String) throws -> Jwk? {
if let privateKey = keyStore[keyAlias] {
return try Crypto.computePublicKey(privateKey: privateKey)
} else {
return nil
}
}

func sign<D>(keyAlias: String, payload: D) throws -> Data where D: DataProtocol {
guard let privateKey = keyStore[keyAlias] else {
throw KeyManagerError.keyAliasNotFound
}

return try Crypto.sign(privateKey: privateKey, payload: payload)
}

func getDeterministicAlias(key: Jwk) throws -> String {
let alias: String

if let keyIdentifier = key.keyIdentifier {
alias = keyIdentifier
} else {
alias = try key.thumbprint()
}

return alias
}
}
Loading

0 comments on commit 2721361

Please sign in to comment.