A whimsical STROBE based encryption protocol
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement KEM and transport builder

authored by

Sachymetsu and committed by
Tangled
1e2da6e4 97d6ebac

+1305 -75
+385
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "aead" 7 + version = "0.6.0-rc.10" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" 10 + dependencies = [ 11 + "crypto-common", 12 + "inout", 13 + ] 14 + 15 + [[package]] 16 + name = "anyhow" 17 + version = "1.0.102" 18 + source = "registry+https://github.com/rust-lang/crates.io-index" 19 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 20 + 21 + [[package]] 22 + name = "autocfg" 23 + version = "1.5.0" 24 + source = "registry+https://github.com/rust-lang/crates.io-index" 25 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 26 + 27 + [[package]] 28 + name = "bitflags" 29 + version = "2.11.1" 30 + source = "registry+https://github.com/rust-lang/crates.io-index" 31 + checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" 32 + 33 + [[package]] 34 + name = "block-buffer" 35 + version = "0.12.0" 36 + source = "registry+https://github.com/rust-lang/crates.io-index" 37 + checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" 38 + dependencies = [ 39 + "hybrid-array", 40 + ] 41 + 42 + [[package]] 6 43 name = "cfg-if" 7 44 version = "1.0.4" 8 45 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 24 61 ] 25 62 26 63 [[package]] 64 + name = "crypto-common" 65 + version = "0.2.1" 66 + source = "registry+https://github.com/rust-lang/crates.io-index" 67 + checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" 68 + dependencies = [ 69 + "hybrid-array", 70 + "rand_core", 71 + ] 72 + 73 + [[package]] 27 74 name = "ctutils" 28 75 version = "0.4.2" 29 76 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 33 80 ] 34 81 35 82 [[package]] 83 + name = "digest" 84 + version = "0.11.2" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" 87 + dependencies = [ 88 + "block-buffer", 89 + "crypto-common", 90 + ] 91 + 92 + [[package]] 93 + name = "equivalent" 94 + version = "1.0.2" 95 + source = "registry+https://github.com/rust-lang/crates.io-index" 96 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 97 + 98 + [[package]] 99 + name = "foldhash" 100 + version = "0.1.5" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 103 + 104 + [[package]] 105 + name = "getrandom" 106 + version = "0.4.2" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" 109 + dependencies = [ 110 + "cfg-if", 111 + "libc", 112 + "r-efi", 113 + "rand_core", 114 + "wasip2", 115 + "wasip3", 116 + ] 117 + 118 + [[package]] 119 + name = "hashbrown" 120 + version = "0.15.5" 121 + source = "registry+https://github.com/rust-lang/crates.io-index" 122 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 123 + dependencies = [ 124 + "foldhash", 125 + ] 126 + 127 + [[package]] 128 + name = "hashbrown" 129 + version = "0.17.0" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" 132 + 133 + [[package]] 134 + name = "heck" 135 + version = "0.5.0" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 138 + 139 + [[package]] 36 140 name = "hex" 37 141 version = "0.4.3" 38 142 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 44 148 source = "registry+https://github.com/rust-lang/crates.io-index" 45 149 checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" 46 150 dependencies = [ 151 + "ctutils", 47 152 "typenum", 153 + "zeroize", 154 + ] 155 + 156 + [[package]] 157 + name = "id-arena" 158 + version = "2.3.0" 159 + source = "registry+https://github.com/rust-lang/crates.io-index" 160 + checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" 161 + 162 + [[package]] 163 + name = "indexmap" 164 + version = "2.14.0" 165 + source = "registry+https://github.com/rust-lang/crates.io-index" 166 + checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" 167 + dependencies = [ 168 + "equivalent", 169 + "hashbrown 0.17.0", 170 + "serde", 171 + "serde_core", 172 + ] 173 + 174 + [[package]] 175 + name = "inout" 176 + version = "0.2.2" 177 + source = "registry+https://github.com/rust-lang/crates.io-index" 178 + checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" 179 + dependencies = [ 180 + "hybrid-array", 48 181 ] 49 182 50 183 [[package]] ··· 65 198 ] 66 199 67 200 [[package]] 201 + name = "kem" 202 + version = "0.3.0" 203 + source = "registry+https://github.com/rust-lang/crates.io-index" 204 + checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" 205 + dependencies = [ 206 + "crypto-common", 207 + "rand_core", 208 + ] 209 + 210 + [[package]] 211 + name = "leb128fmt" 212 + version = "0.1.0" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 215 + 216 + [[package]] 68 217 name = "libc" 69 218 version = "0.2.184" 70 219 source = "registry+https://github.com/rust-lang/crates.io-index" 71 220 checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" 72 221 73 222 [[package]] 223 + name = "log" 224 + version = "0.4.29" 225 + source = "registry+https://github.com/rust-lang/crates.io-index" 226 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 227 + 228 + [[package]] 74 229 name = "memchr" 75 230 version = "2.8.0" 76 231 source = "registry+https://github.com/rust-lang/crates.io-index" 77 232 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 78 233 79 234 [[package]] 235 + name = "ml-kem" 236 + version = "0.3.0-rc.2" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "04437cb1a66c0b78740927b76cc61f218344b9f6ef3dd430e283274a718ef0e9" 239 + dependencies = [ 240 + "hybrid-array", 241 + "kem", 242 + "module-lattice", 243 + "rand_core", 244 + "sha3", 245 + "zeroize", 246 + ] 247 + 248 + [[package]] 249 + name = "module-lattice" 250 + version = "0.2.1" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "164eb3faeaecbd14b0b2a917c1b4d0c035097a9c559b0bed85c2cdd032bc8faa" 253 + dependencies = [ 254 + "ctutils", 255 + "hybrid-array", 256 + "num-traits", 257 + "zeroize", 258 + ] 259 + 260 + [[package]] 261 + name = "num-traits" 262 + version = "0.2.19" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 265 + dependencies = [ 266 + "autocfg", 267 + ] 268 + 269 + [[package]] 270 + name = "prettyplease" 271 + version = "0.2.37" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 274 + dependencies = [ 275 + "proc-macro2", 276 + "syn", 277 + ] 278 + 279 + [[package]] 80 280 name = "proc-macro2" 81 281 version = "1.0.106" 82 282 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 95 295 ] 96 296 97 297 [[package]] 298 + name = "r-efi" 299 + version = "6.0.0" 300 + source = "registry+https://github.com/rust-lang/crates.io-index" 301 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 302 + 303 + [[package]] 304 + name = "rand_core" 305 + version = "0.10.1" 306 + source = "registry+https://github.com/rust-lang/crates.io-index" 307 + checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" 308 + 309 + [[package]] 310 + name = "semver" 311 + version = "1.0.28" 312 + source = "registry+https://github.com/rust-lang/crates.io-index" 313 + checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" 314 + 315 + [[package]] 98 316 name = "serde" 99 317 version = "1.0.228" 100 318 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 147 365 ] 148 366 149 367 [[package]] 368 + name = "sha3" 369 + version = "0.11.0" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" 372 + dependencies = [ 373 + "digest", 374 + "keccak", 375 + ] 376 + 377 + [[package]] 150 378 name = "syn" 151 379 version = "2.0.117" 152 380 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 170 398 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 171 399 172 400 [[package]] 401 + name = "unicode-xid" 402 + version = "0.2.6" 403 + source = "registry+https://github.com/rust-lang/crates.io-index" 404 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 405 + 406 + [[package]] 407 + name = "wasip2" 408 + version = "1.0.3+wasi-0.2.9" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" 411 + dependencies = [ 412 + "wit-bindgen 0.57.1", 413 + ] 414 + 415 + [[package]] 416 + name = "wasip3" 417 + version = "0.4.0+wasi-0.3.0-rc-2026-01-06" 418 + source = "registry+https://github.com/rust-lang/crates.io-index" 419 + checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" 420 + dependencies = [ 421 + "wit-bindgen 0.51.0", 422 + ] 423 + 424 + [[package]] 425 + name = "wasm-encoder" 426 + version = "0.244.0" 427 + source = "registry+https://github.com/rust-lang/crates.io-index" 428 + checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 429 + dependencies = [ 430 + "leb128fmt", 431 + "wasmparser", 432 + ] 433 + 434 + [[package]] 435 + name = "wasm-metadata" 436 + version = "0.244.0" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" 439 + dependencies = [ 440 + "anyhow", 441 + "indexmap", 442 + "wasm-encoder", 443 + "wasmparser", 444 + ] 445 + 446 + [[package]] 447 + name = "wasmparser" 448 + version = "0.244.0" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 451 + dependencies = [ 452 + "bitflags", 453 + "hashbrown 0.15.5", 454 + "indexmap", 455 + "semver", 456 + ] 457 + 458 + [[package]] 173 459 name = "wharrgarbl" 174 460 version = "0.1.0" 175 461 dependencies = [ 462 + "aead", 176 463 "ctutils", 464 + "getrandom", 177 465 "hex", 466 + "hybrid-array", 178 467 "keccak", 468 + "ml-kem", 469 + "rand_core", 179 470 "serde", 180 471 "serde-big-array", 181 472 "serde_json", 182 473 "zeroize", 474 + ] 475 + 476 + [[package]] 477 + name = "wit-bindgen" 478 + version = "0.51.0" 479 + source = "registry+https://github.com/rust-lang/crates.io-index" 480 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 481 + dependencies = [ 482 + "wit-bindgen-rust-macro", 483 + ] 484 + 485 + [[package]] 486 + name = "wit-bindgen" 487 + version = "0.57.1" 488 + source = "registry+https://github.com/rust-lang/crates.io-index" 489 + checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" 490 + 491 + [[package]] 492 + name = "wit-bindgen-core" 493 + version = "0.51.0" 494 + source = "registry+https://github.com/rust-lang/crates.io-index" 495 + checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" 496 + dependencies = [ 497 + "anyhow", 498 + "heck", 499 + "wit-parser", 500 + ] 501 + 502 + [[package]] 503 + name = "wit-bindgen-rust" 504 + version = "0.51.0" 505 + source = "registry+https://github.com/rust-lang/crates.io-index" 506 + checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" 507 + dependencies = [ 508 + "anyhow", 509 + "heck", 510 + "indexmap", 511 + "prettyplease", 512 + "syn", 513 + "wasm-metadata", 514 + "wit-bindgen-core", 515 + "wit-component", 516 + ] 517 + 518 + [[package]] 519 + name = "wit-bindgen-rust-macro" 520 + version = "0.51.0" 521 + source = "registry+https://github.com/rust-lang/crates.io-index" 522 + checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" 523 + dependencies = [ 524 + "anyhow", 525 + "prettyplease", 526 + "proc-macro2", 527 + "quote", 528 + "syn", 529 + "wit-bindgen-core", 530 + "wit-bindgen-rust", 531 + ] 532 + 533 + [[package]] 534 + name = "wit-component" 535 + version = "0.244.0" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 538 + dependencies = [ 539 + "anyhow", 540 + "bitflags", 541 + "indexmap", 542 + "log", 543 + "serde", 544 + "serde_derive", 545 + "serde_json", 546 + "wasm-encoder", 547 + "wasm-metadata", 548 + "wasmparser", 549 + "wit-parser", 550 + ] 551 + 552 + [[package]] 553 + name = "wit-parser" 554 + version = "0.244.0" 555 + source = "registry+https://github.com/rust-lang/crates.io-index" 556 + checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" 557 + dependencies = [ 558 + "anyhow", 559 + "id-arena", 560 + "indexmap", 561 + "log", 562 + "semver", 563 + "serde", 564 + "serde_derive", 565 + "serde_json", 566 + "unicode-xid", 567 + "wasmparser", 183 568 ] 184 569 185 570 [[package]]
+6
Cargo.toml
··· 13 13 keccak = "0.2" 14 14 ctutils = { version = "0.4.2", default-features = false } 15 15 zeroize = { version = "1.8.2", default-features = false } 16 + ml-kem = { version = "0.3.0-rc.2", features = ["zeroize"] } 17 + hybrid-array = { version = "0.4.10", features = ["alloc"] } 18 + rand_core = { version = "0.10", default-features = false } 19 + aead = { version = "0.6.0-rc.10" } 16 20 17 21 [dev-dependencies] 18 22 serde_json = "1" 19 23 hex = "0.4" 20 24 serde = { version = "1.0.210", default-features = false, features = ["derive"] } 21 25 serde-big-array = { version = "0.5" } 26 + getrandom = { version = "0.4.2", features = ["sys_rng"] } 27 + aead = { version = "0.6.0-rc.10", features = ["alloc"] }
+18 -19
src/basic_kats.rs
··· 4 4 //! Some tests in the original repo are omitted because we can no longer panic from this 5 5 //! implementation's public API surface. 6 6 7 + use aead::consts::{U16, U65}; 8 + use hybrid_array::Array; 9 + 7 10 use crate::{ 8 11 keccakf::KECCAK_BUFFER_SIZE, 9 - strobe::{Role, SecurityParameter, StrobeState}, 12 + strobe::{Role, StrobeSecurity, StrobeState}, 10 13 }; 11 14 12 15 extern crate std; 13 16 14 17 #[test] 15 18 fn test_init_128() { 16 - let s = StrobeState::new(b"", SecurityParameter::B128, Role::Sender); 19 + let s = StrobeState::new(b"", StrobeSecurity::B128, Role::Sender); 17 20 18 21 let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ 19 22 0x9c, 0x7f, 0x16, 0x8f, 0xf8, 0xfd, 0x55, 0xda, 0x2a, 0xa7, 0x3c, 0x23, 0x55, 0x65, 0x35, ··· 37 40 38 41 #[test] 39 42 fn test_init_256() { 40 - let s = StrobeState::new(b"", SecurityParameter::B256, Role::Sender); 43 + let s = StrobeState::new(b"", StrobeSecurity::B256, Role::Sender); 41 44 42 45 let expected_st: [u8; KECCAK_BUFFER_SIZE] = [ 43 46 0x37, 0xc1, 0x15, 0x06, 0xed, 0x61, 0xe7, 0xda, 0x7c, 0x1a, 0x2f, 0x2c, 0x1f, 0x49, 0x74, ··· 62 65 #[test] 63 66 fn test_metadata() { 64 67 // We will accumulate output over 3 operations and 3 meta-operations 65 - let mut s = StrobeState::new(b"metadatatest", SecurityParameter::B256, Role::Sender); 68 + let mut s = StrobeState::new(b"metadatatest", StrobeSecurity::B256, Role::Sender); 66 69 let mut output = std::vec::Vec::new(); 67 70 68 71 let buf = b"meta1"; ··· 116 119 117 120 #[test] 118 121 fn test_seq() { 119 - let mut s = StrobeState::new(b"seqtest", SecurityParameter::B256, Role::Sender); 122 + let mut s = StrobeState::new(b"seqtest", StrobeSecurity::B256, Role::Sender); 120 123 121 124 let mut buf = [0u8; 10]; 122 125 s.prf(&mut buf[..]); ··· 172 175 #[test] 173 176 fn test_enc_correctness() { 174 177 let orig_msg = b"Hello there"; 175 - let mut tx = StrobeState::new(b"enccorrectnesstest", SecurityParameter::B256, Role::Sender); 176 - let mut rx = StrobeState::new( 177 - b"enccorrectnesstest", 178 - SecurityParameter::B256, 179 - Role::Receiver, 180 - ); 178 + let mut tx = StrobeState::new(b"enccorrectnesstest", StrobeSecurity::B256, Role::Sender); 179 + let mut rx = StrobeState::new(b"enccorrectnesstest", StrobeSecurity::B256, Role::Receiver); 181 180 182 181 tx.key(b"the-combination-on-my-luggage"); 183 182 rx.key(b"the-combination-on-my-luggage"); ··· 192 191 193 192 #[test] 194 193 fn test_mac_correctness_and_soundness() { 195 - let mut tx = StrobeState::new(b"mactest", SecurityParameter::B256, Role::Sender); 196 - let mut rx = StrobeState::new(b"mactest", SecurityParameter::B256, Role::Receiver); 194 + let mut tx = StrobeState::new(b"mactest", StrobeSecurity::B256, Role::Sender); 195 + let mut rx = StrobeState::new(b"mactest", StrobeSecurity::B256, Role::Receiver); 197 196 198 197 // Just do some stuff with the state 199 198 ··· 201 200 let mut msg = b"attack at dawn".to_vec(); 202 201 tx.send_enc(msg.as_mut_slice()); 203 202 204 - let mut mac = [0u8; 16]; 203 + let mut mac: Array<u8, U16> = Array([0u8; 16]); 205 204 tx.send_mac(&mut mac[..]); 206 205 207 206 rx.key(b"secretsauce"); ··· 221 220 222 221 #[test] 223 222 fn test_long_inputs() { 224 - let mut s = StrobeState::new(b"bigtest", SecurityParameter::B256, Role::Sender); 223 + let mut s = StrobeState::new(b"bigtest", StrobeSecurity::B256, Role::Sender); 225 224 const BIG_N: usize = 9823; 226 225 const SMALL_N: usize = 65; 227 226 let big_data = [0x34u8; BIG_N]; ··· 241 240 s.send_enc(big_data.to_vec().as_mut_slice()); 242 241 s.meta_recv_enc(big_data.to_vec().as_mut_slice()); 243 242 s.recv_enc(big_data.to_vec().as_mut_slice()); 244 - let _ = s.meta_recv_mac(&small_data); 245 - let _ = s.recv_mac(&small_data); 243 + let _ = s.meta_recv_mac::<U65>(&small_data.into()); 244 + let _ = s.recv_mac::<U65>(&small_data.into()); 246 245 247 246 let mut big_buf = [0u8; BIG_N]; 248 247 let mut small_buf = [0u8; SMALL_N]; ··· 278 277 fn test_streaming_correctness() { 279 278 // Compute a few things without breaking up their inputs 280 279 let one_shot_st: std::vec::Vec<u8> = { 281 - let mut s = StrobeState::new(b"streamingtest", SecurityParameter::B256, Role::Receiver); 280 + let mut s = StrobeState::new(b"streamingtest", StrobeSecurity::B256, Role::Receiver); 282 281 283 282 s.ad(b"mynonce"); 284 283 ··· 294 293 }; 295 294 // Now do the same thing but stream the inputs 296 295 let streamed_st: std::vec::Vec<u8> = { 297 - let mut s = StrobeState::new(b"streamingtest", SecurityParameter::B256, Role::Receiver); 296 + let mut s = StrobeState::new(b"streamingtest", StrobeSecurity::B256, Role::Receiver); 298 297 299 298 s.ad(b"my"); 300 299 s.ad(b"nonce");
+112
src/buffer_slice.rs
··· 1 + #[derive(Debug)] 2 + pub struct BufferSlice<'slice> { 3 + buffer: &'slice mut [u8], 4 + end: usize, 5 + } 6 + 7 + impl<'slice> BufferSlice<'slice> { 8 + pub const fn new(buffer: &'slice mut [u8]) -> Self { 9 + Self { 10 + end: buffer.len(), 11 + buffer, 12 + } 13 + } 14 + 15 + pub const fn reset(&mut self) { 16 + self.end = self.buffer.len(); 17 + } 18 + } 19 + 20 + impl AsRef<[u8]> for BufferSlice<'_> { 21 + fn as_ref(&self) -> &[u8] { 22 + &self.buffer[..self.end] 23 + } 24 + } 25 + 26 + impl AsMut<[u8]> for BufferSlice<'_> { 27 + fn as_mut(&mut self) -> &mut [u8] { 28 + &mut self.buffer[..self.end] 29 + } 30 + } 31 + 32 + impl aead::Buffer for BufferSlice<'_> { 33 + fn extend_from_slice(&mut self, other: &[u8]) -> aead::Result<()> { 34 + let index = self.end + other.len(); 35 + 36 + if index > self.buffer.len() { 37 + return Err(aead::Error); 38 + } 39 + 40 + self.buffer[self.end..index].copy_from_slice(other); 41 + 42 + self.end = index; 43 + 44 + Ok(()) 45 + } 46 + 47 + fn truncate(&mut self, len: usize) { 48 + self.end = len.min(self.buffer.len()); 49 + } 50 + } 51 + 52 + #[cfg(test)] 53 + mod tests { 54 + use super::*; 55 + use aead::Buffer; 56 + 57 + #[test] 58 + fn defaults_to_full_buffer_size() { 59 + let mut buf = alloc::vec![0u8; 128]; 60 + 61 + let buf_slice = BufferSlice::new(&mut buf); 62 + 63 + assert_eq!(buf_slice.len(), 128); 64 + } 65 + 66 + #[test] 67 + fn truncate_doesnt_extend_past_max_buffer_length() { 68 + let mut buf = alloc::vec![0u8; 128]; 69 + 70 + let mut buf_slice = BufferSlice::new(&mut buf); 71 + 72 + buf_slice.truncate(64); 73 + 74 + assert_eq!(buf_slice.len(), 64); 75 + 76 + buf_slice.truncate(256); 77 + 78 + assert_eq!(buf_slice.len(), 128); 79 + } 80 + 81 + #[test] 82 + fn extend_from_slice_only_works_within_slice_length() { 83 + let mut buf = alloc::vec![0u8; 128]; 84 + 85 + let mut buf_slice = BufferSlice::new(&mut buf); 86 + 87 + assert_eq!(buf_slice.len(), 128); 88 + assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0]), Err(aead::Error)); 89 + 90 + buf_slice.truncate(64); 91 + 92 + assert_eq!(buf_slice.extend_from_slice(&[0, 0, 0, 0, 0, 0]), Ok(())); 93 + assert_eq!(buf_slice.len(), 70); 94 + } 95 + 96 + #[test] 97 + fn reset_sets_length_to_buffer_max_length() { 98 + let mut buf = alloc::vec![0u8; 128]; 99 + 100 + let mut buf_slice = BufferSlice::new(&mut buf); 101 + 102 + assert_eq!(buf_slice.len(), 128); 103 + 104 + buf_slice.truncate(64); 105 + 106 + assert_eq!(buf_slice.len(), 64); 107 + 108 + buf_slice.reset(); 109 + 110 + assert_eq!(buf_slice.len(), 128); 111 + } 112 + }
+278
src/handshake.rs
··· 1 + use aead::Buffer; 2 + use hybrid_array::typenum::Unsigned; 3 + use rand_core::CryptoRng; 4 + 5 + use crate::{ 6 + WHARRGHARBL_PROTO, 7 + kem::{KemEncap, KemSecurity}, 8 + strobe::{Role, StrobeSecurity, StrobeState}, 9 + transport::{self, AeadState, AeadStrobe}, 10 + }; 11 + 12 + pub struct ClientHandshake { 13 + kem_sec: KemSecurity, 14 + sec_param: StrobeSecurity, 15 + strobe: StrobeState, 16 + decap: Option<crate::kem::KemDecap>, 17 + } 18 + 19 + impl ClientHandshake { 20 + pub fn new(kem_sec: KemSecurity, sec_param: StrobeSecurity, psk: Option<&[u8; 32]>) -> Self { 21 + let mut strobe = StrobeState::new(WHARRGHARBL_PROTO.as_bytes(), sec_param, Role::Sender); 22 + 23 + if let Some(psk) = psk { 24 + strobe.key(psk); 25 + } 26 + 27 + strobe.meta_ad(&kem_sec.to_bytes()); 28 + strobe.meta_ad(&sec_param.to_bytes()); 29 + 30 + Self { 31 + kem_sec, 32 + sec_param, 33 + strobe, 34 + decap: None, 35 + } 36 + } 37 + 38 + pub fn send(&mut self, rng: &mut impl CryptoRng, buf: &mut dyn Buffer) -> aead::Result<()> { 39 + let mut tag: aead::Tag<AeadStrobe> = Default::default(); 40 + let (decap, encap) = self.kem_sec.generate_with_rng(rng); 41 + 42 + let written = encap.serialize(buf.as_mut())?; 43 + 44 + buf.truncate(written); 45 + 46 + self.strobe.send_clr(buf.as_ref()); 47 + self.strobe.send_mac(&mut tag); 48 + self.strobe.ratchet(self.sec_param.rachet_bytes()); 49 + 50 + buf.extend_from_slice(&tag)?; 51 + 52 + self.decap = Some(decap); 53 + 54 + Ok(()) 55 + } 56 + 57 + pub fn receive(&mut self, ciphertext: &[u8]) -> aead::Result<AeadState> { 58 + let decap = self.decap.as_mut().ok_or(aead::Error)?; 59 + 60 + let tag = ciphertext 61 + .len() 62 + .checked_sub(<transport::AeadStrobe as aead::AeadCore>::TagSize::to_usize()) 63 + .ok_or(aead::Error)?; 64 + 65 + let (ciphertext, tag) = ciphertext.split_at(tag); 66 + 67 + let tag: aead::Tag<AeadStrobe> = tag.try_into().unwrap(); 68 + 69 + self.strobe.recv_clr(ciphertext); 70 + self.strobe.recv_mac(&tag)?; 71 + self.strobe.ratchet(self.sec_param.rachet_bytes()); 72 + 73 + let shared = decap.decapsulate(ciphertext)?; 74 + 75 + Ok(self.finish(shared)) 76 + } 77 + 78 + fn finish(&mut self, shared: ml_kem::SharedKey) -> AeadState { 79 + self.strobe.ad(&shared); 80 + 81 + let mut key: aead::Key<AeadStrobe> = Default::default(); 82 + let mut inbound: aead::Nonce<AeadStrobe> = Default::default(); 83 + let mut outbound: aead::Nonce<AeadStrobe> = Default::default(); 84 + 85 + self.strobe.prf(&mut key); 86 + self.strobe.prf(&mut inbound); 87 + self.strobe.prf(&mut outbound); 88 + 89 + AeadState::new(key, self.sec_param, outbound, inbound, Role::Sender) 90 + } 91 + } 92 + 93 + pub struct ServerHandshake { 94 + kem_sec: KemSecurity, 95 + sec_param: StrobeSecurity, 96 + strobe: StrobeState, 97 + } 98 + 99 + impl ServerHandshake { 100 + pub fn new(kem_sec: KemSecurity, sec_param: StrobeSecurity, psk: Option<&[u8; 32]>) -> Self { 101 + let mut strobe = StrobeState::new(WHARRGHARBL_PROTO.as_bytes(), sec_param, Role::Receiver); 102 + 103 + if let Some(psk) = psk { 104 + strobe.key(psk); 105 + } 106 + 107 + strobe.meta_ad(&kem_sec.to_bytes()); 108 + strobe.meta_ad(&sec_param.to_bytes()); 109 + 110 + Self { 111 + kem_sec, 112 + sec_param, 113 + strobe, 114 + } 115 + } 116 + 117 + pub fn respond( 118 + &mut self, 119 + rng: &mut impl CryptoRng, 120 + buf: &mut dyn Buffer, 121 + ) -> aead::Result<AeadState> { 122 + let slice = buf.as_ref(); 123 + 124 + let tag = slice 125 + .len() 126 + .checked_sub(<transport::AeadStrobe as aead::AeadCore>::TagSize::to_usize()) 127 + .ok_or(aead::Error)?; 128 + 129 + let (encap, tag) = buf.as_ref().split_at(tag); 130 + 131 + let tag: aead::Tag<AeadStrobe> = tag.try_into().unwrap(); 132 + 133 + self.strobe.recv_clr(encap); 134 + self.strobe.recv_mac(&tag)?; 135 + self.strobe.ratchet(self.sec_param.rachet_bytes()); 136 + 137 + let (cipher, shared) = KemEncap::encapsulate_from_slice(self.kem_sec, encap, rng)?; 138 + 139 + let mut tag: aead::Tag<AeadStrobe> = Default::default(); 140 + 141 + buf.truncate(0); 142 + 143 + self.strobe.send_clr(cipher.as_ref()); 144 + self.strobe.send_mac(&mut tag); 145 + self.strobe.ratchet(self.sec_param.rachet_bytes()); 146 + 147 + buf.extend_from_slice(cipher.as_ref())?; 148 + buf.extend_from_slice(&tag)?; 149 + 150 + Ok(self.finish(shared)) 151 + } 152 + 153 + fn finish(&mut self, shared: ml_kem::SharedKey) -> AeadState { 154 + self.strobe.ad(&shared); 155 + 156 + let mut key: aead::Key<AeadStrobe> = Default::default(); 157 + let mut inbound: aead::Nonce<AeadStrobe> = Default::default(); 158 + let mut outbound: aead::Nonce<AeadStrobe> = Default::default(); 159 + 160 + self.strobe.prf(&mut key); 161 + self.strobe.prf(&mut inbound); 162 + self.strobe.prf(&mut outbound); 163 + 164 + AeadState::new(key, self.sec_param, outbound, inbound, Role::Receiver) 165 + } 166 + } 167 + 168 + #[cfg(test)] 169 + mod tests { 170 + use crate::buffer_slice::BufferSlice; 171 + 172 + use super::*; 173 + 174 + #[test] 175 + fn handshake_protocol_happy_path() -> aead::Result<()> { 176 + let psk: [u8; 32] = [ 177 + 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 178 + 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 179 + ]; 180 + 181 + let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); 182 + let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); 183 + 184 + // BufferSlice acts as our transport across the webz 185 + let mut buf = alloc::vec![0u8; 2048]; 186 + let mut buf = BufferSlice::new(&mut buf); 187 + 188 + let mut rng = rand_core::UnwrapErr(getrandom::SysRng); 189 + 190 + alice.send(&mut rng, &mut buf)?; 191 + 192 + // Pretend to send ek across the webz: client -> server 193 + let bob = bob.respond(&mut rng, &mut buf)?; 194 + 195 + // Pretend to send ciphertext across the webz: server -> client 196 + let alice = alice.receive(buf.as_ref())?; 197 + 198 + assert_eq!(alice.aead.key, bob.aead.key); 199 + 200 + // Both AeadStates have derived base nonces for each context. 201 + // Inbound context nonces will not match Outbound context nonces. 202 + assert_eq!(alice.trump, bob.trump); 203 + assert_eq!(alice.epstein, bob.epstein); 204 + assert_ne!(alice.trump, alice.epstein); 205 + assert_ne!(bob.trump, bob.epstein); 206 + 207 + Ok(()) 208 + } 209 + 210 + #[test] 211 + #[should_panic] 212 + fn handshake_fails_with_strobe_security_mismatch() { 213 + let psk: [u8; 32] = [ 214 + 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 215 + 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 216 + ]; 217 + 218 + let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); 219 + let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B256, Some(&psk)); 220 + 221 + // BufferSlice acts as our transport across the webz 222 + let mut buf = alloc::vec![0u8; 2048]; 223 + let mut buf = BufferSlice::new(&mut buf); 224 + 225 + let mut rng = rand_core::UnwrapErr(getrandom::SysRng); 226 + 227 + alice.send(&mut rng, &mut buf).unwrap(); 228 + 229 + // Pretend to send ek across the webz: client -> server 230 + let _bob = bob.respond(&mut rng, &mut buf).unwrap(); 231 + } 232 + 233 + #[test] 234 + #[should_panic] 235 + fn handshake_fails_with_kem_security_mismatch() { 236 + let psk: [u8; 32] = [ 237 + 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 238 + 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 239 + ]; 240 + 241 + let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); 242 + let mut bob = ServerHandshake::new(KemSecurity::Level3, StrobeSecurity::B128, Some(&psk)); 243 + 244 + // BufferSlice acts as our transport across the webz 245 + let mut buf = alloc::vec![0u8; 2048]; 246 + let mut buf = BufferSlice::new(&mut buf); 247 + 248 + let mut rng = rand_core::UnwrapErr(getrandom::SysRng); 249 + 250 + alice.send(&mut rng, &mut buf).unwrap(); 251 + 252 + // Pretend to send ek across the webz: client -> server 253 + let _bob = bob.respond(&mut rng, &mut buf).unwrap(); 254 + } 255 + 256 + #[test] 257 + #[should_panic] 258 + fn handshake_fails_with_psk_mismatch() { 259 + let psk: [u8; 32] = [ 260 + 31, 48, 29, 177, 88, 236, 186, 84, 65, 51, 214, 243, 174, 24, 45, 101, 229, 129, 62, 261 + 132, 45, 174, 183, 65, 89, 73, 107, 177, 77, 90, 164, 251, 262 + ]; 263 + 264 + let mut alice = ClientHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, None); 265 + let mut bob = ServerHandshake::new(KemSecurity::Level1, StrobeSecurity::B128, Some(&psk)); 266 + 267 + // BufferSlice acts as our transport across the webz 268 + let mut buf = alloc::vec![0u8; 2048]; 269 + let mut buf = BufferSlice::new(&mut buf); 270 + 271 + let mut rng = rand_core::UnwrapErr(getrandom::SysRng); 272 + 273 + alice.send(&mut rng, &mut buf).unwrap(); 274 + 275 + // Pretend to send ek across the webz: client -> server 276 + let _bob = bob.respond(&mut rng, &mut buf).unwrap(); 277 + } 278 + }
+8 -7
src/herding_kats/harness.rs
··· 2 2 3 3 use std::{string::String, vec::Vec}; 4 4 5 + use aead::consts::U14; 5 6 use serde::{Deserialize, Deserializer, de}; 6 7 7 - use crate::strobe::{Role, SecurityParameter, StrobeState}; 8 + use crate::strobe::{Role, StrobeSecurity, StrobeState}; 8 9 9 10 /// The harness we will put on our KATs so we can herd them and make them do tests. 10 11 /// (This is the top-level structure of the JSON we find in the test vectors) ··· 12 13 struct KatHarness { 13 14 proto_string: String, 14 15 #[serde(deserialize_with = "security_param_from_bits")] 15 - security: SecurityParameter, 16 + security: StrobeSecurity, 16 17 operations: Vec<KatOperation>, 17 18 } 18 19 ··· 79 80 "recv_ENC" => s.recv_enc(data), 80 81 "send_MAC" => s.send_mac(data), 81 82 "recv_MAC" => s 82 - .recv_mac::<14>(data.as_ref().try_into().unwrap()) 83 + .recv_mac::<U14>(data.as_ref().try_into().unwrap()) 83 84 .unwrap_or(()), 84 85 "RATCHET" => panic!("Got RATCHET op without length input"), 85 86 _ => panic!("Unexpected op name: {}", op_name), ··· 95 96 "recv_ENC" => s.meta_recv_enc(data), 96 97 "send_MAC" => s.meta_send_mac(data), 97 98 "recv_MAC" => s 98 - .meta_recv_mac::<14>(data.as_ref().try_into().unwrap()) 99 + .meta_recv_mac::<U14>(data.as_ref().try_into().unwrap()) 99 100 .unwrap_or(()), 100 101 "RATCHET" => panic!("Got RATCHET op without length input"), 101 102 _ => panic!("Unexpected op name: {}", op_name), ··· 153 154 154 155 fn security_param_from_bits<'de, D: Deserializer<'de>>( 155 156 deserializer: D, 156 - ) -> Result<SecurityParameter, D::Error> { 157 + ) -> Result<StrobeSecurity, D::Error> { 157 158 match u64::deserialize(deserializer)? { 158 - 128 => Ok(SecurityParameter::B128), 159 - 256 => Ok(SecurityParameter::B256), 159 + 128 => Ok(StrobeSecurity::B128), 160 + 256 => Ok(StrobeSecurity::B256), 160 161 n => Err(de::Error::custom(std::format!( 161 162 "Invalid security parameter: {}", 162 163 n
+137
src/kem.rs
··· 1 + use alloc::boxed::Box; 2 + use ml_kem::{Decapsulate, Encapsulate, Kem, KeyExport, SharedKey, TryKeyInit}; 3 + use rand_core::CryptoRng; 4 + use zeroize::Zeroize; 5 + 6 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 + #[repr(u8)] 8 + pub enum KemSecurity { 9 + Level1 = 0, 10 + Level3 = 1, 11 + } 12 + 13 + pub enum KemEncap { 14 + Level1(Box<ml_kem::EncapsulationKey512>), 15 + Level3(Box<ml_kem::EncapsulationKey768>), 16 + } 17 + 18 + pub struct KemCipher(Box<[u8]>); 19 + 20 + pub enum KemDecap { 21 + Level1(Box<ml_kem::DecapsulationKey512>), 22 + Level3(Box<ml_kem::DecapsulationKey768>), 23 + } 24 + 25 + impl KemSecurity { 26 + pub fn generate_with_rng(&self, rng: &mut impl CryptoRng) -> (KemDecap, KemEncap) { 27 + match self { 28 + Self::Level1 => { 29 + let (decap, encap) = ml_kem::MlKem512::generate_keypair_from_rng(rng); 30 + 31 + ( 32 + KemDecap::Level1(Box::new(decap)), 33 + KemEncap::Level1(Box::new(encap)), 34 + ) 35 + } 36 + Self::Level3 => { 37 + let (decap, encap) = ml_kem::MlKem768::generate_keypair_from_rng(rng); 38 + 39 + ( 40 + KemDecap::Level3(Box::new(decap)), 41 + KemEncap::Level3(Box::new(encap)), 42 + ) 43 + } 44 + } 45 + } 46 + 47 + pub const fn to_bytes(self) -> [u8; 1] { 48 + (self as u8).to_le_bytes() 49 + } 50 + } 51 + 52 + impl KemEncap { 53 + pub fn encapsulate_from_slice( 54 + sec: KemSecurity, 55 + buf: &[u8], 56 + rng: &mut impl CryptoRng, 57 + ) -> Result<(KemCipher, ml_kem::SharedKey), aead::Error> { 58 + match sec { 59 + KemSecurity::Level1 => { 60 + let encap = 61 + ml_kem::EncapsulationKey512::new_from_slice(buf).map_err(|_| aead::Error)?; 62 + 63 + let (ct, shared) = encap.encapsulate_with_rng(rng); 64 + 65 + Ok((KemCipher(ct.into()), shared)) 66 + } 67 + KemSecurity::Level3 => { 68 + let encap = 69 + ml_kem::EncapsulationKey768::new_from_slice(buf).map_err(|_| aead::Error)?; 70 + 71 + let (ct, shared) = encap.encapsulate_with_rng(rng); 72 + 73 + Ok((KemCipher(ct.into()), shared)) 74 + } 75 + } 76 + } 77 + 78 + pub fn serialize(&self, buf: &mut [u8]) -> Result<usize, aead::Error> { 79 + match self { 80 + Self::Level1(encap) => { 81 + let data = encap.to_bytes(); 82 + let (slice, _) = buf.split_at_mut_checked(data.len()).ok_or(aead::Error)?; 83 + 84 + slice.copy_from_slice(&data); 85 + 86 + Ok(data.len()) 87 + } 88 + Self::Level3(encap) => { 89 + let data = encap.to_bytes(); 90 + let (slice, _) = buf.split_at_mut_checked(data.len()).ok_or(aead::Error)?; 91 + 92 + slice.copy_from_slice(&data); 93 + 94 + Ok(data.len()) 95 + } 96 + } 97 + } 98 + } 99 + 100 + impl AsRef<[u8]> for KemCipher { 101 + fn as_ref(&self) -> &[u8] { 102 + self.0.as_ref() 103 + } 104 + } 105 + 106 + impl KemCipher { 107 + pub const fn len(&self) -> usize { 108 + self.0.len() 109 + } 110 + 111 + pub const fn is_empty(&self) -> bool { 112 + self.0.is_empty() 113 + } 114 + } 115 + 116 + impl Zeroize for KemCipher { 117 + fn zeroize(&mut self) { 118 + self.0.zeroize(); 119 + } 120 + } 121 + 122 + impl Drop for KemCipher { 123 + fn drop(&mut self) { 124 + self.zeroize(); 125 + } 126 + } 127 + 128 + impl KemDecap { 129 + pub fn decapsulate(&mut self, ciphertext: &[u8]) -> Result<SharedKey, aead::Error> { 130 + let key = match self { 131 + Self::Level1(decap) => decap.decapsulate_slice(ciphertext), 132 + Self::Level3(decap) => decap.decapsulate_slice(ciphertext), 133 + }; 134 + 135 + key.map_err(|_| aead::Error) 136 + } 137 + }
+7 -11
src/lib.rs
··· 3 3 4 4 #[cfg(test)] 5 5 mod basic_kats; 6 + pub mod buffer_slice; 7 + pub mod handshake; 6 8 #[cfg(test)] 7 9 mod herding_kats; 8 10 mod keccakf; 11 + pub mod kem; 9 12 mod opflags; 10 13 mod ops; 11 14 pub mod strobe; 15 + pub mod transport; 16 + 17 + extern crate alloc; 12 18 13 19 /// Version of Strobe that this crate implements. 14 20 pub static STROBE_VERSION: &str = "1.0.2"; 15 - 16 - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 17 - pub struct GarbledError; 18 - 19 - impl core::fmt::Display for GarbledError { 20 - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 21 - f.write_str("Protocol Failure") 22 - } 23 - } 24 - 25 - impl core::error::Error for GarbledError {} 21 + pub static WHARRGHARBL_PROTO: &str = "WGBL-v0.0-STv1.0.2";
+1 -16
src/opflags.rs
··· 2 2 3 3 use ctutils::{Choice, CtAssign, CtEq}; 4 4 5 - use crate::strobe::Role; 6 - 7 5 #[derive(Clone, Copy, PartialEq, Eq, Hash)] 8 6 #[repr(transparent)] 9 7 pub struct OpFlags(u8); ··· 38 36 } 39 37 40 38 pub fn set(&mut self, flags: OpFlags, cond: Choice) { 41 - const OPS: [fn(&mut OpFlags, OpFlags); 2] = [OpFlags::remove, OpFlags::insert]; 39 + static OPS: [fn(&mut OpFlags, OpFlags); 2] = [OpFlags::remove, OpFlags::insert]; 42 40 43 41 let index = (cond.to_u8() & 1) as usize; 44 42 ··· 120 118 impl BitXorAssign for OpFlags { 121 119 fn bitxor_assign(&mut self, rhs: Self) { 122 120 self.0 ^= rhs.0; 123 - } 124 - } 125 - 126 - impl BitXorAssign<Role> for OpFlags { 127 - fn bitxor_assign(&mut self, rhs: Role) { 128 - self.0 ^= rhs as u8; 129 - } 130 - } 131 - 132 - // This is for a specific case to toggle INBOUND 133 - impl BitXorAssign<Choice> for OpFlags { 134 - fn bitxor_assign(&mut self, rhs: Choice) { 135 - self.0 ^= rhs.to_u8(); 136 121 } 137 122 } 138 123
+45 -22
src/strobe.rs
··· 1 + use core::ops::BitXor; 2 + 1 3 use ctutils::{Choice, CtAssign, CtEq, CtLt, CtSelect}; 4 + use hybrid_array::{Array, ArraySize}; 2 5 use zeroize::Zeroize; 3 6 4 7 use crate::{ 5 - GarbledError, STROBE_VERSION, 8 + STROBE_VERSION, 6 9 keccakf::{KECCAK_BUFFER_SIZE, KeccakF1600}, 7 10 opflags::OpFlags, 8 11 ops, ··· 18 21 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 22 #[repr(u8)] 20 23 pub enum Role { 21 - Sender, 22 - Receiver, 24 + Sender = 0, 25 + Receiver = 1, 26 + } 27 + 28 + impl BitXor for Role { 29 + fn bitxor(self, rhs: Self) -> Self::Output { 30 + (self as u8) ^ (rhs as u8) 31 + } 32 + 33 + type Output = u8; 23 34 } 24 35 25 36 #[derive(Debug, Clone, Copy)] 26 - #[repr(usize)] 27 - pub enum SecurityParameter { 37 + #[repr(u16)] 38 + pub enum StrobeSecurity { 28 39 B128 = 128, 29 40 B256 = 256, 30 41 } 31 42 43 + impl StrobeSecurity { 44 + pub const fn rachet_bytes(self) -> usize { 45 + (self as usize) >> 3 46 + } 47 + 48 + pub const fn to_bytes(self) -> [u8; 2] { 49 + (self as u16).to_le_bytes() 50 + } 51 + } 52 + 32 53 #[derive(Clone)] 33 54 pub struct StrobeState { 34 55 /// Internal Keccak state 35 56 pub(crate) state: KeccakF1600, 36 57 /// Security parameter (128 or 256 bits) 37 - sec: SecurityParameter, 58 + sec: StrobeSecurity, 38 59 /// This is the `R` parameter in the Strobe spec 39 60 rate: usize, 40 61 /// Index into `state` ··· 103 124 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 104 125 f.write_str("Strobe-Keccak-")?; 105 126 match self.sec { 106 - SecurityParameter::B128 => f.write_str("128")?, 107 - SecurityParameter::B256 => f.write_str("256")?, 127 + StrobeSecurity::B128 => f.write_str("128")?, 128 + StrobeSecurity::B256 => f.write_str("256")?, 108 129 } 109 130 f.write_str("/1600-v")?; 110 131 f.write_str(STROBE_VERSION) ··· 122 143 } 123 144 124 145 impl StrobeState { 125 - /// Makes a new `StrobeTransport` object with a given protocol byte string and security parameter. 126 - pub fn new(protocol: &[u8], sec: SecurityParameter, role: Role) -> Self { 146 + /// Makes a new `StrobeState` object with a given protocol byte string and security parameter. 147 + pub fn new(protocol: &[u8], sec: StrobeSecurity, role: Role) -> Self { 127 148 let rate = KECCAK_BUFFER_SIZE - (sec as usize) / 4 - 2; 128 149 assert!((1..254).contains(&rate)); 129 150 ··· 324 345 // RATCHET is special-cased to never call operate directly 325 346 debug_assert!(flags != ops::KEY && flags.contains(OpFlags::CIPHER).to_bool()); 326 347 327 - const SPECIAL_CASES: [OpFlags; 3] = [ops::PRF, ops::SEND_MAC, ops::SEND_ENC]; 328 - const OPS: [fn(&mut StrobeState, data: &mut [u8]); 4] = [ 348 + static SPECIAL_CASES: [OpFlags; 3] = [ops::PRF, ops::SEND_MAC, ops::SEND_ENC]; 349 + static OPS: [fn(&mut StrobeState, data: &mut [u8]); 4] = [ 329 350 StrobeState::squeeze, 330 351 StrobeState::copy_state, 331 352 StrobeState::absorb_and_set, 332 353 StrobeState::exchange, 333 354 ]; 334 355 335 - // Constant time resolution of op index 356 + // Constant time resolution of op index, applied with a mask to ensure no value greater than 357 + // 3 will ever be produced 336 358 let index = SPECIAL_CASES 337 359 .iter() 338 360 .enumerate() 339 361 .fold(3usize, |mut res, (index, op)| { 340 362 res.ct_assign(&index, flags.ct_eq(op)); 341 363 res 342 - }); 364 + }) 365 + & 0b11; 343 366 344 367 OPS[index](self, data); 345 368 } ··· 362 385 // RATCHET is special cased to never call operate/operate_no_mutate directly 363 386 debug_assert!(flags == ops::KEY || !flags.contains(OpFlags::CIPHER).to_bool()); 364 387 365 - const OPS: [fn(&mut StrobeState, data: &[u8]); 2] = 388 + static OPS: [fn(&mut StrobeState, data: &[u8]); 2] = 366 389 [StrobeState::absorb, StrobeState::overwrite]; 367 390 368 391 let index = (flags.ct_eq(&ops::KEY).to_u8() & 1) as usize; ··· 370 393 OPS[index](self, data); 371 394 } 372 395 373 - fn recv_mac_inner(&mut self, flags: OpFlags, mac_copy: &mut [u8]) -> Result<(), GarbledError> { 396 + fn recv_mac_inner(&mut self, flags: OpFlags, mac_copy: &mut [u8]) -> Result<(), aead::Error> { 374 397 // recv_mac can never be streamed 375 398 self.operate(flags, mac_copy, Choice::FALSE); 376 399 ··· 382 405 if all_zero.to_bool() { 383 406 Ok(()) 384 407 } else { 385 - Err(GarbledError) 408 + Err(aead::Error) 386 409 } 387 410 } 388 411 389 - pub fn recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> { 390 - let mut mac_copy = *mac; 412 + pub fn recv_mac<N: ArraySize>(&mut self, mac: &Array<u8, N>) -> Result<(), aead::Error> { 413 + let mut mac_copy = mac.clone(); 391 414 392 415 self.recv_mac_inner(ops::RECV_MAC, &mut mac_copy) 393 416 } 394 417 395 - pub fn meta_recv_mac<const N: usize>(&mut self, mac: &[u8; N]) -> Result<(), GarbledError> { 396 - let mut mac_copy = *mac; 418 + pub fn meta_recv_mac<N: ArraySize>(&mut self, mac: &Array<u8, N>) -> Result<(), aead::Error> { 419 + let mut mac_copy = mac.clone(); 397 420 398 421 self.recv_mac_inner(ops::META_RECV_MAC, &mut mac_copy) 399 422 } ··· 469 492 470 493 #[test] 471 494 fn version_formatting() { 472 - let s = StrobeState::new(b"", SecurityParameter::B128, Role::Sender); 495 + let s = StrobeState::new(b"", StrobeSecurity::B128, Role::Sender); 473 496 474 497 let display = std::format!("{s}"); 475 498 let debug = std::format!("{s:?}");
+308
src/transport.rs
··· 1 + use aead::{ 2 + AeadInOut, Buffer, Key, TagPosition, 3 + consts::{U16, U32}, 4 + }; 5 + use ctutils::{CtEq, CtSelect}; 6 + 7 + use crate::{ 8 + WHARRGHARBL_PROTO, 9 + strobe::{Role, StrobeSecurity, StrobeState}, 10 + }; 11 + 12 + pub struct AeadStrobe { 13 + pub(crate) key: Key<Self>, 14 + param: StrobeSecurity, 15 + } 16 + 17 + impl aead::AeadCore for AeadStrobe { 18 + type NonceSize = U16; 19 + type TagSize = U16; 20 + const TAG_POSITION: TagPosition = TagPosition::Postfix; 21 + } 22 + 23 + impl aead::KeySizeUser for AeadStrobe { 24 + type KeySize = U32; 25 + } 26 + 27 + impl aead::AeadInOut for AeadStrobe { 28 + fn encrypt_inout_detached( 29 + &self, 30 + nonce: &aead::Nonce<Self>, 31 + associated_data: &[u8], 32 + mut buffer: aead::inout::InOutBuf<'_, '_, u8>, 33 + ) -> aead::Result<aead::Tag<Self>> { 34 + let mut tag: aead::Tag<Self> = Default::default(); 35 + let mut strobe = StrobeState::new( 36 + WHARRGHARBL_PROTO.as_bytes(), 37 + self.param, 38 + crate::strobe::Role::Sender, 39 + ); 40 + 41 + strobe.key(&self.key); 42 + strobe.meta_ad(&self.param.to_bytes()); 43 + strobe.meta_ad(nonce); 44 + strobe.ad(associated_data); 45 + strobe.send_enc(buffer.get_out()); 46 + 47 + strobe.send_mac(&mut tag); 48 + 49 + Ok(tag) 50 + } 51 + 52 + fn decrypt_inout_detached( 53 + &self, 54 + nonce: &aead::Nonce<Self>, 55 + associated_data: &[u8], 56 + mut buffer: aead::inout::InOutBuf<'_, '_, u8>, 57 + tag: &aead::Tag<Self>, 58 + ) -> aead::Result<()> { 59 + let mut strobe = StrobeState::new( 60 + WHARRGHARBL_PROTO.as_bytes(), 61 + self.param, 62 + crate::strobe::Role::Receiver, 63 + ); 64 + 65 + strobe.key(&self.key); 66 + strobe.meta_ad(&self.param.to_bytes()); 67 + strobe.meta_ad(nonce); 68 + strobe.ad(associated_data); 69 + strobe.recv_enc(buffer.get_out()); 70 + 71 + strobe.recv_mac(tag) 72 + } 73 + } 74 + 75 + pub struct AeadState { 76 + pub(crate) aead: AeadStrobe, 77 + pub(crate) epstein: aead::Nonce<AeadStrobe>, 78 + pub(crate) trump: aead::Nonce<AeadStrobe>, 79 + role: Role, 80 + } 81 + 82 + impl AeadState { 83 + pub fn new( 84 + key: Key<AeadStrobe>, 85 + sec: StrobeSecurity, 86 + outbound: aead::Nonce<AeadStrobe>, 87 + inbound: aead::Nonce<AeadStrobe>, 88 + role: Role, 89 + ) -> Self { 90 + assert_ne!( 91 + &inbound, &outbound, 92 + "The Base Nonces MUST NOT equal to each other" 93 + ); 94 + 95 + Self { 96 + aead: AeadStrobe { key, param: sec }, 97 + epstein: outbound, 98 + trump: inbound, 99 + role, 100 + } 101 + } 102 + 103 + fn select_nonce(&self, role: Role) -> aead::Nonce<AeadStrobe> { 104 + let role_context = self.role ^ role; 105 + 106 + self.epstein.ct_select(&self.trump, role_context.ct_eq(&1)) 107 + } 108 + 109 + fn mix_nonce(&self, position: [u8; 8], role: Role) -> aead::Nonce<AeadStrobe> { 110 + let mut nonce = self.select_nonce(role); 111 + 112 + let mid = nonce.len() - position.len(); 113 + 114 + nonce[mid..] 115 + .iter_mut() 116 + .zip(position) 117 + .for_each(|(n, p)| *n ^= p); 118 + 119 + nonce 120 + } 121 + 122 + pub fn split(&self) -> (SendState<'_>, RecvState<'_>) { 123 + ( 124 + SendState { 125 + transport: self, 126 + counter: 0, 127 + }, 128 + RecvState { 129 + transport: self, 130 + counter: 0, 131 + }, 132 + ) 133 + } 134 + } 135 + 136 + pub struct SendState<'a> { 137 + transport: &'a AeadState, 138 + counter: u64, 139 + } 140 + 141 + impl SendState<'_> { 142 + pub fn encrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { 143 + if self.counter.ct_eq(&u64::MAX).into() { 144 + return Err(aead::Error); 145 + } 146 + 147 + let encryption_result = self.transport.aead.encrypt_in_place( 148 + &self 149 + .transport 150 + .mix_nonce(self.counter.to_be_bytes(), Role::Sender), 151 + ad, 152 + buffer, 153 + ); 154 + 155 + self.counter = self.counter.wrapping_add(1); 156 + 157 + encryption_result 158 + } 159 + } 160 + 161 + pub struct RecvState<'a> { 162 + transport: &'a AeadState, 163 + counter: u64, 164 + } 165 + 166 + impl RecvState<'_> { 167 + pub fn decrypt(&mut self, buffer: &mut dyn Buffer, ad: &[u8]) -> aead::Result<()> { 168 + if self.counter.ct_eq(&u64::MAX).into() { 169 + return Err(aead::Error); 170 + } 171 + 172 + // If the message's MAC doesn't evaluate successfully, this op will fail. 173 + let decryption_result = self.transport.aead.decrypt_in_place( 174 + &self 175 + .transport 176 + .mix_nonce(self.counter.to_be_bytes(), Role::Receiver), 177 + ad, 178 + buffer, 179 + ); 180 + 181 + self.counter = self.counter.wrapping_add(1); 182 + 183 + decryption_result 184 + } 185 + } 186 + 187 + #[cfg(test)] 188 + mod tests { 189 + use super::*; 190 + 191 + #[test] 192 + fn two_way_transport_sync_works() -> aead::Result<()> { 193 + let shared_secret = [ 194 + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 195 + 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 196 + 0x9c, 0x9d, 0x9e, 0x9f, 197 + ]; 198 + 199 + let outbound = 123u128.to_ne_bytes(); 200 + let inbound = 234u128.to_ne_bytes(); 201 + 202 + let alice = AeadState::new( 203 + shared_secret.into(), 204 + StrobeSecurity::B128, 205 + outbound.into(), 206 + inbound.into(), 207 + Role::Sender, 208 + ); 209 + let bob = AeadState::new( 210 + shared_secret.into(), 211 + StrobeSecurity::B128, 212 + outbound.into(), 213 + inbound.into(), 214 + Role::Receiver, 215 + ); 216 + 217 + let (mut alice_send, mut alice_recv) = alice.split(); 218 + let (mut bob_send, mut bob_recv) = bob.split(); 219 + 220 + let orig = b"Test Message, Please ignore."; 221 + 222 + let ad = b"random"; 223 + 224 + let mut msg = orig.to_vec(); 225 + 226 + // a -> b 227 + alice_send.encrypt(&mut msg, ad)?; 228 + 229 + assert_ne!(orig.as_slice(), msg.as_slice()); 230 + let ct1 = msg.clone(); 231 + 232 + bob_recv.decrypt(&mut msg, ad)?; 233 + 234 + // a -> b 235 + alice_send.encrypt(&mut msg, b"")?; 236 + 237 + assert_ne!(msg.as_slice(), ct1.as_slice()); 238 + let ct2 = msg.clone(); 239 + 240 + bob_recv.decrypt(&mut msg, b"")?; 241 + 242 + // b -> a 243 + bob_send.encrypt(&mut msg, ad)?; 244 + 245 + // None of the ciphertexts should match each other 246 + assert_ne!(msg.as_slice(), ct1.as_slice()); 247 + assert_ne!(msg.as_slice(), ct2.as_slice()); 248 + assert_ne!(ct1.as_slice(), ct2.as_slice()); 249 + 250 + alice_recv.decrypt(&mut msg, ad)?; 251 + 252 + assert_eq!(orig.as_slice(), msg.as_slice()); 253 + 254 + // Counters are tracked from sender to receiver 255 + assert_eq!(alice_send.counter, bob_recv.counter); 256 + assert_eq!(bob_send.counter, alice_recv.counter); 257 + 258 + // Counters are not linked on the same side 259 + assert_ne!(alice_send.counter, alice_recv.counter); 260 + assert_ne!(bob_send.counter, bob_recv.counter); 261 + 262 + Ok(()) 263 + } 264 + 265 + #[test] 266 + #[should_panic] 267 + fn two_way_transport_fails_with_security_level_mismatch() { 268 + let shared_secret = [ 269 + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 270 + 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 271 + 0x9c, 0x9d, 0x9e, 0x9f, 272 + ]; 273 + 274 + let outbound = 123u128.to_ne_bytes(); 275 + let inbound = 234u128.to_ne_bytes(); 276 + 277 + let alice = AeadState::new( 278 + shared_secret.into(), 279 + StrobeSecurity::B128, 280 + outbound.into(), 281 + inbound.into(), 282 + Role::Sender, 283 + ); 284 + let bob = AeadState::new( 285 + shared_secret.into(), 286 + StrobeSecurity::B256, 287 + outbound.into(), 288 + inbound.into(), 289 + Role::Receiver, 290 + ); 291 + 292 + let (mut alice_send, mut _alice_recv) = alice.split(); 293 + let (mut _bob_send, mut bob_recv) = bob.split(); 294 + 295 + let orig = b"Test Message, Please ignore."; 296 + 297 + let ad = b"random"; 298 + 299 + let mut msg = orig.to_vec(); 300 + 301 + // a -> b 302 + alice_send.encrypt(&mut msg, ad).unwrap(); 303 + 304 + assert_ne!(orig.as_slice(), msg.as_slice()); 305 + 306 + bob_recv.decrypt(&mut msg, ad).unwrap(); 307 + } 308 + }