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

crypto crate implementation #17

Merged
merged 3 commits into from
Nov 27, 2023
Merged
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
18 changes: 15 additions & 3 deletions Cargo.toml
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"
3 changes: 3 additions & 0 deletions bindings/README.md
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.
12 changes: 12 additions & 0 deletions crates/crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "crypto"
version = "0.1.0"
edition = "2021"
Copy link
Contributor

Choose a reason for hiding this comment

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

2023?

Copy link
Contributor Author

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/

homepage.workspace = true
repository.workspace = true
license-file.workspace = true

[dependencies]
ssi-jwk = "0.1.2"
ssi-jws = "0.1.1"
thiserror = { workspace = true }
26 changes: 26 additions & 0 deletions crates/crypto/README.md
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
```
22 changes: 22 additions & 0 deletions crates/crypto/src/key/key.rs
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;
}
8 changes: 8 additions & 0 deletions crates/crypto/src/key/mod.rs
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::*;
59 changes: 59 additions & 0 deletions crates/crypto/src/key/private_key.rs
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)
}
}
57 changes: 57 additions & 0 deletions crates/crypto/src/key/public_key.rs
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());
}
}
37 changes: 37 additions & 0 deletions crates/crypto/src/key_manager/key_manager.rs
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 {

Choose a reason for hiding this comment

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

i noticed you have : Send + Sync bits here, and i think these traits have to do with being able to pass types across threads, and share references between threads.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>;
}
71 changes: 71 additions & 0 deletions crates/crypto/src/key_manager/key_store/in_memory_key_store.rs
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());
}
}
15 changes: 15 additions & 0 deletions crates/crypto/src/key_manager/key_store/key_store.rs
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>;
}
5 changes: 5 additions & 0 deletions crates/crypto/src/key_manager/key_store/mod.rs
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::*;
Loading