ordinary-auth 0.7.0

Auth for Ordinary
// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use anyhow::bail;
use chacha20poly1305::{
    XChaCha20Poly1305, XNonce,
    aead::{Aead, AeadCore, KeyInit, OsRng},
};
use uuid::Uuid;

pub enum KeyAlg {
    Blake2SMac256,
}

impl KeyAlg {
    #[must_use]
    pub fn as_byte(&self) -> u8 {
        match self {
            Self::Blake2SMac256 => 1,
        }
    }
}

#[allow(clippy::type_complexity)]
pub fn generate_256_bit_key(
    cipher: &XChaCha20Poly1305,
    rng: &mut OsRng,
) -> anyhow::Result<(Vec<u8>, [u8; 32], Uuid)> {
    let hmac_key: [u8; 32] = XChaCha20Poly1305::generate_key(rng).into();

    let kid = Uuid::now_v7();
    let mut kid_key = kid.as_bytes().to_vec();

    let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
    let encrypted_hmac_key = match cipher.encrypt(&nonce, &hmac_key[..]) {
        Ok(v) => v,
        Err(err) => bail!("{err}"),
    };

    kid_key.extend_from_slice(&nonce[..]);
    kid_key.extend_from_slice(&encrypted_hmac_key[..]);

    Ok((kid_key, hmac_key, kid))
}

pub fn decrypt_256_bit_key(
    cipher: &XChaCha20Poly1305,
    payload: &[u8],
) -> anyhow::Result<(Uuid, [u8; 32])> {
    let kid: [u8; 16] = payload[0..16].try_into()?;

    let nonce = XNonce::from_slice(&payload[16..40]);

    let key = match cipher.decrypt(nonce, &payload[40..]) {
        Ok(v) => v,
        Err(err) => bail!("{err}"),
    };

    let key_fixed: [u8; 32] = key[0..32].try_into()?;

    let kid = Uuid::from_bytes(kid);

    Ok((kid, key_fixed))
}

#[cfg(test)]
mod test {
    use super::*;

    const ENCRYPTION_KEY: [u8; 32] = [0u8; 32];

    #[test]
    fn a_256_bit() -> anyhow::Result<()> {
        let mut rng = OsRng;
        let cipher = XChaCha20Poly1305::new(&ENCRYPTION_KEY.into());

        let (encrypted, key, generated_kid) = generate_256_bit_key(&cipher, &mut rng)?;

        let (kid, decrypted) = decrypt_256_bit_key(&cipher, &encrypted)?;

        assert_eq!(kid, generated_kid);
        assert_eq!(key, decrypted);

        Ok(())
    }
}