use chacha20::cipher::generic_array::{typenum::Unsigned, GenericArray};
use chacha20poly1305::aead::Aead;
use crypto_box::{aead::AeadCore, ChaChaBox, PublicKey, SecretKey};
use litl::{serde::DeserializeError, Litl};
use rand08::rngs::OsRng;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_derive::{Deserialize, Serialize};
use std::{fmt::Debug, marker::PhantomData, ops::Deref};
use thiserror::Error;
use zeroize::Zeroizing;
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename = "Conundrum/EncrPubKey:Curve25519XSalsa20Poly1305")]
pub struct RawAsymmEncrPublicKey(PublicKey);
impl Deref for RawAsymmEncrPublicKey {
type Target = PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Ord for RawAsymmEncrPublicKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.as_bytes().cmp(other.0.as_bytes())
}
}
impl PartialOrd for RawAsymmEncrPublicKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct AsymmEncrPublicKey<S> {
pub_key: RawAsymmEncrPublicKey,
purpose: S,
}
impl<S> Deref for AsymmEncrPublicKey<S> {
type Target = RawAsymmEncrPublicKey;
fn deref(&self) -> &Self::Target {
&self.pub_key
}
}
impl<S: Serialize> Debug for AsymmEncrPublicKey<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Litl::from_se(self).fmt(f)
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "Conundrum/AnonAsymmEncrypted")]
pub struct AnonAsymmEncrypted<T, S> {
ephemeral_pubkey: AsymmEncrPublicKey<S>,
pub recipient_pubkey: AsymmEncrPublicKey<S>,
#[serde(with = "serde_bytes")]
ciphertext: Vec<u8>,
#[serde(skip)]
_marker: PhantomData<T>,
}
impl<T, S: Serialize> Debug for AnonAsymmEncrypted<T, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Litl::from_se(self).fmt(f)
}
}
impl<T, S: Clone> Clone for AnonAsymmEncrypted<T, S> {
fn clone(&self) -> Self {
Self {
ephemeral_pubkey: self.ephemeral_pubkey.clone(),
recipient_pubkey: self.recipient_pubkey.clone(),
ciphertext: self.ciphertext.clone(),
_marker: PhantomData,
}
}
}
impl<T, S: PartialEq> PartialEq for AnonAsymmEncrypted<T, S> {
fn eq(&self, other: &Self) -> bool {
self.ephemeral_pubkey == other.ephemeral_pubkey
&& self.recipient_pubkey == other.recipient_pubkey
&& self.ciphertext == other.ciphertext
}
}
impl<T, S: Eq> Eq for AnonAsymmEncrypted<T, S> {}
impl<'de, T: Serialize + Deserialize<'de>, S: Clone + Default> AnonAsymmEncrypted<T, S> {
pub fn encrypt_for(
recipient_pubkey: &AsymmEncrPublicKey<S>,
data: &T,
) -> AnonAsymmEncrypted<T, S> {
let ephemeral_keypair = AsymmEncrSecretKey::new_random();
let ephemeral_pubkey = ephemeral_keypair.public();
let nonce = nonce_for(&ephemeral_keypair.public(), recipient_pubkey);
let chacha_box = ChaChaBox::new(recipient_pubkey, &ephemeral_keypair);
AnonAsymmEncrypted {
ephemeral_pubkey,
recipient_pubkey: recipient_pubkey.clone(),
ciphertext: chacha_box
.encrypt(&nonce, Litl::write_from(data).as_slice())
.expect("Unable to encrypt"),
_marker: PhantomData,
}
}
}
#[derive(Error, Debug)]
pub enum AsymmDecryptionError {
#[error("Decryption error.")]
DecryptionError,
#[error("Error converting from decrypted bytes.")]
DeserializeError(DeserializeError),
}
type Nonce = GenericArray<u8, <ChaChaBox as AeadCore>::NonceSize>;
fn nonce_for(ephemeral_pubkey: &PublicKey, recipient_pubkey: &PublicKey) -> Nonce {
let mut hasher = blake3::Hasher::new();
hasher.update(ephemeral_pubkey.as_bytes());
hasher.update(recipient_pubkey.as_bytes());
*Nonce::from_slice(
&hasher.finalize().as_bytes()[0..<ChaChaBox as AeadCore>::NonceSize::to_usize()],
)
}
#[derive(Serialize, Deserialize)]
#[serde(rename = "Conundrum/EncrSecretKey:Curve25519XSalsa20Poly1305")]
pub struct RawAsymmEncrSecretKey(
#[serde(deserialize_with = "deserialize_secret_key")]
#[serde(serialize_with = "serialize_secret_key")]
SecretKey,
);
fn deserialize_secret_key<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
where
D: Deserializer<'de>,
{
let bytes = <[u8; crypto_box::KEY_SIZE]>::deserialize(deserializer)?;
Ok(SecretKey::from(bytes))
}
fn serialize_secret_key<S>(key: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
key.as_bytes().serialize(serializer)
}
impl Deref for RawAsymmEncrSecretKey {
type Target = SecretKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl RawAsymmEncrSecretKey {
pub fn new_random() -> Self {
RawAsymmEncrSecretKey(SecretKey::generate(&mut OsRng {}))
}
pub fn public(&self) -> RawAsymmEncrPublicKey {
RawAsymmEncrPublicKey(self.0.public_key())
}
}
#[derive(Serialize, Deserialize)]
pub struct AsymmEncrSecretKey<S> {
keypair: RawAsymmEncrSecretKey,
purpose: S,
}
impl<S: Clone + Default> AsymmEncrSecretKey<S> {
pub fn new_random() -> AsymmEncrSecretKey<S> {
AsymmEncrSecretKey {
keypair: RawAsymmEncrSecretKey::new_random(),
purpose: S::default(),
}
}
pub fn public(&self) -> AsymmEncrPublicKey<S> {
AsymmEncrPublicKey {
pub_key: self.keypair.public(),
purpose: self.purpose.clone(),
}
}
pub fn decrypt<'de, T: Deserialize<'de>>(
&self,
encr: &AnonAsymmEncrypted<T, S>,
) -> Result<T, AsymmDecryptionError> {
let recipient_pubkey = self.public();
let nonce = nonce_for(&encr.ephemeral_pubkey, &recipient_pubkey);
let chacha_box = ChaChaBox::new(&encr.ephemeral_pubkey, self);
let plaintext = Zeroizing::new(
chacha_box
.decrypt(&nonce, encr.ciphertext.as_slice())
.map_err(|_aead_err| AsymmDecryptionError::DecryptionError)?,
);
Litl::read_as::<T>(&plaintext).map_err(AsymmDecryptionError::DeserializeError)
}
}
impl<S> Deref for AsymmEncrSecretKey<S> {
type Target = RawAsymmEncrSecretKey;
fn deref(&self) -> &Self::Target {
&self.keypair
}
}
#[cfg(test)]
mod test {
use litl::Litl;
use serde_derive::{Deserialize, Serialize};
use crate::{
asymm_encr::{AnonAsymmEncrypted, AsymmEncrSecretKey},
purpose,
};
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
struct TestData {
bla: [u8; 4],
}
purpose!(TestPurpose);
#[test]
fn encryption_roundtrip_works() {
let data = TestData { bla: [1, 2, 3, 4] };
let recipient = AsymmEncrSecretKey::<TestPurpose>::new_random();
let encrypted = AnonAsymmEncrypted::encrypt_for(&recipient.public(), &data);
println!("{:?}", Litl::from_se(&encrypted));
let decrypted = recipient.decrypt(&encrypted);
assert_eq!(decrypted.unwrap(), data);
}
#[test]
fn can_not_decrypt_with_wrong_secret_key() {
let data = TestData { bla: [1, 2, 3, 4] };
let recipient = AsymmEncrSecretKey::<TestPurpose>::new_random();
let encrypted = AnonAsymmEncrypted::encrypt_for(&recipient.public(), &data);
let fake_recipient = AsymmEncrSecretKey::<TestPurpose>::new_random();
let decrypted = fake_recipient.decrypt(&encrypted);
assert!(matches!(decrypted, Err(_),));
}
#[test]
fn can_not_decrypt_invalid_ciphertext() {
let data = TestData { bla: [1, 2, 3, 4] };
let recipient = AsymmEncrSecretKey::<TestPurpose>::new_random();
let mut encrypted = AnonAsymmEncrypted::encrypt_for(&recipient.public(), &data);
encrypted.ciphertext = vec![0, 0, 0, 1, 6];
let decrypted = recipient.decrypt(&encrypted);
assert!(matches!(decrypted, Err(_),));
}
}