Skip to content
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
271 changes: 69 additions & 202 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,30 @@ name = "devolutions_crypto"
crate-type = ["cdylib", "rlib"]

[dependencies]
aead = { version = "0.5", features = ["stream"] }
aead-stream = { version = "0.6", features = ["alloc"] }
aes = "0.9"
base64 = "0.22"
cbc = { version = "0.2", features = ["block-padding", "alloc"] }
byteorder = "1"
chacha20poly1305 = "0.10"
chacha20poly1305 = { version = "0.11", features = ["zeroize"] }
cfg-if = "1"
hmac = "0.13"
num_enum = "0.7"
pbkdf2 = { version = "0.13", default-features = false }
scrypt = { version = "0.12", default-features = false }
blahaj = { version = "0.6", default-features = false }
blahaj = { version = "0.6", default-features = false, features = ["zeroize_memory"] }
sha2 = "0.11"
strum = { version = "0.28", features = ["derive"] }
subtle = "2"
zeroize = { version = "1.8" }
zeroize = { version = "1.8", features = ["derive"] }
rand = "0.10"
rand_08 = { package = "rand", version = "0.8" }
thiserror = "2.0.12"
typed-builder = "0.23.2"
rust-argon2 = { workspace = true }

ed25519-dalek = { version = "2", features = [ "rand_core" ] }
x25519-dalek = { version = "2", features = [ "static_secrets" ] }
ed25519-dalek = { version = "3", features = [ "rand_core" ] }
x25519-dalek = { version = "3", features = [ "static_secrets" ] }

# used for fuzzing
arbitrary = { version = "1", features = ["derive"], optional = true }
Expand Down
20 changes: 10 additions & 10 deletions src/ciphertext/ciphertext_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::Ciphertext;
use std::convert::TryFrom;

use chacha20poly1305::aead::{Aead, Payload};
use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce};
use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce};

use sha2::{Digest, Sha256};
use x25519_dalek::StaticSecret;
Expand Down Expand Up @@ -97,7 +97,7 @@ impl CiphertextV2Symmetric {
.try_fill_bytes(&mut nonce_bytes)
.map_err(|_| Error::RandomError)?;

let nonce = XNonce::from_slice(&nonce_bytes);
let nonce = XNonce::from(nonce_bytes);

// Authenticate the header
let mut mac_data: Zeroizing<Vec<u8>> = Zeroizing::new(header.into());
Expand All @@ -110,9 +110,9 @@ impl CiphertextV2Symmetric {

// Encrypt
let ciphertext = {
let key = Key::from_slice(key.as_slice());
let cipher = XChaCha20Poly1305::new(key);
cipher.encrypt(nonce, payload)?
let cipher = XChaCha20Poly1305::new_from_slice(key.as_slice())
.expect("key length is hardcoded and correct");
cipher.encrypt(&nonce, payload)?
};

Ok(CiphertextV2Symmetric {
Expand All @@ -136,11 +136,11 @@ impl CiphertextV2Symmetric {

let result = {
// Decrypt
let key = Key::from_slice(key.as_slice());
let nonce = XNonce::from_slice(&self.nonce);
let nonce = XNonce::from(self.nonce);

let cipher = XChaCha20Poly1305::new(key);
cipher.decrypt(nonce, payload)?
let cipher = XChaCha20Poly1305::new_from_slice(key.as_slice())
.expect("key length is hardcoded and correct");
cipher.decrypt(&nonce, payload)?
};

Ok(result)
Expand Down Expand Up @@ -186,7 +186,7 @@ impl CiphertextV2Asymmetric {
) -> Result<Self> {
let public_key = x25519_dalek::PublicKey::from(public_key);

let ephemeral_private_key = StaticSecret::random_from_rng(rand_08::rngs::OsRng);
let ephemeral_private_key = StaticSecret::from(*crate::utils::random_bytes::<32>()?);
let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key);

let key = ephemeral_private_key.diffie_hellman(&public_key);
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ impl From<UnpadError> for Error {
}
}

impl From<aead::Error> for Error {
fn from(_error: aead::Error) -> Error {
impl From<chacha20poly1305::aead::Error> for Error {
fn from(_error: chacha20poly1305::aead::Error) -> Error {
Error::InvalidMac
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/key/key_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::Error;
use super::Result;

use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::Zeroizing;

use std::convert::TryFrom;

Expand Down Expand Up @@ -53,7 +54,7 @@ impl<'a> Arbitrary<'a> for KeyV1Public {

impl From<KeyV1Private> for Vec<u8> {
fn from(key: KeyV1Private) -> Self {
key.key.to_bytes().to_vec()
Zeroizing::new(key.key.to_bytes()).to_vec()
}
}

Expand All @@ -71,10 +72,10 @@ impl TryFrom<&[u8]> for KeyV1Private {
return Err(Error::InvalidLength);
}

let mut key_bytes = [0u8; 32];
let mut key_bytes = Zeroizing::new([0u8; 32]);
key_bytes.copy_from_slice(&key[0..32]);
Ok(Self {
key: StaticSecret::from(key_bytes),
key: StaticSecret::from(*key_bytes),
})
}
}
Expand All @@ -96,7 +97,9 @@ impl TryFrom<&[u8]> for KeyV1Public {
}

pub fn generate_keypair() -> KeyV1Pair {
let private = StaticSecret::random_from_rng(rand_08::rngs::OsRng);
let bytes =
crate::utils::random_bytes::<32>().expect("the OS entropy source should be available");
let private = StaticSecret::from(*bytes);
let public = PublicKey::from(&private);

KeyV1Pair {
Expand Down
11 changes: 4 additions & 7 deletions src/key/secret_key_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use super::Error;
use super::Result;

use rand_08::RngCore;
use zeroize::Zeroizing;

use std::convert::TryFrom;
Expand Down Expand Up @@ -33,8 +32,8 @@ impl<'a> Arbitrary<'a> for SecretKeyV1 {

impl SecretKeyV1 {
pub fn generate() -> Self {
let mut key = Zeroizing::new([0u8; 32]);
rand_08::rngs::OsRng.fill_bytes(key.as_mut());
let key =
crate::utils::random_bytes::<32>().expect("the OS entropy source should be available");
Self { key }
}

Expand All @@ -57,10 +56,8 @@ impl TryFrom<&[u8]> for SecretKeyV1 {
return Err(Error::InvalidLength);
}

let mut key = [0u8; 32];
let mut key = Zeroizing::new([0u8; 32]);
key.copy_from_slice(data);
Ok(Self {
key: Zeroizing::new(key),
})
Ok(Self { key })
}
}
67 changes: 53 additions & 14 deletions src/online_ciphertext/mod.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,82 @@
//! Module for symmetric/asymmetric encryption/decryption.
//! Module for chunked, streaming symmetric/asymmetric encryption/decryption.
//!
//! This module contains everything related to encryption. You can use it to encrypt and decrypt data using either a shared key of a keypair.
//! Either way, the encryption will give you a `Ciphertext`, which has a method to decrypt it.
//! Unlike the [`ciphertext`](super::ciphertext) module, which encrypts a whole buffer at
//! once, this module processes the data one chunk at a time using the STREAM construction.
//! This lets you encrypt or decrypt data that does not fit in memory, or that arrives
//! progressively, without ever holding the full plaintext at once.
//!
//! You start by creating an `OnlineCiphertextEncryptor`, which produces an
//! `OnlineCiphertextHeader`. That header is not secret but is required to decrypt: keep it
//! alongside the ciphertext. You then feed each chunk to `encrypt_next_chunk`, and the final
//! (possibly smaller) chunk to `encrypt_last_chunk`. Every chunk fed to `encrypt_next_chunk`
//! must be exactly `chunk_size` bytes; the last chunk may be smaller. Each produced chunk is
//! `get_tag_size()` bytes larger than its input because of the authentication tag.
//!
//! ### Symmetric
//!
//! ```rust
//! use devolutions_crypto::utils::generate_key;
//! use devolutions_crypto::ciphertext::{ encrypt, CiphertextVersion, Ciphertext };
//! use devolutions_crypto::online_ciphertext::{
//! OnlineCiphertextEncryptor, OnlineCiphertextHeader, OnlineCiphertextVersion,
//! };
//! use std::convert::TryFrom;
//!
//! let key: Vec<u8> = generate_key(32).expect("generate key shouldn't fail");
//! let chunk_size = 16u32;
//!
//! // Encrypt the data one chunk at a time.
//! let mut encryptor =
//! OnlineCiphertextEncryptor::new(&key, b"", chunk_size, OnlineCiphertextVersion::Latest)
//! .expect("creating the encryptor shouldn't fail");
//!
//! // The header is required to decrypt; store or transmit it alongside the ciphertext.
//! let header: Vec<u8> = (&encryptor.get_header()).into();
//!
//! let data = b"somesecretdata";
//! // Each `_next_chunk` input must be exactly `chunk_size` bytes; the last chunk may be smaller.
//! let chunk1 = encryptor.encrypt_next_chunk(b"0123456789abcdef", b"").expect("encrypting a chunk shouldn't fail");
//! let chunk2 = encryptor.encrypt_last_chunk(b"the last chunk", b"").expect("encrypting the last chunk shouldn't fail");
//!
//! let encrypted_data: Ciphertext = encrypt(data, &key, CiphertextVersion::Latest).expect("encryption shouldn't fail");
//! // Decrypt using the header.
//! let header = OnlineCiphertextHeader::try_from(header.as_slice()).expect("the header should be valid");
//! let mut decryptor = header.into_decryptor(&key, b"").expect("creating the decryptor shouldn't fail");
//!
//! let decrypted_data = encrypted_data.decrypt(&key).expect("The decryption shouldn't fail");
//! let mut decrypted = decryptor.decrypt_next_chunk(&chunk1, b"").expect("decrypting a chunk shouldn't fail");
//! decrypted.extend_from_slice(&decryptor.decrypt_last_chunk(&chunk2, b"").expect("decrypting the last chunk shouldn't fail"));
//!
//! assert_eq!(decrypted_data, data);
//! assert_eq!(decrypted, b"0123456789abcdefthe last chunk");
//! ```
//!
//! ### Asymmetric
//! Here, you will need a `PublicKey` to encrypt data and the corresponding
//! `PrivateKey` to decrypt it. You can generate them by using `generate_keypair`
//! in the [Key module](#key).
//! in the [Key module](super::key).
//!
//! ```rust
//! use devolutions_crypto::key::{generate_keypair, KeyVersion, KeyPair};
//! use devolutions_crypto::ciphertext::{ encrypt_asymmetric, CiphertextVersion, Ciphertext };
//! use devolutions_crypto::online_ciphertext::{
//! OnlineCiphertextEncryptor, OnlineCiphertextHeader, OnlineCiphertextVersion,
//! };
//! use std::convert::TryFrom;
//!
//! let keypair: KeyPair = generate_keypair(KeyVersion::Latest);
//! let chunk_size = 16u32;
//!
//! let mut encryptor = OnlineCiphertextEncryptor::new_asymmetric(
//! &keypair.public_key, b"", chunk_size, OnlineCiphertextVersion::Latest,
//! ).expect("creating the encryptor shouldn't fail");
//!
//! let header: Vec<u8> = (&encryptor.get_header()).into();
//!
//! let data = b"somesecretdata";
//! let chunk1 = encryptor.encrypt_next_chunk(b"0123456789abcdef", b"").expect("encrypting a chunk shouldn't fail");
//! let chunk2 = encryptor.encrypt_last_chunk(b"the last chunk", b"").expect("encrypting the last chunk shouldn't fail");
//!
//! let encrypted_data: Ciphertext = encrypt_asymmetric(data, &keypair.public_key, CiphertextVersion::Latest).expect("encryption shouldn't fail");
//! let header = OnlineCiphertextHeader::try_from(header.as_slice()).expect("the header should be valid");
//! let mut decryptor = header.into_decryptor_asymmetric(&keypair.private_key, b"").expect("creating the decryptor shouldn't fail");
//!
//! let decrypted_data = encrypted_data.decrypt_asymmetric(&keypair.private_key).expect("The decryption shouldn't fail");
//! let mut decrypted = decryptor.decrypt_next_chunk(&chunk1, b"").expect("decrypting a chunk shouldn't fail");
//! decrypted.extend_from_slice(&decryptor.decrypt_last_chunk(&chunk2, b"").expect("decrypting the last chunk shouldn't fail"));
//!
//! assert_eq!(decrypted_data, data);
//! assert_eq!(decrypted, b"0123456789abcdefthe last chunk");
//! ```

mod online_ciphertext_v1;
Expand Down
20 changes: 11 additions & 9 deletions src/online_ciphertext/online_ciphertext_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ use super::Result;

use std::borrow::Borrow;

use chacha20poly1305::aead::{
stream::{DecryptorLE31, EncryptorLE31},
Payload,
};
use aead_stream::{DecryptorLE31, EncryptorLE31};
use chacha20poly1305::aead::Payload;
use chacha20poly1305::{KeyInit, XChaCha20Poly1305};

use x25519_dalek::StaticSecret;
Expand Down Expand Up @@ -304,7 +302,8 @@ impl OnlineCiphertextV1Encryptor {

// Derive the key
let key = Zeroizing::new(blake3::derive_key(CONTEXT, key));
let cipher = XChaCha20Poly1305::new(key.as_ref().into());
let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref())
.expect("derived key length is hardcoded and correct");

// Create the STREAM encryptor
let cipher = EncryptorLE31::from_aead(cipher, &nonce.into());
Expand All @@ -330,7 +329,7 @@ impl OnlineCiphertextV1Encryptor {
// Perform a ECDH exchange as per ECIES
let public_key = x25519_dalek::PublicKey::from(public_key);

let ephemeral_private_key = StaticSecret::random_from_rng(rand_08::rngs::OsRng);
let ephemeral_private_key = StaticSecret::from(*crate::utils::random_bytes::<32>()?);
let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key);

let key = ephemeral_private_key.diffie_hellman(&public_key);
Expand All @@ -343,7 +342,8 @@ impl OnlineCiphertextV1Encryptor {

// Derive the key
let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes()));
let cipher = XChaCha20Poly1305::new(key.as_ref().into());
let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref())
.expect("derived key length is hardcoded and correct");

// Create the STREAM encryptor
let cipher = EncryptorLE31::from_aead(cipher, &nonce.into());
Expand All @@ -369,7 +369,8 @@ impl OnlineCiphertextV1Decryptor {
pub fn new(key: &[u8], mut aad: Vec<u8>, header: OnlineCiphertextV1HeaderSymmetric) -> Self {
// Derive the key
let key = Zeroizing::new(blake3::derive_key(CONTEXT, key));
let cipher = XChaCha20Poly1305::new(key.as_ref().into());
let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref())
.expect("derived key length is hardcoded and correct");

// Create the STREAM decryptor
let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into());
Expand All @@ -396,7 +397,8 @@ impl OnlineCiphertextV1Decryptor {

// Derive the key
let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes()));
let cipher = XChaCha20Poly1305::new(key.as_ref().into());
let cipher = XChaCha20Poly1305::new_from_slice(key.as_ref())
.expect("derived key length is hardcoded and correct");

// Create the STREAM decryptor
let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into());
Expand Down
11 changes: 6 additions & 5 deletions src/secret_sharing/secret_sharing_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ use zeroize::Zeroizing;
#[cfg(feature = "fuzz")]
use arbitrary::Arbitrary;

// This will need some work in the Sharks crate to get the zeroize working.
//#[derive(Zeroize)]
//#[zeroize(drop)]
// `blahaj::Share` zeroizes on drop (`zeroize_memory` feature); `threshold` is not secret.
#[derive(Clone)]
pub struct ShareV1 {
threshold: u8,
Expand Down Expand Up @@ -42,8 +40,11 @@ impl core::fmt::Debug for ShareV1 {

impl From<ShareV1> for Vec<u8> {
fn from(share: ShareV1) -> Vec<u8> {
let mut data: Vec<u8> = vec![share.threshold];
data.append(&mut (&share.share).into());
let share_bytes: Zeroizing<Vec<u8>> = Zeroizing::new((&share.share).into());

let mut data: Vec<u8> = Vec::with_capacity(1 + share_bytes.len());
data.push(share.threshold);
data.extend_from_slice(&share_bytes);

data
}
Expand Down
8 changes: 5 additions & 3 deletions src/signing_key/signing_key_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::Error;
use super::Result;

use ed25519_dalek::{SigningKey, VerifyingKey};
use zeroize::Zeroizing;

use std::convert::TryFrom;

Expand Down Expand Up @@ -58,7 +59,7 @@ impl<'a> Arbitrary<'a> for SigningKeyV1Public {

impl From<SigningKeyV1Pair> for Vec<u8> {
fn from(key: SigningKeyV1Pair) -> Self {
key.keypair.to_keypair_bytes().to_vec()
Zeroizing::new(key.keypair.to_keypair_bytes()).to_vec()
}
}

Expand Down Expand Up @@ -99,9 +100,10 @@ impl TryFrom<&[u8]> for SigningKeyV1Public {
}

pub fn generate_signing_keypair() -> SigningKeyV1Pair {
let mut csprng = rand_08::rngs::OsRng;
let secret =
crate::utils::random_bytes::<32>().expect("the OS entropy source should be available");

let keypair = SigningKey::generate(&mut csprng);
let keypair = SigningKey::from_bytes(&secret);

SigningKeyV1Pair { keypair }
}
Expand Down
Loading
Loading