pub use super::hltypes::SecretKey;
use crate::{
errors::UnknownCryptoError,
hazardous::{
aead,
mac::poly1305::POLY1305_OUTSIZE,
stream::{
chacha20,
xchacha20::{Nonce, XCHACHA_NONCESIZE},
},
},
};
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn seal(secret_key: &SecretKey, plaintext: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
if plaintext.is_empty() {
return Err(UnknownCryptoError);
}
let out_len = match plaintext
.len()
.checked_add(XCHACHA_NONCESIZE + POLY1305_OUTSIZE)
{
Some(min_out_len) => min_out_len,
None => return Err(UnknownCryptoError),
};
let mut dst_out = vec![0u8; out_len];
let nonce = Nonce::generate();
dst_out[..XCHACHA_NONCESIZE].copy_from_slice(nonce.as_ref());
aead::xchacha20poly1305::seal(
&chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?,
&nonce,
plaintext,
None,
&mut dst_out[XCHACHA_NONCESIZE..],
)?;
Ok(dst_out)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn open(
secret_key: &SecretKey,
ciphertext_with_tag_and_nonce: &[u8],
) -> Result<Vec<u8>, UnknownCryptoError> {
if ciphertext_with_tag_and_nonce.len() <= (XCHACHA_NONCESIZE + POLY1305_OUTSIZE) {
return Err(UnknownCryptoError);
}
let mut dst_out =
vec![0u8; ciphertext_with_tag_and_nonce.len() - (XCHACHA_NONCESIZE + POLY1305_OUTSIZE)];
aead::xchacha20poly1305::open(
&chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?,
&Nonce::from_slice(&ciphertext_with_tag_and_nonce[..XCHACHA_NONCESIZE])?,
&ciphertext_with_tag_and_nonce[XCHACHA_NONCESIZE..],
None,
&mut dst_out,
)?;
Ok(dst_out)
}
pub mod streaming {
use super::*;
pub use crate::hazardous::aead::streaming::Nonce;
pub use crate::hazardous::aead::streaming::StreamTag;
#[derive(Debug)]
pub struct StreamSealer {
internal_sealer: aead::streaming::StreamXChaCha20Poly1305,
}
impl StreamSealer {
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn new(secret_key: &SecretKey) -> Result<(Self, Nonce), UnknownCryptoError> {
let nonce = Nonce::generate();
let sk = &aead::streaming::SecretKey::from_slice(secret_key.unprotected_as_bytes())?;
let sealer = Self {
internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(sk, &nonce),
};
Ok((sealer, nonce))
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn seal_chunk(
&mut self,
plaintext: &[u8],
tag: StreamTag,
) -> Result<Vec<u8>, UnknownCryptoError> {
let sealed_chunk_len = plaintext.len().checked_add(aead::streaming::ABYTES);
if sealed_chunk_len.is_none() {
return Err(UnknownCryptoError);
}
let mut sealed_chunk = vec![0u8; sealed_chunk_len.unwrap()];
self.internal_sealer
.seal_chunk(plaintext, None, &mut sealed_chunk, tag)?;
Ok(sealed_chunk)
}
}
#[derive(Debug)]
pub struct StreamOpener {
internal_sealer: aead::streaming::StreamXChaCha20Poly1305,
}
impl StreamOpener {
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn new(secret_key: &SecretKey, nonce: &Nonce) -> Result<Self, UnknownCryptoError> {
let sk = &chacha20::SecretKey::from_slice(secret_key.unprotected_as_bytes())?;
Ok(Self {
internal_sealer: aead::streaming::StreamXChaCha20Poly1305::new(sk, nonce),
})
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn open_chunk(
&mut self,
ciphertext: &[u8],
) -> Result<(Vec<u8>, StreamTag), UnknownCryptoError> {
if ciphertext.len() < aead::streaming::ABYTES {
return Err(UnknownCryptoError);
}
let mut opened_chunk = vec![0u8; ciphertext.len() - aead::streaming::ABYTES];
let tag = self
.internal_sealer
.open_chunk(ciphertext, None, &mut opened_chunk)?;
Ok((opened_chunk, tag))
}
}
}
#[cfg(test)]
mod public {
use super::*;
mod test_seal_open {
use super::*;
#[test]
fn test_auth_enc_encryption_decryption() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let dst_ciphertext = seal(&key, plaintext).unwrap();
assert!(dst_ciphertext.len() == plaintext.len() + (24 + 16));
let dst_plaintext = open(&key, &dst_ciphertext).unwrap();
assert_eq!(plaintext, &dst_plaintext[..]);
}
#[test]
fn test_auth_enc_plaintext_empty_err() {
let key = SecretKey::default();
let plaintext = "".as_bytes();
assert!(seal(&key, plaintext).is_err());
}
#[test]
fn test_auth_enc_ciphertext_less_than_41_err() {
let key = SecretKey::default();
let ciphertext = [0u8; XCHACHA_NONCESIZE + POLY1305_OUTSIZE];
assert!(open(&key, &ciphertext).is_err());
}
#[test]
fn test_modified_nonce_err() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = seal(&key, plaintext).unwrap();
dst_ciphertext[10] ^= 1;
assert!(open(&key, &dst_ciphertext).is_err());
}
#[test]
fn test_modified_ciphertext_err() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = seal(&key, plaintext).unwrap();
dst_ciphertext[25] ^= 1;
assert!(open(&key, &dst_ciphertext).is_err());
}
#[test]
fn test_modified_tag_err() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = seal(&key, plaintext).unwrap();
let dst_ciphertext_len = dst_ciphertext.len();
dst_ciphertext[dst_ciphertext_len - 6] ^= 1;
assert!(open(&key, &dst_ciphertext).is_err());
}
#[test]
fn test_diff_secret_key_err() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let dst_ciphertext = seal(&key, plaintext).unwrap();
let bad_key = SecretKey::default();
assert!(open(&bad_key, &dst_ciphertext).is_err());
}
#[test]
fn test_secret_length_err() {
let key = SecretKey::generate(31).unwrap();
let plaintext = "Secret message".as_bytes();
assert!(seal(&key, plaintext).is_err());
assert!(open(&key, plaintext).is_err());
}
}
mod test_stream_seal_open {
use super::streaming::*;
use super::*;
#[test]
fn test_auth_enc_encryption_decryption() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let plaintext = "Secret message".as_bytes();
let dst_ciphertext = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
assert!(dst_ciphertext.len() == plaintext.len() + 17);
let (dst_plaintext, tag) = opener.open_chunk(&dst_ciphertext).unwrap();
assert_eq!(plaintext, &dst_plaintext[..]);
assert_eq!(tag, StreamTag::MESSAGE);
}
#[test]
fn test_seal_chunk_plaintext_empty_ok() {
let key = SecretKey::default();
let (mut sealer, _) = StreamSealer::new(&key).unwrap();
let plaintext = "".as_bytes();
assert!(sealer.seal_chunk(plaintext, StreamTag::MESSAGE).is_ok());
}
#[test]
fn test_open_chunk_less_than_abytes_err() {
let key = SecretKey::default();
let ciphertext = [0u8; aead::streaming::ABYTES - 1];
let (_, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
assert!(opener.open_chunk(&ciphertext).is_err());
}
#[test]
fn test_open_chunk_abytes_exact_ok() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let ciphertext = sealer
.seal_chunk("".as_bytes(), StreamTag::MESSAGE)
.unwrap();
let (pt, tag) = opener.open_chunk(&ciphertext).unwrap();
assert!(pt.is_empty());
assert!(tag.as_byte() == 0u8);
}
#[test]
fn test_modified_tag_err() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
dst_ciphertext[0] ^= 1;
assert!(opener.open_chunk(&dst_ciphertext).is_err());
}
#[test]
fn test_modified_ciphertext_err() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
dst_ciphertext[1] ^= 1;
assert!(opener.open_chunk(&dst_ciphertext).is_err());
}
#[test]
fn test_modified_mac_err() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let plaintext = "Secret message".as_bytes();
let mut dst_ciphertext = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
let macpos = dst_ciphertext.len() - 1;
dst_ciphertext[macpos] ^= 1;
assert!(opener.open_chunk(&dst_ciphertext).is_err());
}
#[test]
fn test_diff_secret_key_err() {
let key = SecretKey::default();
let plaintext = "Secret message".as_bytes();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let bad_key = SecretKey::default();
let mut opener = StreamOpener::new(&bad_key, &nonce).unwrap();
let dst_ciphertext = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
assert!(opener.open_chunk(&dst_ciphertext).is_err());
}
#[test]
fn test_secret_length_err() {
let key = SecretKey::generate(31).unwrap();
assert!(StreamSealer::new(&key).is_err());
assert!(StreamOpener::new(&key, &Nonce::generate()).is_err());
}
#[test]
fn same_input_generates_different_ciphertext() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let plaintext = "Secret message 1".as_bytes();
let cipher1 = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
let cipher2 = sealer.seal_chunk(plaintext, StreamTag::MESSAGE).unwrap();
assert!(cipher1 != cipher2);
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let (dec1, tag1) = opener.open_chunk(&cipher1).unwrap();
let (dec2, tag2) = opener.open_chunk(&cipher2).unwrap();
assert_eq!(plaintext, &dec1[..]);
assert_eq!(plaintext, &dec2[..]);
assert_eq!(tag1, StreamTag::MESSAGE);
assert_eq!(tag2, StreamTag::MESSAGE);
}
#[test]
fn same_input_on_same_init_different_ct() {
let key = SecretKey::default();
let (mut sealer_first, _) = StreamSealer::new(&key).unwrap();
let (mut sealer_second, _) = StreamSealer::new(&key).unwrap();
let plaintext = "Secret message 1".as_bytes();
let cipher1 = sealer_first
.seal_chunk(plaintext, StreamTag::MESSAGE)
.unwrap();
let cipher2 = sealer_second
.seal_chunk(plaintext, StreamTag::MESSAGE)
.unwrap();
assert!(cipher1 != cipher2);
}
#[test]
fn test_stream_seal_and_open() {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let plaintext1 = "Secret message 1".as_bytes();
let plaintext2 = "Secret message 2".as_bytes();
let plaintext3 = "Secret message 3".as_bytes();
let cipher1 = sealer.seal_chunk(plaintext1, StreamTag::MESSAGE).unwrap();
let cipher2 = sealer.seal_chunk(plaintext2, StreamTag::FINISH).unwrap();
let cipher3 = sealer.seal_chunk(plaintext3, StreamTag::MESSAGE).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let (dec1, tag1) = opener.open_chunk(&cipher1).unwrap();
let (dec2, tag2) = opener.open_chunk(&cipher2).unwrap();
let (dec3, tag3) = opener.open_chunk(&cipher3).unwrap();
assert_eq!(plaintext1, &dec1[..]);
assert_eq!(plaintext2, &dec2[..]);
assert_eq!(plaintext3, &dec3[..]);
assert_eq!(tag1, StreamTag::MESSAGE);
assert_eq!(tag2, StreamTag::FINISH);
assert_eq!(tag3, StreamTag::MESSAGE);
}
}
mod proptest {
use super::streaming::*;
use super::*;
quickcheck! {
fn prop_stream_seal_open_same_input(input: Vec<u8>) -> bool {
let key = SecretKey::default();
let (mut sealer, nonce) = StreamSealer::new(&key).unwrap();
let ct = sealer.seal_chunk(&input[..],StreamTag::MESSAGE).unwrap();
let mut opener = StreamOpener::new(&key, &nonce).unwrap();
let (pt_decrypted, tag) = opener.open_chunk(&ct).unwrap();
input == pt_decrypted && tag == StreamTag::MESSAGE
}
}
quickcheck! {
fn prop_seal_open_same_input(input: Vec<u8>) -> bool {
let pt = if input.is_empty() {
vec![1u8; 10]
} else {
input
};
let sk = SecretKey::default();
let ct = seal(&sk, &pt).unwrap();
let pt_decrypted = open(&sk, &ct).unwrap();
pt == pt_decrypted
}
}
quickcheck! {
fn prop_fail_on_diff_key(input: Vec<u8>) -> bool {
let pt = if input.is_empty() {
vec![1u8; 10]
} else {
input
};
let sk = SecretKey::default();
let sk2 = SecretKey::default();
let ct = seal(&sk, &pt).unwrap();
open(&sk2, &ct).is_err()
}
}
}
}