lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

message.rs (12505B)


      1 use crate::error::RadrootsSimplexSmpCryptoError;
      2 use alloc::vec;
      3 use alloc::vec::Vec;
      4 use getrandom::getrandom;
      5 use hkdf::Hkdf;
      6 use poly1305::Poly1305;
      7 use poly1305::universal_hash::KeyInit as Poly1305KeyInit;
      8 use salsa20::cipher::consts::U10;
      9 use salsa20::cipher::{KeyIvInit, StreamCipher};
     10 use salsa20::{Salsa20, hsalsa};
     11 use sha2::{Digest, Sha256, Sha512};
     12 use subtle::ConstantTimeEq;
     13 use x25519_dalek::{PublicKey, StaticSecret};
     14 
     15 pub const RADROOTS_SIMPLEX_SMP_NONCE_LENGTH: usize = 24;
     16 pub const RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH: usize = 32;
     17 const RADROOTS_SIMPLEX_SMP_AUTH_TAG_LENGTH: usize = 16;
     18 const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INIT_INFO: &[u8] = b"SimpleXSbChainInit";
     19 const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INFO: &[u8] = b"SimpleXSbChain";
     20 const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH: usize = 32;
     21 const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INIT_OUTPUT_LENGTH: usize = 64;
     22 const RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_STEP_OUTPUT_LENGTH: usize = 88;
     23 
     24 type RadrootsSimplexSmpSecretBoxKeyAndNonce = (Vec<u8>, [u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH]);
     25 type RadrootsSimplexSmpSecretBoxChainStep = (
     26     RadrootsSimplexSmpSecretBoxKeyAndNonce,
     27     RadrootsSimplexSmpSecretBoxChainKey,
     28 );
     29 
     30 #[derive(Debug, Clone, PartialEq, Eq)]
     31 pub struct RadrootsSimplexSmpX25519Keypair {
     32     pub public_key: Vec<u8>,
     33     pub private_key: Vec<u8>,
     34 }
     35 
     36 impl RadrootsSimplexSmpX25519Keypair {
     37     pub fn generate() -> Result<Self, RadrootsSimplexSmpCryptoError> {
     38         let mut secret = [0_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH];
     39         getrandom(&mut secret).map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
     40         Ok(Self::from_secret_bytes(secret))
     41     }
     42 
     43     pub fn from_seed(seed: &[u8]) -> Self {
     44         let digest = Sha256::digest(seed);
     45         let mut secret = [0_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH];
     46         secret.copy_from_slice(&digest[..RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH]);
     47         Self::from_secret_bytes(secret)
     48     }
     49 
     50     pub fn public_key_from_private(
     51         private_key: &[u8],
     52     ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
     53         let private: [u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH] =
     54             private_key.try_into().map_err(|_| {
     55                 RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len())
     56             })?;
     57         Ok(PublicKey::from(&StaticSecret::from(private))
     58             .as_bytes()
     59             .to_vec())
     60     }
     61 
     62     fn from_secret_bytes(secret: [u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH]) -> Self {
     63         let private = StaticSecret::from(secret);
     64         let public = PublicKey::from(&private);
     65         Self {
     66             public_key: public.as_bytes().to_vec(),
     67             private_key: private.to_bytes().to_vec(),
     68         }
     69     }
     70 }
     71 
     72 #[derive(Debug, Clone, PartialEq, Eq)]
     73 pub struct RadrootsSimplexSmpSecretBoxChainKey {
     74     bytes: [u8; RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH],
     75 }
     76 
     77 impl RadrootsSimplexSmpSecretBoxChainKey {
     78     fn from_slice(value: &[u8]) -> Result<Self, RadrootsSimplexSmpCryptoError> {
     79         let bytes: [u8; RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH] =
     80             value.try_into().map_err(|_| {
     81                 RadrootsSimplexSmpCryptoError::InvalidSecretBoxChainKeyLength(value.len())
     82             })?;
     83         Ok(Self { bytes })
     84     }
     85 }
     86 
     87 pub fn init_secretbox_chain(
     88     session_identifier: &[u8],
     89     shared_secret: &[u8],
     90 ) -> Result<
     91     (
     92         RadrootsSimplexSmpSecretBoxChainKey,
     93         RadrootsSimplexSmpSecretBoxChainKey,
     94     ),
     95     RadrootsSimplexSmpCryptoError,
     96 > {
     97     let output = hkdf_expand(
     98         session_identifier,
     99         shared_secret,
    100         RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INIT_INFO,
    101         RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INIT_OUTPUT_LENGTH,
    102     )?;
    103     let (first, second) = output.split_at(RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH);
    104     Ok((
    105         RadrootsSimplexSmpSecretBoxChainKey::from_slice(first)?,
    106         RadrootsSimplexSmpSecretBoxChainKey::from_slice(second)?,
    107     ))
    108 }
    109 
    110 pub fn advance_secretbox_chain(
    111     chain_key: &RadrootsSimplexSmpSecretBoxChainKey,
    112 ) -> Result<RadrootsSimplexSmpSecretBoxChainStep, RadrootsSimplexSmpCryptoError> {
    113     let output = hkdf_expand(
    114         b"",
    115         &chain_key.bytes,
    116         RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_INFO,
    117         RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_STEP_OUTPUT_LENGTH,
    118     )?;
    119     let (next_chain_key, remainder) =
    120         output.split_at(RADROOTS_SIMPLEX_SMP_SECRETBOX_CHAIN_KEY_LENGTH);
    121     let (secretbox_key, nonce_bytes) =
    122         remainder.split_at(RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH);
    123     Ok((
    124         (
    125             secretbox_key.to_vec(),
    126             nonce_bytes.try_into().map_err(|_| {
    127                 RadrootsSimplexSmpCryptoError::InvalidNonceLength(nonce_bytes.len())
    128             })?,
    129         ),
    130         RadrootsSimplexSmpSecretBoxChainKey::from_slice(next_chain_key)?,
    131     ))
    132 }
    133 
    134 pub fn derive_shared_secret(
    135     private_key: &[u8],
    136     public_key: &[u8],
    137 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    138     let private: [u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH] = private_key
    139         .try_into()
    140         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len()))?;
    141     let public: [u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH] = public_key
    142         .try_into()
    143         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(public_key.len()))?;
    144     let secret = StaticSecret::from(private).diffie_hellman(&PublicKey::from(public));
    145     Ok(secret.as_bytes().to_vec())
    146 }
    147 
    148 pub fn random_nonce()
    149 -> Result<[u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH], RadrootsSimplexSmpCryptoError> {
    150     let mut nonce = [0_u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH];
    151     getrandom(&mut nonce).map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
    152     Ok(nonce)
    153 }
    154 
    155 pub fn encrypt_padded(
    156     shared_secret: &[u8],
    157     nonce: &[u8],
    158     plaintext: &[u8],
    159     padded_len: usize,
    160 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    161     if plaintext.len().saturating_add(2) > padded_len {
    162         return Err(RadrootsSimplexSmpCryptoError::InvalidMessageLength {
    163             actual: plaintext.len(),
    164             padded: padded_len,
    165         });
    166     }
    167     let mut padded = Vec::with_capacity(padded_len);
    168     padded.extend_from_slice(&(plaintext.len() as u16).to_be_bytes());
    169     padded.extend_from_slice(plaintext);
    170     padded.resize(padded_len, b'#');
    171     encrypt_no_pad(shared_secret, nonce, &padded)
    172 }
    173 
    174 pub fn decrypt_padded(
    175     shared_secret: &[u8],
    176     nonce: &[u8],
    177     ciphertext: &[u8],
    178 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    179     let padded = decrypt_no_pad(shared_secret, nonce, ciphertext)?;
    180     if padded.len() < 2 {
    181         return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
    182             padded.len(),
    183         ));
    184     }
    185     let length = u16::from_be_bytes([padded[0], padded[1]]) as usize;
    186     if length > padded.len().saturating_sub(2) {
    187         return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
    188             padded.len(),
    189         ));
    190     }
    191     Ok(padded[2..2 + length].to_vec())
    192 }
    193 
    194 pub fn encrypt_no_pad(
    195     shared_secret: &[u8],
    196     nonce: &[u8],
    197     plaintext: &[u8],
    198 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    199     let stream = simplex_secretbox_stream(shared_secret, nonce, plaintext.len())?;
    200     let (mac_key, mask) = stream.split_at(RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH);
    201     let mut encrypted = Vec::with_capacity(RADROOTS_SIMPLEX_SMP_AUTH_TAG_LENGTH + plaintext.len());
    202     let mut ciphertext = plaintext.to_vec();
    203     xor_in_place(&mut ciphertext, mask);
    204     let tag = Poly1305::new(mac_key.into()).compute_unpadded(&ciphertext);
    205     encrypted.extend_from_slice(&tag);
    206     encrypted.extend_from_slice(&ciphertext);
    207     Ok(encrypted)
    208 }
    209 
    210 pub fn decrypt_no_pad(
    211     shared_secret: &[u8],
    212     nonce: &[u8],
    213     ciphertext: &[u8],
    214 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    215     if ciphertext.len() < RADROOTS_SIMPLEX_SMP_AUTH_TAG_LENGTH {
    216         return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
    217             ciphertext.len(),
    218         ));
    219     }
    220     let (tag_bytes, encrypted) = ciphertext.split_at(RADROOTS_SIMPLEX_SMP_AUTH_TAG_LENGTH);
    221     let stream = simplex_secretbox_stream(shared_secret, nonce, encrypted.len())?;
    222     let (mac_key, mask) = stream.split_at(RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH);
    223     let tag = Poly1305::new(mac_key.into()).compute_unpadded(encrypted);
    224     if tag.as_slice().ct_eq(tag_bytes).unwrap_u8() != 1 {
    225         return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
    226             ciphertext.len(),
    227         ));
    228     }
    229     let mut buffer = encrypted.to_vec();
    230     xor_in_place(&mut buffer, mask);
    231     Ok(buffer)
    232 }
    233 
    234 fn simplex_secretbox_stream(
    235     shared_secret: &[u8],
    236     nonce: &[u8],
    237     plaintext_len: usize,
    238 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    239     if shared_secret.len() != RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH {
    240         return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    241             shared_secret.len(),
    242         ));
    243     }
    244     let nonce: [u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH] = nonce
    245         .try_into()
    246         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidNonceLength(nonce.len()))?;
    247     let first_key = hsalsa::<U10>(shared_secret.into(), (&[0_u8; 16]).into());
    248     let second_key = hsalsa::<U10>(&first_key, (&nonce[..16]).into());
    249     let mut cipher = Salsa20::new(&second_key, (&nonce[16..]).into());
    250     let mut stream = vec![0_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH + plaintext_len];
    251     cipher.apply_keystream(&mut stream);
    252     Ok(stream)
    253 }
    254 
    255 fn xor_in_place(value: &mut [u8], mask: &[u8]) {
    256     for (byte, mask) in value.iter_mut().zip(mask.iter()) {
    257         *byte ^= mask;
    258     }
    259 }
    260 
    261 fn hkdf_expand(
    262     salt: &[u8],
    263     ikm: &[u8],
    264     info: &[u8],
    265     output_len: usize,
    266 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    267     let hkdf = Hkdf::<Sha512>::new(Some(salt), ikm);
    268     let mut output = vec![0_u8; output_len];
    269     hkdf.expand(info, &mut output)
    270         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidKeyDerivationLength(output_len))?;
    271     Ok(output)
    272 }
    273 
    274 #[cfg(test)]
    275 mod tests {
    276     use super::*;
    277 
    278     #[test]
    279     fn derives_repeatable_keypair_from_seed() {
    280         let first = RadrootsSimplexSmpX25519Keypair::from_seed(b"seed");
    281         let second = RadrootsSimplexSmpX25519Keypair::from_seed(b"seed");
    282         assert_eq!(first, second);
    283     }
    284 
    285     #[test]
    286     fn encrypts_and_decrypts_padded_message() {
    287         let alice = RadrootsSimplexSmpX25519Keypair::from_seed(b"alice");
    288         let bob = RadrootsSimplexSmpX25519Keypair::from_seed(b"bob");
    289         let alice_secret = derive_shared_secret(&alice.private_key, &bob.public_key).unwrap();
    290         let bob_secret = derive_shared_secret(&bob.private_key, &alice.public_key).unwrap();
    291         assert_eq!(alice_secret, bob_secret);
    292 
    293         let nonce = [5_u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH];
    294         let ciphertext = encrypt_padded(&alice_secret, &nonce, b"hello", 32).unwrap();
    295         let plaintext = decrypt_padded(&bob_secret, &nonce, &ciphertext).unwrap();
    296         assert_eq!(plaintext, b"hello");
    297     }
    298 
    299     #[test]
    300     fn encrypt_padded_uses_simplex_transport_padding() {
    301         let key = [7_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH];
    302         let nonce = [11_u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH];
    303         let ciphertext = encrypt_padded(&key, &nonce, b"hello", 12).unwrap();
    304         let padded = decrypt_no_pad(&key, &nonce, &ciphertext).unwrap();
    305 
    306         assert_eq!(&padded[..7], &[0, 5, b'h', b'e', b'l', b'l', b'o']);
    307         assert!(padded[7..].iter().all(|byte| *byte == b'#'));
    308     }
    309 
    310     #[test]
    311     fn derives_repeatable_secretbox_chain_progression() {
    312         let (rcv_first, snd_first) = init_secretbox_chain(b"session", b"shared-secret").unwrap();
    313         let (rcv_second, snd_second) = init_secretbox_chain(b"session", b"shared-secret").unwrap();
    314         assert_eq!(rcv_first, rcv_second);
    315         assert_eq!(snd_first, snd_second);
    316 
    317         let ((send_key, send_nonce), next_send) = advance_secretbox_chain(&snd_first).unwrap();
    318         let ((recv_key, recv_nonce), next_recv) = advance_secretbox_chain(&rcv_first).unwrap();
    319 
    320         assert_eq!(send_key.len(), RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH);
    321         assert_eq!(recv_key.len(), RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH);
    322         assert_ne!(send_nonce, recv_nonce);
    323         assert_ne!(next_send, snd_first);
    324         assert_ne!(next_recv, rcv_first);
    325     }
    326 }