cyrtophora

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 9b3451ca36d0ffcdb9731e552cc49d74e2fee8c4
parent b76d05dc5725068147856ff6fe28bd4d31cc0530
Author: Jackson G. Kaindume <seestem@merely.tech>
Date:   Sun, 21 Aug 2022 11:33:17 +0200

move crypto to crypto.rs file

Diffstat:
Asrc/crypto.rs | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 191 insertions(+), 0 deletions(-)

diff --git a/src/crypto.rs b/src/crypto.rs @@ -0,0 +1,191 @@ +use anyhow::Result; +use chacha20poly1305::{ + aead::{Aead, NewAead}, + Key, XChaCha20Poly1305, XNonce, +}; +use ring::{ + digest::{self, digest, Digest}, + rand::SystemRandom, + signature::{Ed25519KeyPair, KeyPair}, +}; +use scrypt::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Scrypt, +}; + +const CREDENTIAL_LEN: usize = digest::SHA256_OUTPUT_LEN; + +/// Nonce used when encrypting the Crypto's keypair (Crypto::encrypt_keypair_document()) +const AED_NONCE: &str = "people the information "; +pub type Credential = [u8; CREDENTIAL_LEN]; + +pub struct Credentials { + /// Base64 encoded key pair serialized as a PKCS#8 document + pub keypair_doc: String, + /// Base64 encoded public key + pub public_key: String, +} + +pub struct Crypto {} + +impl Crypto { + /// Generate Ed25519 key pair, for signing + pub fn gen_ed25519_document() -> Result<Credentials> { + let rng = SystemRandom::new(); + // TODO: don't use expect() + let keypair_document = + Ed25519KeyPair::generate_pkcs8(&rng).expect("Could not generate Ed25519KeyPair"); + let keypair = Ed25519KeyPair::from_pkcs8(keypair_document.as_ref()) + .expect("Could not decode keypair from keypair document"); + let public_key_string = base64::encode(keypair.public_key().as_ref()); + let keypair_document_string = base64::encode(keypair_document.as_ref()); + let cred = Credentials { + keypair_doc: keypair_document_string, + public_key: public_key_string, + }; + + Ok(cred) + } + + /// Generate scrypt based hashed password + pub fn hash_password(password: &str) -> Result<String> { + let salt = SaltString::generate(&mut OsRng); + Ok(Scrypt + .hash_password(password.as_bytes(), &salt)? + .to_string()) + } + + /// Verify scrypt based password hash + fn verify_password_hash(attempted_password: &str, hash: &str) -> Result<bool> { + let parsed_hash = PasswordHash::new(hash)?; + + if Scrypt + .verify_password(attempted_password.as_bytes(), &parsed_hash) + .is_ok() + { + Ok(true) + } else { + Ok(false) + } + } + + /// Encrypt the keypair (ed25519) document, using XChaCha20Poly1305 + /// so that it can be safely persisted into the database. The + /// Crypto's login password is used a the secret key. + pub fn encrypt_keypair_document(keypair_document: &str, password: &str) -> Result<String> { + let secret_key = Crypto::gen_secret_key(password); + let key = Key::from_slice(&secret_key.as_ref()[..32]); // 32-bytes + let aead = XChaCha20Poly1305::new(key); + let nonce = XNonce::from_slice(AED_NONCE.as_bytes()); // 24-bytes; unique + let keypair_document_bytes = base64::decode(keypair_document)?; + let res = base64::encode( + aead.encrypt(nonce, keypair_document_bytes.as_ref()) + .expect("encryption failure!"), + ); + Ok(res) + } + + /// Decrypt the encrypted keypair (ed25519), using XChaCha20Poly1305 + fn decrypt_keypair_document(cyphertext: &str, password: &str) -> Result<String> { + let secret_key = Crypto::gen_secret_key(password); + let key = Key::from_slice(&secret_key.as_ref()[..32]); // 32-bytes + let nonce = XNonce::from_slice(AED_NONCE.as_bytes()); // 24-bytes; unique + let aead = XChaCha20Poly1305::new(key); + let cyphertext_bytes = base64::decode(cyphertext)?; + let res = base64::encode( + aead.decrypt(nonce, cyphertext_bytes.as_ref()) + .expect("decryption failure!"), + ); + Ok(res) + } + + /// Generate secret key, used to encrypt the ed25519 keypair + fn gen_secret_key(password: &str) -> Digest { + digest(&digest::SHA256, password.as_bytes()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ring::signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey, ED25519}; + + #[test] + fn hash_password() { + let password1 = "a bad password"; + let hash1 = Crypto::hash_password(password1).unwrap(); + + let password2 = "a bad password"; + let hash2 = Crypto::hash_password(password2).unwrap(); + + // The generated hashes are all 88 bytes long + assert_eq!(hash1.len(), 88); + assert_eq!(hash2.len(), 88); + + // Even if the user passwords are the same they do not generate + // the same hashes because the username is also used as a + // seed when the hash is generated. + assert_ne!(hash1, hash2); + + // Verify password + assert!(Crypto::verify_password_hash("a bad password", &hash1).unwrap()); + assert!(!Crypto::verify_password_hash("wrong password", &hash1).unwrap()); + } + + #[test] + fn gen_keypairs() { + let cred = Crypto::gen_ed25519_document().unwrap(); + let document = base64::decode(cred.keypair_doc).unwrap(); + let key_pair = Ed25519KeyPair::from_pkcs8(&document).unwrap(); + + // Sign the message "hello, world". + const MESSAGE: &[u8] = b"hello, world"; + let sig = key_pair.sign(MESSAGE); + + // Normally an application would extract the bytes of the signature and + // send them in a protocol message to the peer(s). Here we just get the + // public key key directly from the key pair. + let peer_public_key_bytes = key_pair.public_key().as_ref(); + + // Verify the signature of the message using the public key. Normally the + // verifier of the message would parse the inputs to this code out of the + // protocol message(s) sent by the signer. + let peer_public_key = UnparsedPublicKey::new(&ED25519, peer_public_key_bytes); + peer_public_key.verify(MESSAGE, sig.as_ref()).unwrap(); + } + + #[test] + fn symmetric_encryption_decryption() { + let password = "my very weak password"; + let cred = Crypto::gen_ed25519_document().unwrap(); + let document = base64::decode(cred.keypair_doc.clone()).unwrap(); + let expected_key_pair = Ed25519KeyPair::from_pkcs8(&document).unwrap(); + + let wrong_cred = Crypto::gen_ed25519_document().unwrap(); + let wrong_document = base64::decode(wrong_cred.keypair_doc).unwrap(); + let wrong_key_pair = Ed25519KeyPair::from_pkcs8(&wrong_document).unwrap(); + + // encrypt the document + let cyphertext = Crypto::encrypt_keypair_document(&cred.keypair_doc, password).unwrap(); + let cleartext = Crypto::decrypt_keypair_document(&cyphertext, password).unwrap(); + + let cleartext_bytes = base64::decode(cleartext.clone()).unwrap(); + let key_pair = Ed25519KeyPair::from_pkcs8(cleartext_bytes.as_ref()).unwrap(); + + assert_ne!(cyphertext, cleartext); + assert_eq!( + expected_key_pair.public_key().as_ref(), + key_pair.public_key().as_ref() + ); + + assert_ne!( + expected_key_pair.public_key().as_ref(), + wrong_key_pair.public_key().as_ref() + ); + + assert_ne!( + key_pair.public_key().as_ref(), + wrong_key_pair.public_key().as_ref() + ); + } +}