...
This commit is contained in:
161
packages/crypt/vault/src/key/asymmetric.rs
Normal file
161
packages/crypt/vault/src/key/asymmetric.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
//! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305
|
||||
//! for the actual encryption.
|
||||
|
||||
use k256::{SecretKey, ecdh::diffie_hellman, elliptic_curve::sec1::ToEncodedPoint};
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::{error::CryptoError, key::symmetric::SymmetricKey};
|
||||
|
||||
/// A keypair for use in asymmetric encryption operations.
|
||||
pub struct AsymmetricKeypair {
|
||||
/// Private part of the key
|
||||
private: SecretKey,
|
||||
/// Public part of the key
|
||||
public: k256::PublicKey,
|
||||
}
|
||||
|
||||
/// The public key part of an asymmetric keypair.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct PublicKey(k256::PublicKey);
|
||||
|
||||
impl AsymmetricKeypair {
|
||||
/// Generates a new random keypair
|
||||
pub fn new() -> Result<Self, CryptoError> {
|
||||
let mut raw_private = [0u8; 32];
|
||||
rand::fill(&mut raw_private);
|
||||
let sk = SecretKey::from_slice(&raw_private)
|
||||
.expect("Key is provided generated with fixed valid size");
|
||||
let pk = sk.public_key();
|
||||
|
||||
Ok(Self {
|
||||
private: sk,
|
||||
public: pk,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new key from existing bytes.
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
|
||||
if bytes.len() == 32 {
|
||||
let sk = SecretKey::from_slice(&bytes).expect("Key was checked to be a valid size");
|
||||
let pk = sk.public_key();
|
||||
Ok(Self {
|
||||
private: sk,
|
||||
public: pk,
|
||||
})
|
||||
} else {
|
||||
Err(CryptoError::InvalidKeySize)
|
||||
}
|
||||
}
|
||||
|
||||
/// View the raw bytes of the private key of this keypair.
|
||||
pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
|
||||
self.private.as_scalar_primitive().to_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Get the public part of this keypair.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
PublicKey(self.public.clone())
|
||||
}
|
||||
|
||||
/// Encrypt data for a receiver. First a shared secret is derived using the own private key and
|
||||
/// the receivers public key. Then, this shared secret is used for symmetric encryption of the
|
||||
/// plaintext. The receiver can decrypt this by generating the same shared secret, using his
|
||||
/// own private key and our public key.
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
remote_key: &PublicKey,
|
||||
plaintext: &[u8],
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
let mut symmetric_key = [0u8; 32];
|
||||
diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
|
||||
.extract::<Sha256>(None)
|
||||
.expand(&[], &mut symmetric_key)
|
||||
.map_err(|_| CryptoError::InvalidKeySize)?;
|
||||
|
||||
let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
|
||||
|
||||
sym_key.encrypt(plaintext)
|
||||
}
|
||||
|
||||
/// Decrypt data from a sender. The remote key must be the public key of the keypair used by
|
||||
/// the sender to encrypt this message.
|
||||
pub fn decrypt(
|
||||
&self,
|
||||
remote_key: &PublicKey,
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
let mut symmetric_key = [0u8; 32];
|
||||
diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
|
||||
.extract::<Sha256>(None)
|
||||
.expand(&[], &mut symmetric_key)
|
||||
.map_err(|_| CryptoError::InvalidKeySize)?;
|
||||
|
||||
let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
|
||||
|
||||
sym_key.decrypt(ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
/// Import a public key from raw bytes
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
|
||||
Ok(Self(k256::PublicKey::from_sec1_bytes(bytes)?))
|
||||
}
|
||||
|
||||
/// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
|
||||
///
|
||||
/// The public key is SEC-1 encoded and compressed.
|
||||
pub fn as_bytes(&self) -> Box<[u8]> {
|
||||
self.0.to_encoded_point(true).to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
/// Export a public key and import it later
|
||||
#[test]
|
||||
fn import_public_key() {
|
||||
let kp = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
let pk1 = kp.public_key();
|
||||
let pk_bytes = pk1.as_bytes();
|
||||
let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
|
||||
|
||||
assert_eq!(pk1, pk2);
|
||||
}
|
||||
/// Make sure 2 random keypairs derive the same shared secret (and thus encryption key), by
|
||||
/// encrypting a random message, decrypting it, and verifying it matches.
|
||||
#[test]
|
||||
fn encrypt_and_decrypt() {
|
||||
let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
|
||||
let pk1 = kp1.public_key();
|
||||
let pk2 = kp2.public_key();
|
||||
|
||||
let message = b"this is a random message to encrypt and decrypt";
|
||||
|
||||
let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
|
||||
let dec = kp2.decrypt(&pk1, &enc).expect("Can decrypt message");
|
||||
|
||||
assert_eq!(message.as_slice(), dec.as_slice());
|
||||
}
|
||||
|
||||
/// Use a different public key for decrypting than the expected one, this should fail the
|
||||
/// decryption process as we use AEAD encryption with the symmetric key.
|
||||
#[test]
|
||||
fn decrypt_with_wrong_key() {
|
||||
let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
let kp3 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
|
||||
|
||||
let pk2 = kp2.public_key();
|
||||
let pk3 = kp3.public_key();
|
||||
|
||||
let message = b"this is a random message to encrypt and decrypt";
|
||||
|
||||
let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
|
||||
let dec = kp2.decrypt(&pk3, &enc);
|
||||
|
||||
assert!(dec.is_err());
|
||||
}
|
||||
}
|
142
packages/crypt/vault/src/key/signature.rs
Normal file
142
packages/crypt/vault/src/key/signature.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
//! An implementation of digitial signatures using secp256k1 ECDSA.
|
||||
|
||||
use k256::ecdsa::{
|
||||
Signature, SigningKey, VerifyingKey,
|
||||
signature::{Signer, Verifier},
|
||||
};
|
||||
|
||||
use crate::error::CryptoError;
|
||||
|
||||
pub struct SigningKeypair {
|
||||
sk: SigningKey,
|
||||
vk: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct PublicKey(VerifyingKey);
|
||||
|
||||
impl SigningKeypair {
|
||||
/// Generates a new random keypair
|
||||
pub fn new() -> Result<Self, CryptoError> {
|
||||
let mut raw_private = [0u8; 32];
|
||||
rand::fill(&mut raw_private);
|
||||
let sk = SigningKey::from_slice(&raw_private)
|
||||
.expect("Key is provided generated with fixed valid size");
|
||||
let vk = sk.verifying_key().to_owned();
|
||||
|
||||
Ok(Self { sk, vk })
|
||||
}
|
||||
|
||||
/// Create a new key from existing bytes.
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
|
||||
if bytes.len() == 32 {
|
||||
let sk = SigningKey::from_slice(&bytes).expect("Key was checked to be a valid size");
|
||||
let vk = sk.verifying_key().to_owned();
|
||||
Ok(Self { sk, vk })
|
||||
} else {
|
||||
Err(CryptoError::InvalidKeySize)
|
||||
}
|
||||
}
|
||||
|
||||
/// View the raw bytes of the private key of this keypair.
|
||||
pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
|
||||
self.sk.as_nonzero_scalar().to_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Get the public part of this keypair.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
PublicKey(self.vk)
|
||||
}
|
||||
|
||||
/// Sign data with the private key of this `SigningKeypair`. Other parties can use the public
|
||||
/// key to verify the signature. The generated signature is a detached signature.
|
||||
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
let sig: Signature = self.sk.sign(message);
|
||||
Ok(sig.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
/// Import a public key from raw bytes
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
|
||||
Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?))
|
||||
}
|
||||
|
||||
/// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
|
||||
///
|
||||
/// The public key is SEC-1 encoded and compressed.
|
||||
pub fn as_bytes(&self) -> Box<[u8]> {
|
||||
self.0.to_encoded_point(true).to_bytes()
|
||||
}
|
||||
|
||||
pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> {
|
||||
let sig = Signature::from_slice(sig).map_err(|_| CryptoError::InvalidKeySize)?;
|
||||
self.0
|
||||
.verify(message, &sig)
|
||||
.map_err(|_| CryptoError::SignatureFailed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
/// Generate a key, get the public key, export the bytes of said public key, import them again
|
||||
/// as a public key, and verify the keys match. This make sure public keys can be exchanged.
|
||||
#[test]
|
||||
fn recover_public_key() {
|
||||
let sk = super::SigningKeypair::new().expect("Can generate new key");
|
||||
let pk = sk.public_key();
|
||||
let pk_bytes = pk.as_bytes();
|
||||
|
||||
let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
|
||||
|
||||
assert_eq!(pk, pk2);
|
||||
}
|
||||
|
||||
/// Sign a message and validate the signature with the public key. Together with the above test
|
||||
/// this makes sure a remote system can receive our public key and validate messages we sign.
|
||||
#[test]
|
||||
fn validate_signature() {
|
||||
let sk = super::SigningKeypair::new().expect("Can generate new key");
|
||||
let pk = sk.public_key();
|
||||
|
||||
let message = b"this is an arbitrary message we want to sign";
|
||||
|
||||
let sig = sk.sign(message).expect("Message can be signed");
|
||||
|
||||
assert!(pk.verify_signature(message, &sig).is_ok());
|
||||
}
|
||||
|
||||
/// Make sure a signature which is tampered with does not pass signature validation
|
||||
#[test]
|
||||
fn corrupt_signature_does_not_validate() {
|
||||
let sk = super::SigningKeypair::new().expect("Can generate new key");
|
||||
let pk = sk.public_key();
|
||||
|
||||
let message = b"this is an arbitrary message we want to sign";
|
||||
|
||||
let mut sig = sk.sign(message).expect("Message can be signed");
|
||||
|
||||
// Tamper with the sig
|
||||
sig[0] = sig[0].wrapping_add(1);
|
||||
|
||||
assert!(pk.verify_signature(message, &sig).is_err());
|
||||
}
|
||||
|
||||
/// Make sure a valid signature does not work for a message which has been modified
|
||||
#[test]
|
||||
fn tampered_message_does_not_validate() {
|
||||
let sk = super::SigningKeypair::new().expect("Can generate new key");
|
||||
let pk = sk.public_key();
|
||||
|
||||
let message = b"this is an arbitrary message we want to sign";
|
||||
let mut message_clone = message.to_vec();
|
||||
|
||||
let sig = sk.sign(message).expect("Message can be signed");
|
||||
|
||||
// Modify the message
|
||||
message_clone[0] = message[0].wrapping_add(1);
|
||||
|
||||
assert!(pk.verify_signature(&message_clone, &sig).is_err());
|
||||
}
|
||||
}
|
151
packages/crypt/vault/src/key/symmetric.rs
Normal file
151
packages/crypt/vault/src/key/symmetric.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
//! An implementation of symmetric keys for ChaCha20Poly1305 encryption.
|
||||
//!
|
||||
//! The ciphertext is authenticated.
|
||||
//! The 12-byte nonce is appended to the generated ciphertext.
|
||||
//! Keys are 32 bytes in size.
|
||||
|
||||
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead};
|
||||
|
||||
use crate::error::CryptoError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct SymmetricKey([u8; 32]);
|
||||
|
||||
/// Size of a nonce in ChaCha20Poly1305.
|
||||
const NONCE_SIZE: usize = 12;
|
||||
|
||||
impl SymmetricKey {
|
||||
/// Generate a new random SymmetricKey.
|
||||
pub fn new() -> Self {
|
||||
let mut key = [0u8; 32];
|
||||
rand::fill(&mut key);
|
||||
Self(key)
|
||||
}
|
||||
|
||||
/// Create a new key from existing bytes.
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<SymmetricKey, CryptoError> {
|
||||
if bytes.len() == 32 {
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(bytes);
|
||||
Ok(SymmetricKey(key))
|
||||
} else {
|
||||
Err(CryptoError::InvalidKeySize)
|
||||
}
|
||||
}
|
||||
|
||||
/// View the raw bytes of this key
|
||||
pub(crate) fn as_raw_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Encrypt a plaintext with the key. A nonce is generated and appended to the end of the
|
||||
/// message.
|
||||
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
// Create cipher
|
||||
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
|
||||
.expect("Key is a fixed 32 byte array so size is always ok");
|
||||
|
||||
// Generate random nonce
|
||||
let mut nonce_bytes = [0u8; NONCE_SIZE];
|
||||
rand::fill(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
// Encrypt message
|
||||
let mut ciphertext = cipher
|
||||
.encrypt(nonce, plaintext)
|
||||
.map_err(|_| CryptoError::EncryptionFailed)?;
|
||||
|
||||
// Append nonce to ciphertext
|
||||
ciphertext.extend_from_slice(&nonce_bytes);
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
/// Decrypts a ciphertext with appended nonce.
|
||||
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
// Check if ciphertext is long enough to contain a nonce
|
||||
if ciphertext.len() <= NONCE_SIZE {
|
||||
return Err(CryptoError::DecryptionFailed);
|
||||
}
|
||||
|
||||
// Extract nonce from the end of ciphertext
|
||||
let ciphertext_len = ciphertext.len() - NONCE_SIZE;
|
||||
let nonce_bytes = &ciphertext[ciphertext_len..];
|
||||
let ciphertext = &ciphertext[0..ciphertext_len];
|
||||
|
||||
// Create cipher
|
||||
let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
|
||||
.expect("Key is a fixed 32 byte array so size is always ok");
|
||||
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
|
||||
// Decrypt message
|
||||
cipher
|
||||
.decrypt(nonce, ciphertext)
|
||||
.map_err(|_| CryptoError::DecryptionFailed)
|
||||
}
|
||||
|
||||
/// Derives a new symmetric key from a password.
|
||||
///
|
||||
/// Derivation is done using pbkdf2 with Sha256 hashing.
|
||||
pub fn derive_from_password(password: &str) -> Self {
|
||||
/// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same
|
||||
/// key. Additionally, it does not really matter what this is, as long as its unique.
|
||||
const SALT: &[u8; 10] = b"vault_salt";
|
||||
/// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this
|
||||
/// also chagnes the generated keys.
|
||||
const ROUNDS: u32 = 100_000;
|
||||
|
||||
let mut key = [0; 32];
|
||||
|
||||
pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), SALT, ROUNDS, &mut key);
|
||||
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
/// Using the same password derives the same key
|
||||
#[test]
|
||||
fn same_password_derives_same_key() {
|
||||
const EXPECTED_KEY: [u8; 32] = [
|
||||
4, 179, 233, 202, 225, 70, 211, 200, 7, 73, 115, 1, 85, 149, 90, 42, 160, 68, 16, 106,
|
||||
136, 19, 197, 195, 153, 145, 179, 21, 37, 13, 37, 90,
|
||||
];
|
||||
const PASSWORD: &str = "test123";
|
||||
|
||||
let key = super::SymmetricKey::derive_from_password(PASSWORD);
|
||||
|
||||
assert_eq!(key.0, EXPECTED_KEY);
|
||||
}
|
||||
|
||||
/// Make sure an encrypted value with some key can be decrypted with the same key
|
||||
#[test]
|
||||
fn can_decrypt() {
|
||||
let key = super::SymmetricKey::new();
|
||||
|
||||
let message = b"this is a message to decrypt";
|
||||
|
||||
let enc = key.encrypt(message).expect("Can encrypt message");
|
||||
let dec = key.decrypt(&enc).expect("Can decrypt message");
|
||||
|
||||
assert_eq!(message.as_slice(), dec.as_slice());
|
||||
}
|
||||
|
||||
/// Make sure a value encrypted with one key can't be decrypted with a different key. Since we
|
||||
/// use AEAD encryption we will notice this when trying to decrypt
|
||||
#[test]
|
||||
fn different_key_cant_decrypt() {
|
||||
let key1 = super::SymmetricKey::new();
|
||||
let key2 = super::SymmetricKey::new();
|
||||
|
||||
let message = b"this is a message to decrypt";
|
||||
|
||||
let enc = key1.encrypt(message).expect("Can encrypt message");
|
||||
let dec = key2.decrypt(&enc);
|
||||
|
||||
assert!(dec.is_err());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user