-
Notifications
You must be signed in to change notification settings - Fork 8
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
crypto crate implementation #17
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,17 @@ | ||
[workspace] | ||
resolver = "2" | ||
members = [ | ||
"crates/*" | ||
] | ||
"bindings/*", | ||
"crates/*", | ||
] | ||
default-members = [ | ||
"crates/*", | ||
] | ||
resolver = "2" | ||
|
||
[workspace.package] | ||
homepage = "https://github.com/TBD54566975/web5-rs" | ||
repository = "https://github.com/TBD54566975/web5-rs.git" | ||
license-file = "LICENSE" | ||
|
||
[workspace.dependencies] | ||
thiserror = "1.0.50" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Bindings | ||
|
||
This directory contains crates that generate bindings for various languages. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "crypto" | ||
version = "0.1.0" | ||
edition = "2021" | ||
homepage.workspace = true | ||
repository.workspace = true | ||
license-file.workspace = true | ||
|
||
[dependencies] | ||
ssi-jwk = "0.1.2" | ||
ssi-jws = "0.1.1" | ||
thiserror = { workspace = true } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Crypto | ||
|
||
`Crypto` is a library for cryptographic primitives in Rust, essential for Web5. | ||
|
||
This crate should _not_ include any binding specific code, and should be usable within any Rust application, conforming | ||
to the Rust API guidelines. All binding related code should be placed in the `bindings` folder at the root of the workspace. | ||
|
||
## Build | ||
|
||
This crate is set to build with the workspace by default. | ||
|
||
To build this crate only, run: | ||
|
||
```bash | ||
cargo build -p crypto | ||
``` | ||
|
||
## Test | ||
|
||
This crate is set to test with the workspace by default. | ||
|
||
To test this crate only, run: | ||
|
||
```bash | ||
cargo test -p crypto | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use ssi_jwk::JWK; | ||
use ssi_jws::Error as JWSError; | ||
|
||
/// Enum defining all supported cryptographic key types. | ||
pub enum KeyType { | ||
Secp256k1, | ||
Secp256r1, | ||
Ed25519, | ||
} | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum KeyError { | ||
#[error(transparent)] | ||
JWSError(#[from] JWSError), | ||
#[error("Algorithm not found on JWK")] | ||
AlgorithmNotFound, | ||
} | ||
|
||
/// Trait defining all common behavior for cryptographic keys. | ||
pub trait Key { | ||
fn jwk(&self) -> &JWK; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
mod key; | ||
pub use key::*; | ||
|
||
mod private_key; | ||
pub use private_key::*; | ||
|
||
mod public_key; | ||
pub use public_key::*; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
use crate::key::{Key, KeyError, PublicKey}; | ||
use ssi_jwk::JWK; | ||
use ssi_jws::sign_bytes; | ||
|
||
#[derive(Clone, PartialEq, Debug)] | ||
pub struct PrivateKey(pub(crate) JWK); | ||
|
||
impl PrivateKey { | ||
/// Derive a [`PublicKey`] from the target [`PrivateKey`]. | ||
pub fn to_public(&self) -> PublicKey { | ||
PublicKey(self.0.to_public()) | ||
jiyoontbd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// Sign a payload using the target [`PrivateKey`]. | ||
pub fn sign(&self, payload: &[u8]) -> Result<Vec<u8>, KeyError> { | ||
let algorithm = self.0.get_algorithm().ok_or(KeyError::AlgorithmNotFound)?; | ||
let signed_bytes = sign_bytes(algorithm, &payload, &self.0)?; | ||
|
||
Ok(signed_bytes) | ||
} | ||
} | ||
|
||
impl Key for PrivateKey { | ||
fn jwk(&self) -> &JWK { | ||
&self.0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use ssi_jwk::JWK; | ||
|
||
fn new_private_key() -> PrivateKey { | ||
PrivateKey(JWK::generate_secp256k1().unwrap()) | ||
} | ||
|
||
#[test] | ||
fn test_to_public() { | ||
let private_key = new_private_key(); | ||
let public_key = private_key.to_public(); | ||
|
||
assert_eq!( | ||
private_key.jwk().thumbprint().unwrap(), | ||
public_key.jwk().thumbprint().unwrap() | ||
); | ||
|
||
assert_ne!(private_key.jwk(), public_key.jwk()) | ||
} | ||
|
||
#[test] | ||
fn test_sign() { | ||
let private_key = new_private_key(); | ||
let payload: &[u8] = b"hello world"; | ||
let signature = private_key.sign(payload).unwrap(); | ||
|
||
assert_ne!(payload, &signature) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use crate::key::{Key, KeyError}; | ||
use ssi_jwk::JWK; | ||
use ssi_jws::{verify_bytes_warnable, VerificationWarnings}; | ||
|
||
#[derive(PartialEq, Debug)] | ||
pub struct PublicKey(pub(crate) JWK); | ||
|
||
impl PublicKey { | ||
/// Verifies a payload with a given signature using the target [`PublicKey`]. | ||
pub fn verify( | ||
&self, | ||
payload: &[u8], | ||
signature: &[u8], | ||
) -> Result<VerificationWarnings, KeyError> { | ||
let algorithm = self.0.get_algorithm().ok_or(KeyError::AlgorithmNotFound)?; | ||
|
||
let verification_warnings = | ||
verify_bytes_warnable(algorithm, &payload, &self.0, &signature)?; | ||
|
||
Ok(verification_warnings) | ||
} | ||
} | ||
|
||
impl Key for PublicKey { | ||
fn jwk(&self) -> &JWK { | ||
&self.0 | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::key::PrivateKey; | ||
|
||
#[test] | ||
fn test_verify() { | ||
let private_key = PrivateKey(JWK::generate_secp256k1().unwrap()); | ||
let payload: &[u8] = b"hello world"; | ||
let signature = private_key.sign(payload).unwrap(); | ||
|
||
let public_key = private_key.to_public(); | ||
let verification_warnings = public_key.verify(&payload, &signature).unwrap(); | ||
assert_eq!(verification_warnings.len(), 0); | ||
} | ||
|
||
#[test] | ||
fn test_verify_failure() { | ||
let private_key = PrivateKey(JWK::generate_secp256k1().unwrap()); | ||
let payload: &[u8] = b"hello world"; | ||
let signature = private_key.sign(payload).unwrap(); | ||
|
||
// public_key is unrelated to the private_key used to sign the payload, so it should fail | ||
let public_key = PublicKey(JWK::generate_secp256k1().unwrap()); | ||
let verification_warnings = public_key.verify(&payload, &signature); | ||
assert!(verification_warnings.is_err()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use crate::key::{KeyError, KeyType, PublicKey}; | ||
use crate::key_manager::key_store::KeyStoreError; | ||
use ssi_jwk::Error as JWKError; | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum KeyManagerError { | ||
#[error("Signing key not found in KeyManager")] | ||
SigningKeyNotFound, | ||
#[error(transparent)] | ||
JWKError(#[from] JWKError), | ||
#[error(transparent)] | ||
KeyError(#[from] KeyError), | ||
#[error(transparent)] | ||
KeyStoreError(#[from] KeyStoreError), | ||
} | ||
|
||
/// A key management trait for generating, storing, and utilizing keys private keys and their | ||
/// associated public keys. | ||
/// | ||
/// Implementations of this trait might provide key management through various Key Management | ||
/// Systems (KMS), such as AWS KMS, Google Cloud KMD, Hardware Security Modules (HSM), or simple | ||
/// in-memory storage, each adhering to the same consistent API for usage within applications. | ||
pub trait KeyManager: Send + Sync { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i noticed you have i don't know enough about rust to fully understand the docs but could you tell me why you chose to add these two traits? is it because we need to use Arc in all key manager's implementations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent question! It's actually to do with exposing this trait for external bindings via uniffi (which will be upcoming in a future PR). There's a requirement that exposed traits MUST implement Send + Sync, because which thread it comes across isn't guaranteed. There's a bit of explanation about it here: https://mozilla.github.io/uniffi-rs/udl/interfaces.html#exposing-traits-as-interfaces So, for now, it's mostly for things yet to come! |
||
/// Generates and securely stores a private key based on the provided `key_type`, | ||
/// returning a unique alias that can be utilized to reference the generated key for future | ||
/// operations. | ||
fn generate_private_key(&self, key_type: KeyType) -> Result<String, KeyManagerError>; | ||
|
||
/// Returns the public key associated with the provided `key_alias`, if one exists. | ||
fn get_public_key(&self, key_alias: &str) -> Result<Option<PublicKey>, KeyManagerError>; | ||
|
||
/// Signs the provided payload using the private key identified by the provided `key_alias`. | ||
fn sign(&self, key_alias: &str, payload: &[u8]) -> Result<Vec<u8>, KeyManagerError>; | ||
|
||
/// Returns the key alias of a public key, as was originally returned by `generate_private_key`. | ||
fn alias(&self, public_key: &PublicKey) -> Result<String, KeyManagerError>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
use crate::key::PrivateKey; | ||
use crate::key_manager::key_store::{KeyStore, KeyStoreError}; | ||
use std::collections::HashMap; | ||
use std::sync::RwLock; | ||
|
||
/// An in-memory implementation of the [`KeyStore`] trait. | ||
pub struct InMemoryKeyStore { | ||
map: RwLock<HashMap<String, PrivateKey>>, | ||
} | ||
|
||
impl InMemoryKeyStore { | ||
pub fn new() -> Self { | ||
let map = RwLock::new(HashMap::new()); | ||
Self { map } | ||
} | ||
} | ||
|
||
impl KeyStore for InMemoryKeyStore { | ||
fn get(&self, key_alias: &str) -> Result<Option<PrivateKey>, KeyStoreError> { | ||
let map_lock = self.map.read().map_err(|e| { | ||
KeyStoreError::InternalKeyStoreError(format!("Unable to acquire Mutex lock: {}", e)) | ||
})?; | ||
|
||
if let Some(private_key) = map_lock.get(key_alias) { | ||
Ok(Some(private_key.clone())) | ||
} else { | ||
Ok(None) | ||
jiyoontbd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
fn insert(&self, key_alias: &str, private_key: PrivateKey) -> Result<(), KeyStoreError> { | ||
let mut map_lock = self.map.write().map_err(|e| { | ||
KeyStoreError::InternalKeyStoreError(format!("Unable to acquire Mutex lock: {}", e)) | ||
})?; | ||
|
||
map_lock.insert(key_alias.to_string(), private_key); | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use crate::key::PrivateKey; | ||
use ssi_jwk::JWK; | ||
|
||
fn new_private_key() -> PrivateKey { | ||
PrivateKey(JWK::generate_secp256k1().unwrap()) | ||
} | ||
|
||
#[test] | ||
fn test_insert_get() { | ||
let key_alias = "key-alias"; | ||
let private_key = new_private_key(); | ||
|
||
let key_store = InMemoryKeyStore::new(); | ||
key_store.insert(key_alias, private_key.clone()).unwrap(); | ||
|
||
let retrieved_private_key = key_store.get(key_alias).unwrap().unwrap(); | ||
assert_eq!(private_key, retrieved_private_key); | ||
} | ||
|
||
#[test] | ||
fn test_get_missing() { | ||
let key_alias = "key-alias"; | ||
|
||
let key_store = InMemoryKeyStore::new(); | ||
let retrieved_private_key = key_store.get(key_alias).unwrap(); | ||
assert!(retrieved_private_key.is_none()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use crate::key::PrivateKey; | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum KeyStoreError { | ||
#[error("{0}")] | ||
InternalKeyStoreError(String), | ||
} | ||
|
||
// Trait for storing and retrieving private keys. | ||
// | ||
// Implementations of this trait should be thread-safe and allow for concurrent access. | ||
pub trait KeyStore: Send + Sync { | ||
fn get(&self, key_alias: &str) -> Result<Option<PrivateKey>, KeyStoreError>; | ||
fn insert(&self, key_alias: &str, private_key: PrivateKey) -> Result<(), KeyStoreError>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod key_store; | ||
pub use key_store::*; | ||
|
||
mod in_memory_key_store; | ||
pub use in_memory_key_store::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2023?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every once in a while, there's a change to the Rust language that isn't backwards compatible. Editions are how that's conveyed in the
Cargo.toml
file. 2021 was the latest change to language, and we are just declaring that we're compatible with versions from that point on.More info on editions here! https://doc.rust-lang.org/edition-guide/editions/