Skip to content

Commit

Permalink
add credentials module, for minsize
Browse files Browse the repository at this point in the history
  • Loading branch information
DougAnderson444 committed Sep 17, 2024
1 parent 6a9a79e commit c39b5cc
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions crates/seed-keeper-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ readme = "README.md"
repository = "https://github.com/DougAnderson444/seed-keeper.git"
version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
aes-kw = { version = "0.2.1", features = ["std"] }
argon2 = "0.5.0"
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
rand = "0.8.5"
thiserror = "1.0.51"
serde = { version = "1.0", features = ["derive"], optional = true }

[features]
default = ["serde", "argon2/std"]
43 changes: 43 additions & 0 deletions crates/seed-keeper-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,46 @@ let encrypted = encrypt(key.clone(), seed.clone()).unwrap();
let decrypted = decrypt(key.clone(), &encrypted).unwrap();
assert_eq!(*seed, *decrypted.as_slice());
```

## Full Credentials and Storage

The inputs for Aegon2 must be at least 8 characters long strings, so seed-keeper exports helpers to help ensure that your users input the minimum length bytes, and give helpful errors when they don't. To use these helpers, just use the `credentials` module.

```rust
use seed_keeper_core::error;
use seed_keeper_core::credentials::{MinString, Credentials, Wallet};

fn it_works() -> Result<(), error::Error> {
// Create a new wallet
// [Credentials] supports Deserialization, so you can use it with serde_json from JavaScript
let credentials = Credentials {
username: MinString::new("username")?,
password: MinString::new("password")?,
encrypted_seed: None,
};

let wallet = Wallet::new(credentials)?;

// Encrypt the seed
let encrypted_seed = wallet.encrypted_seed()?;

// Create a new wallet with the encrypted seed
let credentials = Credentials {
username: MinString::new("username")?,
password: MinString::new("password")?,
encrypted_seed: Some(encrypted_seed.clone()),
};

let wallet = Wallet::new(credentials)?;

// Encrypt the seed
let encrypted_seed_2 = wallet.encrypted_seed()?;

assert!(!encrypted_seed_2.is_empty());

// Should match
assert_eq!(encrypted_seed, encrypted_seed_2);

Ok(())
}
```
152 changes: 152 additions & 0 deletions crates/seed-keeper-core/src/credentials.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use std::{marker::PhantomData, ops::Deref};

use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};

use crate::{
derive_key,
error::{self, Error},
seed::rand_seed,
wrap::{decrypt, encrypt},
};

/// Username, password and Option of Encrypted seed
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct Credentials {
pub username: MinString<8>,
pub password: MinString<8>,
pub encrypted_seed: Option<Vec<u8>>,
}

impl Credentials {
pub fn new(
username: &str,
password: &str,
encrypted_seed: Option<Vec<u8>>,
) -> Result<Self, error::Error> {
Ok(Credentials {
username: MinString::new(username)?,
password: MinString::new(password)?,
encrypted_seed,
})
}
}

#[derive(Serialize, Deserialize, Default, Debug, Zeroize, ZeroizeOnDrop)]
pub struct MinString<const N: usize> {
#[zeroize]
value: String,
_marker: PhantomData<()>,
}

impl<const N: usize> MinString<N> {
pub fn new(value: &str) -> Result<Self, error::Error> {
if value.len() >= N {
Ok(MinString {
value: value.to_string(),
_marker: PhantomData,
})
} else {
Err(Error::ValueTooShort(N))
}
}

pub fn value(&self) -> &str {
&self.value
}
}

impl<const N: usize> Deref for MinString<{ N }> {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.value
}
}

pub struct Wallet {
username: MinString<8>,
password: MinString<8>,
seed: Zeroizing<Vec<u8>>,
}

impl Wallet {
/// Creates a new Wallet from the given credentials
pub fn new(credentials: Credentials) -> Result<Self, error::Error> {
let seed = match credentials.encrypted_seed {
Some(encrypted_seed) => {
let key = derive_key(
credentials.username.value().as_bytes(),
credentials.password.value().as_bytes(),
)
.map_err(|e| e.to_string())?;

// Decrypt the given seed with the key, if it fails the username or password is wrong & return error
let decrypted = decrypt(key.clone(), &encrypted_seed)?;

if decrypted.len() != 32 {
return Err(Error::SeedLength);
}

decrypted
}
None => Zeroizing::new(rand_seed().as_slice().to_vec()),
};

Ok(Wallet {
seed,
username: credentials.username,
password: credentials.password,
})
}

/// Returns the encrypted seed
pub fn encrypted_seed(&self) -> Result<Vec<u8>, error::Error> {
let key = derive_key(
self.username.value().as_bytes(),
self.password.value().as_bytes(),
)?;

encrypt(key, self.seed.clone())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() -> Result<(), error::Error> {
// Create a new wallet
// [Credentials] supports Deserialization, so you can use it with serde_json from JavaScript
let credentials = Credentials {
username: MinString::new("username")?,
password: MinString::new("password")?,
encrypted_seed: None,
};

let wallet = Wallet::new(credentials)?;

// Encrypt the seed
let encrypted_seed = wallet.encrypted_seed()?;

// Create a new wallet with the encrypted seed
let credentials = Credentials {
username: MinString::new("username")?,
password: MinString::new("password")?,
encrypted_seed: Some(encrypted_seed.clone()),
};

let wallet = Wallet::new(credentials)?;

// Encrypt the seed
let encrypted_seed_2 = wallet.encrypted_seed()?;

assert!(!encrypted_seed_2.is_empty());

// Should match
assert_eq!(encrypted_seed, encrypted_seed_2);

Ok(())
}
}
26 changes: 26 additions & 0 deletions crates/seed-keeper-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,30 @@ pub enum Error {
/// AES-KW Error
#[error("AES-Key Wrapping (encrypt/decrypt) Error: {0}")]
KeyWrap(#[from] aes_kw::Error),

/// Argon2 error
#[error("Argon2 Error")]
Argon2(#[from] argon2::Error),

/// String Message
#[error("{0}")]
Message(String),

/// Failed to create Wallet
#[error("Failed to create Wallet")]
WalletCreation,

/// Seed too short "Seed must be 32 bytes long"
#[error("Seed must be 32 bytes long")]
SeedLength,

/// Value too short, "Must be at least {} characters long"
#[error("Must be at least {0} characters long")]
ValueTooShort(usize),
}

impl From<String> for Error {
fn from(e: String) -> Self {
Error::Message(e)
}
}
1 change: 1 addition & 0 deletions crates/seed-keeper-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// include readme
#![doc = include_str!("../README.md")]

pub mod credentials;
pub mod error;
pub mod seed;
pub mod wrap;
Expand Down

0 comments on commit c39b5cc

Please sign in to comment.