short_link.rs (12280B)
1 use crate::auth::{RadrootsSimplexSmpEd25519Keypair, verify_signature}; 2 use crate::error::RadrootsSimplexSmpCryptoError; 3 use crate::message::{ 4 RADROOTS_SIMPLEX_SMP_NONCE_LENGTH, RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH, decrypt_padded, 5 encrypt_padded, random_nonce, 6 }; 7 use alloc::vec; 8 use alloc::vec::Vec; 9 use ed25519_dalek::Signer; 10 use hkdf::Hkdf; 11 use radroots_simplex_smp_proto::prelude::RadrootsSimplexSmpQueueLinkData; 12 use sha2::Sha512; 13 use sha3::{Digest, Sha3_256}; 14 15 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_ID_LENGTH: usize = 24; 16 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH: usize = 32; 17 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_CONTACT_KDF_OUTPUT_LENGTH: usize = 56; 18 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_FIXED_DATA_PADDED_LENGTH: usize = 2008; 19 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_USER_DATA_PADDED_LENGTH: usize = 13784; 20 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_CONTACT_INFO: &[u8] = b"SimpleXContactLink"; 21 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_INVITATION_INFO: &[u8] = b"SimpleXInvLink"; 22 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH: usize = 64; 23 24 #[derive(Debug, Clone, PartialEq, Eq)] 25 pub struct RadrootsSimplexSmpContactShortLinkKeyMaterial { 26 pub link_id: Vec<u8>, 27 pub link_data_key: Vec<u8>, 28 } 29 30 #[derive(Debug, Clone, PartialEq, Eq)] 31 pub struct RadrootsSimplexSmpVerifiedShortLinkData { 32 pub fixed_data: Vec<u8>, 33 pub user_data: Vec<u8>, 34 } 35 36 pub fn derive_invitation_short_link_data_key( 37 link_key: &[u8], 38 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> { 39 validate_link_key(link_key)?; 40 hkdf_expand( 41 link_key, 42 RADROOTS_SIMPLEX_SMP_SHORT_LINK_INVITATION_INFO, 43 RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH, 44 ) 45 } 46 47 pub fn derive_contact_short_link_key_material( 48 link_key: &[u8], 49 ) -> Result<RadrootsSimplexSmpContactShortLinkKeyMaterial, RadrootsSimplexSmpCryptoError> { 50 validate_link_key(link_key)?; 51 let output = hkdf_expand( 52 link_key, 53 RADROOTS_SIMPLEX_SMP_SHORT_LINK_CONTACT_INFO, 54 RADROOTS_SIMPLEX_SMP_SHORT_LINK_CONTACT_KDF_OUTPUT_LENGTH, 55 )?; 56 let (link_id, link_data_key) = output.split_at(RADROOTS_SIMPLEX_SMP_SHORT_LINK_ID_LENGTH); 57 Ok(RadrootsSimplexSmpContactShortLinkKeyMaterial { 58 link_id: link_id.to_vec(), 59 link_data_key: link_data_key.to_vec(), 60 }) 61 } 62 63 pub fn sign_short_link_data( 64 root_keypair: &RadrootsSimplexSmpEd25519Keypair, 65 fixed_data: &[u8], 66 user_data: &[u8], 67 ) -> Result<(Vec<u8>, RadrootsSimplexSmpQueueLinkData), RadrootsSimplexSmpCryptoError> { 68 let link_key = short_link_data_hash(fixed_data); 69 let signing_key = root_keypair.signing_key()?; 70 Ok(( 71 link_key, 72 RadrootsSimplexSmpQueueLinkData { 73 fixed_data: sign_payload(&signing_key, fixed_data), 74 user_data: sign_payload(&signing_key, user_data), 75 }, 76 )) 77 } 78 79 pub fn encrypt_short_link_data( 80 link_data_key: &[u8], 81 signed_data: &RadrootsSimplexSmpQueueLinkData, 82 ) -> Result<RadrootsSimplexSmpQueueLinkData, RadrootsSimplexSmpCryptoError> { 83 let fixed_nonce = random_nonce()?; 84 let user_nonce = random_nonce()?; 85 encrypt_short_link_data_with_nonces(link_data_key, signed_data, &fixed_nonce, &user_nonce) 86 } 87 88 pub fn encrypt_short_link_data_with_nonces( 89 link_data_key: &[u8], 90 signed_data: &RadrootsSimplexSmpQueueLinkData, 91 fixed_nonce: &[u8], 92 user_nonce: &[u8], 93 ) -> Result<RadrootsSimplexSmpQueueLinkData, RadrootsSimplexSmpCryptoError> { 94 validate_link_key(link_data_key)?; 95 Ok(RadrootsSimplexSmpQueueLinkData { 96 fixed_data: encrypt_link_data_part( 97 link_data_key, 98 fixed_nonce, 99 &signed_data.fixed_data, 100 RADROOTS_SIMPLEX_SMP_SHORT_LINK_FIXED_DATA_PADDED_LENGTH, 101 )?, 102 user_data: encrypt_link_data_part( 103 link_data_key, 104 user_nonce, 105 &signed_data.user_data, 106 RADROOTS_SIMPLEX_SMP_SHORT_LINK_USER_DATA_PADDED_LENGTH, 107 )?, 108 }) 109 } 110 111 pub fn decrypt_short_link_data( 112 link_data_key: &[u8], 113 encrypted_data: &RadrootsSimplexSmpQueueLinkData, 114 ) -> Result<RadrootsSimplexSmpQueueLinkData, RadrootsSimplexSmpCryptoError> { 115 validate_link_key(link_data_key)?; 116 Ok(RadrootsSimplexSmpQueueLinkData { 117 fixed_data: decrypt_link_data_part( 118 "fixed_data", 119 link_data_key, 120 &encrypted_data.fixed_data, 121 )?, 122 user_data: decrypt_link_data_part("user_data", link_data_key, &encrypted_data.user_data)?, 123 }) 124 } 125 126 pub fn verify_signed_short_link_data( 127 link_key: &[u8], 128 root_public_key: &[u8], 129 signed_data: &RadrootsSimplexSmpQueueLinkData, 130 ) -> Result<RadrootsSimplexSmpVerifiedShortLinkData, RadrootsSimplexSmpCryptoError> { 131 validate_link_key(link_key)?; 132 let fixed = split_signed_payload("fixed_data", &signed_data.fixed_data)?; 133 let user = split_signed_payload("user_data", &signed_data.user_data)?; 134 135 if short_link_data_hash(fixed.payload).as_slice() != link_key { 136 return Err(RadrootsSimplexSmpCryptoError::ShortLinkDataHashMismatch); 137 } 138 verify_signature(fixed.payload, root_public_key, fixed.signature)?; 139 verify_signature(user.payload, root_public_key, user.signature)?; 140 Ok(RadrootsSimplexSmpVerifiedShortLinkData { 141 fixed_data: fixed.payload.to_vec(), 142 user_data: user.payload.to_vec(), 143 }) 144 } 145 146 pub fn decrypt_verify_short_link_data( 147 link_key: &[u8], 148 link_data_key: &[u8], 149 root_public_key: &[u8], 150 encrypted_data: &RadrootsSimplexSmpQueueLinkData, 151 ) -> Result<RadrootsSimplexSmpVerifiedShortLinkData, RadrootsSimplexSmpCryptoError> { 152 let signed_data = decrypt_short_link_data(link_data_key, encrypted_data)?; 153 verify_signed_short_link_data(link_key, root_public_key, &signed_data) 154 } 155 156 fn short_link_data_hash(data: &[u8]) -> Vec<u8> { 157 Sha3_256::digest(data).to_vec() 158 } 159 160 fn sign_payload(signing_key: &ed25519_dalek::SigningKey, payload: &[u8]) -> Vec<u8> { 161 let signature = signing_key.sign(payload); 162 let mut signed = 163 Vec::with_capacity(RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH + payload.len()); 164 signed.extend_from_slice(&signature.to_bytes()); 165 signed.extend_from_slice(payload); 166 signed 167 } 168 169 fn encrypt_link_data_part( 170 link_data_key: &[u8], 171 nonce: &[u8], 172 data: &[u8], 173 padded_len: usize, 174 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> { 175 let mut encrypted = Vec::with_capacity(RADROOTS_SIMPLEX_SMP_NONCE_LENGTH + 16 + padded_len); 176 let nonce_bytes: [u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH] = nonce 177 .try_into() 178 .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidNonceLength(nonce.len()))?; 179 encrypted.extend_from_slice(&nonce_bytes); 180 encrypted.extend_from_slice(&encrypt_padded( 181 link_data_key, 182 &nonce_bytes, 183 data, 184 padded_len, 185 )?); 186 Ok(encrypted) 187 } 188 189 fn decrypt_link_data_part( 190 field: &'static str, 191 link_data_key: &[u8], 192 data: &[u8], 193 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> { 194 if data.len() <= RADROOTS_SIMPLEX_SMP_NONCE_LENGTH { 195 return Err(RadrootsSimplexSmpCryptoError::InvalidShortLinkDataLength { 196 field, 197 length: data.len(), 198 }); 199 } 200 let (nonce, ciphertext) = data.split_at(RADROOTS_SIMPLEX_SMP_NONCE_LENGTH); 201 decrypt_padded(link_data_key, nonce, ciphertext) 202 } 203 204 struct SignedPayload<'a> { 205 signature: &'a [u8], 206 payload: &'a [u8], 207 } 208 209 fn split_signed_payload<'a>( 210 field: &'static str, 211 data: &'a [u8], 212 ) -> Result<SignedPayload<'a>, RadrootsSimplexSmpCryptoError> { 213 if data.len() <= RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH { 214 return Err(RadrootsSimplexSmpCryptoError::InvalidShortLinkDataLength { 215 field, 216 length: data.len(), 217 }); 218 } 219 let (signature, payload) = data.split_at(RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH); 220 Ok(SignedPayload { signature, payload }) 221 } 222 223 fn validate_link_key(link_key: &[u8]) -> Result<(), RadrootsSimplexSmpCryptoError> { 224 if link_key.len() != RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH { 225 return Err(RadrootsSimplexSmpCryptoError::InvalidShortLinkKeyLength( 226 link_key.len(), 227 )); 228 } 229 Ok(()) 230 } 231 232 fn hkdf_expand( 233 ikm: &[u8], 234 info: &[u8], 235 output_len: usize, 236 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> { 237 let hkdf = Hkdf::<Sha512>::new(Some(b""), ikm); 238 let mut output = vec![0_u8; output_len]; 239 hkdf.expand(info, &mut output) 240 .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidKeyDerivationLength(output_len))?; 241 Ok(output) 242 } 243 244 #[cfg(test)] 245 mod tests { 246 use super::*; 247 use ed25519_dalek::SigningKey; 248 249 fn keypair(seed: u8) -> RadrootsSimplexSmpEd25519Keypair { 250 let private_key = [seed; 32]; 251 let signing_key = SigningKey::from_bytes(&private_key); 252 RadrootsSimplexSmpEd25519Keypair { 253 public_key: signing_key.verifying_key().to_bytes().to_vec(), 254 private_key: private_key.to_vec(), 255 } 256 } 257 258 #[test] 259 fn derives_invitation_and_contact_short_link_keys() { 260 let link_key = [7_u8; RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH]; 261 262 let invitation = derive_invitation_short_link_data_key(&link_key).unwrap(); 263 let contact = derive_contact_short_link_key_material(&link_key).unwrap(); 264 265 assert_eq!(invitation.len(), RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH); 266 assert_eq!( 267 contact.link_id.len(), 268 RADROOTS_SIMPLEX_SMP_SHORT_LINK_ID_LENGTH 269 ); 270 assert_eq!( 271 contact.link_data_key.len(), 272 RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH 273 ); 274 assert_ne!(invitation, contact.link_data_key); 275 } 276 277 #[test] 278 fn signs_encrypts_decrypts_and_verifies_short_link_data() { 279 let root = keypair(11); 280 let fixed_data = b"rr-synth-fixed-link-data".to_vec(); 281 let user_data = b"rr-synth-user-link-data".to_vec(); 282 let (link_key, signed_data) = sign_short_link_data(&root, &fixed_data, &user_data).unwrap(); 283 let link_data_key = derive_invitation_short_link_data_key(&link_key).unwrap(); 284 let fixed_nonce = [1_u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH]; 285 let user_nonce = [2_u8; RADROOTS_SIMPLEX_SMP_NONCE_LENGTH]; 286 287 let encrypted = encrypt_short_link_data_with_nonces( 288 &link_data_key, 289 &signed_data, 290 &fixed_nonce, 291 &user_nonce, 292 ) 293 .unwrap(); 294 295 assert_eq!( 296 encrypted.fixed_data.len(), 297 RADROOTS_SIMPLEX_SMP_NONCE_LENGTH 298 + 16 299 + RADROOTS_SIMPLEX_SMP_SHORT_LINK_FIXED_DATA_PADDED_LENGTH 300 ); 301 assert_eq!( 302 encrypted.user_data.len(), 303 RADROOTS_SIMPLEX_SMP_NONCE_LENGTH 304 + 16 305 + RADROOTS_SIMPLEX_SMP_SHORT_LINK_USER_DATA_PADDED_LENGTH 306 ); 307 let verified = 308 decrypt_verify_short_link_data(&link_key, &link_data_key, &root.public_key, &encrypted) 309 .unwrap(); 310 assert_eq!(verified.fixed_data, fixed_data); 311 assert_eq!(verified.user_data, user_data); 312 } 313 314 #[test] 315 fn rejects_short_link_hash_mismatch() { 316 let root = keypair(13); 317 let (link_key, signed_data) = 318 sign_short_link_data(&root, b"rr-synth-fixed", b"rr-synth-user").unwrap(); 319 let mut wrong_link_key = link_key; 320 wrong_link_key[0] ^= 0xff; 321 322 let error = verify_signed_short_link_data(&wrong_link_key, &root.public_key, &signed_data) 323 .unwrap_err(); 324 325 assert!(matches!( 326 error, 327 RadrootsSimplexSmpCryptoError::ShortLinkDataHashMismatch 328 )); 329 } 330 331 #[test] 332 fn rejects_tampered_signed_user_data() { 333 let root = keypair(17); 334 let (link_key, mut signed_data) = 335 sign_short_link_data(&root, b"rr-synth-fixed", b"rr-synth-user").unwrap(); 336 let last = signed_data.user_data.last_mut().unwrap(); 337 *last ^= 0xff; 338 339 let error = 340 verify_signed_short_link_data(&link_key, &root.public_key, &signed_data).unwrap_err(); 341 342 assert!(matches!( 343 error, 344 RadrootsSimplexSmpCryptoError::SignatureVerificationFailed 345 )); 346 } 347 }