diff --git a/demo/hsm/src/cli/mod.rs b/demo/hsm/src/cli/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/demo/hsm/src/cryptography/aws_kms.rs b/demo/hsm/src/cryptography/aws_kms.rs new file mode 100644 index 000000000..7e408eea0 --- /dev/null +++ b/demo/hsm/src/cryptography/aws_kms.rs @@ -0,0 +1,28 @@ +use crate::cryptography::Secp256k1; +use aws_sdk_kms::types::{KeySpec, KeyUsageType, SigningAlgorithmSpec}; + +/// Defines the needed methods for providing a definition of cryptography used with AWS KMS +pub trait AwsKmsCryptography { + /// Returns the [KeySpec] for the desired cryptography + fn key_spec() -> KeySpec; + + /// Returns the [KeyUsageType] for the desired cryptography + fn key_usage_type() -> KeyUsageType; + + /// Returns the [SigningAlgorithmSpec] for the desired cryptography + fn signing_algorithm_spec() -> SigningAlgorithmSpec; +} + +impl AwsKmsCryptography for Secp256k1 { + fn key_spec() -> KeySpec { + KeySpec::EccSecgP256K1 + } + + fn key_usage_type() -> KeyUsageType { + KeyUsageType::SignVerify + } + + fn signing_algorithm_spec() -> SigningAlgorithmSpec { + SigningAlgorithmSpec::EcdsaSha256 + } +} diff --git a/demo/hsm/src/cryptography/google_kms.rs b/demo/hsm/src/cryptography/google_kms.rs new file mode 100644 index 000000000..e69de29bb diff --git a/demo/hsm/src/cryptography/hashicorp_vault.rs b/demo/hsm/src/cryptography/hashicorp_vault.rs new file mode 100644 index 000000000..0ec19ac1c --- /dev/null +++ b/demo/hsm/src/cryptography/hashicorp_vault.rs @@ -0,0 +1,14 @@ +use crate::cryptography::Ed25519; +use vaultrs::api::transit::KeyType; + +/// Defines the needed methods for providing a definition of cryptography used with HashiCorp Vault +pub trait HashiCorpVaultCryptography { + /// Returns the [KeyType] for the desired cryptography + fn key_type() -> KeyType; +} + +impl HashiCorpVaultCryptography for Ed25519 { + fn key_type() -> KeyType { + KeyType::Ed25519 + } +} diff --git a/demo/hsm/src/cryptography/mod.rs b/demo/hsm/src/cryptography/mod.rs new file mode 100644 index 000000000..46c39abeb --- /dev/null +++ b/demo/hsm/src/cryptography/mod.rs @@ -0,0 +1,18 @@ +pub mod aws_kms; +pub mod google_kms; +pub mod hashicorp_vault; +pub mod verifier; + +/// The Secp256k1 curve. +#[derive(Debug, Clone, Copy)] +pub struct Secp256k1; + +/// The Ed25519 curve. +#[derive(Debug, Clone, Copy)] +pub struct Ed25519; + +#[derive(Debug, Clone, Copy)] +pub enum Curve { + Secp256k1(Secp256k1), + Ed25519(Ed25519), +} diff --git a/demo/hsm/src/cryptography/verifier.rs b/demo/hsm/src/cryptography/verifier.rs new file mode 100644 index 000000000..b7f25c522 --- /dev/null +++ b/demo/hsm/src/cryptography/verifier.rs @@ -0,0 +1,71 @@ +use crate::{Bytes, PublicKey, Signature}; + +#[async_trait::async_trait] +pub trait LocalVerifier { + /// Verifies a signature for a given message and public key. + async fn verify( + message: Bytes, + public_key: PublicKey, + signature: Signature, + ) -> Result; +} + +pub mod secp256k1 { + use super::*; + use crate::cryptography::Secp256k1; + use anyhow::Context; + use k256::ecdsa::{self, VerifyingKey}; + use k256::pkcs8::DecodePublicKey; + use ring_compat::signature::Verifier; + + #[async_trait::async_trait] + impl LocalVerifier for Secp256k1 { + async fn verify( + message: Bytes, + public_key: PublicKey, + signature: Signature, + ) -> Result { + let verifying_key = VerifyingKey::from_public_key_der(&public_key.0 .0) + .context("Failed to create verifying key")?; + + let signature = ecdsa::Signature::from_der(&signature.0 .0) + .context("Failed to create signature")?; + + match verifying_key.verify(message.0.as_slice(), &signature) { + Ok(_) => Ok(true), + Err(e) => { + println!("Error verifying signature: {:?}", e); + Ok(false) + } + } + } + } +} + +pub mod ed25519 { + + use super::*; + use crate::cryptography::Ed25519; + use anyhow::Context; + use ring_compat::signature::{ + ed25519::{self, VerifyingKey}, + Verifier, + }; + + #[async_trait::async_trait] + impl LocalVerifier for Ed25519 { + async fn verify( + message: Bytes, + public_key: PublicKey, + signature: Signature, + ) -> Result { + let verifying_key = VerifyingKey::from_slice(public_key.0 .0.as_slice()) + .context("Failed to create verifying key")?; + + let signature = ed25519::Signature::from_slice(signature.0 .0.as_slice()) + .context("Failed to create signature")?; + + Ok(verifying_key.verify(message.0.as_slice(), &signature).is_ok()) + } + } +} diff --git a/demo/hsm/src/hsm/aws_kms.rs b/demo/hsm/src/hsm/aws_kms.rs index 23ccab0e5..f1f7570fd 100644 --- a/demo/hsm/src/hsm/aws_kms.rs +++ b/demo/hsm/src/hsm/aws_kms.rs @@ -1,24 +1,26 @@ +use crate::cryptography::aws_kms::AwsKmsCryptography; +use crate::cryptography::verifier::LocalVerifier; use crate::{Bytes, Hsm, PublicKey, Signature}; use anyhow::Context; use aws_sdk_kms::primitives::Blob; -use aws_sdk_kms::types::{KeySpec, KeyUsageType, SigningAlgorithmSpec}; use aws_sdk_kms::Client; use dotenv::dotenv; -use k256::ecdsa::{self, VerifyingKey}; -use k256::pkcs8::DecodePublicKey; -use ring_compat::signature::Verifier; /// A AWS KMS HSM. -pub struct AwsKms { +pub struct AwsKms { client: Client, key_id: String, - pub public_key: PublicKey, + public_key: PublicKey, + _cryptography_marker: std::marker::PhantomData, } -impl AwsKms { +impl AwsKms +where + C: AwsKmsCryptography, +{ /// Creates a new AWS KMS HSM pub fn new(client: Client, key_id: String, public_key: PublicKey) -> Self { - Self { client, key_id, public_key } + Self { client, key_id, public_key, _cryptography_marker: std::marker::PhantomData } } /// Tries to create a new AWS KMS HSM from the environment @@ -38,8 +40,8 @@ impl AwsKms { let res = self .client .create_key() - .key_spec(KeySpec::EccSecgP256K1) - .key_usage(KeyUsageType::SignVerify) + .key_spec(C::key_spec()) + .key_usage(C::key_usage_type()) .send() .await?; @@ -58,17 +60,25 @@ impl AwsKms { self.public_key = public_key; Ok(self) } + + /// Gets a reference to the public key + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } } #[async_trait::async_trait] -impl Hsm for AwsKms { +impl Hsm for AwsKms +where + C: AwsKmsCryptography + LocalVerifier + Send + Sync, +{ async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { let blob = Blob::new(message.clone().0); let request = self .client .sign() .key_id(&self.key_id) - .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .signing_algorithm(C::signing_algorithm_spec()) .message(blob); let res = request.send().await?; @@ -85,18 +95,6 @@ impl Hsm for AwsKms { public_key: PublicKey, signature: Signature, ) -> Result { - let verifying_key = VerifyingKey::from_public_key_der(&public_key.0 .0) - .context("Failed to create verifying key")?; - - let signature = - ecdsa::Signature::from_der(&signature.0 .0).context("Failed to create signature")?; - - match verifying_key.verify(message.0.as_slice(), &signature) { - Ok(_) => Ok(true), - Err(e) => { - println!("Error verifying signature: {:?}", e); - Ok(false) - } - } + C::verify(message, public_key, signature).await } } diff --git a/demo/hsm/src/hsm/hashi_corp_vault.rs b/demo/hsm/src/hsm/hashi_corp_vault.rs index 8e27d82e5..a44509d78 100644 --- a/demo/hsm/src/hsm/hashi_corp_vault.rs +++ b/demo/hsm/src/hsm/hashi_corp_vault.rs @@ -1,24 +1,25 @@ +use crate::cryptography::hashicorp_vault::HashiCorpVaultCryptography; +use crate::cryptography::verifier::LocalVerifier; use crate::{Bytes, Hsm, PublicKey, Signature}; use anyhow::Context; -use ring_compat::signature::{ - ed25519::{self, VerifyingKey}, - Verifier, -}; -use vaultrs::api::transit::KeyType; use vaultrs::api::transit::{requests::CreateKeyRequest, responses::ReadKeyData}; use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; use vaultrs::transit::data; use vaultrs::transit::key; /// A HashiCorp Vault HSM. -pub struct HashiCorpVault { +pub struct HashiCorpVault { client: VaultClient, key_name: String, mount_name: String, pub public_key: PublicKey, + _cryptography_marker: std::marker::PhantomData, } -impl HashiCorpVault { +impl HashiCorpVault +where + C: HashiCorpVaultCryptography, +{ /// Creates a new HashiCorp Vault HSM pub fn new( client: VaultClient, @@ -26,7 +27,13 @@ impl HashiCorpVault { mount_name: String, public_key: PublicKey, ) -> Self { - Self { client, key_name, mount_name, public_key } + Self { + client, + key_name, + mount_name, + public_key, + _cryptography_marker: std::marker::PhantomData, + } } /// Tries to create a new HashiCorp Vault HSM from the environment @@ -60,7 +67,7 @@ impl HashiCorpVault { &self.client, self.mount_name.as_str(), self.key_name.as_str(), - Some(CreateKeyRequest::builder().key_type(KeyType::Ed25519).derived(false)), + Some(CreateKeyRequest::builder().key_type(C::key_type()).derived(false)), ) .await .context("Failed to create key")?; @@ -91,7 +98,10 @@ impl HashiCorpVault { } #[async_trait::async_trait] -impl Hsm for HashiCorpVault { +impl Hsm for HashiCorpVault +where + C: HashiCorpVaultCryptography + LocalVerifier + Send + Sync, +{ async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { let res = data::sign( &self.client, @@ -125,12 +135,6 @@ impl Hsm for HashiCorpVault { public_key: PublicKey, signature: Signature, ) -> Result { - let verifying_key = VerifyingKey::from_slice(public_key.0 .0.as_slice()) - .context("Failed to create verifying key")?; - - let signature = ed25519::Signature::from_slice(signature.0 .0.as_slice()) - .context("Failed to create signature")?; - - Ok(verifying_key.verify(message.0.as_slice(), &signature).is_ok()) + C::verify(message, public_key, signature).await } } diff --git a/demo/hsm/src/hsm/mod.rs b/demo/hsm/src/hsm/mod.rs index 08cd9d479..a2d89f982 100644 --- a/demo/hsm/src/hsm/mod.rs +++ b/demo/hsm/src/hsm/mod.rs @@ -1,3 +1,10 @@ pub mod aws_kms; pub mod google_kms; pub mod hashi_corp_vault; + +#[derive(Debug, Clone, Copy)] +pub enum Provider { + AWS, + GCP, + Vault, +} diff --git a/demo/hsm/src/lib.rs b/demo/hsm/src/lib.rs index 7aace54e2..7b2f622db 100644 --- a/demo/hsm/src/lib.rs +++ b/demo/hsm/src/lib.rs @@ -1,4 +1,6 @@ pub mod action_stream; +pub mod cli; +pub mod cryptography; pub mod hsm; pub mod server; diff --git a/demo/hsm/src/main.rs b/demo/hsm/src/main.rs index 9a23eb0e0..cf73a52e3 100644 --- a/demo/hsm/src/main.rs +++ b/demo/hsm/src/main.rs @@ -1,4 +1,11 @@ use axum::Server; +use dotenv::dotenv; +use hsm_demo::server::create_server; +use hsm_demo::{ + action_stream, + cryptography::{Ed25519, Secp256k1}, + Application, +}; use hsm_demo::{hsm, Bytes, Hsm, PublicKey, Signature}; use reqwest::Client; use serde::Serialize; @@ -6,133 +13,128 @@ use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; use tokio::task; -use dotenv::dotenv; - -use hsm_demo::{action_stream, Application}; -use hsm_demo::server::create_server; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - dotenv().ok(); // Load environment variables from .env file - - // Initialize HSM based on PROVIDER - let provider = std::env::var("PROVIDER").unwrap_or_else(|_| "AWS".to_string()); - let (hsm, public_key) = match provider.as_str() { - "AWS" => { - let aws_kms_hsm = hsm::aws_kms::AwsKms::try_from_env() - .await? - .create_key() - .await? - .fill_with_public_key() - .await?; - let public_key = aws_kms_hsm.public_key.clone(); - (Arc::new(Mutex::new(aws_kms_hsm)) as Arc>, public_key) - } - "VAULT" => { - let vault_hsm = hsm::hashi_corp_vault::HashiCorpVault::try_from_env()? - .create_key() - .await? - .fill_with_public_key() - .await?; - let public_key = vault_hsm.public_key.clone(); - (Arc::new(Mutex::new(vault_hsm)) as Arc>, public_key) - } - _ => { - return Err(anyhow::anyhow!("Unsupported provider: {}", provider)); - } - }; - - // Start the server task - let server_hsm = hsm.clone(); - let server_task = task::spawn(async move { - let app = create_server(server_hsm); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - println!("Server listening on {}", addr); - - Server::bind(&addr) - .serve(app.into_make_service()) - .await - .expect("Server failed"); - }); - - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - // Start the Application - let client = Client::new(); - let random_stream = action_stream::random::Random; - let notify_verify_stream = action_stream::notify_verify::NotifyVerify::new(); - let join_stream = action_stream::join::Join::new(vec![ - Box::new(random_stream), - Box::new(notify_verify_stream), - ]); - - // Replace HSM with the HTTP client that sends requests to the server - let hsm_proxy = HttpHsmProxy::new(client, "http://127.0.0.1:3000/sign".to_string(), public_key); - let mut app = Application::new(Box::new(hsm_proxy), Box::new(join_stream)); - - app.run().await?; - - server_task.await?; - Ok(()) + dotenv().ok(); // Load environment variables from .env file + + // Initialize HSM based on PROVIDER + let provider = std::env::var("PROVIDER").unwrap_or_else(|_| "AWS".to_string()); + let (hsm, public_key) = match provider.as_str() { + "AWS" => { + let aws_kms_hsm = hsm::aws_kms::AwsKms::::try_from_env() + .await? + .create_key() + .await? + .fill_with_public_key() + .await?; + let public_key = aws_kms_hsm.public_key().clone(); + ( + Arc::new(Mutex::new(aws_kms_hsm)) as Arc>, + public_key, + ) + } + "VAULT" => { + let vault_hsm = hsm::hashi_corp_vault::HashiCorpVault::::try_from_env()? + .create_key() + .await? + .fill_with_public_key() + .await?; + let public_key = vault_hsm.public_key.clone(); + ( + Arc::new(Mutex::new(vault_hsm)) as Arc>, + public_key, + ) + } + _ => { + return Err(anyhow::anyhow!("Unsupported provider: {}", provider)); + } + }; + + // Start the server task + let server_hsm = hsm.clone(); + let server_task = task::spawn(async move { + let app = create_server(server_hsm); + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + println!("Server listening on {}", addr); + + Server::bind(&addr).serve(app.into_make_service()).await.expect("Server failed"); + }); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + // Start the Application + let client = Client::new(); + let random_stream = action_stream::random::Random; + let notify_verify_stream = action_stream::notify_verify::NotifyVerify::new(); + let join_stream = action_stream::join::Join::new(vec![ + Box::new(random_stream), + Box::new(notify_verify_stream), + ]); + + // Replace HSM with the HTTP client that sends requests to the server + let hsm_proxy = HttpHsmProxy::new(client, "http://127.0.0.1:3000/sign".to_string(), public_key); + let mut app = Application::new(Box::new(hsm_proxy), Box::new(join_stream)); + + app.run().await?; + + server_task.await?; + Ok(()) } #[derive(Serialize)] struct SignRequest { - message: Vec, + message: Vec, } #[derive(serde::Deserialize)] struct SignedResponse { - signature: Vec, + signature: Vec, } pub struct HttpHsmProxy { - client: Client, - server_url: String, - public_key: PublicKey, + client: Client, + server_url: String, + public_key: PublicKey, } impl HttpHsmProxy { - pub fn new(client: Client, server_url: String, public_key: PublicKey) -> Self { - Self { client, server_url, public_key } - } + pub fn new(client: Client, server_url: String, public_key: PublicKey) -> Self { + Self { client, server_url, public_key } + } - pub fn get_public_key(&self) -> PublicKey { - self.public_key.clone() - } + pub fn get_public_key(&self) -> PublicKey { + self.public_key.clone() + } } #[async_trait::async_trait] impl Hsm for HttpHsmProxy { - async fn sign( - &self, - message: Bytes, - ) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { - let payload = SignRequest { message: message.0.clone() }; - - let response = self - .client - .post(&self.server_url) - .json(&payload) - .send() - .await? - .json::() - .await?; - - let signature = Signature(Bytes(response.signature)); - - // Return the stored public key along with the signature - Ok((message, self.public_key.clone(), signature)) - } - - async fn verify( - &self, - _message: Bytes, - _public_key: PublicKey, - _signature: Signature, - ) -> Result { - // Verification would need another endpoint or can be skipped because Application already verifies - Ok(true) - } + async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { + let payload = SignRequest { message: message.0.clone() }; + + let response = self + .client + .post(&self.server_url) + .json(&payload) + .send() + .await? + .json::() + .await?; + + let signature = Signature(Bytes(response.signature)); + + // Return the stored public key along with the signature + Ok((message, self.public_key.clone(), signature)) + } + + async fn verify( + &self, + _message: Bytes, + _public_key: PublicKey, + _signature: Signature, + ) -> Result { + // Verification would need another endpoint or can be skipped because Application already verifies + Ok(true) + } } -