use anyhow::bail;
use chacha20poly1305::{XChaCha20Poly1305, aead::Aead};
use ed25519_dalek::Signer;
use getrandom::{SysRng, rand_core::UnwrapErr};
use classic_mceliece_rust::{
CRYPTO_CIPHERTEXTBYTES as KEM_CIPHERTEXT_SIZE, CRYPTO_SECRETKEYBYTES as KEM_SECRET_KEY_SIZE,
};
mod modes;
pub use modes::{
DH_MODE, DH_WITH_HMAC_MODE, HMAC_MODE, KEM_MODE, KEM_WITH_DH_HYBRID_MODE, KemKeyReader,
SESSION_MODE, SESSION_WITH_KEYGEN_MODE, generate_dh_keys, generate_kem_keys,
};
use modes::{
dh_decrypt, dh_encrypt, dh_extract, hmac_decrypt, hmac_encrypt, hmac_extract, kem_decrypt,
kem_encrypt, kem_extract, session_decrypt, session_encrypt, session_extract,
};
mod decrypt;
pub use decrypt::*;
mod encrypt;
pub use encrypt::*;
mod extract;
use crate::ZEROED_KEY;
pub use extract::*;
const NONCE_SIZE: usize = 24;
const KEY_SIZE: usize = 32;
const SIGNATURE_SIZE: usize = 64;
#[must_use]
pub fn generate_fingerprint() -> ([u8; 32], [u8; 32]) {
let mut rng = UnwrapErr(SysRng);
let fingerprint = ed25519_dalek::SigningKey::generate(&mut rng);
(
fingerprint.to_bytes(),
fingerprint.verifying_key().to_bytes(),
)
}
fn usize_to_bytes(val: usize) -> anyhow::Result<(usize, [u8; 9])> {
let mut out = [0u8; 9];
if val < 128 {
out[0] = u8::try_from((val << 1) | 1)?;
Ok((1, out))
} else {
match val {
128..=255 => {
out[0] = 0 << 2;
out[1] = u8::try_from(val)?;
Ok((2, out))
}
256..=65_535 => {
out[0] = 1 << 2;
out[1..3].copy_from_slice(&u16::try_from(val)?.to_be_bytes());
Ok((3, out))
}
65_536..=4_294_967_295 => {
out[0] = 2 << 2;
out[1..5].copy_from_slice(&u32::try_from(val)?.to_be_bytes());
Ok((5, out))
}
_ => {
out[0] = 3 << 2;
out[1..9].copy_from_slice(&(val as u64).to_be_bytes());
Ok((9, out))
}
}
}
}
fn bytes_to_usize(bytes: &[u8]) -> anyhow::Result<(usize, usize)> {
let first_byte = bytes[0];
if (first_byte & 0b0000_0001) != 0 {
Ok((1, (first_byte >> 1) as usize))
} else {
Ok(match (first_byte >> 2) & 0b0000_0011 {
0 => (2, bytes[1] as usize),
1 => (3, u16::from_be_bytes(bytes[1..3].try_into()?) as usize),
2 => (5, u32::from_be_bytes(bytes[1..5].try_into()?) as usize),
3 => (
9,
usize::try_from(u64::from_be_bytes(bytes[1..9].try_into()?))?,
),
_ => unreachable!(),
})
}
}
fn sign(fingerprint: &[u8; 32], content: &[u8]) -> [u8; 64] {
let fingerprint = ed25519_dalek::SigningKey::from_bytes(fingerprint);
let signature = fingerprint.sign(content);
signature.to_bytes()
}
fn base_encrypt(
fingerprint: &[u8; 32],
nonce: &[u8; 24],
key: &[u8; 32],
content: &mut Vec<u8>,
) -> anyhow::Result<Vec<u8>> {
use chacha20poly1305::KeyInit;
let signature = sign(fingerprint, content);
content.extend(signature);
let content_cipher = XChaCha20Poly1305::new(key.into());
match content_cipher.encrypt(nonce.into(), content.as_ref()) {
Ok(encrypted_content) => Ok(encrypted_content),
Err(_) => bail!("failed to encrypt content"),
}
}
fn verify(signature: &[u8; 64], verifier: &[u8; 32], content: &[u8]) -> anyhow::Result<()> {
if verifier == &ZEROED_KEY {
bail!("verifier cannot be all 0s");
}
let signature = ed25519_dalek::Signature::from_bytes(signature);
let Ok(verifier) = ed25519_dalek::VerifyingKey::from_bytes(verifier) else {
bail!("failed to convert verifying key");
};
match verifier.verify_strict(content, &signature) {
Ok(()) => Ok(()),
Err(_) => bail!("failed to verify signature"),
}
}
fn base_decrypt(
verifier: Option<&[u8; 32]>,
nonce: &[u8; 24],
key: &[u8; 32],
encrypted_content: &[u8],
) -> anyhow::Result<(Vec<u8>, [u8; KEY_SIZE])> {
let content_cipher = {
use chacha20poly1305::KeyInit;
XChaCha20Poly1305::new(key.into())
};
match content_cipher.decrypt(nonce.into(), encrypted_content) {
Ok(mut content) => {
let signature = content.split_off(content.len() - SIGNATURE_SIZE);
match verifier {
Some(verifier) => {
let signature_as_bytes: [u8; SIGNATURE_SIZE] = match signature.try_into() {
Ok(v) => v,
Err(_) => bail!("failed to convert signature to bytes"),
};
verify(&signature_as_bytes, verifier, &content)?;
Ok((content, *key))
}
None => Ok((content, *key)),
}
}
Err(_) => bail!("failed to decrypt content"),
}
}