relay-ciphers 0.2.0-beta.4

Cipher implementations for Relay Mail
Documentation
use ed25519_dalek::{Signature, Signer as _, SigningKey, VerifyingKey};
use serde::{Deserialize, Serialize};

use relay_crypto::{
    alg::{AlgorithmError, SigningAlgorithm},
    hex,
    kid::kid_from_key,
    rand_core::{CryptoRng, RngCore},
    record::Key,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Meta {
    #[serde(with = "hex::serde")]
    pub public_key: [u8; 32],
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Secrets {
    #[serde(with = "hex::serde")]
    pub secret: [u8; 32],
}

pub struct Ed25519;

impl Ed25519 {
    pub fn generate(
        &self,
        mut rng: impl RngCore + CryptoRng,
    ) -> Result<(Key, Secrets), AlgorithmError> {
        let mut secret = [0u8; 32];
        rng.fill_bytes(&mut secret);

        let signing = SigningKey::from_bytes(&secret);
        let verifying = signing.verifying_key();
        let public = verifying.to_bytes();
        let kid = kid_from_key(public.to_vec());

        Ok((
            Key {
                alg: Self::alg().to_string(),
                kid,
                data: public.to_vec(),
            },
            Secrets { secret },
        ))
    }
}

impl SigningAlgorithm for Ed25519 {
    type Meta = Meta;
    type Secrets = Secrets;

    fn alg() -> &'static str {
        "ed25519"
    }

    fn sign_inner(
        &self,
        payload: &[u8],
        secrets: &Self::Secrets,
    ) -> Result<(Self::Meta, Vec<u8>), AlgorithmError> {
        let signing = SigningKey::from_bytes(&secrets.secret);
        let public = signing.verifying_key().to_bytes();
        let signature = signing.sign(payload);

        Ok((Meta { public_key: public }, signature.to_bytes().to_vec()))
    }

    fn verify_inner(
        &self,
        meta: &Self::Meta,
        signature: &[u8],
        payload: &[u8],
    ) -> Result<(), AlgorithmError> {
        let signature = Signature::from_slice(signature)
            .map_err(|e| AlgorithmError::Error(format!("invalid signature: {e}")))?;
        let verifying = VerifyingKey::from_bytes(&meta.public_key)
            .map_err(|e| AlgorithmError::InvalidMeta(format!("invalid public key: {e}")))?;

        verifying
            .verify_strict(payload, &signature)
            .map_err(|e| AlgorithmError::Error(format!("signature verification failed: {e}")))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::x25519_xchacha20poly1305::X25519XChaCha20Poly1305;
    use relay_core::{chrono::Utc, prelude::Payload};
    use relay_crypto::{
        alg::{EncryptionAlgorithm, EncryptionContext, SigningAlgorithm},
        record::{Key, PubRecord},
        rng::os_rng_hkdf,
    };

    #[test]
    fn sign_verify() {
        let mut rng = os_rng_hkdf(None, b"test").unwrap();

        let (_key, secrets) = Ed25519.generate(&mut rng).expect("key generation failed");

        let payload = Payload::<()>::new(
            relay_core::info::EncryptionInfo {
                alg: "none".into(),
                data: vec![],
            },
            None,
            b"hello world".to_vec(),
        );

        let signed = Ed25519
            .sign(payload, &serde_json::to_vec(&secrets).unwrap())
            .expect("sign failed");

        assert!(signed.is_signed());

        Ed25519.verify(&signed).expect("verify failed");
    }

    #[test]
    fn encrypt_sign_verify_decrypt() {
        let mut rng = os_rng_hkdf(None, b"test").unwrap();

        let (enc_key, enc_secrets) = X25519XChaCha20Poly1305
            .generate(&mut rng)
            .expect("enc generation failed");

        let (_sign_key, sign_secrets) = Ed25519.generate(&mut rng).expect("sign generation failed");

        let record = PubRecord {
            id: "recipient".to_string(),
            created_at: Utc::now(),
            expires_at: None,
            encryption: enc_key,
            signing: Key {
                alg: String::new(),
                kid: String::new(),
                data: vec![],
            },
        };

        let message = b"Hello, world!";
        let aad = b"aad";

        let payload: Payload<()> = X25519XChaCha20Poly1305
            .encrypt(&mut rng, &record, message, aad)
            .expect("encryption failed");

        let signed = Ed25519
            .sign(payload, &serde_json::to_vec(&sign_secrets).unwrap())
            .expect("sign failed");

        Ed25519.verify(&signed).expect("verify failed");

        let context = X25519XChaCha20Poly1305
            .open(&signed.info, &serde_json::to_vec(&enc_secrets).unwrap())
            .unwrap();

        let decrypted = context.decrypt(&signed, aad).unwrap();

        assert_eq!(decrypted, message);
    }
}