diff --git a/Cargo.lock b/Cargo.lock index f87915b..408d357 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,6 +126,34 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "assert_tv" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a4141926c2544d566b0c5815040151fa6c1e96398810b43951d3c68e13dfc2a" +dependencies = [ + "anyhow", + "assert_tv_macros", + "base64", + "log", + "serde", + "serde_json", + "serde_yaml", + "toml", + "zstd", +] + +[[package]] +name = "assert_tv_macros" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fe5672253d886b06afd14bf16aec6111e2d111cbc83cd094a4f5f69f003332" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -157,6 +185,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -2041,6 +2075,8 @@ name = "rosenpass" version = "0.3.0-dev" dependencies = [ "anyhow", + "assert_tv", + "base64", "clap", "clap_complete", "clap_mangen", @@ -2193,6 +2229,8 @@ dependencies = [ "allocator-api2", "allocator-api2-tests", "anyhow", + "assert_tv", + "base64", "base64ct", "log", "memsec", @@ -2200,6 +2238,8 @@ dependencies = [ "rand 0.8.5", "rosenpass-to", "rosenpass-util", + "serde", + "serde_json", "tempfile", "zeroize", ] @@ -2396,6 +2436,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "3.2.0" @@ -2753,6 +2806,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3292,3 +3351,31 @@ dependencies = [ "quote", "syn 2.0.98", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 0b8de5b..ae45898 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,8 @@ uds = { git = "https://github.com/rosenpass/uds" } lazy_static = "1.5" #Dev dependencies +assert_tv = { version = "0.6.4" } +base64 = { version = "0.22.1" } serial_test = "3.2.0" tempfile = "3" stacker = "0.1.17" diff --git a/pkgs/rosenpass.nix b/pkgs/rosenpass.nix index c25809f..eaeb40f 100644 --- a/pkgs/rosenpass.nix +++ b/pkgs/rosenpass.nix @@ -24,6 +24,7 @@ let "service" "target" "toml" + "zstd" # used for offloaded test vector values ]; # Files to explicitly include files = [ "to/README.md" ]; diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml new file mode 100644 index 0000000..89b2e4b --- /dev/null +++ b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml @@ -0,0 +1,351 @@ +[[entries]] +entry_type = "Const" +description = "test setup: peer a secret key" +name = "peer_a_sk" +code_location = "tests/test_vector_crypto_server.rs:95" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Const" +description = "test setup: peer a public key" +name = "peer_a_pk" +code_location = "tests/test_vector_crypto_server.rs:96" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Const" +description = "test setup: peer b secret key" +name = "peer_b_sk" +code_location = "tests/test_vector_crypto_server.rs:101" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Const" +description = "test setup: peer b public key" +name = "peer_b_pk" +code_location = "tests/test_vector_crypto_server.rs:102" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Const" +description = "pre-shared key" +name = "psk" +value = "Vw7bZ1vyXfZo4D5/633F5IbTOntNFBjZRCWf/0cP3Ss=" +code_location = "tests/test_vector_crypto_server.rs:105" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::cookie_secrets[0]" +value = "COU/279CXvyB3mwVxT6fCQ==" +code_location = "tests/test_vector_crypto_server.rs:167" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::cookie_secrets[1]" +value = "5Q+e2O020FTcmBWlAPnWlw==" +code_location = "tests/test_vector_crypto_server.rs:172" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::biscuit_keys[0]" +value = "5h9KZ2KfLP5uNbEdimMNLHUjPFm0L3helyg8QE7pd/E=" +code_location = "tests/test_vector_crypto_server.rs:177" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::biscuit_keys[1]" +value = "uwyhSvwuEeXwVUg4+9CBoPWPL0AY+oSVUhFpGpnBaY8=" +code_location = "tests/test_vector_crypto_server.rs:179" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::cookie_secrets[0]" +value = "KtKzsfp1NMN3mrln54w0TA==" +code_location = "tests/test_vector_crypto_server.rs:167" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::cookie_secrets[1]" +value = "cc1hSqM+Jhal8AII5NIWLA==" +code_location = "tests/test_vector_crypto_server.rs:172" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::biscuit_keys[0]" +value = "ih8UroUklL5NLdegMFQAEWE6huLEO4HBNL44jpk0VsI=" +code_location = "tests/test_vector_crypto_server.rs:177" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "CryptoServer::biscuit_keys[1]" +value = "mXEYSvzhz5E9eUskbKJBZlXlYF2N3Q1MwmbGeXmhXoI=" +code_location = "tests/test_vector_crypto_server.rs:179" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:69" + +[[entries]] +entry_type = "Const" +name = "hs.cookie_value.value" +value = "BryZRk1KR5Gc+AEm0plH1Q==" +code_location = "rosenpass/src/protocol/protocol.rs:3559" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Const" +name = "hs.core.sidi" +value = "avpJ6g==" +code_location = "rosenpass/src/protocol/protocol.rs:3572" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Const" +name = "hs.eski" +value = "KpIH2rHL5MR1TQosRVTAUhpDGYUfE7FbeDA/VgN2shd0gdwBRkdeTEZqKKdAY+ikmKydZQwNlFQW8WuH8fwknbUt/SCzo2iPbDg22sAuClQFN1sXckmEpDG2G4cqXHqkdVSuwhymt9V0w1lKFotTwNIN9Fo0eKq19twT16DP+nIPwrkDveAAm8OqwNp1iAODI8GuB3aL3Str/HDC7YR8QtuuzJQwfEa8CbihEpk+zZqVCJVZDrlfH6ydcJsahypuWpJQ4RwS+NejEvJirvgKngsgdPkGXyYLzQQhS4qagQDMkklWaxwL5pdSqlB+M/Cbx1iqgXgOp2PEykkOpgpEBKfDoCQxBnIYNEisx9B8XuibzqQiq0qctty3GBUf+peApMdataC6WwGqH1KpQiW8aUufIWFDK8xW56t3/bMV/AIsMWx3XpaxbGOQOgqAmRAxeYh23TeQ6lVturw/aMsV2phXBkgG6iQnzyd9z6Zufgezz3Ui2hdeJ/iCzfG0A7SMWlS8zETM46wExxO5smNh8jCoTzeSMpOeH3GfFyRs44PFybSQHtAbdcpcA+pj2VljGfRzq5U1AjpdbSKm8JO6n2PDOQlXHaNp5wZMTHZeywq/BrRWO0FXU9davLcEqUJ2xkwRIvEo24ae0MSD1SSd0rRK5LFilFWZXpDCkaoHvst87mdMeIBUYppMD+WkSuKun0XMXXSSWNPIvrGKFJlt4yTCNYsdfRBthGEa5sEApYd4jmi49zKbRSGGm/u915gdo0AvsFYa85EZzzBI0wfK3uW0X+YvGSEzwHGOAMaAyJu3tCGHt2tuevqND2lybzkJS+ih80l3jyaBbNmqQzBRt8YZtLg+e0ijH/x8IyGq+efCioRQjeN50sBAJrsSgncVtgoSraWbPLNu9movqQMtLiwGkUaZSqVANtQPQuccOFyzK7XD8zy5V0Bx/We0ijOvlyUPJWW+1LoALiArHhG7xIRQL8oe9RDP1mzPoGQd1MNRvCuePNpRX+JAO7a49vwsqUtsK6iAY5C1WIxc1TAVhrIz2YzKuyOQYJCbtkOTx5EZ+AbCsTa/E7VHJacj5xQXtYpy3NogXpAnI/ubjABhCVKTfNFytwKkQUAR1ZcoqzJlngdFILIFBeTEUJChXpBb3/obCCQOFVWCKaYfuLam34JgWkmKYUoao4ZP68yn7yc1l4lDRswKSgFt6YI31PcWGAbKM9QeKLF1Tso2bQOXlEF8MssCQjhN+PimSYU/A1ANTHEyQMxM+uUGXBAJwViSGSshHpioLkF3nBc2h7FT5Fi6hMU4xqUwsBgkfZKYQ6gMsMIqZjd/gIkbawwGU6VdLNNf4tw1f4ssD3QbKSA9LfsSHHV8YjcJ3vVoIVVd/Pmbe/kV8Ccz0zLPYWxyrrEanTOJwPqzYxBqTOBilVWlEfREu1ddYaccILMGEMdsQ6eBj4ySZYhj24Ajc3R8afR+anR2NNorfgIlF1d7VuW1BQG+loA3UqdO9lE4mwrARoy87LZKagYHUshet/CAYxinr0UlfCKqbEydljslyafPC4x/mmRWqjTNsTAVfZdOc1ESLyQ6fCkZ/ewOMmsVgUZsBPHI5LgE/2qjYhgXxzg72OmYynR4+8EWunEd2JScjTAurTVZBEPDfGq9wvySBeMbyIGgY0yIfJF/oGRI6/oyNoOBMyKff7oCVNzH+UBGPQJE0De7U1wmEqBNIOu6MMx4nFVgdNzA3TyLrhaOT3uDoJMo/9NnnvWDS5xhp9l8URkcVsol12MhmZAhqlhtGIOR72e5Jml6DNkLvYCX49ZKfMGBbnHNk/RhN5k3dccuD+UvIwxR3irMUXt6jITE0RbPCYWX14N4Jlcdz2oxA6U+RXW8ccJ0YQYjsNgmj1xpsnwY8/O6LkiB+Sm/fsJAkRGsKrWeyPKRcNR1uioBr6YpqKa0aHC0meQX7YqKNWZujOQX6ZJb2aMgx4CRJOc/fbqrtgpQy8mEy4JG2dmEPCdYKmY3YTGlpiVb65pLciEtJRIRiCqh4QNmCwtrixM5QgO5Y5gNfX/fXULtbtZdCDgQJ8CKaJNgxosHESp7YfeW+bO2tmmEwSHG+/s4YEEVQZm2OfJ46gLFPIixcTmADwFGOKEgTRofmHFxOUlIH1gXBkuqH0zWp71FqEpAoucjrROw" +code_location = "rosenpass/src/protocol/protocol.rs:3579" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Const" +name = "hs.core.ck" +value = "qUtsK6iAY5C1WIxc1TAVhrIz2YzKuyOQYJCbtkOTx5EZ+AbCsTa/E7VHJacj5xQXtYpy3NogXpAnI/ubjABhCVKTfNFytwKkQUAR1ZcoqzJlngdFILIFBeTEUJChXpBb3/obCCQOFVWCKaYfuLam34JgWkmKYUoao4ZP68yn7yc1l4lDRswKSgFt6YI31PcWGAbKM9QeKLF1Tso2bQOXlEF8MssCQjhN+PimSYU/A1ANTHEyQMxM+uUGXBAJwViSGSshHpioLkF3nBc2h7FT5Fi6hMU4xqUwsBgkfZKYQ6gMsMIqZjd/gIkbawwGU6VdLNNf4tw1f4ssD3QbKSA9LfsSHHV8YjcJ3vVoIVVd/Pmbe/kV8Ccz0zLPYWxyrrEanTOJwPqzYxBqTOBilVWlEfREu1ddYaccILMGEMdsQ6eBj4ySZYhj24Ajc3R8afR+anR2NNorfgIlF1d7VuW1BQG+loA3UqdO9lE4mwrARoy87LZKagYHUshet/CAYxinr0UlfCKqbEydljslyafPC4x/mmRWqjTNsTAVfZdOc1ESLyQ6fCkZ/ewOMmsVgUZsBPHI5LgE/2qjYhgXxzg72OmYynR4+8EWunEd2JScjTAurTVZBEPDfGq9wvySBeMbyIGgY0yIfJF/oGRI6/oyNoOBMyKff7oCVNzH+UBGPQJE0De7U1wmEqBNIOu6MMx4nFVgdNzA3TyLrhaOT3uDoJMo/9NnnvWDS5xhp9l8URkcVsol12MhmZAhqlhtGIOR72e5Jml6DNkLvYCX49ZKfMGBbnHNk/RhN5k3dccuD+UvIwxR3irMUXt6jITE0RbPCYWX14N4Jlcdz2oxA6U+RXW8ccJ0YQYjsNgmj1xpsnwY8/O6LkiB+Sm/fsJAkRGsKrWeyPKRcNR1uioBr6YpqKa0aHC0meQX7YqKNWZujOQX6ZJb2aMgx4CRJOc/fbqrtgpQy8mEy4JG2dmEPCdYKmY3YTGlpiVb65pLciEtJRIRiCqh4QNmCwtrixM5QgO5Y5gNfX/fXULtbtZdCDgQJ8CKaJNgxosHESp7YfeW+bM=" +code_location = "rosenpass/src/protocol/protocol.rs:3580" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "hs.core.ck 1" +value = "z2JAxQ4DPIikqHMSpFDAlmhjSBumhLZatJMrFAP+D1U=" +code_location = "rosenpass/src/protocol/protocol.rs:3587" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Const" +value = "crPEWzqlCERLJr83BrZupxLUOxWHsYZalisk94tma0k=" +code_location = "rosenpass/src/protocol/protocol.rs:3263" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Const" +value = "eJXlj5zOJo/Eq85TzOTi7QY8zqWBP0QOOl3PAcpFDYnZyWmuzdpwDZJTbagAP52Md9r+j2vME4SW9UvdvOQo+jbNfuCHytU5K3yxI77srR4aWepM+xPAmxbyIwD4VGjDOQNAjP4Rn8n5L9cCju1tgit7yeM0S4Mx2KDR/Emr8V7gQnERob6LrPJgu6BMWkbZA2+dLVZOsvWPFmmn" +code_location = "rosenpass/src/protocol/protocol.rs:3264" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Output" +name = "hs.core.ck 2" +value = "UVoOgm+xgnV9tXlaLrlBHiM8XDAH+4tPm3DTbP9uJzs=" +code_location = "rosenpass/src/protocol/protocol.rs:3602" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "ih.pidic" +value = "Y3Wstn84+vmUb/a/CtWHFixkdyTFKEaE7joUFM0vBZPehPAeDOXls/u5I1PvViF6" +code_location = "rosenpass/src/protocol/protocol.rs:3615" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "hs.core.ck 3" +value = "RTCFQy236fEakHlQn8Pq4+ZbWAg4zvkP5iSp+RU7CpQ=" +code_location = "rosenpass/src/protocol/protocol.rs:3616" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "hs.core.ck 4" +value = "9bjg2ICzmCeqYMt4ACfX5UMQUde7WgeuI6H+vdQuWck=" +code_location = "rosenpass/src/protocol/protocol.rs:3627" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "ih.auth" +value = "E+AP6wyOrpngo7vE9Vpkjg==" +code_location = "rosenpass/src/protocol/protocol.rs:3636" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +name = "hs.core.ck 5" +value = "MkkapMEoAWftWlVPIcroCCTpqOrMv5TYSff5h8Un/OE=" +code_location = "rosenpass/src/protocol/protocol.rs:3637" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:44" + +[[entries]] +entry_type = "Output" +description = "message exchanged by the protocol parties" +name = "message" +code_location = "tests/test_vector_crypto_server.rs:136" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr IHR 4" +value = "z2JAxQ4DPIikqHMSpFDAlmhjSBumhLZatJMrFAP+D1U=" +code_location = "rosenpass/src/protocol/protocol.rs:3693" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr IHR 5" +value = "UVoOgm+xgnV9tXlaLrlBHiM8XDAH+4tPm3DTbP9uJzs=" +code_location = "rosenpass/src/protocol/protocol.rs:3702" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr IHR 6" +value = "RTCFQy236fEakHlQn8Pq4+ZbWAg4zvkP5iSp+RU7CpQ=" +code_location = "rosenpass/src/protocol/protocol.rs:3714" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr IHR 7" +value = "9bjg2ICzmCeqYMt4ACfX5UMQUde7WgeuI6H+vdQuWck=" +code_location = "rosenpass/src/protocol/protocol.rs:3724" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr IHR 8" +value = "MkkapMEoAWftWlVPIcroCCTpqOrMv5TYSff5h8Un/OE=" +code_location = "rosenpass/src/protocol/protocol.rs:3733" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Const" +name = "session_id" +value = "+qqWGQ==" +code_location = "rosenpass/src/protocol/protocol.rs:3741" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr RHR 3" +value = "rPSnj3sQzMcnolHZJZpDlZ71VcFEtIEENGLWUPyRZws=" +code_location = "rosenpass/src/protocol/protocol.rs:3749" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Const" +value = "aQGM2N7OhVgSlsa7pEaqdiRZLacxgDhwuEplHORsv3s=" +code_location = "rosenpass/src/protocol/protocol.rs:3263" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Const" +value = "XIOzetMa7cemW0Qq+vEu1frv9J/MhfFiT68mfbWZrZbzM5AI2lVPLXi75KvjbNC7YrqVG20d2DhZ38g9vjIAG50Lc8LHw3C+tQV2g/lCa4p1XySyQOJUAj/CpBGito8MdcdcxpFzLhCnq0owBSonbgyboxZq/hsX5z+unJu+5UiJYlxpcvu++w954OQiXi9lJpfnC4biY8Mw+8UL5d2UgWp8Yn3y3ZoIKSNGA5hR5/JUg1QStbSvWrhNiiDrfNUR0k2zKmLPXPS1pmaBvSuWQptq3WiA1OtkRsCSrAzjOtyUPWSZyXrttAZ4VJCBkEnd4OZy7eC/mBa3FjwJAfcvVY7i8mU1yDpLtS7MtdAZKMeGn0fQDWOx5z+vM+jbwOxXer79jPKPcdigxjTfARz+SxhfWfqGO5hBh86Ra0F+vgw3KH/YQgTchroWVF8WIwZlNwp6DqvwXBXwRPbtrv+uxmRHT9Oe+ZxJr88Nd1gRtjL0FN6faE1quGDAHWPuAfhFcxa7kdPpHN46cHSC+nzO1gtabPX/b0PM2awmNPB5QPpWoGHzz4HTYV7MYHmC4mxdKU7BT+U3vJd4+vl+7wGQSVxSCwzWHqCjRwOkIDLeiyZss7iz8BnOeErDySbPTMYI/Jv/R7lalOk4+73a1m2m4uZKetXRGEjg8btiKfhFaPZMc9p8xZeJNbsUbqoMl8/R1QGRqXHxzk9tTZW0AHV0t4uLnvHJgu9mR6LApZ99sAJpNcuU9TnsSPfT32DlXWNxtb0DnTQXxLQ7TnSWFYPCYyv7hbfNk8lDdGJqStSvnCe16KYoYjxpExEIfCAFicYhhar5MOpkinJMeyhMhVd2ctVIIIzZXePfZXcdOod2JbYh6+dmp0bnTyussHv9R+qYCCv5AyU/crKSmTbjgyOsmaVpyoTljsozC32uhqZh4M7KBUz1XyPJfD6L4NE1ovCKSfwJepYOqBw0MkdWoXwScyhMWNpVhS8X1aZ/QOZFVIuyYxoiwqpF/y3xV6QYFZmd" +code_location = "rosenpass/src/protocol/protocol.rs:3264" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr RHR 4" +value = "WCh3N6kKat+5vvn3WNTxsF9yXGYTQS7hlMUplMac/14=" +code_location = "rosenpass/src/protocol/protocol.rs:3764" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Const" +value = "Sx7y7DSdVQVHWxR4OOMjyrDr8vuZa7MPX/U8GnPCIHY=" +code_location = "rosenpass/src/protocol/protocol.rs:3263" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Const" +value = "VwqEOlvdW0Evc1CasejxXQ0dN3mZ4HPMA6ogk3Q7OUzzgCIvbTU8BvBK6d1UH2R7Y5Nd/JdHgag42f5FpjVljX2vlXMk3XxUtREpORxqJ5qUgHejsgJyRO9caOPX/ZXmUZ6vORP7pIulLt2i47ykPNNlWuJmxVJj6NK+lE1BDMIuivHebR31atigV/fFFJui26vy5V9y6xZmnCJm" +code_location = "rosenpass/src/protocol/protocol.rs:3264" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:23" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr RHR 5" +value = "7gd9Mw7X0ZFmkIkP/JDofflRSAw21F1RjG4HXeMJfTI=" +code_location = "rosenpass/src/protocol/protocol.rs:3779" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +value = "QyffgzBjc8qAqK18vYsH9TKs9lc6Njg8Q8nANsF/FMMBAAAAAAAAAAAAAADuB30zDtfRkWaQiQ/8kOh9+VFIDDbUXVGMbgdd4wl9Mg==" +code_location = "rosenpass/src/protocol/protocol.rs:3336" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:31" + +[[entries]] +entry_type = "Const" +description = "Biscuit key after being cycled" +name = "CryptoServer::biscuit_key[r]" +value = "TIDswcSHh8WKsEsuwMCyldE0zVH8IEjV97ES8v3nHw0=" +code_location = "rosenpass/src/protocol/protocol.rs:1430" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:127" + +[[entries]] +entry_type = "Const" +value = "iSR2RKOw5VeBICb4b+i8P4QtF8XnJq2N" +code_location = "rosenpass/src/protocol/protocol.rs:3353" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:31" + +[[entries]] +entry_type = "Output" +value = "iSR2RKOw5VeBICb4b+i8P4QtF8XnJq2Nq1gaCMgEJ+v1LulDYsjbfU5O0alWStfs4fBy7zS69RkzT8p0m3mVcK0TY9RRdWfxmCzU1U8DP7CbhfZxZHJC9CO8xwIa5r1NjVE1gxJJUEJCTHvtCc3PtFj861c=" +code_location = "rosenpass/src/protocol/protocol.rs:3361" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:31" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr RHR 6" +value = "urS3a0/ufhUxj7bAnafEQ3j7it4q2XOfO+DVCeQJp4U=" +code_location = "rosenpass/src/protocol/protocol.rs:3788" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +name = "chaining_key_ihr RHR 7" +value = "bbWd4iHXPK6helZLik4t96JKB7yiyw+qcNWbvlDiaQs=" +code_location = "rosenpass/src/protocol/protocol.rs:3797" +test_vec_set_code_location = "rosenpass/src/protocol/test_vector_sets.rs:84" + +[[entries]] +entry_type = "Output" +description = "message exchanged by the protocol parties" +name = "message" +code_location = "tests/test_vector_crypto_server.rs:136" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Output" +description = "message exchanged by the protocol parties" +name = "message" +code_location = "tests/test_vector_crypto_server.rs:136" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Output" +description = "message exchanged by the protocol parties" +name = "message" +code_location = "tests/test_vector_crypto_server.rs:136" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" +offload = true + +[[entries]] +entry_type = "Output" +description = "final exchanged key" +name = "exchanged_key" +value = "NKG5/vCPc0akKnm5RxctcPigO8fvd0zqJeO2+xbDpq4=" +code_location = "tests/test_vector_crypto_server.rs:153" +test_vec_set_code_location = "rosenpass/tests/test_vector_crypto_server.rs:33" diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_0.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_0.zstd new file mode 100644 index 0000000..bd2c672 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_0.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_1.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_1.zstd new file mode 100644 index 0000000..ad42b87 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_1.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_13.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_13.zstd new file mode 100644 index 0000000..31d2a20 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_13.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_14.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_14.zstd new file mode 100644 index 0000000..6380baa Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_14.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_15.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_15.zstd new file mode 100644 index 0000000..1943e18 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_15.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_16.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_16.zstd new file mode 100644 index 0000000..2c6a4dc Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_16.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_2.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_2.zstd new file mode 100644 index 0000000..fedf9ca Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_2.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_26.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_26.zstd new file mode 100644 index 0000000..4da81a7 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_26.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_3.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_3.zstd new file mode 100644 index 0000000..dc02c65 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_3.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_46.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_46.zstd new file mode 100644 index 0000000..e78b9e0 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_46.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_47.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_47.zstd new file mode 100644 index 0000000..825bc09 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_47.zstd differ diff --git a/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_48.zstd b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_48.zstd new file mode 100644 index 0000000..ba46669 Binary files /dev/null and b/rosenpass/.test_vectors/crypto_server_test_vector_1.toml_offloaded_value_48.zstd differ diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index ef79b6e..5ac9a30 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -79,6 +79,9 @@ command-fds = { workspace = true, optional = true } rustix = { workspace = true, optional = true } uds = { workspace = true, optional = true, features = ["mio_1xx"] } libcrux-test-utils = { workspace = true, optional = true } +assert_tv = { workspace = true } +base64 = { workspace = true } +serde_json = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/rosenpass/src/protocol/mod.rs b/rosenpass/src/protocol/mod.rs index 7de618f..8e14c41 100644 --- a/rosenpass/src/protocol/mod.rs +++ b/rosenpass/src/protocol/mod.rs @@ -86,6 +86,7 @@ pub mod constants; pub mod cookies; pub mod index; pub mod osk_domain_separator; +pub mod test_vector_sets; pub mod testutils; pub mod timing; pub mod zerocopy; diff --git a/rosenpass/src/protocol/protocol.rs b/rosenpass/src/protocol/protocol.rs index 568ef08..ff1a155 100644 --- a/rosenpass/src/protocol/protocol.rs +++ b/rosenpass/src/protocol/protocol.rs @@ -15,6 +15,7 @@ use std::{ }; use anyhow::{bail, ensure, Context, Result}; +use assert_tv::{TestVector, TestVectorNOP}; use memoffset::span_of; use zerocopy::{AsBytes, FromBytes, Ref}; @@ -33,6 +34,10 @@ use rosenpass_util::{ time::Timebase, }; +use crate::protocol::test_vector_sets::{ + CycledBiscuitSecretKeyTestValues, EncapsAndMixTestValues, HandleInitHelloTestValues, + HandleInitiationTestValues, StoreBiscuitTestValues, +}; use crate::{hash_domains, msgs::*, RosenpassError}; use super::basic_types::{ @@ -1392,6 +1397,20 @@ impl CryptoServer { /// /// Swap the biscuit keys, also advancing both biscuit key's mortality pub fn active_biscuit_key(&mut self) -> BiscuitKeyPtr { + self.active_biscuit_key_with_test_vector::() + } + + /// Generic variant of [`Self::active_biscuit_key`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::active_biscuit_key`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn active_biscuit_key_with_test_vector(&mut self) -> BiscuitKeyPtr { let (a, b) = (BiscuitKeyPtr(0), BiscuitKeyPtr(1)); let (t, u) = (a.get(self).created_at, b.get(self).created_at); @@ -1407,6 +1426,11 @@ impl CryptoServer { let r = if t < u { a } else { b }; let tb = self.timebase.clone(); r.get_mut(self).randomize(&tb); + let test_values: CycledBiscuitSecretKeyTestValues = TV::initialize_values(); + TV::expose_mut_value( + &test_values.cycled_biscuit_secret_key, + &mut self.biscuit_keys[r.0].value, + ); r } @@ -1725,6 +1749,7 @@ impl Mortal for KnownInitConfResponsePtr { /// # Examples /// /// ``` +/// use assert_tv::TestVector; /// use rosenpass::protocol::{timing::Timing, Mortal, MortalExt, Lifecycle, CryptoServer, ProtocolVersion}; /// use rosenpass::protocol::testutils::{ServerForTesting, time_travel_forward}; /// @@ -1865,13 +1890,31 @@ impl CryptoServer { /// /// See [Self::poll] on how to use this function with poll. pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result { + self.initiate_handshake_with_test_vector::(peer, tx_buf) + } + + /// Generic variant of [`Self::initiate_handshake`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::initiate_handshake`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn initiate_handshake_with_test_vector( + &mut self, + peer: PeerPtr, + tx_buf: &mut [u8], + ) -> Result { // NOTE retransmission? yes if initiator, no if responder // TODO remove unnecessary copying between global tx_buf and per-peer buf // TODO move retransmission storage to io server // // Envelope::::default(); // TODO let mut msg = truncating_cast_into::>(tx_buf)?; - self.handle_initiation(peer, &mut msg.payload)?; + self.handle_initiation_with_test_vector::(peer, &mut msg.payload)?; let len = self.seal_and_commit_msg(peer, MsgType::InitHello, &mut msg)?; peer.hs() .store_msg_for_retransmission(self, msg.as_bytes())?; @@ -2111,6 +2154,24 @@ impl CryptoServer { /// /// See [Self::poll] on how to use this function with poll. pub fn handle_msg(&mut self, rx_buf: &[u8], tx_buf: &mut [u8]) -> Result { + self.handle_msg_with_test_vector::(rx_buf, tx_buf) + } + + /// Generic message handler that allows selecting a [`TestVector`] + /// implementation. + /// + /// This function is primarily intended for **testing** with different + /// test vector strategies. In production code, prefer using + /// [`Self::handle_msg`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn handle_msg_with_test_vector( + &mut self, + rx_buf: &[u8], + tx_buf: &mut [u8], + ) -> Result { let seal_broken = "Message seal broken!"; // length of the response. We assume no response, so None for now let mut len = 0; @@ -2131,7 +2192,7 @@ impl CryptoServer { // At this point, we do not know the hash functon used by the peer, thus we try both, // with a preference for SHAKE256. - let peer_shake256 = self.handle_init_hello( + let peer_shake256 = self.handle_init_hello_with_test_vector::( &msg_in.payload, &mut msg_out.payload, KeyedHash::keyed_shake256(), @@ -2139,7 +2200,7 @@ impl CryptoServer { let (peer, peer_hash_choice) = match peer_shake256 { Ok(peer) => (peer, KeyedHash::keyed_shake256()), Err(_) => { - let peer_blake2b = self.handle_init_hello( + let peer_blake2b = self.handle_init_hello_with_test_vector::( &msg_in.payload, &mut msg_out.payload, KeyedHash::incorrect_hmac_blake2b(), @@ -3159,8 +3220,48 @@ impl HandshakeState { ct: &mut [u8; KEM_CT_LEN], pk: &[u8; KEM_PK_LEN], ) -> Result<&mut Self> { + self.encaps_and_mix_with_test_vector(kem, ct, pk, std::marker::PhantomData::) + } + + /// Generic variant of [`Self::encaps_and_mix`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::encaps_and_mix`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example. + /// + /// Note on `_tv: PhantomData` parameter: + /// - Rust’s type inference ties generic parameter specification together; + /// explicitly selecting `TV` with turbofish can force you to also spell + /// out the const generics. + /// - By adding a value parameter of type `PhantomData`, you can choose + /// `TV` at the call site while allowing the compiler to infer `KEM_*` + /// const generics from `ct` and `pk`. + /// - Call like: `encaps_and_mix_with_test_vector(&StaticKem, &mut ct, pk, + /// PhantomData::)?;` + pub fn encaps_and_mix_with_test_vector< + const KEM_SK_LEN: usize, + const KEM_PK_LEN: usize, + const KEM_CT_LEN: usize, + const KEM_SHK_LEN: usize, + KemImpl: Kem, + TV: TestVector, + >( + &mut self, + kem: &KemImpl, + ct: &mut [u8; KEM_CT_LEN], + pk: &[u8; KEM_PK_LEN], + _tv: std::marker::PhantomData, + ) -> Result<&mut Self> { + let test_values: EncapsAndMixTestValues = TV::initialize_values(); let mut shk = Secret::::zero(); kem.encaps(shk.secret_mut(), ct, pk)?; + TV::expose_mut_value(&test_values.shk, &mut shk); + TV::expose_mut_value(&test_values.ct, ct); self.mix(pk)?.mix(shk.secret())?.mix(ct) } @@ -3199,6 +3300,26 @@ impl HandshakeState { peer: PeerPtr, biscuit_ct: &mut [u8], ) -> Result<&mut Self> { + self.store_biscuit_with_test_vector::(srv, peer, biscuit_ct) + } + + /// Generic variant of [`Self::store_biscuit`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::store_biscuit`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn store_biscuit_with_test_vector( + &mut self, + srv: &mut CryptoServer, + peer: PeerPtr, + biscuit_ct: &mut [u8], + ) -> Result<&mut Self> { + let test_values: StoreBiscuitTestValues = TV::initialize_values(); let mut biscuit = Secret::::zero(); // pt buffer let mut biscuit: Ref<&mut [u8], Biscuit> = Ref::new(biscuit.secret_mut().as_mut_slice()).unwrap(); @@ -3212,6 +3333,8 @@ impl HandshakeState { .ck .copy_from_slice(self.ck.clone().danger_into_secret().secret()); + TV::check_value(&test_values.biscuit, &biscuit.as_bytes().to_vec()); + // calculate ad contents let ad = hash_domains::biscuit_ad(peer.get(srv).protocol_version.keyed_hash())? .mix(srv.spkm.deref())? @@ -3224,14 +3347,18 @@ impl HandshakeState { // The first bit of the nonce indicates which biscuit key was used // TODO: This is premature optimization. Remove! - let bk = srv.active_biscuit_key(); + let bk = srv.active_biscuit_key_with_test_vector::(); let mut n = XAEADNonce::random(); + + TV::expose_mut_value(&test_values.n, &mut n); + n[0] &= 0b0111_1111; n[0] |= (bk.0 as u8 & 0x1) << 7; let k = bk.get(srv).value.secret(); let pt = biscuit.as_bytes(); XAead.encrypt_with_nonce_in_ctxt(biscuit_ct, k, &n, &ad, pt)?; + TV::check_value(&test_values.biscuit_ct, &biscuit_ct.to_vec()); self.mix(biscuit_ct) } @@ -3400,6 +3527,26 @@ impl CryptoServer { /// Core cryptographic protocol implementation: Kicks of the handshake /// on the initiator side, producing the InitHello message. pub fn handle_initiation(&mut self, peer: PeerPtr, ih: &mut InitHello) -> Result { + self.handle_initiation_with_test_vector::(peer, ih) + } + + /// Generic variant of [`Self::handle_initiation`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::handle_initiation`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn handle_initiation_with_test_vector( + &mut self, + peer: PeerPtr, + ih: &mut InitHello, + ) -> Result { + let test_values: HandleInitiationTestValues = TV::initialize_values(); + #[cfg(feature = "trace_bench")] let _span_guard = rosenpass_util::trace_bench::trace().emit_span("handle_initiation"); @@ -3408,6 +3555,12 @@ impl CryptoServer { peer.get(self).protocol_version.keyed_hash(), ); + // hs.cookie_value.created_at = tv_const!(hs.cookie_value.created_at, "init_handshake-cookie-created_at"); + TV::expose_mut_value( + &test_values.init_handshake_cookie, + &mut hs.cookie_value.value, + ); + // IHI1 protocol_section!("IHI1", { hs.core.init(peer.get(self).spkt.deref())?; @@ -3416,24 +3569,40 @@ impl CryptoServer { // IHI2 protocol_section!("IHI2", { hs.core.sidi.randomize(); + TV::expose_mut_value(&test_values.init_handshake_sidi, &mut hs.core.sidi); ih.sidi.copy_from_slice(&hs.core.sidi.value); }); // IHI3 protocol_section!("IHI3", { EphemeralKem.keygen(hs.eski.secret_mut(), &mut hs.epki)?; + TV::expose_mut_value(&test_values.init_handshake_eski, &mut hs.eski); + TV::expose_mut_value(&test_values.init_handshake_epki, &mut hs.epki); ih.epki.copy_from_slice(&hs.epki.value); }); // IHI4 protocol_section!("IHI4", { hs.core.mix(ih.sidi.as_slice())?.mix(ih.epki.as_slice())?; + TV::check_value( + &test_values.init_handshake_mix_1, + &hs.core.ck.clone().danger_into_secret(), + ); }); // IHI5 protocol_section!("IHI5", { - hs.core - .encaps_and_mix(&StaticKem, &mut ih.sctr, peer.get(self).spkt.deref())?; + use std::marker::PhantomData; + hs.core.encaps_and_mix_with_test_vector( + &StaticKem, + &mut ih.sctr, + peer.get(self).spkt.deref(), + PhantomData::, + )?; + TV::check_value( + &test_values.init_handshake_mix_2, + &hs.core.ck.clone().danger_into_secret(), + ); }); // IHI6 @@ -3443,6 +3612,11 @@ impl CryptoServer { self.pidm(peer.get(self).protocol_version.keyed_hash())? .as_ref(), )?; + TV::check_value(&test_values.init_hello_pidic, &ih.pidic); + TV::check_value( + &test_values.init_handshake_mix_3, + &hs.core.ck.clone().danger_into_secret(), + ); }); // IHI7 @@ -3450,16 +3624,24 @@ impl CryptoServer { hs.core .mix(self.spkm.deref())? .mix(peer.get(self).psk.secret())?; + TV::check_value( + &test_values.init_handshake_mix_4, + &hs.core.ck.clone().danger_into_secret(), + ); }); // IHI8 protocol_section!("IHI8", { hs.core.encrypt_and_mix(ih.auth.as_mut_slice(), &[])?; + TV::check_value(&test_values.init_hello_auth, &ih.auth); + TV::check_value( + &test_values.init_handshake_mix_5, + &hs.core.ck.clone().danger_into_secret(), + ); }); // Update the handshake hash last (not changing any state on prior error peer.hs().insert(self, hs)?; - Ok(peer) } @@ -3471,6 +3653,27 @@ impl CryptoServer { rh: &mut RespHello, keyed_hash: KeyedHash, ) -> Result { + self.handle_init_hello_with_test_vector::(ih, rh, keyed_hash) + } + + /// Generic variant of [`Self::handle_init_hello`] that allows selecting + /// a [`TestVector`] implementation. + /// + /// This function is primarily intended for testing with different + /// test vector strategies. In production code, prefer using + /// [`Self::handle_init_hello`], which defaults to [`assert_tv::TestVectorNOP`]. + /// + /// Use this function with [`assert_tv::TestVectorActive`] in tests that require + /// applying actual test vectors. See the `tests::test_vector_crypto_server` + /// test for an example + pub fn handle_init_hello_with_test_vector( + &mut self, + ih: &InitHello, + rh: &mut RespHello, + keyed_hash: KeyedHash, + ) -> Result { + let test_values: HandleInitHelloTestValues = TV::initialize_values(); + #[cfg(feature = "trace_bench")] let _span_guard = rosenpass_util::trace_bench::trace().emit_span("handle_init_hello"); @@ -3487,11 +3690,19 @@ impl CryptoServer { protocol_section!("IHR4", { core.mix(&ih.sidi)?.mix(&ih.epki)?; }); + TV::check_value( + &test_values.chaining_key_ihr_4, + &core.ck.clone().danger_into_secret(), + ); // IHR5 protocol_section!("IHR5", { core.decaps_and_mix(&StaticKem, self.sskm.secret(), self.spkm.deref(), &ih.sctr)?; }); + TV::check_value( + &test_values.chaining_key_ihr_5, + &core.ck.clone().danger_into_secret(), + ); // IHR6 let peer = protocol_section!("IHR6", { @@ -3500,21 +3711,34 @@ impl CryptoServer { self.find_peer(peerid) .with_context(|| format!("No such peer {peerid:?}."))? }); + TV::check_value( + &test_values.chaining_key_ihr_6, + &core.ck.clone().danger_into_secret(), + ); // IHR7 protocol_section!("IHR7", { core.mix(peer.get(self).spkt.deref())? .mix(peer.get(self).psk.secret())?; }); + TV::check_value( + &test_values.chaining_key_ihr_7, + &core.ck.clone().danger_into_secret(), + ); // IHR8 protocol_section!("IHR8", { core.decrypt_and_mix(&mut [0u8; 0], &ih.auth)?; }); + TV::check_value( + &test_values.chaining_key_ihr_8, + &core.ck.clone().danger_into_secret(), + ); // RHR1 protocol_section!("RHR1", { core.sidr.randomize(); + TV::expose_mut_value(&test_values.session_id, &mut core.sidr); rh.sidi.copy_from_slice(core.sidi.as_ref()); rh.sidr.copy_from_slice(core.sidr.as_ref()); }); @@ -3522,28 +3746,59 @@ impl CryptoServer { // RHR3 protocol_section!("RHR3", { core.mix(&rh.sidr)?.mix(&rh.sidi)?; + TV::check_value( + &test_values.chaining_key_rhr_3, + &core.ck.clone().danger_into_secret(), + ); }); // RHR4 protocol_section!("RHR4", { - core.encaps_and_mix(&EphemeralKem, &mut rh.ecti, &ih.epki)?; + use std::marker::PhantomData; + core.encaps_and_mix_with_test_vector( + &EphemeralKem, + &mut rh.ecti, + &ih.epki, + PhantomData::, + )?; + TV::check_value( + &test_values.chaining_key_rhr_4, + &core.ck.clone().danger_into_secret(), + ); }); // RHR5 protocol_section!("RHR5", { - core.encaps_and_mix(&StaticKem, &mut rh.scti, peer.get(self).spkt.deref())?; + use std::marker::PhantomData; + core.encaps_and_mix_with_test_vector( + &StaticKem, + &mut rh.scti, + peer.get(self).spkt.deref(), + PhantomData::, + )?; + TV::check_value( + &test_values.chaining_key_rhr_5, + &core.ck.clone().danger_into_secret(), + ); }); // RHR6 protocol_section!("RHR6", { - core.store_biscuit(self, peer, &mut rh.biscuit)?; + core.store_biscuit_with_test_vector::(self, peer, &mut rh.biscuit)?; + TV::check_value( + &test_values.chaining_key_rhr_6, + &core.ck.clone().danger_into_secret(), + ); }); // RHR7 protocol_section!("RHR7", { core.encrypt_and_mix(&mut rh.auth, &[])?; + TV::check_value( + &test_values.chaining_key_rhr_7, + &core.ck.clone().danger_into_secret(), + ); }); - Ok(peer) } diff --git a/rosenpass/src/protocol/test_vector_sets.rs b/rosenpass/src/protocol/test_vector_sets.rs new file mode 100644 index 0000000..615f13a --- /dev/null +++ b/rosenpass/src/protocol/test_vector_sets.rs @@ -0,0 +1,176 @@ +//! Test vector definitions for derandomizing protocol internals. +//! +//! This module contains the definitions of internal test vector values used by +//! `CryptoServer` and the functions in `protocol.rs`. These values allow the +//! protocol implementation to be derandomized for deterministic testing and +//! reproducible behavior across runs. +//! +//! For an example of a test that uses these test vector values, see: +//! `rosenpass/tests/test_vector_crypto_server.rs`. + +use crate::msgs::SESSION_ID_LEN; +use crate::protocol::basic_types::SessionId; +use crate::protocol::constants::COOKIE_VALUE_LEN; +use anyhow::anyhow; +use assert_tv::TestValue; +use assert_tv::TestVectorSet; +use base64::Engine; +use rosenpass_cipher_traits::primitives::{Aead, Kem}; +use rosenpass_ciphers::{EphemeralKem, XAead, KEY_LEN}; +use rosenpass_secret_memory::{Public, PublicBox, Secret}; +use serde_json::Value; + +#[derive(TestVectorSet)] +pub struct EncapsAndMixTestValues { + #[test_vec(serialize_with = "serialize_byte_arr")] + #[test_vec(deserialize_with = "deserialize_byte_arr")] + pub ct: TestValue<[u8; KEM_CT_LEN]>, + pub shk: TestValue>, +} + +#[derive(TestVectorSet)] +pub struct StoreBiscuitTestValues { + #[test_vec(serialize_with = "serialize_byte_vec")] + #[test_vec(deserialize_with = "deserialize_byte_vec")] + pub biscuit: TestValue>, + + pub n: TestValue>, + + #[test_vec(serialize_with = "serialize_byte_vec")] + #[test_vec(deserialize_with = "deserialize_byte_vec")] + pub biscuit_ct: TestValue>, +} + +#[derive(TestVectorSet)] +pub struct HandleInitiationTestValues { + #[test_vec(name = "hs.cookie_value.value")] + pub init_handshake_cookie: TestValue>, + + #[test_vec(name = "hs.core.sidi")] + pub init_handshake_sidi: TestValue>, + + #[test_vec(name = "hs.eski")] + pub init_handshake_eski: TestValue>, + + #[test_vec(name = "hs.core.ck")] + pub init_handshake_epki: TestValue>, + + #[test_vec(name = "hs.core.ck 1")] + pub init_handshake_mix_1: TestValue>, + + #[test_vec(name = "hs.core.ck 2")] + pub init_handshake_mix_2: TestValue>, + + #[test_vec(name = "ih.pidic")] + #[test_vec(serialize_with = "serialize_byte_arr")] + #[test_vec(deserialize_with = "deserialize_byte_arr")] + pub init_hello_pidic: TestValue<[u8; rosenpass_ciphers::Aead::TAG_LEN + 32]>, + + #[test_vec(name = "hs.core.ck 3")] + pub init_handshake_mix_3: TestValue>, + + #[test_vec(name = "hs.core.ck 4")] + pub init_handshake_mix_4: TestValue>, + + #[test_vec(name = "ih.auth")] + #[test_vec(serialize_with = "serialize_byte_arr")] + #[test_vec(deserialize_with = "deserialize_byte_arr")] + pub init_hello_auth: TestValue<[u8; rosenpass_ciphers::Aead::TAG_LEN]>, + + #[test_vec(name = "hs.core.ck 5")] + pub init_handshake_mix_5: TestValue>, +} + +#[derive(TestVectorSet)] +pub struct HandleInitHelloTestValues { + #[test_vec(name = "chaining_key_ihr IHR 4")] + pub chaining_key_ihr_4: TestValue>, + + #[test_vec(name = "chaining_key_ihr IHR 5")] + pub chaining_key_ihr_5: TestValue>, + + #[test_vec(name = "chaining_key_ihr IHR 6")] + pub chaining_key_ihr_6: TestValue>, + + #[test_vec(name = "chaining_key_ihr IHR 7")] + pub chaining_key_ihr_7: TestValue>, + + #[test_vec(name = "chaining_key_ihr IHR 8")] + pub chaining_key_ihr_8: TestValue>, + + #[test_vec(name = "session_id")] + pub session_id: TestValue, + + #[test_vec(name = "chaining_key_ihr RHR 3")] + pub chaining_key_rhr_3: TestValue>, + + #[test_vec(name = "chaining_key_ihr RHR 4")] + pub chaining_key_rhr_4: TestValue>, + + #[test_vec(name = "chaining_key_ihr RHR 5")] + pub chaining_key_rhr_5: TestValue>, + + #[test_vec(name = "chaining_key_ihr RHR 6")] + pub chaining_key_rhr_6: TestValue>, + + #[test_vec(name = "chaining_key_ihr RHR 7")] + pub chaining_key_rhr_7: TestValue>, +} + +#[derive(TestVectorSet)] +pub struct InitHandshakeTestValues { + #[test_vec(serialize_with = "serialize_byte_vec")] + #[test_vec(deserialize_with = "deserialize_byte_vec")] + pub msg: TestValue>, +} + +#[derive(TestVectorSet)] +pub struct CycledBiscuitSecretKeyTestValues { + #[test_vec(name = "CryptoServer::biscuit_key[r]")] + #[test_vec(description = "Biscuit key after being cycled")] + pub cycled_biscuit_secret_key: TestValue>, +} + +// Serialization helpers for raw byte arrays and vectors. +// +// These functions provide a small bridge implementation to serialize/deserialize +// standard values that do not carry serde implementations with the desired +// base64 format by default. They are used by the test vector machinery to +// encode `[u8; N]` and `Vec` values consistently. + +/// Serialize a byte array as a base64 JSON string (bridge for `[u8; N]`). +pub fn serialize_byte_arr(observed_value: &[u8; N]) -> anyhow::Result { + let encoded = base64::engine::general_purpose::STANDARD.encode(observed_value); + Ok(Value::String(encoded)) +} + +/// Deserialize a base64 JSON string into a byte array (bridge for `[u8; N]`). +pub fn deserialize_byte_arr(value: &Value) -> anyhow::Result<[u8; N]> { + let value: &str = value + .as_str() + .ok_or_else(|| anyhow!("Unexpected value, expected string"))?; + let decoded = base64::engine::general_purpose::STANDARD + .decode(value.as_bytes()) + .map_err(|e| anyhow!("Couldn't decode value: {e}"))?; + decoded + .as_slice() + .try_into() + .map_err(|e| anyhow!("Couldn't convert to array of size={}: {e}", N)) +} + +/// Serialize a byte vector as a base64 JSON string (bridge for `Vec`). +pub fn serialize_byte_vec(observed_value: &Vec) -> anyhow::Result { + let encoded = base64::engine::general_purpose::STANDARD.encode(observed_value); + Ok(Value::String(encoded)) +} + +/// Deserialize a base64 JSON string into a byte vector (bridge for `Vec`). +pub fn deserialize_byte_vec(value: &Value) -> anyhow::Result> { + let value: &str = value + .as_str() + .ok_or_else(|| anyhow!("Unexpected value, expected string"))?; + let decoded = base64::engine::general_purpose::STANDARD + .decode(value.as_bytes()) + .map_err(|e| anyhow!("Couldn't decode value: {e}"))?; + Ok(decoded) +} diff --git a/rosenpass/tests/test_vector_crypto_server.rs b/rosenpass/tests/test_vector_crypto_server.rs new file mode 100644 index 0000000..31313b8 --- /dev/null +++ b/rosenpass/tests/test_vector_crypto_server.rs @@ -0,0 +1,180 @@ +//! # Deterministic protocol test based on captured internal randomness +//! +//! This test validates the rosenpass protocol implementation by recording all internal randomness +//! during execution and saving it to a test vector file (`crypto_server_test_vector_1.toml`). +//! On subsequent runs, the test replays this randomness to enforce deterministic behavior, +//! making any changes to the protocol's internal logic observable through test failures. +//! +//! ## Reinitializing the Test Vector +//! +//! If the test fails due to a mismatch between current output and the recorded test vector, +//! it likely indicates a change in implementation. To accept and re-record the new behavior, +//! re-run the test in initialization mode by setting the environment variable: +//! +//! ```bash +//! TEST_MODE=init cargo test crypto_server_test_vector_1 +//! ``` + +use assert_tv::{test_vec_case, TestValue, TestVector, TestVectorActive, TestVectorSet}; +use rosenpass::protocol::basic_types::{MsgBuf, SPk, SSk, SymKey}; +use rosenpass::protocol::osk_domain_separator::OskDomainSeparator; +use rosenpass::protocol::test_vector_sets::deserialize_byte_vec; +use rosenpass::protocol::test_vector_sets::serialize_byte_vec; +use rosenpass::protocol::{CryptoServer, PeerPtr, ProtocolVersion}; +use rosenpass_cipher_traits::primitives::Kem; +use rosenpass_ciphers::StaticKem; +use rosenpass_secret_memory::policy::*; +use rosenpass_secret_memory::{PublicBox, Secret}; +use std::ops::DerefMut; + +use rosenpass::protocol::constants::COOKIE_SECRET_LEN; +use rosenpass_ciphers::KEY_LEN; + +#[derive(TestVectorSet)] +pub struct TestCaseValues { + #[test_vec(name = "peer_a_sk")] + #[test_vec(description = "test setup: peer a secret key")] + #[test_vec(offload = true)] + peer_a_sk: TestValue>, + #[test_vec(name = "peer_a_pk")] + #[test_vec(description = "test setup: peer a public key")] + #[test_vec(offload = true)] + peer_a_pk: TestValue>, + + #[test_vec(name = "peer_b_sk")] + #[test_vec(description = "test setup: peer b secret key")] + #[test_vec(offload = true)] + peer_b_sk: TestValue>, + #[test_vec(name = "peer_b_pk")] + #[test_vec(description = "test setup: peer b public key")] + #[test_vec(offload = true)] + peer_b_pk: TestValue>, + + #[test_vec(name = "psk")] + #[test_vec(description = "pre-shared key")] + psk: TestValue>, + + #[test_vec(name = "message")] + #[test_vec(description = "message exchanged by the protocol parties")] + #[test_vec(serialize_with = "serialize_byte_vec")] + #[test_vec(deserialize_with = "deserialize_byte_vec")] + #[test_vec(offload = true)] + message: TestValue>, + + #[test_vec(name = "exchanged_key")] + #[test_vec(description = "final exchanged key")] + exchanged_key: TestValue>, +} + +#[derive(TestVectorSet)] +struct CryptoServerTestValues { + #[test_vec(name = "CryptoServer::cookie_secrets[0]")] + cookie_secret_0: TestValue>, + + #[test_vec(name = "CryptoServer::cookie_secrets[1]")] + cookie_secret_1: TestValue>, + + #[test_vec(name = "CryptoServer::biscuit_keys[0]")] + biscuit_key_0: TestValue>, + + #[test_vec(name = "CryptoServer::biscuit_keys[1]")] + biscuit_key_1: TestValue>, +} + +#[test_vec_case(format = "toml")] +fn crypto_server_test_vector_1() -> anyhow::Result<()> { + type TV = TestVectorActive; + let test_values: TestCaseValues = TV::initialize_values(); + + // Set security policy for storing secrets + secret_policy_try_use_memfd_secrets(); + + // initialize secret and public key for peer a ... + let (mut peer_a_sk, mut peer_a_pk) = gen_keypair::(); + + TV::expose_mut_value(&test_values.peer_a_sk, &mut peer_a_sk); + TV::expose_mut_value(&test_values.peer_a_pk, &mut peer_a_pk); + + // ... and for peer b + let (mut peer_b_sk, mut peer_b_pk) = gen_keypair::(); + + TV::expose_mut_value(&test_values.peer_b_sk, &mut peer_b_sk); + TV::expose_mut_value(&test_values.peer_b_pk, &mut peer_b_pk); + + // initialize server and a pre-shared key + let psk = TV::expose_value(&test_values.psk, SymKey::random()); + + let mut a = CryptoServer::new(peer_a_sk, peer_a_pk.clone()); + de_randomize_time_base_cookie_secrets::(&mut a); + + let mut b = CryptoServer::new(peer_b_sk, peer_b_pk.clone()); + de_randomize_time_base_cookie_secrets::(&mut b); + + // introduce peers to each other + a.add_peer( + Some(psk.clone()), + peer_b_pk, + ProtocolVersion::V03, + OskDomainSeparator::default(), + )?; + b.add_peer( + Some(psk), + peer_a_pk, + ProtocolVersion::V03, + OskDomainSeparator::default(), + )?; + + // declare buffers for message exchange + let (mut a_buf, mut b_buf) = (MsgBuf::zero(), MsgBuf::zero()); + + // let a initiate a handshake + let mut maybe_len = + Some(a.initiate_handshake_with_test_vector::(PeerPtr(0), a_buf.as_mut_slice())?); + + // let a and b communicate + while let Some(len) = maybe_len { + TV::check_value(&test_values.message, &a_buf[..len].to_vec()); + maybe_len = b + .handle_msg_with_test_vector::(&a_buf[..len], &mut b_buf[..])? + .resp; + std::mem::swap(&mut a, &mut b); + std::mem::swap(&mut a_buf, &mut b_buf); + } + + // all done! Extract the shared keys and ensure they are identical + let a_key = a.osk(PeerPtr(0))?; + let b_key = b.osk(PeerPtr(0))?; + assert_eq!( + a_key.secret(), + b_key.secret(), + "the key exchanged failed to establish a shared secret" + ); + + TV::check_value(&test_values.exchanged_key, &a_key); + Ok(()) +} +fn gen_keypair() -> (SSk, SPk) { + let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); + StaticKem + .keygen(sk.secret_mut(), pk.deref_mut()) + .expect("Error generating keypair"); + (sk, pk) +} + +pub fn de_randomize_time_base_cookie_secrets(cs: &mut CryptoServer) { + let test_values: CryptoServerTestValues = TV::initialize_values(); + + TV::expose_mut_value( + &test_values.cookie_secret_0, + &mut cs.cookie_secrets[0].value, + ); + + TV::expose_mut_value( + &test_values.cookie_secret_1, + &mut cs.cookie_secrets[1].value, + ); + + TV::expose_mut_value(&test_values.biscuit_key_0, &mut cs.biscuit_keys[0].value); + + TV::expose_mut_value(&test_values.biscuit_key_1, &mut cs.biscuit_keys[1].value); +} diff --git a/secret-memory/Cargo.toml b/secret-memory/Cargo.toml index 50f5881..aa286e0 100644 --- a/secret-memory/Cargo.toml +++ b/secret-memory/Cargo.toml @@ -19,6 +19,10 @@ rand = { workspace = true } memsec = { workspace = true } allocator-api2 = { workspace = true } log = { workspace = true } +assert_tv = { workspace = true } +base64 = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } [dev-dependencies] allocator-api2-tests = { workspace = true } diff --git a/secret-memory/src/lib.rs b/secret-memory/src/lib.rs index 035c44d..8aa9316 100644 --- a/secret-memory/src/lib.rs +++ b/secret-memory/src/lib.rs @@ -47,4 +47,6 @@ mod secret; pub use crate::secret::Secret; pub mod policy; +mod serialization; + pub use crate::policy::*; diff --git a/secret-memory/src/serialization.rs b/secret-memory/src/serialization.rs new file mode 100644 index 0000000..a8d496e --- /dev/null +++ b/secret-memory/src/serialization.rs @@ -0,0 +1,214 @@ +use crate::{Public, PublicBox, Secret}; +use base64::Engine; +use serde::de::{Error as DeError, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +fn encode_b64(bytes: &[u8]) -> String { + base64::engine::general_purpose::STANDARD.encode(bytes) +} + +fn decode_b64(s: &str) -> Result, String> { + base64::engine::general_purpose::STANDARD + .decode(s.as_bytes()) + .map_err(|e| format!("Couldn't decode base64: {e}")) +} + +struct B64BytesVisitor; + +impl<'de> Visitor<'de> for B64BytesVisitor { + type Value = Vec; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "a base64-encoded string") + } + + fn visit_str(self, v: &str) -> Result { + decode_b64(v).map_err(E::custom) + } + + fn visit_string(self, v: String) -> Result { + self.visit_str(&v) + } +} + +impl Serialize for Secret { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&encode_b64(self.secret())) + } +} + +impl<'de, const N: usize> Deserialize<'de> for Secret { + fn deserialize>(deserializer: D) -> Result { + let bytes: Vec = deserializer.deserialize_string(B64BytesVisitor)?; + if bytes.len() != N { + return Err(D::Error::custom(format!( + "Unexpected length: got {}, expected {}", + bytes.len(), + N + ))); + } + // Copies from heap bytes into the internal storage; + // no large stack temporaries. + Ok(Secret::::from_slice(bytes.as_slice())) + } +} + +impl Serialize for Public { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&encode_b64(&self.value)) + } +} + +impl<'de, const N: usize> Deserialize<'de> for Public { + fn deserialize>(deserializer: D) -> Result { + let bytes: Vec = deserializer.deserialize_string(B64BytesVisitor)?; + if bytes.len() != N { + return Err(D::Error::custom(format!( + "Unexpected length: got {}, expected {}", + bytes.len(), + N + ))); + } + Ok(Public::::from_slice(bytes.as_slice())) + } +} + +impl Serialize for PublicBox { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&encode_b64(self.inner.value.as_slice())) + } +} + +impl<'de, const N: usize> Deserialize<'de> for PublicBox { + fn deserialize>(deserializer: D) -> Result { + let bytes: Vec = deserializer.deserialize_string(B64BytesVisitor)?; + if bytes.len() != N { + return Err(D::Error::custom(format!( + "Unexpected length: got {}, expected {}", + bytes.len(), + N + ))); + } + // Allocate Public on the heap and copy bytes into it + let mut inner = Box::new(Public::::zero()); + inner.copy_from_slice(bytes.as_slice()); + Ok(PublicBox { inner }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::secret_policy_use_only_malloc_secrets; + use serde::{de::DeserializeOwned, Serialize}; + use serde_json; + + // Generic helper: serialize to JSON, then deserialize back. + fn roundtrip_json(value: &T) -> T + where + T: Serialize + DeserializeOwned, + { + let json = serde_json::to_string(value).expect("serialize"); + serde_json::from_str::(&json).expect("deserialize") + } + + pub fn test_init_secret_memory_policy() { + secret_policy_use_only_malloc_secrets(); + } + + #[test] + fn secret_roundtrip_json() { + test_init_secret_memory_policy(); + const N: usize = 32; + let src = [0xABu8; N]; + let original = Secret::::from_slice(&src); + let recovered: Secret = roundtrip_json(&original); + assert_eq!( + original.secret(), + recovered.secret(), + "Secret bytes must match after roundtrip" + ); + } + + #[test] + fn public_roundtrip_json() { + const N: usize = 48; + let src = [0x11u8; N]; + let original = Public::::from_slice(&src); + let json = serde_json::to_string(&original).expect("serialize"); + let recovered: Public = serde_json::from_str(&json).expect("deserialize"); + // Avoid relying on private fields: compare canonical serialization strings. + let json2 = serde_json::to_string(&recovered).expect("re-serialize"); + assert_eq!( + json, json2, + "Public must serialize identically after roundtrip" + ); + } + + #[test] + fn public_box_roundtrip_json() { + const N: usize = 64; + let src = [0x7Fu8; N]; + + let original = PublicBox::::new(src); + + let json = serde_json::to_string(&original).expect("serialize"); + let recovered: PublicBox = serde_json::from_str(&json).expect("deserialize"); + let json2 = serde_json::to_string(&recovered).expect("re-serialize"); + assert_eq!( + json, json2, + "PublicBox must serialize identically after roundtrip" + ); + } + + #[test] + fn secret_len_mismatch_is_error() { + test_init_secret_memory_policy(); + const N_SMALL: usize = 16; + const N_BIG: usize = 32; + + let src = [0x55u8; N_SMALL]; + let small = Secret::::from_slice(&src); + let json = serde_json::to_string(&small).expect("serialize"); + + // Attempt to deserialize a 16-byte payload into Secret<32> should fail. + let res = serde_json::from_str::>(&json); + assert!( + res.is_err(), + "Deserializing into a larger fixed size must error" + ); + } + + #[test] + fn public_len_mismatch_is_error() { + const N_SMALL: usize = 24; + const N_BIG: usize = 40; + + let src = [0x33u8; N_SMALL]; + let small = Public::::from_slice(&src); + let json = serde_json::to_string(&small).expect("serialize"); + + let res = serde_json::from_str::>(&json); + assert!( + res.is_err(), + "Deserializing into a larger fixed size must error" + ); + } + + #[test] + fn public_box_len_mismatch_is_error() { + const N_SMALL: usize = 8; + const N_BIG: usize = 12; + + let src = [0xE0u8; N_SMALL]; + let small = PublicBox::::new(src); + let json = serde_json::to_string(&small).expect("serialize"); + + let res = serde_json::from_str::>(&json); + assert!( + res.is_err(), + "Deserializing into a different fixed size must error" + ); + } +} diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 2772ccb..6834781 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -1,4 +1,27 @@ # cargo-vet audits file -[audits] +[[audits.assert_tv]] +who = "Amin Faez " +criteria = "safe-to-deploy" +version = "0.5.1" + +[[audits.assert_tv]] +who = "Amin Faez " +criteria = "safe-to-deploy" +version = "0.5.1" + +[[audits.assert_tv_macros]] +who = "Amin Faez " +criteria = "safe-to-run" +version = "0.5.1" + +[[audits.ryu]] +who = "Amin Faez " +criteria = "safe-to-deploy" +version = "1.0.10" + +[[audits.serde_json]] +who = "Amin Faez " +criteria = "safe-to-deploy" +version = "1.0.138" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index ab28b36..396bd69 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -77,6 +77,14 @@ criteria = "safe-to-deploy" version = "1.0.98" criteria = "safe-to-deploy" +[[exemptions.assert_tv]] +version = "0.6.4" +criteria = "safe-to-deploy" + +[[exemptions.assert_tv_macros]] +version = "0.6.4" +criteria = "safe-to-deploy" + [[exemptions.atomic-polyfill]] version = "1.0.3" criteria = "safe-to-deploy" @@ -597,10 +605,6 @@ criteria = "safe-to-deploy" version = "0.38.44" criteria = "safe-to-deploy" -[[exemptions.ryu]] -version = "1.0.19" -criteria = "safe-to-deploy" - [[exemptions.scc]] version = "2.3.3" criteria = "safe-to-run" @@ -621,6 +625,10 @@ criteria = "safe-to-deploy" version = "0.6.8" criteria = "safe-to-deploy" +[[exemptions.serde_yaml]] +version = "0.9.34+deprecated" +criteria = "safe-to-deploy" + [[exemptions.serial_test]] version = "3.2.0" criteria = "safe-to-run" @@ -721,6 +729,10 @@ criteria = "safe-to-deploy" version = "1.0.17" criteria = "safe-to-deploy" +[[exemptions.unsafe-libyaml]] +version = "0.2.11" +criteria = "safe-to-deploy" + [[exemptions.uuid]] version = "1.14.0" criteria = "safe-to-deploy" @@ -952,3 +964,15 @@ criteria = "safe-to-deploy" [[exemptions.zerocopy-derive]] version = "0.8.24" criteria = "safe-to-deploy" + +[[exemptions.zstd]] +version = "0.13.3" +criteria = "safe-to-deploy" + +[[exemptions.zstd-safe]] +version = "7.2.4" +criteria = "safe-to-deploy" + +[[exemptions.zstd-sys]] +version = "2.0.15+zstd.1.5.7" +criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 328667a..8b451be 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -59,6 +59,17 @@ who = "Nick Fitzgerald " criteria = "safe-to-deploy" version = "1.4.1" +[[audits.bytecode-alliance.audits.base64]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.21.0" +notes = "This crate has no dependencies, no build.rs, and contains no unsafe code." + +[[audits.bytecode-alliance.audits.base64]] +who = "Andrew Brown " +criteria = "safe-to-deploy" +delta = "0.21.3 -> 0.22.1" + [[audits.bytecode-alliance.audits.bitflags]] who = "Jamey Sharp " criteria = "safe-to-deploy" @@ -1138,6 +1149,21 @@ criteria = "safe-to-run" version = "1.2.1" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" +[[audits.isrg.audits.base64]] +who = "Tim Geoghegan " +criteria = "safe-to-deploy" +delta = "0.21.0 -> 0.21.1" + +[[audits.isrg.audits.base64]] +who = "Brandon Pitman " +criteria = "safe-to-deploy" +delta = "0.21.1 -> 0.21.2" + +[[audits.isrg.audits.base64]] +who = "David Cook " +criteria = "safe-to-deploy" +delta = "0.21.2 -> 0.21.3" + [[audits.isrg.audits.block-buffer]] who = "David Cook " criteria = "safe-to-deploy" @@ -1567,6 +1593,24 @@ version = "1.1.0" notes = "Straightforward crate with no unsafe code, does what it says on the tin." aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.ryu]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.10 -> 1.0.11" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.ryu]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "1.0.11 -> 1.0.12" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.ryu]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "1.0.12 -> 1.0.19" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + [[audits.mozilla.audits.semver]] who = "Jan-Erik Rediger " criteria = "safe-to-deploy"