Repo of no-std crates for my personal embedded projects
at main 442 lines 12 kB view raw
1#![no_std] 2 3use core::ops::BitXor; 4 5use chacha20poly1305::{AeadInOut, ChaCha20Poly1305, KeyInit, aead}; 6use dhkem::{ 7 Encapsulate, Expander, Kem, X25519DecapsulationKey, X25519EncapsulationKey, X25519Kem, 8 kem::{Ciphertext, Decapsulate, Key, KeyExport, SharedKey, TryKeyInit}, 9}; 10use elliptic_curve::subtle::ConstantTimeEq; 11 12/// Error type. 13/// 14/// This type is deliberately opaque as to avoid potential side-channel 15/// leakage (e.g. padding oracle). 16#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 17pub struct ProtoError; 18 19impl core::fmt::Display for ProtoError { 20 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 21 f.write_str("ProtoError") 22 } 23} 24 25impl core::error::Error for ProtoError {} 26 27impl From<chacha20poly1305::Error> for ProtoError { 28 fn from(_value: chacha20poly1305::Error) -> Self { 29 Self 30 } 31} 32 33pub struct ClientHandshake(X25519DecapsulationKey); 34 35pub struct EncapsulatedPublicKey(X25519EncapsulationKey); 36 37/// The role of the participant, whether sending/receiving during handshake, 38/// and then whether sending/receiving during communication. 39#[derive(Debug, PartialEq, Eq, Clone, Copy)] 40pub enum Role { 41 /// Participant SENDS data 42 Sender, 43 /// Participant RECEIVES data 44 Receiver, 45} 46 47impl From<Role> for u8 { 48 fn from(value: Role) -> Self { 49 match value { 50 Role::Sender => 0, 51 Role::Receiver => 1, 52 } 53 } 54} 55 56impl BitXor for Role { 57 type Output = u8; 58 59 fn bitxor(self, rhs: Self) -> u8 { 60 u8::from(self) ^ u8::from(rhs) 61 } 62} 63 64impl EncapsulatedPublicKey { 65 pub fn serialize(&self) -> Key<X25519EncapsulationKey> { 66 self.0.to_bytes() 67 } 68 69 pub fn deserialize(buf: &[u8]) -> Result<Self, ProtoError> { 70 Ok(Self( 71 X25519EncapsulationKey::new_from_slice(buf).map_err(|_| ProtoError)?, 72 )) 73 } 74 75 pub fn encapsulate(&self) -> (Ciphertext<X25519Kem>, SharedKey<X25519Kem>) { 76 self.0.encapsulate() 77 } 78} 79 80impl ClientHandshake { 81 pub fn send() -> (EncapsulatedPublicKey, Self) { 82 let (decap, encap) = X25519Kem::generate_keypair(); 83 84 (EncapsulatedPublicKey(encap), Self(decap)) 85 } 86 87 pub fn finish(self, ciphertext: &[u8], psk: &[u8; 32]) -> Result<TransportState, ProtoError> { 88 let shared = self 89 .0 90 .decapsulate_slice(ciphertext) 91 .map_err(|_| ProtoError)?; 92 93 TransportState::init(psk, shared, Role::Sender) 94 } 95} 96 97pub struct ServerHandshake(SharedKey<X25519Kem>); 98 99impl ServerHandshake { 100 pub fn receive(buf: &[u8]) -> Result<(Ciphertext<X25519Kem>, Self), ProtoError> { 101 let encap = EncapsulatedPublicKey::deserialize(buf)?; 102 103 let (ciphertext, sk) = encap.encapsulate(); 104 105 Ok((ciphertext, Self(sk))) 106 } 107 108 pub fn finish(self, psk: &[u8; 32]) -> Result<TransportState, ProtoError> { 109 TransportState::init(psk, self.0, Role::Receiver) 110 } 111} 112 113pub struct SendingState<'a> { 114 transport: &'a TransportState, 115 counter: u64, 116} 117 118impl SendingState<'_> { 119 pub fn encrypt( 120 &mut self, 121 msg: &mut dyn aead::Buffer, 122 associated_data: &[u8], 123 ) -> Result<(), ProtoError> { 124 if self.counter.ct_eq(&u64::MAX).into() { 125 return Err(ProtoError); 126 } 127 128 self.transport.aead.encrypt_in_place( 129 &self 130 .transport 131 .mix_nonce(&self.counter.to_be_bytes(), Role::Sender), 132 associated_data, 133 msg, 134 )?; 135 136 self.counter = self.counter.wrapping_add(1); 137 138 Ok(()) 139 } 140} 141 142pub struct ReceivingState<'a> { 143 transport: &'a TransportState, 144 counter: u64, 145} 146 147impl ReceivingState<'_> { 148 pub fn decrypt( 149 &mut self, 150 msg: &mut dyn aead::Buffer, 151 associated_data: &[u8], 152 ) -> Result<(), ProtoError> { 153 if self.counter.ct_eq(&u64::MAX).into() { 154 return Err(ProtoError); 155 } 156 157 self.transport.aead.decrypt_in_place( 158 &self 159 .transport 160 .mix_nonce(&self.counter.to_be_bytes(), Role::Receiver), 161 associated_data, 162 msg, 163 )?; 164 165 self.counter = self.counter.wrapping_add(1); 166 167 Ok(()) 168 } 169} 170 171#[repr(align(4))] 172pub struct TransportState { 173 aead: ChaCha20Poly1305, 174 client: aead::Nonce<ChaCha20Poly1305>, 175 server: aead::Nonce<ChaCha20Poly1305>, 176 role: Role, 177} 178 179impl TransportState { 180 pub fn init( 181 psk: &[u8; 32], 182 shared: SharedKey<X25519Kem>, 183 role: Role, 184 ) -> Result<Self, ProtoError> { 185 let kdf = Expander::<sha2::Sha256>::new_labeled_hpke(psk, b"Sachy-Crypto", &shared) 186 .map_err(|_| ProtoError)?; 187 188 let mut key = [0u8; 32]; 189 let mut client = aead::Nonce::<ChaCha20Poly1305>::default(); 190 let mut server = aead::Nonce::<ChaCha20Poly1305>::default(); 191 192 kdf.expand(b"SecretKey012", &mut key) 193 .map_err(|_| ProtoError)?; 194 kdf.expand(b"NonceClient*", &mut client) 195 .map_err(|_| ProtoError)?; 196 kdf.expand(b"NonceServer#", &mut server) 197 .map_err(|_| ProtoError)?; 198 199 Ok(Self { 200 aead: ChaCha20Poly1305::new(&key.into()), 201 client, 202 server, 203 role, 204 }) 205 } 206 207 pub fn split(&self) -> (SendingState<'_>, ReceivingState<'_>) { 208 ( 209 SendingState { 210 transport: self, 211 counter: 0, 212 }, 213 ReceivingState { 214 transport: self, 215 counter: 0, 216 }, 217 ) 218 } 219 220 /// Selects which nonce to use for encrypting/decrypting, which matters for 221 /// ensuring the same nonce is used only for one direction of communication. 222 fn select_nonce_context(&self, send: Role) -> &aead::Nonce<ChaCha20Poly1305> { 223 let context_select = self.role ^ send; 224 225 // Handshake ROLE XOR Transport ROLE selects either one or other nonce context, 226 // (0) for first context, (1) for second context 227 // Sending: Client ^ Sender = 0 (select first/client context) 228 // Receiving: Server ^ Receiver = 0 (select first/client context) 229 // Sending: Server ^ Sender = 1 (select second/server context) 230 // Receiving: Client ^ Receiver = 1 (select second/server context) 231 if context_select.ct_eq(&0).into() { 232 &self.client 233 } else { 234 &self.server 235 } 236 } 237 238 fn mix_nonce(&self, position: &[u8; 8], send: Role) -> aead::Nonce<ChaCha20Poly1305> { 239 let mut trump = aead::Nonce::<ChaCha20Poly1305>::default(); 240 241 let epstein = self.select_nonce_context(send); 242 243 let index = trump.len() - position.len(); 244 245 // Copy position bytes into BE format onto derived nonce 246 trump[index..].copy_from_slice(position); 247 248 // XOR the base nonce onto the derived nonce bytes 249 trump 250 .iter_mut() 251 .zip(epstein) 252 .for_each(|(trump, epstein)| *trump ^= *epstein); 253 254 trump 255 } 256} 257 258#[derive(Debug)] 259pub struct BufferSlice<'a> { 260 slice: &'a mut [u8], 261 end: usize, 262} 263 264impl<'a> BufferSlice<'a> { 265 pub fn new(slice: &'a mut [u8]) -> Self { 266 Self { 267 end: slice.len(), 268 slice, 269 } 270 } 271 272 pub fn reset(&mut self) { 273 self.end = self.slice.len(); 274 } 275} 276 277impl AsRef<[u8]> for BufferSlice<'_> { 278 fn as_ref(&self) -> &[u8] { 279 &self.slice[..self.end] 280 } 281} 282 283impl AsMut<[u8]> for BufferSlice<'_> { 284 fn as_mut(&mut self) -> &mut [u8] { 285 &mut self.slice[..self.end] 286 } 287} 288 289impl aead::Buffer for BufferSlice<'_> { 290 fn extend_from_slice(&mut self, other: &[u8]) -> aead::Result<()> { 291 let index = self.end + other.len(); 292 293 if index > self.slice.len() { 294 return Err(aead::Error); 295 } 296 297 self.slice[self.end..index].copy_from_slice(other); 298 299 self.end = index; 300 301 Ok(()) 302 } 303 304 fn truncate(&mut self, len: usize) { 305 self.end = len; 306 } 307} 308 309#[cfg(test)] 310mod tests { 311 use alloc::vec; 312 use chacha20poly1305::aead::Buffer; 313 use dhkem::Generate; 314 use elliptic_curve::array::Array; 315 316 extern crate alloc; 317 318 use super::*; 319 320 #[test] 321 fn buffer_slice_works() { 322 let mut buf = vec![0u8; 128]; 323 324 let mut buf_slice = BufferSlice::new(&mut buf); 325 326 assert_eq!(buf_slice.len(), 128); 327 assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0]), Err(aead::Error)); 328 329 buf_slice.truncate(64); 330 331 assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0, 0, 0, 0]), Ok(())); 332 assert_eq!(buf_slice.len(), 70); 333 334 buf_slice.reset(); 335 336 assert_eq!(buf_slice.len(), 128); 337 } 338 339 #[test] 340 fn handshake_protocol_works() -> Result<(), ProtoError> { 341 let psk: [u8; 32] = [ 342 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 343 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 344 ]; 345 346 let (ek, client) = ClientHandshake::send(); 347 348 // Pretend to send ek across the webz: client -> server 349 let (ciphertext, server) = ServerHandshake::receive(&ek.serialize())?; 350 351 // Pretend to send ciphertext across the webz: server -> client 352 let alice = client.finish(&ciphertext, &psk)?; 353 let bob = server.finish(&psk)?; 354 355 let nonce = aead::Nonce::<ChaCha20Poly1305>::generate(); 356 357 let mut buffer1 = vec![0u8; 64]; 358 let mut buffer2 = vec![0u8; 64]; 359 360 // Using the same nonce to check that the internal AEAD states match. Normally, client/server 361 // would work with unique derived nonces, because nonce reuse is BAD 362 alice.aead.encrypt_in_place(&nonce, &[], &mut buffer1)?; 363 bob.aead.encrypt_in_place(&nonce, &[], &mut buffer2)?; 364 365 // If the nonces match, then we can assume the rest of the internal state is the same too 366 // so the outputs should match each other 367 assert_eq!(&buffer1, &buffer2); 368 369 // Both Transports have derived base nonces for each context. 370 // Client context nonces will not match Server context nonces. 371 assert_eq!(alice.client, bob.client); 372 assert_eq!(alice.server, bob.server); 373 assert_ne!(alice.client, alice.server); 374 assert_ne!(bob.client, bob.server); 375 376 Ok(()) 377 } 378 379 #[test] 380 fn two_way_transport_sync_works() -> Result<(), ProtoError> { 381 let shared_secret = [ 382 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 383 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 384 0x9c, 0x9d, 0x9e, 0x9f, 385 ]; 386 387 let psk: [u8; 32] = [ 388 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 389 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 390 ]; 391 392 let alice = TransportState::init(&psk, Array(shared_secret), Role::Sender)?; 393 let bob = TransportState::init(&psk, Array(shared_secret), Role::Receiver)?; 394 395 let (mut alice_send, mut alice_recv) = alice.split(); 396 let (mut bob_send, mut bob_recv) = bob.split(); 397 398 let orig = b"Test Message, Please ignore."; 399 400 let ad = b"random"; 401 402 let mut msg = orig.to_vec(); 403 404 // a -> b 405 alice_send.encrypt(&mut msg, ad)?; 406 407 assert_ne!(orig.as_slice(), msg.as_slice()); 408 let ct1 = msg.clone(); 409 410 bob_recv.decrypt(&mut msg, ad)?; 411 412 // a -> b 413 alice_send.encrypt(&mut msg, b"")?; 414 415 assert_ne!(msg.as_slice(), ct1.as_slice()); 416 let ct2 = msg.clone(); 417 418 bob_recv.decrypt(&mut msg, b"")?; 419 420 // b -> a 421 bob_send.encrypt(&mut msg, ad)?; 422 423 // None of the ciphertexts should match each other 424 assert_ne!(msg.as_slice(), ct1.as_slice()); 425 assert_ne!(msg.as_slice(), ct2.as_slice()); 426 assert_ne!(ct1.as_slice(), ct2.as_slice()); 427 428 alice_recv.decrypt(&mut msg, ad)?; 429 430 assert_eq!(orig.as_slice(), msg.as_slice()); 431 432 // Counters are tracked from sender to receiver 433 assert_eq!(alice_send.counter, bob_recv.counter); 434 assert_eq!(bob_send.counter, alice_recv.counter); 435 436 // Counters are not linked on the same side 437 assert_ne!(alice_send.counter, alice_recv.counter); 438 assert_ne!(bob_send.counter, bob_recv.counter); 439 440 Ok(()) 441 } 442}