Implement proper key types

Signed-off-by: Lee Smet <lee.smet@hotmail.com>
This commit is contained in:
Lee Smet
2025-05-13 17:37:33 +02:00
parent dfe6c91273
commit e44ee83e74
43 changed files with 4180 additions and 7 deletions

112
vault/src/key/asymmetric.rs Normal file
View File

@@ -0,0 +1,112 @@
//! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305
//! for the actual encryption.
use k256::{
SecretKey,
ecdh::diffie_hellman,
};
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.
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> {
if bytes.len() == 64 {
Ok(Self(
k256::PublicKey::from_sec1_bytes(bytes).expect("Key is of valid size"),
))
} else {
Err(CryptoError::InvalidKeySize)
}
}
}

View File

@@ -0,0 +1,76 @@
//! 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,
}
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> {
if bytes.len() == 64 {
Ok(Self(
VerifyingKey::from_sec1_bytes(bytes).expect("Key is of valid size"),
))
} else {
Err(CryptoError::InvalidKeySize)
}
}
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)
}
}

View File

@@ -0,0 +1,86 @@
//! 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;
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 ciphertext = &ciphertext[0..ciphertext_len];
let nonce_bytes = &ciphertext[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)
}
}