Project import generated by Copybara. GitOrigin-RevId: 06b0c815d958bef0a60978d0766d65af839e37b6 Change-Id: Ifac0c45ae509ca8a8ed793fac5c981d4e23d3bf9
diff --git a/.gitmodules b/.gitmodules index caccb04..937bff2 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,3 +1,9 @@ [submodule "third_party/abseil-cpp"] path = third_party/abseil-cpp url = https://github.com/abseil/abseil-cpp.git +[submodule "third_party/benchmark"] + path = third_party/benchmark + url = https://github.com/google/benchmark.git +[submodule "third_party/boringssl"] + path = third_party/boringssl + url = https://boringssl.googlesource.com/boringssl
diff --git a/Dockerfile b/Dockerfile index 00dffc7..1f0462e 100644 --- a/Dockerfile +++ b/Dockerfile
@@ -21,7 +21,7 @@ RUN apt upgrade -y # install cargo with default settings -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.68.1 +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.72.0 ENV PATH="/root/.cargo/bin:${PATH}" RUN cargo install --locked cargo-deny --color never 2>&1 RUN cargo install cargo-fuzz --color never 2>&1 @@ -36,6 +36,7 @@ RUN cargo install bindgen-cli --version 0.64.0 RUN cargo install wasm-pack --color never 2>&1 RUN rustup toolchain add nightly +RUN rustup target add wasm32-unknown-unknown # boringssl build wants go RUN curl -L https://go.dev/dl/go1.20.2.linux-amd64.tar.gz | tar -C /usr/local -xz ENV PATH="$PATH:/usr/local/go/bin"
diff --git a/cmd-runner/Cargo.lock b/cmd-runner/Cargo.lock new file mode 100644 index 0000000..4dadf2e --- /dev/null +++ b/cmd-runner/Cargo.lock
@@ -0,0 +1,30 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "cmd-runner" +version = "0.1.0" +dependencies = [ + "anyhow", + "owo-colors", + "shell-escape", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
diff --git a/cmd-runner/Cargo.toml b/cmd-runner/Cargo.toml new file mode 100644 index 0000000..e79317e --- /dev/null +++ b/cmd-runner/Cargo.toml
@@ -0,0 +1,11 @@ +[package] +name = "cmd-runner" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +anyhow = "1.0.64" +shell-escape = "0.1.5" +owo-colors = "3.5.0" +
diff --git a/nearby/src/support.rs b/cmd-runner/src/lib.rs similarity index 91% rename from nearby/src/support.rs rename to cmd-runner/src/lib.rs index cc81352..fa2f1f4 100644 --- a/nearby/src/support.rs +++ b/cmd-runner/src/lib.rs
@@ -33,7 +33,12 @@ dir: &path::Path, cmd: impl AsRef<ffi::OsStr>, ) -> anyhow::Result<SuccessOutput> { - run::<C>(dir, process::Command::new("sh").current_dir(dir).args(["-c".as_ref(), cmd.as_ref()])) + run::<C>( + dir, + process::Command::new("sh") + .current_dir(dir) + .args(["-c".as_ref(), cmd.as_ref()]), + ) } /// Run a cmd with explicit args directly without a shell. @@ -53,7 +58,12 @@ A: Clone + IntoIterator<Item = S>, S: AsRef<ffi::OsStr>, { - run::<C>(dir, process::Command::new(cmd.as_ref()).current_dir(dir).args(args)) + run::<C>( + dir, + process::Command::new(cmd.as_ref()) + .current_dir(dir) + .args(args), + ) } /// Run the specified command. @@ -73,8 +83,16 @@ }, ); - let context = format!("{} [{}]", cmd_with_args.to_string_lossy(), dir.to_string_lossy(),); - println!("[{}] [{}]", cmd_with_args.to_string_lossy().green(), dir.to_string_lossy().blue()); + let context = format!( + "{} [{}]", + cmd_with_args.to_string_lossy(), + dir.to_string_lossy(), + ); + println!( + "[{}] [{}]", + cmd_with_args.to_string_lossy().green(), + dir.to_string_lossy().blue() + ); let mut child = command .env_clear()
diff --git a/nearby/.cargo/config-boringssl.toml b/nearby/.cargo/config-boringssl.toml index bab3518..2bde6df 100644 --- a/nearby/.cargo/config-boringssl.toml +++ b/nearby/.cargo/config-boringssl.toml
@@ -1,13 +1,9 @@ # The packages to override paths = [ - "../boringssl-build/boringssl/rust/bssl-crypto", - "../boringssl-build/boringssl/rust/bssl-sys", + "../third_party/boringssl/rust/bssl-sys", "../boringssl-build/rust-openssl/openssl", "../boringssl-build/rust-openssl/openssl-sys", ] [env] WORKSPACE_DIR = { value = "", relative = true } - - -
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock index 76582bf..7441c1b 100644 --- a/nearby/Cargo.lock +++ b/nearby/Cargo.lock
@@ -3,15 +3,6 @@ version = 3 [[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,6 +31,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -55,17 +60,6 @@ ] [[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", -] - -[[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -81,12 +75,6 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -188,21 +176,6 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -268,6 +241,13 @@ [[package]] name = "bssl-crypto" version = "0.1.0" +dependencies = [ + "bssl-sys 0.1.0", +] + +[[package]] +name = "bssl-sys" +version = "0.1.0" [[package]] name = "bssl-sys" @@ -290,20 +270,20 @@ version = "0.1.0" dependencies = [ "anyhow", - "base64 0.21.2", "chrono", "clap 4.3.19", + "cmd-runner", "crossbeam", "env_logger", + "file-header", "globset", "log", "owo-colors", - "reqwest", "semver", + "serde_json", "shell-escape", "tempfile", "thiserror", - "tinytemplate", "walkdir", "which", ] @@ -471,6 +451,15 @@ checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] +name = "cmd-runner" +version = "0.1.0" +dependencies = [ + "anyhow", + "owo-colors", + "shell-escape", +] + +[[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -487,6 +476,10 @@ ] [[package]] +name = "connections_adv" +version = "0.1.0" + +[[package]] name = "const-oid" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -652,6 +645,7 @@ "hex-literal", "rand", "rand_ext", + "tinyvec", ] [[package]] @@ -660,7 +654,6 @@ dependencies = [ "bssl-crypto", "crypto_provider", - "crypto_provider_stubs", ] [[package]] @@ -685,7 +678,7 @@ "hex-literal", "openssl", "ouroboros", - "rstest 0.17.0", + "rstest", ] [[package]] @@ -694,6 +687,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -731,7 +725,7 @@ "hex-literal", "rand", "rand_ext", - "rstest 0.17.0", + "rstest", "rstest_reuse", "test_helper", "wycheproof", @@ -748,9 +742,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ "cfg-if", "cpufeatures", @@ -824,9 +818,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -862,15 +856,6 @@ ] [[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] name = "env_logger" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -922,9 +907,22 @@ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7" + +[[package]] +name = "file-header" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5568149106e77ae33bc3a2c3ef3839cbe63ffa4a8dd4a81612a6f9dfdbc2e9f" +dependencies = [ + "crossbeam", + "lazy_static", + "license", + "thiserror", + "walkdir", +] [[package]] name = "flate2" @@ -958,110 +956,6 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1084,10 +978,14 @@ ] [[package]] -name = "gimli" -version = "0.27.3" +name = "ghash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] [[package]] name = "globset" @@ -1114,25 +1012,6 @@ ] [[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1144,10 +1023,8 @@ dependencies = [ "criterion", "crypto_provider", - "hashbrown 0.14.0", - "lock_api", - "portable-atomic", - "spin 0.9.8", + "lazy_static", + "lock_adapter", ] [[package]] @@ -1157,16 +1034,6 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] name = "hdrhistogram" version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1232,84 +1099,12 @@ ] [[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "hyper" -version = "0.14.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" -dependencies = [ - "futures-util", - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] name = "iana-time-zone" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1333,23 +1128,13 @@ ] [[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -1369,12 +1154,6 @@ ] [[package]] -name = "ipnet" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" - -[[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1518,6 +1297,17 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] +name = "license" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778718185117620a06e95d2b1e57d50166b1d6bfad93c8abfc1b3344c863ad8c" +dependencies = [ + "reword", + "serde", + "serde_json", +] + +[[package]] name = "linux-raw-sys" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1562,12 +1352,6 @@ ] [[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1583,17 +1367,6 @@ ] [[package]] -name = "mio" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1622,6 +1395,8 @@ "np_hkdf", "rand", "rand_ext", + "rmp-serde", + "serde", "serde_json", "sink", "strum", @@ -1649,8 +1424,9 @@ "crypto_provider", "crypto_provider_default", "handle_map", + "lazy_static", + "lock_adapter", "np_adv", - "spin 0.9.8", ] [[package]] @@ -1711,15 +1487,6 @@ ] [[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1769,7 +1536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" dependencies = [ - "bssl-sys", + "bssl-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc", "libc", "pkg-config", @@ -1823,22 +1590,10 @@ ] [[package]] -name = "percent-encoding" -version = "2.3.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pkcs8" @@ -1903,12 +1658,6 @@ ] [[package]] -name = "portable-atomic" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" - -[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2140,69 +1889,34 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] -name = "reqwest" -version = "0.11.18" +name = "reword" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "fe272098dce9ed76b479995953f748d1851261390b08f8a0ff619c885a1f0765" dependencies = [ - "base64 0.21.2", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", + "unicode-segmentation", +] + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rstest" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros 0.16.0", - "rustc_version", ] [[package]] @@ -2211,26 +1925,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" dependencies = [ - "rstest_macros 0.17.0", + "rstest_macros", "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", - "unicode-ident", -] - -[[package]] -name = "rstest_macros" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" @@ -2245,23 +1945,17 @@ [[package]] name = "rstest_reuse" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" dependencies = [ "quote", "rand", "rustc_version", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2284,37 +1978,6 @@ ] [[package]] -name = "rustls" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" -dependencies = [ - "base64 0.21.2", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2342,16 +2005,6 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2372,18 +2025,18 @@ [[package]] name = "serde" -version = "1.0.179" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -2402,18 +2055,6 @@ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] name = "sha2" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2444,25 +2085,6 @@ ] [[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2501,21 +2123,21 @@ [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] @@ -2617,88 +2239,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" -dependencies = [ - "autocfg", - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "pin-project-lite", - "socket2", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" @@ -2711,8 +2251,7 @@ version = "0.1.0" dependencies = [ "cfg-if", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", + "crypto_provider_default", "lazy_static", "lock_adapter", "log", @@ -2729,11 +2268,10 @@ "bytes", "criterion", "crypto_provider", - "crypto_provider_openssl", + "crypto_provider_default", "crypto_provider_rustcrypto", "nom", "rand", - "rstest 0.16.0", "ukey2_proto", "ukey2_rs", ] @@ -2743,8 +2281,7 @@ version = "0.1.0" dependencies = [ "cfg-if", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", + "crypto_provider_default", "jni", "lazy_static", "lock_adapter", @@ -2769,13 +2306,11 @@ version = "0.1.0" dependencies = [ "crypto_provider", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", + "crypto_provider_default", "derive-getters", "log", "num-bigint", "rand", - "rstest 0.16.0", "sha2", "ukey2_proto", ] @@ -2791,25 +2326,16 @@ ] [[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "unicode-segmentation" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "universal-hash" @@ -2822,23 +2348,6 @@ ] [[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2867,15 +2376,6 @@ ] [[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2907,18 +2407,6 @@ ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2958,25 +2446,6 @@ ] [[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] name = "which" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3160,21 +2629,12 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] name = "wycheproof" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183c789620c674b79dac33cd3aadb6c8006b66cba6a680402235aaebc743e3df" +checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12" dependencies = [ - "base64 0.13.1", + "base64 0.21.2", "hex", "serde", "serde_json", @@ -3182,9 +2642,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core 0.6.4", @@ -3217,6 +2677,7 @@ "rand_pcg", "regex", "test_helper", + "wycheproof", "xts-mode", ]
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml index 361628a..1416e24 100644 --- a/nearby/Cargo.toml +++ b/nearby/Cargo.toml
@@ -1,13 +1,12 @@ [workspace] members = [ - "connections/ukey2/lock_adapter", + "connections/connections_adv/connections_adv", "connections/ukey2/ukey2", "connections/ukey2/ukey2_connections", "connections/ukey2/ukey2_c_ffi", "connections/ukey2/ukey2_jni", "connections/ukey2/ukey2_proto", "connections/ukey2/ukey2_shell", - "crypto/bssl-crypto", "crypto/crypto_provider", "crypto/crypto_provider_openssl", "crypto/crypto_provider_rustcrypto", @@ -16,7 +15,6 @@ "crypto/crypto_provider_default", "crypto/rand_core_05_adapter", "presence/array_view", - "presence/handle_map", "presence/ldt", "presence/ldt_np_adv", "presence/ldt_np_jni", @@ -29,6 +27,8 @@ "presence/sink", "presence/test_helper", "presence/xts_aes", + "util/lock_adapter", + "util/handle_map", ] # TODO: remove ldt_np_adv_ffi once support for no_std + alloc no longer requires nightly @@ -44,14 +44,15 @@ # local crates array_ref = { path = "presence/array_ref" } array_view = { path = "presence/array_view" } -crypto_provider = { path = "crypto/crypto_provider" } +crypto_provider = { path = "crypto/crypto_provider", default-features = false } crypto_provider_default = { path = "crypto/crypto_provider_default", default-features = false } crypto_provider_openssl = { path = "crypto/crypto_provider_openssl" } +crypto_provider_boringssl = { path = "crypto/crypto_provider_boringssl" } crypto_provider_rustcrypto = { path = "crypto/crypto_provider_rustcrypto" } crypto_provider_stubs = { path = "crypto/crypto_provider_stubs" } crypto_provider_test = { path = "crypto/crypto_provider_test" } -lock_adapter = { path = "connections/ukey2/lock_adapter" } -handle_map = { path = "presence/handle_map" } +lock_adapter = { path = "util/lock_adapter" } +handle_map = { path = "util/handle_map" } rand_core_05_adapter = { path = "crypto/rand_core_05_adapter" } rand_ext = { path = "presence/rand_ext" } test_helper = { path = "presence/test_helper" } @@ -68,33 +69,33 @@ # from crates.io rand = { version = "0.8.5", default-features = false } -rand_core = "0.6.4" +rand_core = { version = "0.6.4", features = ["getrandom"] } rand_pcg = "0.3.1" sha2 = { version = "0.10.6", default-features = false } aes = "0.8.2" -cbc = { version = "0.1.2", features = ["alloc", "block-padding"] } +cbc = { version = "0.1.2", features = ["block-padding"] } ctr = "0.9.1" -hashbrown = "0.14.0" hkdf = "0.12.3" hmac = "0.12.1" -ed25519-dalek = { version = "2.0.0-rc.3", default-features = false } +ed25519-dalek = { version = "2.0.0", default-features = false } ed25519 = "2.2.0" -aes-gcm = "0.10.1" +aes-gcm = "0.10.3" hex = "0.4.3" +serde = { version = "1.0.189" } serde_json = { version = "1.0.96", features = [ "alloc", ], default-features = false } base64 = "0.21.0" -x25519-dalek = { version = "2.0.0-rc.3", default-features = false } +x25519-dalek = { version = "2.0.0", default-features = false } subtle = { version = "2.5.0", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } p256 = { version = "0.13.2", default-features = false } sec1 = "0.7.2" -portable-atomic = "1.3.2" protobuf = "3.2.0" protobuf-codegen = "3.2.0" +reqwest = { version = "0.11.19", default-features = false, features = ["blocking", "rustls-tls"] } jni = "0.21.1" -lock_api = "0.4.9" +lock_api = "0.4.11" spin = { version = "0.9.8", features = ["once", "lock_api", "rwlock"] } anyhow = "1.0.64" log = "0.4.17" @@ -108,18 +109,19 @@ blake2 = "0.10.4" hdrhistogram = "7.5.0" regex = "1.7.0" -tokio = { version = "1.20.3", features = ["full"] } +tokio = { version = "1.32.0", features = ["full"] } xts-mode = "0.5.1" rstest = { version = "0.17.0", default-features = false } -rstest_reuse = "0.5.0" -wycheproof = "0.4.0" -chrono = { version = "0.4.24", default-features = false, features = ["clock"] } +rstest_reuse = "0.6.0" +wycheproof = "0.5.1" +chrono = { version = "0.4.26", default-features = false, features = ["clock"] } tempfile = "3.5.0" thiserror = "1.0.40" -tinyvec = {version = "1.6.0", features = ["rustc_1_55"]} +tinyvec = { version = "1.6.0", features = ["rustc_1_55"] } mlua = "0.8.8" -strum = "0.24.1" -strum_macros = "0.24.2" +strum = { version = "0.25.0", default-features=false } +strum_macros = { version = "0.25.3", default-features=false } +owo-colors = "3.5.0" [workspace.package] version = "0.1.0" @@ -150,27 +152,25 @@ version.workspace = true edition.workspace = true publish.workspace = true +rust-version = "1.71.0" [dependencies] clap.workspace = true +cmd-runner = { path = "../cmd-runner" } anyhow.workspace = true shell-escape = "0.1.5" -owo-colors = "3.5.0" -reqwest = { version = "0.11.17", default-features = false, features = [ - "blocking", - "rustls-tls", -] } +owo-colors.workspace = true semver = "1.0.17" -base64.workspace = true walkdir = "2.3.3" globset = "0.4.10" crossbeam = "0.8.2" -tinytemplate = "1.2.1" chrono.workspace = true thiserror.workspace = true log.workspace = true env_logger.workspace = true which = "4.4.0" +file-header = "0.1.2" +serde_json.workspace = true [dev-dependencies] tempfile.workspace = true
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/nearby/connections/connections_adv/connections_adv/Cargo.toml similarity index 88% rename from nearby/crypto/bssl-crypto/Cargo.toml rename to nearby/connections/connections_adv/connections_adv/Cargo.toml index bfe3964..ff17ad2 100644 --- a/nearby/crypto/bssl-crypto/Cargo.toml +++ b/nearby/connections/connections_adv/connections_adv/Cargo.toml
@@ -1,5 +1,5 @@ [package] -name = "bssl-crypto" +name = "connections_adv" version.workspace = true edition.workspace = true publish.workspace = true
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/nearby/connections/connections_adv/connections_adv/src/lib.rs similarity index 72% copy from nearby/crypto/bssl-crypto/src/lib.rs copy to nearby/connections/connections_adv/connections_adv/src/lib.rs index 89e6968..4f7198b 100644 --- a/nearby/crypto/bssl-crypto/src/lib.rs +++ b/nearby/connections/connections_adv/connections_adv/src/lib.rs
@@ -12,5 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the -//! `build-boringssl` subcommand of the top level crate. +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +}
diff --git a/nearby/connections/ukey2/lock_adapter/src/spin.rs b/nearby/connections/ukey2/lock_adapter/src/spin.rs deleted file mode 100644 index be363d4..0000000 --- a/nearby/connections/ukey2/lock_adapter/src/spin.rs +++ /dev/null
@@ -1,33 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::NoPoisonMutex; - -pub struct Mutex<T>(spin::Mutex<T>); - -impl<T> NoPoisonMutex<T> for Mutex<T> { - type MutexGuard<'a> = spin::MutexGuard<'a, T> where T: 'a; - - fn lock(&self) -> Self::MutexGuard<'_> { - self.0.lock() - } - - fn try_lock(&self) -> Option<Self::MutexGuard<'_>> { - self.0.try_lock() - } - - fn new(value: T) -> Self { - Self(spin::Mutex::new(value)) - } -}
diff --git a/nearby/connections/ukey2/lock_adapter/src/std.rs b/nearby/connections/ukey2/lock_adapter/src/std.rs deleted file mode 100644 index ffd3ecc..0000000 --- a/nearby/connections/ukey2/lock_adapter/src/std.rs +++ /dev/null
@@ -1,37 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::NoPoisonMutex; - -pub struct Mutex<T>(std::sync::Mutex<T>); - -impl<T> NoPoisonMutex<T> for Mutex<T> { - type MutexGuard<'a> = std::sync::MutexGuard<'a, T> where T: 'a; - - fn lock(&self) -> Self::MutexGuard<'_> { - self.0.lock().unwrap_or_else(|poison| poison.into_inner()) - } - - fn try_lock(&self) -> Option<Self::MutexGuard<'_>> { - match self.0.try_lock() { - Ok(guard) => Some(guard), - Err(std::sync::TryLockError::Poisoned(guard)) => Some(guard.into_inner()), - Err(std::sync::TryLockError::WouldBlock) => None, - } - } - - fn new(value: T) -> Self { - Self(std::sync::Mutex::new(value)) - } -}
diff --git a/nearby/connections/ukey2/ukey2/Cargo.toml b/nearby/connections/ukey2/ukey2/Cargo.toml index 7c10b41..afae71e 100644 --- a/nearby/connections/ukey2/ukey2/Cargo.toml +++ b/nearby/connections/ukey2/ukey2/Cargo.toml
@@ -4,6 +4,12 @@ edition.workspace = true publish.workspace = true +[features] +default = [] +test_rustcrypto = ["crypto_provider_default/rustcrypto"] +test_openssl = ["crypto_provider_default/openssl"] +test_boringssl = ["crypto_provider_default/boringssl"] + [dependencies] crypto_provider.workspace = true rand.workspace = true @@ -14,8 +20,6 @@ num-bigint = "0.4.3" [dev-dependencies] -rand = { workspace = true, features = ["std_rng"] } -crypto_provider_rustcrypto = {workspace = true, features = ["std"] } -crypto_provider_openssl.workspace = true -rstest = "0.16.0" +rand = { workspace = true, features = ["std_rng", "getrandom"] } +crypto_provider_default.workspace = true sha2.workspace = true
diff --git a/nearby/connections/ukey2/ukey2/src/tests.rs b/nearby/connections/ukey2/ukey2/src/tests.rs index b58cee6..65274a6 100644 --- a/nearby/connections/ukey2/ukey2/src/tests.rs +++ b/nearby/connections/ukey2/ukey2/src/tests.rs
@@ -21,42 +21,41 @@ use crypto_provider::p256::P256; use crypto_provider::x25519::X25519; use crypto_provider::{CryptoProvider, CryptoRng}; -use crypto_provider_openssl::Openssl; -use crypto_provider_rustcrypto::RustCrypto; +use crypto_provider_default::CryptoProviderImpl; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; -use rstest::rstest; use sha2::Digest; use std::collections::hash_set; use ukey2_proto::protobuf::Message; use ukey2_proto::ukey2_all_proto::ukey; -#[rstest] -fn advance_from_init_to_finish_client_test<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +type X25519EphemeralSecret = + <<CryptoProviderImpl as CryptoProvider>::X25519 as EcdhProvider<X25519>>::EphemeralSecret; +type X25519PublicKey = + <<CryptoProviderImpl as CryptoProvider>::X25519 as EcdhProvider<X25519>>::PublicKey; +type P256EphemeralSecret = + <<CryptoProviderImpl as CryptoProvider>::P256 as EcdhProvider<P256>>::EphemeralSecret; + +#[test] +fn advance_from_init_to_finish_client_test() { let mut rng = StdRng::from_entropy(); - let client1 = Ukey2ClientStage1::<C>::from( + let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from( &mut rng, "next protocol".to_string(), HandshakeImplementation::Spec, ); let secret = - <C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random( - &mut <<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret< - X25519, - >>::Rng::new(), - ); - let public_key = - <C::X25519 as EcdhProvider<X25519>>::PublicKey::from_bytes(&secret.public_key_bytes()) - .unwrap(); + X25519EphemeralSecret::generate_random(&mut <X25519EphemeralSecret as EphemeralSecret< + X25519, + >>::Rng::new()); + let public_key = X25519PublicKey::from_bytes(secret.public_key_bytes().as_ref()).unwrap(); let random: [u8; 32] = rng.gen(); let message_data: ukey::Ukey2ServerInit = ukey::Ukey2ServerInit { version: Some(1), random: Some(random.to_vec()), handshake_cipher: Some(ukey::Ukey2HandshakeCipher::CURVE25519_SHA512.into()), - public_key: Some(public_key.to_bytes()), + public_key: Some(public_key.to_bytes().as_ref().to_vec()), ..Default::default() }; @@ -66,25 +65,24 @@ // TODO assertions on client state } -#[rstest] -fn advance_from_init_to_complete_server_x25519_test<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn advance_from_init_to_complete_server_x25519_test() { let mut rng = StdRng::from_entropy(); let mut next_protocols = hash_set::HashSet::new(); let _ = next_protocols.insert("AES_256_CBC-HMAC_SHA256".to_string()); - let server1 = Ukey2ServerStage1::<C>::from(next_protocols, HandshakeImplementation::Spec); + let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from( + next_protocols, + HandshakeImplementation::Spec, + ); // We construct a ClientInit message for the server to get it into the state to handle // ClientFinish messages. let secret = - <C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random( - &mut <<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret< - X25519, - >>::Rng::new(), - ); + X25519EphemeralSecret::generate_random(&mut <X25519EphemeralSecret as EphemeralSecret< + X25519, + >>::Rng::new()); let client_finished_msg = { let mut msg = ukey::Ukey2ClientFinished::default(); - msg.set_public_key(secret.public_key_bytes()); + msg.set_public_key(secret.public_key_bytes().as_ref().to_vec()); msg.to_wrapped_msg() }; let client_finished_bytes = client_finished_msg.write_to_bytes().unwrap(); @@ -120,23 +118,23 @@ // TODO assertions on server state } -#[rstest] -fn advance_from_init_to_complete_server_p256_test<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn advance_from_init_to_complete_server_p256_test() { let mut rng = StdRng::from_entropy(); let mut next_protocols = hash_set::HashSet::new(); let _ = next_protocols.insert("AES_256_CBC-HMAC_SHA256".to_string()); - let server1 = Ukey2ServerStage1::<C>::from(next_protocols, HandshakeImplementation::Spec); + let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from( + next_protocols, + HandshakeImplementation::Spec, + ); // We construct a ClientInit message for the server to get it into the state to handle // ClientFinish messages. - let secret = <C::P256 as EcdhProvider<P256>>::EphemeralSecret::generate_random( - &mut <<C::P256 as EcdhProvider<P256>>::EphemeralSecret as EphemeralSecret<P256>>::Rng::new( - ), + let secret = P256EphemeralSecret::generate_random( + &mut <P256EphemeralSecret as EphemeralSecret<P256>>::Rng::new(), ); let client_finished_msg = { let mut msg = ukey::Ukey2ClientFinished::default(); - msg.set_public_key(secret.public_key_bytes()); + msg.set_public_key(secret.public_key_bytes().as_ref().to_vec()); msg.to_wrapped_msg() }; let client_finished_bytes = client_finished_msg.write_to_bytes().unwrap();
diff --git a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs index a22c0ed..70db4a3 100644 --- a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs +++ b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
@@ -133,7 +133,7 @@ match public_key { GenericPublicKey::Ec256(key) => { debug_assert_eq!(cipher, HandshakeCipher::P256Sha512); - Some(key.to_bytes()) + Some(key.to_bytes().to_vec()) } } } @@ -267,9 +267,12 @@ server_init.set_random(random.to_vec()); server_init.set_handshake_cipher(commitment.cipher().as_proto()); server_init.set_public_key(match &key_pair { - ServerKeyPair::Curve25519(es) => es.public_key_bytes(), + ServerKeyPair::Curve25519(es) => es.public_key_bytes().as_ref().to_vec(), ServerKeyPair::P256(es) => handshake_impl - .encode_public_key::<C>(es.public_key_bytes(), HandshakeCipher::P256Sha512) + .encode_public_key::<C>( + es.public_key_bytes().as_ref().to_vec(), + HandshakeCipher::P256Sha512, + ) .unwrap(), }); @@ -392,7 +395,7 @@ ); let curve25519_client_finished_bytes = { let client_finished = ukey::Ukey2ClientFinished { - public_key: Some(curve25519_secret.public_key_bytes()), + public_key: Some(curve25519_secret.public_key_bytes().as_ref().to_vec()), ..Default::default() }; client_finished.to_wrapped_msg().write_to_bytes().unwrap() @@ -411,7 +414,7 @@ public_key: Some( handshake_impl .encode_public_key::<C>( - p256_secret.public_key_bytes(), + p256_secret.public_key_bytes().as_ref().to_vec(), HandshakeCipher::P256Sha512, ) .expect("Output of p256_secret.public_key_bytes should always be valid input for encode_public_key"),
diff --git a/nearby/connections/ukey2/ukey2/tests/tests.rs b/nearby/connections/ukey2/ukey2/tests/tests.rs index 31ba416..2f51a8c 100644 --- a/nearby/connections/ukey2/ukey2/tests/tests.rs +++ b/nearby/connections/ukey2/ukey2/tests/tests.rs
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crypto_provider_rustcrypto::RustCrypto; +use crypto_provider_default::CryptoProviderImpl; use rand::{rngs::StdRng, SeedableRng}; use std::collections::hash_set; use ukey2_rs::*; @@ -22,10 +22,12 @@ let mut next_protocols = hash_set::HashSet::new(); let next_protocol = "AES_256_CBC-HMAC_SHA256".to_string(); let _ = next_protocols.insert(next_protocol.clone()); - let server1 = - Ukey2ServerStage1::<RustCrypto>::from(next_protocols, HandshakeImplementation::Spec); + let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from( + next_protocols, + HandshakeImplementation::Spec, + ); let mut rng = StdRng::from_entropy(); - let client1 = Ukey2ClientStage1::<RustCrypto>::from( + let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from( &mut rng, next_protocol, HandshakeImplementation::Spec, @@ -37,12 +39,18 @@ let server3 = server2.advance_state(&mut rng, client2.client_finished_msg()).unwrap(); assert_eq!( - server3.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>(), - client2.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>() + server3.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>(), + client2.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>() ); assert_eq!( - server3.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>(), - client2.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>() + server3 + .completed_handshake() + .next_protocol_secret::<CryptoProviderImpl>() + .derive_array::<32>(), + client2 + .completed_handshake() + .next_protocol_secret::<CryptoProviderImpl>() + .derive_array::<32>() ); } @@ -51,12 +59,12 @@ let mut next_protocols = hash_set::HashSet::new(); let next_protocol = "AES_256_CBC-HMAC_SHA256".to_string(); let _ = next_protocols.insert(next_protocol.clone()); - let server1 = Ukey2ServerStage1::<RustCrypto>::from( + let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from( next_protocols, HandshakeImplementation::PublicKeyInProtobuf, ); let mut rng = StdRng::from_entropy(); - let client1 = Ukey2ClientStage1::<RustCrypto>::from( + let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from( &mut rng, next_protocol, HandshakeImplementation::PublicKeyInProtobuf, @@ -68,11 +76,17 @@ let server3 = server2.advance_state(&mut rng, client2.client_finished_msg()).unwrap(); assert_eq!( - server3.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>(), - client2.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>() + server3.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>(), + client2.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>() ); assert_eq!( - server3.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>(), - client2.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>() + server3 + .completed_handshake() + .next_protocol_secret::<CryptoProviderImpl>() + .derive_array::<32>(), + client2 + .completed_handshake() + .next_protocol_secret::<CryptoProviderImpl>() + .derive_array::<32>() ); }
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml index 3ca55f4..182ecf2 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
@@ -8,9 +8,8 @@ ukey2_connections = { path = "../ukey2_connections" } ukey2_rs = { path = "../ukey2" } cfg-if.workspace = true -crypto_provider_openssl = { workspace = true, optional = true } -crypto_provider_rustcrypto = { workspace = true, optional = true, features = ["alloc"] } -lock_adapter.workspace = true +crypto_provider_default.workspace = true +lock_adapter = {workspace = true, features = ["spin"]} lazy_static.workspace = true log.workspace = true @@ -18,11 +17,11 @@ rand_chacha.workspace = true [features] -default = ["rustcrypto"] -std = ["crypto_provider_rustcrypto/std", "lock_adapter/std"] -openssl = ["dep:crypto_provider_openssl", "std"] -rustcrypto = ["crypto_provider_rustcrypto"] -crypto_provider_rustcrypto = ["dep:crypto_provider_rustcrypto"] +default = ["rustcrypto", "std"] +std = ["lock_adapter/std"] +openssl = ["crypto_provider_default/openssl", "std"] +boringssl = ["crypto_provider_default/boringssl", "std"] +rustcrypto = ["crypto_provider_default/rustcrypto"] [lib]
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt index c1247eb..165d861 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt +++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
@@ -14,6 +14,8 @@ cmake_minimum_required(VERSION 3.14) +project(Ukey2) + enable_testing() include_directories(
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h index b269da8..654e293 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h +++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
@@ -13,6 +13,7 @@ // limitations under the License. #include "ukey2_bindings.h" + #include <string> struct D2DRestoreConnectionContextV1Result;
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc index 1e913b5..962ada8 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc +++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
@@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "ukey2_ffi.h" + #include <string> -#include <gtest/gtest.h> -#include "ukey2_ffi.h" +#include "gtest/gtest.h" namespace {
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs index a79fe60..e5cc548 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs +++ b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
@@ -15,6 +15,7 @@ use std::collections::HashMap; use std::ptr::null_mut; +use crypto_provider_default::CryptoProviderImpl as CryptoProvider; use lazy_static::lazy_static; use lock_adapter::NoPoisonMutex; use rand::Rng; @@ -31,13 +32,6 @@ InitiatorD2DHandshakeContext, ServerD2DHandshakeContext, }; -cfg_if::cfg_if! { - if #[cfg(feature = "rustcrypto")] { - use crypto_provider_rustcrypto::RustCrypto as CryptoProvider; - } else { - use crypto_provider_openssl::Openssl as CryptoProvider; - } -} #[repr(C)] pub struct RustFFIByteArray { ptr: *mut u8,
diff --git a/nearby/connections/ukey2/ukey2_connections/Cargo.toml b/nearby/connections/ukey2/ukey2_connections/Cargo.toml index 41fe10d..8337e96 100644 --- a/nearby/connections/ukey2/ukey2_connections/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
@@ -4,6 +4,12 @@ edition.workspace = true publish.workspace = true +[features] +default = [] +test_boringssl = ["crypto_provider_default/boringssl"] +test_rustcrypto = ["crypto_provider_default/rustcrypto"] +test_openssl = ["crypto_provider_default/openssl"] + [dependencies] ukey2_rs = { path = "../ukey2" } @@ -12,12 +18,13 @@ ukey2_proto.workspace = true nom = { version = "7.1.1", features = ["alloc"] } bytes = "1.2.1" -criterion = "0.4.0" +criterion.workspace = true [dev-dependencies] -crypto_provider_openssl.workspace = true +crypto_provider_default.workspace = true +# This would only be used when the feature "test_rustcrypto" is set, but optional dev-dependencies +# are not allowed ¯\_(ツ)_/¯ crypto_provider_rustcrypto = { workspace = true, features = ["alloc", "std"] } -rstest = "0.16.0" [[bench]] name = "ukey2_benches"
diff --git a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs index b609761..e48400a 100644 --- a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs +++ b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
@@ -16,7 +16,7 @@ use rand::{Rng, SeedableRng}; use crypto_provider::CryptoProvider; -use crypto_provider_rustcrypto::RustCrypto; +use crypto_provider_default::CryptoProviderImpl; use ukey2_connections::{ D2DConnectionContextV1, D2DHandshakeContext, InitiatorD2DHandshakeContext, ServerD2DHandshakeContext, @@ -63,16 +63,18 @@ let mut group = c.benchmark_group("throughput"); let mut plaintext = Vec::new(); let (mut initiator_ctx, mut server_ctx) = - run_handshake_with_rng::<RustCrypto, _>(rand::rngs::StdRng::from_entropy()); + run_handshake_with_rng::<CryptoProviderImpl, _>(rand::rngs::StdRng::from_entropy()); for len in [10 * kib, 1024 * kib] { group.throughput(Throughput::Bytes(len as u64)); plaintext.resize(len, 0); rand::thread_rng().fill(&mut plaintext[..]); group.bench_function(format!("UKEY2 encrypt/decrypt {}KiB", len / kib), |b| { b.iter(|| { - let msg = - initiator_ctx.encode_message_to_peer::<RustCrypto, &[u8]>(&plaintext, None); - black_box(server_ctx.decode_message_from_peer::<RustCrypto, &[u8]>(&msg, None)) + let msg = initiator_ctx + .encode_message_to_peer::<CryptoProviderImpl, &[u8]>(&plaintext, None); + black_box( + server_ctx.decode_message_from_peer::<CryptoProviderImpl, &[u8]>(&msg, None), + ) }) }); }
diff --git a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock index 4a00416..3f72ae0 100644 --- a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock +++ b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -336,6 +350,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_rustcrypto" @@ -343,6 +360,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -372,9 +390,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -451,9 +469,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -555,6 +573,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1233,6 +1261,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1495,9 +1529,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/connections/ukey2/ukey2_connections/src/tests.rs b/nearby/connections/ukey2/ukey2_connections/src/tests.rs index 35a1e70..32a6b28 100644 --- a/nearby/connections/ukey2/ukey2_connections/src/tests.rs +++ b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
@@ -12,14 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crypto_provider_rustcrypto::RustCryptoImpl; +use crypto_provider::CryptoProvider; +use crypto_provider_default::CryptoProviderImpl; use rand::SeedableRng; use rand::{rngs::StdRng, CryptoRng, RngCore}; -use rstest::rstest; - -use crypto_provider::CryptoProvider; -use crypto_provider_openssl::Openssl; -use crypto_provider_rustcrypto::RustCrypto; use ukey2_rs::HandshakeImplementation; use crate::{ @@ -28,27 +24,25 @@ InitiatorD2DHandshakeContext, ServerD2DHandshakeContext, }; -#[rstest] -fn crypto_test_encrypt_decrypt<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +type AesCbcPkcs7Padded = <CryptoProviderImpl as CryptoProvider>::AesCbcPkcs7Padded; + +#[test] +fn crypto_test_encrypt_decrypt() { let message = b"Hello World!"; let key = b"42424242424242424242424242424242"; let (ciphertext, iv) = - encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rand::rngs::StdRng::from_entropy()); - let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(key, ciphertext.as_slice(), &iv); + encrypt::<_, AesCbcPkcs7Padded>(key, message, &mut rand::rngs::StdRng::from_entropy()); + let decrypt_result = decrypt::<AesCbcPkcs7Padded>(key, ciphertext.as_slice(), &iv); let ptext = decrypt_result.expect("Decrypt should be successful"); assert_eq!(ptext, message.to_vec()); } -#[rstest] -fn crypto_test_encrypt_seeded<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn crypto_test_encrypt_seeded() { let message = b"Hello World!"; let key = b"42424242424242424242424242424242"; let mut rng = MockRng; - let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rng); + let (ciphertext, iv) = encrypt::<_, AesCbcPkcs7Padded>(key, message, &mut rng); // Expected values extracted from the results of the current implementation. // This test makes sure that we don't accidentally change the encryption logic that // causes incompatibility between versions. @@ -59,43 +53,36 @@ ); } -#[rstest] -fn crypto_test_decrypt_seeded<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn crypto_test_decrypt_seeded() { let iv = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; let ciphertext = [20, 59, 195, 101, 11, 208, 245, 128, 247, 196, 81, 80, 158, 77, 174, 61]; let key = b"42424242424242424242424242424242"; - let plaintext = decrypt::<C::AesCbcPkcs7Padded>(key, &ciphertext, &iv).unwrap(); + let plaintext = decrypt::<AesCbcPkcs7Padded>(key, &ciphertext, &iv).unwrap(); assert_eq!(plaintext, b"Hello World!"); } -#[rstest] -fn decrypt_test_wrong_key<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn decrypt_test_wrong_key() { let message = b"Hello World!"; let good_key = b"42424242424242424242424242424242"; - let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>( - good_key, - message, - &mut rand::rngs::StdRng::from_entropy(), - ); + let (ciphertext, iv) = + encrypt::<_, AesCbcPkcs7Padded>(good_key, message, &mut rand::rngs::StdRng::from_entropy()); let bad_key = b"43434343434343434343434343434343"; - let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(bad_key, ciphertext.as_slice(), &iv); + let decrypt_result = decrypt::<AesCbcPkcs7Padded>(bad_key, ciphertext.as_slice(), &iv); match decrypt_result { // The padding is valid, but the decrypted value should be bad since the keys don't match Ok(decrypted_bad) => assert_ne!(decrypted_bad, message), // The padding is bad, so it returns an error and is unable to decrypt Err(crypto_provider::aes::cbc::DecryptionError::BadPadding) => (), } - let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(good_key, ciphertext.as_slice(), &iv); + let decrypt_result = decrypt::<AesCbcPkcs7Padded>(good_key, ciphertext.as_slice(), &iv); let ptext = decrypt_result.unwrap(); assert_eq!(ptext, message.to_vec()); } -fn run_handshake<C: CryptoProvider>() -> (D2DConnectionContextV1, D2DConnectionContextV1) { - run_handshake_with_rng::<C, _>(rand::rngs::StdRng::from_entropy()) +fn run_handshake() -> (D2DConnectionContextV1, D2DConnectionContextV1) { + run_handshake_with_rng::<CryptoProviderImpl, _>(rand::rngs::StdRng::from_entropy()) } fn run_handshake_with_rng<C, R>( @@ -133,15 +120,17 @@ (initiator_ctx.to_connection_context().unwrap(), server_ctx.to_connection_context().unwrap()) } -#[rstest] -fn send_receive_message_seeded<C: CryptoProvider>( - // TODO: Find a way to inject RNG / generated ephemeral secrets in openSSL and test them here - #[values(RustCryptoImpl::< MockRng >::new())] _crypto_provider: C, -) { +// TODO: Find a way to inject RNG / generated ephemeral secrets in openSSL and test them here +#[cfg(feature = "test_rustcrypto")] +#[test] +fn send_receive_message_seeded() { + use crypto_provider_rustcrypto::RustCryptoImpl; let rng = MockRng; let message = b"Hello World!"; - let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake_with_rng::<C, _>(rng); - let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None); + let (mut init_conn_ctx, mut server_conn_ctx) = + run_handshake_with_rng::<RustCryptoImpl<MockRng>, _>(rng); + let encoded = + init_conn_ctx.encode_message_to_peer::<RustCryptoImpl<MockRng>, &[u8]>(message, None); // Expected values extracted from the results of the current implementation. // This test makes sure that we don't accidentally change the encryption logic that // causes incompatibility between versions. @@ -155,102 +144,104 @@ 45, 239, 234, 248, 148, 9, 150, 204, 117, 32, 216, 5, 126, 224, 39 ] ); - let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(&encoded, None).unwrap(); + let decoded = server_conn_ctx + .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(&encoded, None) + .unwrap(); assert_eq!(message, &decoded[..]); } -#[rstest] -fn send_receive_message<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn send_receive_message() { let message = b"Hello World!"; - let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>(); - let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None); - let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None); + let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake(); + let encoded = init_conn_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None); + let decoded = server_conn_ctx + .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None); assert_eq!(message.to_vec(), decoded.expect("Decode should be successful")); } -#[rstest] -fn send_receive_message_associated_data<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { +#[test] +fn send_receive_message_associated_data() { let message = b"Hello World!"; - let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>(); - let encoded = init_conn_ctx.encode_message_to_peer::<C, _>(message, Some(b"associated data")); - let decoded = server_conn_ctx - .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"associated data")); + let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake(); + let encoded = init_conn_ctx + .encode_message_to_peer::<CryptoProviderImpl, _>(message, Some(b"associated data")); + let decoded = server_conn_ctx.decode_message_from_peer::<CryptoProviderImpl, _>( + encoded.as_slice(), + Some(b"associated data"), + ); assert_eq!(message.to_vec(), decoded.expect("Decode should be successful")); // Make sure decode fails with missing associated data. - let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None); + let decoded = server_conn_ctx + .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None); assert!(decoded.is_err()); // Make sure decode fails with different associated data. - let decoded = server_conn_ctx - .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"assoc1ated data")); + let decoded = server_conn_ctx.decode_message_from_peer::<CryptoProviderImpl, _>( + encoded.as_slice(), + Some(b"assoc1ated data"), + ); assert!(decoded.is_err()); } -#[rstest] -fn test_save_restore_session<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { - let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>(); +#[test] +fn test_save_restore_session() { + let (init_conn_ctx, server_conn_ctx) = run_handshake(); let init_session = init_conn_ctx.save_session(); let server_session = server_conn_ctx.save_session(); let mut init_restored_ctx = - D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice()) + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice()) .expect("failed to restore client session"); let mut server_restored_ctx = - D2DConnectionContextV1::from_saved_session::<C>(server_session.as_slice()) + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(server_session.as_slice()) .expect("failed to restore server session"); let message = b"Hello World!"; - let encoded = init_restored_ctx.encode_message_to_peer::<C, &[u8]>(message, None); - let decoded = - server_restored_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None); + let encoded = + init_restored_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None); + let decoded = server_restored_ctx + .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None); assert_eq!(message.to_vec(), decoded.expect("Decode should be successful")); } -#[rstest] -fn test_save_restore_bad_session<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { - let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>(); +#[test] +fn test_save_restore_bad_session() { + let (init_conn_ctx, server_conn_ctx) = run_handshake(); let init_session = init_conn_ctx.save_session(); let server_session = server_conn_ctx.save_session(); - let _ = D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice()) - .expect("failed to restore client session"); + let _ = + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice()) + .expect("failed to restore client session"); let server_restored_ctx = - D2DConnectionContextV1::from_saved_session::<C>(&server_session[0..60]); + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(&server_session[0..60]); assert_eq!(server_restored_ctx.unwrap_err(), DeserializeError::BadDataLength); } -#[rstest] -fn test_save_restore_bad_protocol_version<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { - let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>(); +#[test] +fn test_save_restore_bad_protocol_version() { + let (init_conn_ctx, server_conn_ctx) = run_handshake(); let init_session = init_conn_ctx.save_session(); let mut server_session = server_conn_ctx.save_session(); - let _ = D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice()) - .expect("failed to restore client session"); + let _ = + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice()) + .expect("failed to restore client session"); server_session[0] = 0; // Change the protocol version to an invalid one (0) - let server_restored_ctx = D2DConnectionContextV1::from_saved_session::<C>(&server_session); + let server_restored_ctx = + D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(&server_session); assert_eq!(server_restored_ctx.unwrap_err(), DeserializeError::BadProtocolVersion); } -#[rstest] -fn test_unique_session<C: CryptoProvider>( - #[values(RustCrypto::new(), Openssl)] _crypto_provider: C, -) { - let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>(); - let init_session = init_conn_ctx.get_session_unique::<C>(); - let server_session = server_conn_ctx.get_session_unique::<C>(); +#[test] +fn test_unique_session() { + let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake(); + let init_session = init_conn_ctx.get_session_unique::<CryptoProviderImpl>(); + let server_session = server_conn_ctx.get_session_unique::<CryptoProviderImpl>(); let message = b"Hello World!"; - let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None); - let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None); + let encoded = init_conn_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None); + let decoded = server_conn_ctx + .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None); assert_eq!(message.to_vec(), decoded.expect("Decode should be successful")); - let init_session_after = init_conn_ctx.get_session_unique::<C>(); - let server_session_after = server_conn_ctx.get_session_unique::<C>(); - let bad_server_ctx = D2DConnectionContextV1::new::<C>( + let init_session_after = init_conn_ctx.get_session_unique::<CryptoProviderImpl>(); + let server_session_after = server_conn_ctx.get_session_unique::<CryptoProviderImpl>(); + let bad_server_ctx = D2DConnectionContextV1::new::<CryptoProviderImpl>( server_conn_ctx.get_sequence_number_for_decoding(), server_conn_ctx.get_sequence_number_for_encoding(), Aes256Key::default(), @@ -260,7 +251,7 @@ assert_eq!(init_session, init_session_after); assert_eq!(server_session, server_session_after); assert_eq!(init_session, server_session); - assert_ne!(server_session, bad_server_ctx.get_session_unique::<C>()); + assert_ne!(server_session, bad_server_ctx.get_session_unique::<CryptoProviderImpl>()); } #[test]
diff --git a/nearby/connections/ukey2/ukey2_jni/Cargo.toml b/nearby/connections/ukey2/ukey2_jni/Cargo.toml index 7a399d3..4852444 100644 --- a/nearby/connections/ukey2/ukey2_jni/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
@@ -9,11 +9,10 @@ [dependencies] ukey2_connections = { path = "../ukey2_connections" } ukey2_rs = { path = "../ukey2" } -lock_adapter.workspace = true +lock_adapter = {workspace = true, features = ["spin"]} cfg-if.workspace = true -crypto_provider_openssl = { workspace = true, optional = true } -crypto_provider_rustcrypto = { workspace = true, optional = true, features = [ "alloc" ] } +crypto_provider_default = { workspace = true } lazy_static.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -22,10 +21,10 @@ [features] default = ["rustcrypto"] -openssl = ["dep:crypto_provider_openssl", "std"] -rustcrypto = ["crypto_provider_rustcrypto"] -std = ["lock_adapter/std", "crypto_provider_rustcrypto/std"] -crypto_provider_rustcrypto = ["dep:crypto_provider_rustcrypto"] +openssl = ["crypto_provider_default/openssl", "std"] +rustcrypto = ["crypto_provider_default/rustcrypto"] +boringssl = ["crypto_provider_default/boringssl"] +std = ["lock_adapter/std"] [lib] crate_type = ["cdylib"] \ No newline at end of file
diff --git a/nearby/connections/ukey2/ukey2_jni/src/lib.rs b/nearby/connections/ukey2/ukey2_jni/src/lib.rs index 570e74b..82d317e 100644 --- a/nearby/connections/ukey2/ukey2_jni/src/lib.rs +++ b/nearby/connections/ukey2/ukey2_jni/src/lib.rs
@@ -34,13 +34,7 @@ ServerD2DHandshakeContext, }; -cfg_if::cfg_if! { - if #[cfg(feature = "rustcrypto")] { - use crypto_provider_rustcrypto::RustCrypto as CryptoProvider; - } else { - use crypto_provider_openssl::Openssl as CryptoProvider; - } -} +use crypto_provider_default::CryptoProviderImpl as CryptoProvider; // Handle management type D2DBox = Box<dyn D2DHandshakeContext>;
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml index 60f8626..e7dd8ba 100644 --- a/nearby/crypto/crypto_provider/Cargo.toml +++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -4,11 +4,14 @@ edition.workspace = true publish.workspace = true +[dependencies] +tinyvec.workspace = true + [dev-dependencies] criterion.workspace = true hex-literal.workspace = true crypto_provider_openssl.workspace = true -crypto_provider_rustcrypto.workspace = true +crypto_provider_rustcrypto = { workspace = true, features = ["std"] } rand_ext.workspace = true rand.workspace = true @@ -17,6 +20,7 @@ std = [] alloc = [] test_vectors = [] +raw_private_key_permit = [] [[bench]] name = "hmac_bench"
diff --git a/nearby/crypto/crypto_provider/src/aead.rs b/nearby/crypto/crypto_provider/src/aead.rs new file mode 100644 index 0000000..165272e --- /dev/null +++ b/nearby/crypto/crypto_provider/src/aead.rs
@@ -0,0 +1,86 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// An implementation of AES-GCM-SIV. +/// +/// An AesGcmSiv impl may be used for encryption and decryption. +pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {} + +/// An implementation of AES-GCM. +/// +/// An AesGcm impl may be used for encryption and decryption. +pub trait AesGcm: Aead<Nonce = [u8; 12]> {} + +/// Error returned on unsuccessful AEAD operation. +#[derive(Debug)] +pub struct AeadError; + +/// Initializes an AEAD +pub trait AeadInit<K: crate::aes::AesKey> { + /// Instantiates a new instance of the AEAD from key material. + fn new(key: &K) -> Self; +} + +/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the +/// Nonce. Encrypts and decrypts buffers in-place. +pub trait Aead { + /// The size of the authentication tag, this is appended to the message on the encrypt operation + /// and truncated from the plaintext after decrypting. + const TAG_SIZE: usize; + + /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with + /// the same key. This is critically important - nonce reuse may completely undermine the + /// security of the AEAD. Nonces may be predictable and public, so long as they are unique. + type Nonce: AsRef<[u8]>; + + /// The type of the tag, which should always be [u8; Self::TAG_SIZE]. + type Tag: AsRef<[u8]>; + + /// Encrypt the given buffer containing a plaintext message. On success returns the encrypted + /// `msg` and appended auth tag, which will result in a Vec which is `Self::TAG_SIZE` bytes + /// greater than the initial message. + #[cfg(feature = "alloc")] + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>; + + /// Encrypt the given buffer containing a plaintext message in-place, and returns the tag in the + /// result value. + fn encrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError>; + + /// Decrypt the message, returning the decrypted plaintext or an error in the event the + /// provided authentication tag does not match the given ciphertext. On success the returned + /// Vec will only contain the plaintext and so will be `Self::TAG_SIZE` bytes less than the + /// initial message. + #[cfg(feature = "alloc")] + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>; + + /// Decrypt the message in-place, returning an error and leaving the input `msg` unchanged in + /// the event the provided authentication tag does not match the given ciphertext. + fn decrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + tag: &Self::Tag, + ) -> Result<(), AeadError>; +}
diff --git a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs deleted file mode 100644 index 3be7db3..0000000 --- a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Traits for AES-GCM-SIV. - -extern crate alloc; -use crate::aead::Aead; - -/// An implementation of AES-GCM-SIV. -/// -/// An AesGcmSiv impl may be used for encryption and decryption. -pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {}
diff --git a/nearby/crypto/crypto_provider/src/aead/mod.rs b/nearby/crypto/crypto_provider/src/aead/mod.rs deleted file mode 100644 index 27284a9..0000000 --- a/nearby/crypto/crypto_provider/src/aead/mod.rs +++ /dev/null
@@ -1,50 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -extern crate alloc; -use alloc::vec::Vec; - -/// Contains traits for the AES-GCM-SIV AEAD algorithm. -pub mod aes_gcm_siv; - -/// Error returned on unsuccessful AEAD operation. -pub struct AeadError; - -/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the -/// Nonce. Encrypts and decrypts buffers in-place. -pub trait Aead { - /// The size of the authentication tag, this is appended to the message on the encrypt operation - /// and truncated from the plaintext after decrypting. - const TAG_SIZE: usize; - - /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with - /// the same key. This is critically important - nonce reuse may completely undermine the - /// security of the AEAD. Nonces may be predictable and public, so long as they are unique. - type Nonce; - - /// The key material used to initialize the AEAD. - type Key; - - /// Instantiates a new instance of the AEAD from key material. - fn new(key: &Self::Key) -> Self; - - /// Encrypt the given buffer containing a plaintext message in-place. On success increases the - /// buffer by `Self::TAG_SIZE` bytes and appends the auth tag to the end of `msg`. - fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>; - - /// Decrypt the message in-place, returning an error in the event the provided authentication - /// tag does not match the given ciphertext. The buffer will be truncated to the length of the - /// original plaintext message upon success. - fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>; -}
diff --git a/nearby/crypto/crypto_provider/src/aes/cbc.rs b/nearby/crypto/crypto_provider/src/aes/cbc.rs index e32c588..59ce52d 100644 --- a/nearby/crypto/crypto_provider/src/aes/cbc.rs +++ b/nearby/crypto/crypto_provider/src/aes/cbc.rs
@@ -14,7 +14,10 @@ //! Traits for AES-CBC 256 with PKCS7 padding. +#[cfg(feature = "alloc")] extern crate alloc; +use crate::tinyvec::SliceVec; +#[cfg(feature = "alloc")] use alloc::vec::Vec; use super::Aes256Key; @@ -24,15 +27,53 @@ /// Trait for implementing AES-CBC with PKCS7 padding. pub trait AesCbcPkcs7Padded { + /// Calculate the padded output length (e.g. output of `encrypt`) from the unpadded length (e.g. + /// input message of `encrypt`). + fn padded_output_len(unpadded_len: usize) -> usize { + (unpadded_len - (unpadded_len % 16)) + .checked_add(16) + .expect("Padded output length is larger than usize::MAX") + } + /// Encrypt message using `key` and `iv`, returning a ciphertext. + #[cfg(feature = "alloc")] fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8>; + + /// Encrypt message using `key` and `iv` in-place in the given `message` vec. The given slice + /// vec should have enough capacity to contain both the ciphertext and the padding (which can be + /// calculated from `padded_output_len()`). If it doesn't have enough capacity, an error will be + /// returned. The contents of the input `message` buffer is undefined in that case. + fn encrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError>; + /// Decrypt ciphertext using `key` and `iv`, returning the original message if `Ok()` otherwise /// a `DecryptionError` indicating the type of error that occurred while decrypting. + #[cfg(feature = "alloc")] fn decrypt( key: &Aes256Key, iv: &AesCbcIv, ciphertext: &[u8], ) -> Result<Vec<u8>, DecryptionError>; + + /// Decrypt ciphertext using `key` and `iv` and unpad it in-place. Returning the original + /// message if `Ok()` otherwise a `DecryptionError` indicating the type of error that occurred + /// while decrypting. In that case, the contents of the `ciphertext` buffer is undefined. + fn decrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError>; +} + +/// Error type for describing what went wrong encrypting a message. +#[derive(Debug, PartialEq, Eq)] +pub enum EncryptionError { + /// Failed to add PKCS7 padding to the output when encrypting a message. This typically happens + /// when the given output buffer does not have enough capacity to append the padding. + PaddingFailed, } /// Error type for describing what went wrong decrypting a ciphertext. @@ -43,3 +84,65 @@ /// correctly. Exposing padding errors can cause a padding oracle vulnerability. BadPadding, } + +#[cfg(test)] +mod test { + #[cfg(feature = "alloc")] + extern crate alloc; + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + + use crate::aes::Aes256Key; + use crate::tinyvec::SliceVec; + + use super::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError}; + + #[test] + fn test_padded_output_len() { + assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(0), 16); + assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(15), 16); + assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(16), 32); + assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(30), 32); + assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(32), 48); + } + + #[test] + #[should_panic] + fn test_padded_output_len_overflow() { + AesCbcPkcs7PaddedStub::padded_output_len(usize::MAX); + } + + struct AesCbcPkcs7PaddedStub; + + impl AesCbcPkcs7Padded for AesCbcPkcs7PaddedStub { + #[cfg(feature = "alloc")] + fn encrypt(_key: &Aes256Key, _iv: &AesCbcIv, _message: &[u8]) -> Vec<u8> { + unimplemented!() + } + + fn encrypt_in_place( + _key: &Aes256Key, + _iv: &AesCbcIv, + _message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError> { + unimplemented!() + } + + #[cfg(feature = "alloc")] + fn decrypt( + _key: &Aes256Key, + _iv: &AesCbcIv, + _ciphertext: &[u8], + ) -> Result<Vec<u8>, DecryptionError> { + unimplemented!() + } + + fn decrypt_in_place( + _key: &Aes256Key, + _iv: &AesCbcIv, + _ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError> { + unimplemented!() + } + } +}
diff --git a/nearby/crypto/crypto_provider/src/aes/ctr.rs b/nearby/crypto/crypto_provider/src/aes/ctr.rs index 73d4f55..6779fe8 100644 --- a/nearby/crypto/crypto_provider/src/aes/ctr.rs +++ b/nearby/crypto/crypto_provider/src/aes/ctr.rs
@@ -36,10 +36,8 @@ /// Build a `Self` from key material. fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self; - /// Encrypt the data in place, advancing the counter state appropriately. - fn encrypt(&mut self, data: &mut [u8]); - /// Decrypt the data in place, advancing the counter state appropriately. - fn decrypt(&mut self, data: &mut [u8]); + /// Applies the key stream to the data in place, advancing the counter state appropriately. + fn apply_keystream(&mut self, data: &mut [u8]); } /// The combined nonce and counter that CTR increments and encrypts to form the keystream.
diff --git a/nearby/crypto/crypto_provider/src/aes/mod.rs b/nearby/crypto/crypto_provider/src/aes/mod.rs index 83e48de..e359bad 100644 --- a/nearby/crypto/crypto_provider/src/aes/mod.rs +++ b/nearby/crypto/crypto_provider/src/aes/mod.rs
@@ -20,7 +20,6 @@ pub mod ctr; -#[cfg(feature = "alloc")] pub mod cbc; /// Block size in bytes for AES (and XTS-AES)
diff --git a/nearby/crypto/crypto_provider/src/ed25519.rs b/nearby/crypto/crypto_provider/src/ed25519.rs index 48c2c50..b435ca2 100644 --- a/nearby/crypto/crypto_provider/src/ed25519.rs +++ b/nearby/crypto/crypto_provider/src/ed25519.rs
@@ -43,6 +43,59 @@ /// A byte buffer the size of a ed25519 `PrivateKey`. pub type RawPrivateKey = [u8; PRIVATE_KEY_LENGTH]; +/// A permission token which may be supplied to methods which allow +/// converting private keys to/from raw bytes. +/// +/// In general, operations of this kind should only be done in +/// development-tools, tests, or in credential storage layers +/// to prevent accidental exposure of the private key. +pub struct RawPrivateKeyPermit { + _marker: (), +} + +impl RawPrivateKeyPermit { + pub(crate) fn new() -> Self { + Self { _marker: () } + } +} + +#[cfg(feature = "raw_private_key_permit")] +impl core::default::Default for RawPrivateKeyPermit { + fn default() -> Self { + Self::new() + } +} + +/// A crypto-provider-independent representation of the private +/// key of an ed25519 key-pair, kept in such a way that +/// it does not permit de-structuring it into raw bytes, +/// nor constructing one from raw bytes. +/// +/// Useful for when you want a data-structure to be +/// crypto-provider independent and contain a private key. +#[derive(Clone)] +pub struct PrivateKey { + wrapped: RawPrivateKey, +} + +impl PrivateKey { + /// Derives the public key corresponding to this private key. + pub fn derive_public_key<E: Ed25519Provider>(&self) -> E::PublicKey { + let key_pair = E::KeyPair::from_private_key(self); + key_pair.public() + } + /// Returns the raw bytes of this private key. + /// This operation is only possible while holding a [`RawPrivateKeyPermit`]. + pub fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey { + self.wrapped + } + /// Constructs a private key from the raw bytes of the key. + /// This operation is only possible while holding a [`RawPrivateKeyPermit`]. + pub fn from_raw_private_key(wrapped: RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self { + Self { wrapped } + } +} + /// The keypair which includes both public and secret halves of an asymmetric key. pub trait KeyPair: Sized { /// The ed25519 public key, used when verifying a message @@ -52,15 +105,37 @@ type Signature: Signature; /// Returns the private key bytes of the `KeyPair`. - /// This method should only ever be called by code which securely stores private credentials. - fn private_key(&self) -> RawPrivateKey; + /// This operation is only possible while holding a [`RawPrivateKeyPermit`]. + fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey; /// Builds a key-pair from a `RawPrivateKey` array of bytes. - /// This should only ever be called by code which securely stores private credentials. - fn from_private_key(bytes: &RawPrivateKey) -> Self + /// This operation is only possible while holding a [`RawPrivateKeyPermit`]. + fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self where Self: Sized; + /// Returns the private key of the `KeyPair` in an opaque form. + fn private_key(&self) -> PrivateKey { + // We're okay to reach in and grab the bytes of the private key, + // since the way that we're exposing it would require a valid + // [`RawPrivateKeyPermit`] to extract them again. + let wrapped = self.raw_private_key(&RawPrivateKeyPermit::new()); + PrivateKey { wrapped } + } + + /// Builds a key-pair from a [`PrivateKey`], given in an opaque form. + fn from_private_key(private_key: &PrivateKey) -> Self + where + Self: Sized, + { + // We're okay to reach in and construct an instance from + // the bytes of the private key, since the way that they + // were originally expressed would still require a valid + // [`RawPrivateKeyPermit`] to access them. + let raw_private_key = &private_key.wrapped; + Self::from_raw_private_key(raw_private_key, &RawPrivateKeyPermit::new()) + } + /// Sign the given message and return a digital signature fn sign(&self, msg: &[u8]) -> Self::Signature;
diff --git a/nearby/crypto/crypto_provider/src/elliptic_curve.rs b/nearby/crypto/crypto_provider/src/elliptic_curve.rs index d06a21d..d176769 100644 --- a/nearby/crypto/crypto_provider/src/elliptic_curve.rs +++ b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
@@ -12,12 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate alloc; - use core::fmt::Debug; -use alloc::vec::Vec; - /// Marker trait for an elliptic curve used for diffie-hellman. pub trait Curve {} @@ -42,12 +38,16 @@ /// The random number generator to be used for generating a secret type Rng: crate::CryptoRng; + /// The for encoded public key bytes, for example a `[u8; N]` array if the size is fixed, or + /// `ArrayVec<[u8; N]>` if the size is bounded but not fixed. + type EncodedPublicKey: AsRef<[u8]> + Debug; + /// Generates a new random ephemeral secret. fn generate_random(rng: &mut Self::Rng) -> Self; /// Returns the bytes of the public key for this ephemeral secret that is suitable for sending /// over the wire for key exchange. - fn public_key_bytes(&self) -> Vec<u8>; + fn public_key_bytes(&self) -> Self::EncodedPublicKey; /// Performs diffie-hellman key exchange using this ephemeral secret with the given public key /// `other_pub`. @@ -59,6 +59,8 @@ /// Trait for a public key used for elliptic curve diffie hellman. pub trait PublicKey<E: Curve>: Sized + PartialEq + Debug { + /// The type for an encoded public key. + type EncodedPublicKey: AsRef<[u8]> + Debug; /// The error type associated with Public Key. type Error: Debug; @@ -71,5 +73,5 @@ /// the sec1 encoding, may return equivalent but different byte-representations due to point /// compression, so it is not necessarily true that `from_bytes(bytes)?.to_bytes() == bytes` /// (but it is always true that `from_bytes(key.to_bytes())? == key`). - fn to_bytes(&self) -> Vec<u8>; + fn to_bytes(&self) -> Self::EncodedPublicKey; }
diff --git a/nearby/crypto/crypto_provider/src/lib.rs b/nearby/crypto/crypto_provider/src/lib.rs index 624072a..0031ebb 100644 --- a/nearby/crypto/crypto_provider/src/lib.rs +++ b/nearby/crypto/crypto_provider/src/lib.rs
@@ -48,6 +48,8 @@ /// mod containing traits for ed25519 key generation, signing, and verification pub mod ed25519; +pub use tinyvec; + /// Uber crypto trait which defines the traits for all crypto primitives as associated types pub trait CryptoProvider: Clone + Debug + PartialEq + Eq + Send { /// The Hkdf type which implements the hkdf trait @@ -80,10 +82,13 @@ /// using SHA-512 (SHA-2) and Curve25519 type Ed25519: ed25519::Ed25519Provider; /// The trait defining AES-128-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 16 bytes. - type Aes128GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes128Key>; + type Aes128GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes128Key>; /// The trait defining AES-256-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 32 bytes. - type Aes256GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes256Key>; - + type Aes256GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes256Key>; + /// The trait defining AES-128-GCM, an AEAD with a key size of 16 bytes. + type Aes128Gcm: aead::AesGcm + aead::AeadInit<Aes128Key>; + /// The trait defining AES-256-GCM, an AEAD with a key size of 32 bytes. + type Aes256Gcm: aead::AesGcm + aead::AeadInit<Aes256Key>; /// The cryptographically secure random number generator type CryptoRng: CryptoRng;
diff --git a/nearby/crypto/crypto_provider/src/p256.rs b/nearby/crypto/crypto_provider/src/p256.rs index 90d3542..fd7b531 100644 --- a/nearby/crypto/crypto_provider/src/p256.rs +++ b/nearby/crypto/crypto_provider/src/p256.rs
@@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate alloc; +use tinyvec::ArrayVec; use crate::elliptic_curve::{Curve, EcdhProvider, PublicKey}; -use alloc::vec::Vec; use core::fmt::Debug; /// Marker type for P256 implementation. This is used by EcdhProvider as its type parameter. @@ -23,6 +22,24 @@ pub enum P256 {} impl Curve for P256 {} +/// Longest length for a sec-1 encoded P256 public key, which is the uncompressed format +/// `04 || X || Y` as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography") +/// standard. +const P256_PUBLIC_KEY_MAX_LENGTH: usize = 65; + +/// Whether an elliptic curve point should be compressed or not. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PointCompression { + /// The elliptic curve point should be compressed (`02 || X` or `03 || X`), + /// as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve + /// Cryptography"). + Compressed, + /// The elliptic curve point should be uncompressed (`04 || X || Y`), as + /// defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve + /// Cryptography"). + Uncompressed, +} + /// Trait for a NIST-P256 public key. pub trait P256PublicKey: Sized + PartialEq + Debug { /// The error type associated with this implementation. @@ -36,7 +53,10 @@ /// ("Elliptic Curve Cryptography") standard. Note that it is not necessarily true that /// `from_sec1_bytes(bytes)?.to_sec1_bytes() == bytes` because of point compression. (But it is /// always true that `from_sec1_bytes(key.to_sec1_bytes())? == key`). - fn to_sec1_bytes(&self) -> Vec<u8>; + fn to_sec1_bytes( + &self, + point_compression: PointCompression, + ) -> ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>; /// Converts this public key's x and y coordinates on the elliptic curve to big endian octet /// strings. @@ -48,13 +68,14 @@ impl<P: P256PublicKey> PublicKey<P256> for P { type Error = <Self as P256PublicKey>::Error; + type EncodedPublicKey = ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>; fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { Self::from_sec1_bytes(bytes) } - fn to_bytes(&self) -> Vec<u8> { - Self::to_sec1_bytes(self) + fn to_bytes(&self) -> Self::EncodedPublicKey { + Self::to_sec1_bytes(self, PointCompression::Uncompressed) } }
diff --git a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml b/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml deleted file mode 100644 index f5ab7fa..0000000 --- a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml +++ /dev/null
@@ -1,3 +0,0 @@ -paths = [ - "../../../boringssl-build/boringssl/rust/bssl-crypto", -] \ No newline at end of file
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.lock b/nearby/crypto/crypto_provider_boringssl/Cargo.lock index 14402c1..7773e56 100644 --- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock +++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
@@ -4,13 +4,20 @@ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bssl-crypto" version = "0.1.0" +dependencies = [ + "bssl-sys", +] + +[[package]] +name = "bssl-sys" +version = "0.1.0" [[package]] name = "cfg-if" @@ -21,6 +28,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_boringssl" @@ -28,18 +38,10 @@ dependencies = [ "bssl-crypto", "crypto_provider", - "crypto_provider_stubs", "crypto_provider_test", ] [[package]] -name = "crypto_provider_stubs" -version = "0.1.0" -dependencies = [ - "crypto_provider", -] - -[[package]] name = "crypto_provider_test" version = "0.1.0" dependencies = [ @@ -56,9 +58,9 @@ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -79,24 +81,21 @@ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "ppv-lite86" @@ -106,18 +105,18 @@ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -197,14 +196,14 @@ [[package]] name = "rstest_reuse" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" dependencies = [ "quote", "rand", "rustc_version", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -218,41 +217,41 @@ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "semver" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.162" +version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.162" +version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -272,9 +271,9 @@ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -290,10 +289,16 @@ ] [[package]] -name = "unicode-ident" -version = "1.0.8" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "wasi" @@ -303,9 +308,9 @@ [[package]] name = "wycheproof" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183c789620c674b79dac33cd3aadb6c8006b66cba6a680402235aaebc743e3df" +checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12" dependencies = [ "base64", "hex",
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.toml b/nearby/crypto/crypto_provider_boringssl/Cargo.toml index 24ce66c..fb3bfdf 100644 --- a/nearby/crypto/crypto_provider_boringssl/Cargo.toml +++ b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
@@ -6,10 +6,9 @@ [dependencies] crypto_provider = { path = "../crypto_provider", features = ["alloc", "std"] } -crypto_provider_stubs = { path = "../crypto_provider_stubs" } -# Note: before this crate will work you need to run `scripts/prepare-boringssl.sh` -bssl-crypto = {path = "../bssl-crypto"} +# Note: before this crate will work you need to run `cargo run -p build_scripts -- build-boringssl` +bssl-crypto = {path = "../../../third_party/boringssl/rust/bssl-crypto"} [dev-dependencies] crypto_provider_test = {path = "../crypto_provider_test"}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs new file mode 100644 index 0000000..03bb087 --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs
@@ -0,0 +1,89 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +use alloc::vec::Vec; +use bssl_crypto::aead::Aead as _; +use crypto_provider::aead::{Aead, AeadError, AeadInit}; +use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey}; + +pub struct AesGcm(bssl_crypto::aead::AesGcm); + +impl AeadInit<Aes128Key> for AesGcm { + fn new(key: &Aes128Key) -> Self { + Self(bssl_crypto::aead::new_aes_128_gcm(key.as_array())) + } +} + +impl AeadInit<Aes256Key> for AesGcm { + fn new(key: &Aes256Key) -> Self { + Self(bssl_crypto::aead::new_aes_256_gcm(key.as_array())) + } +} + +impl crypto_provider::aead::AesGcm for AesGcm {} + +impl Aead for AesGcm { + const TAG_SIZE: usize = 16; + type Nonce = [u8; 12]; + type Tag = [u8; 16]; + + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError) + } + + fn encrypt_detached( + &self, + _msg: &mut [u8], + _aad: &[u8], + _nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { + unimplemented!("Not yet supported by boringssl") + } + + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError) + } + + fn decrypt_detached( + &self, + _msg: &mut [u8], + _aad: &[u8], + _nonce: &Self::Nonce, + _tag: &Self::Tag, + ) -> Result<(), AeadError> { + unimplemented!("Not yet supported by boringssl") + } +} + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use crypto_provider_test::aead::aes_gcm::*; + use crypto_provider_test::aes::*; + + use super::*; + + #[apply(aes_128_gcm_test_cases)] + fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm>) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_test_cases)] + fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs new file mode 100644 index 0000000..d1fb0e7 --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs
@@ -0,0 +1,88 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +extern crate alloc; + +use alloc::vec::Vec; +use bssl_crypto::aead::Aead as _; +use crypto_provider::aead::{Aead, AeadError, AeadInit}; +use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey}; + +pub struct AesGcmSiv(bssl_crypto::aead::AesGcmSiv); + +impl AeadInit<Aes128Key> for AesGcmSiv { + fn new(key: &Aes128Key) -> Self { + Self(bssl_crypto::aead::new_aes_128_gcm_siv(key.as_array())) + } +} + +impl AeadInit<Aes256Key> for AesGcmSiv { + fn new(key: &Aes256Key) -> Self { + Self(bssl_crypto::aead::new_aes_256_gcm_siv(key.as_array())) + } +} + +impl crypto_provider::aead::AesGcmSiv for AesGcmSiv {} + +impl Aead for AesGcmSiv { + const TAG_SIZE: usize = 16; + type Nonce = [u8; 12]; + type Tag = [u8; 16]; + + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError) + } + + fn encrypt_detached( + &self, + _msg: &mut [u8], + _aad: &[u8], + _nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { + unimplemented!("Not yet supported by boringssl") + } + + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError) + } + + fn decrypt_detached( + &self, + _msg: &mut [u8], + _aad: &[u8], + _nonce: &Self::Nonce, + _tag: &Self::Tag, + ) -> Result<(), AeadError> { + unimplemented!("Not yet supported by boringssl") + } +} + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use crypto_provider_test::aead::aes_gcm_siv::*; + use crypto_provider_test::aes::*; + + use super::*; + + #[apply(aes_128_gcm_siv_test_cases)] + fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv>) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_siv_test_cases)] + fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs similarity index 73% copy from nearby/crypto/bssl-crypto/src/lib.rs copy to nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs index 89e6968..424f16e 100644 --- a/nearby/crypto/bssl-crypto/src/lib.rs +++ b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -12,5 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the -//! `build-boringssl` subcommand of the top level crate. +pub(crate) mod aes_gcm; +pub(crate) mod aes_gcm_siv;
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs new file mode 100644 index 0000000..4d3fd3f --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs
@@ -0,0 +1,81 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +use alloc::vec::Vec; +use bssl_crypto::cipher::BlockCipher; +use crypto_provider::{ + aes::{ + cbc::{AesCbcIv, DecryptionError, EncryptionError}, + Aes256Key, AesKey, + }, + tinyvec::SliceVec, +}; + +/// BoringSSL implementation of AES-CBC with PKCS7 padding +pub enum AesCbcPkcs7Padded {} +impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded { + #[allow(clippy::expect_used)] + fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> { + let encryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_encrypt(key.as_array(), iv); + encryptor.encrypt_padded(message).expect("Encrypting AES-CBC should be infallible") + } + + fn encrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError> { + let result = Self::encrypt(key, iv, message); + if message.capacity() < result.len() { + return Err(EncryptionError::PaddingFailed); + } + message.clear(); + message.extend_from_slice(&result); + Ok(()) + } + + fn decrypt( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &[u8], + ) -> Result<Vec<u8>, DecryptionError> { + let decryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_decrypt(key.as_array(), iv); + decryptor.decrypt_padded(ciphertext).map_err(|_| DecryptionError::BadPadding) + } + + fn decrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError> { + Self::decrypt(key, iv, ciphertext).map(|result| { + ciphertext.clear(); + ciphertext.extend_from_slice(&result); + }) + } +} + +#[cfg(test)] +mod tests { + use super::AesCbcPkcs7Padded; + use core::marker::PhantomData; + use crypto_provider_test::aes::cbc::*; + + #[apply(aes_256_cbc_test_cases)] + fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs new file mode 100644 index 0000000..c13ab31 --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs
@@ -0,0 +1,74 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bssl_crypto::cipher::StreamCipher; +use crypto_provider::aes::{ctr::NonceAndCounter, Aes128Key, Aes256Key, AesKey}; + +/// BoringSSL implementation of AES-CTR 128. +pub struct AesCtr128(bssl_crypto::cipher::aes_ctr::Aes128Ctr); + +impl crypto_provider::aes::ctr::AesCtr for AesCtr128 { + type Key = Aes128Key; + + fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self { + Self(bssl_crypto::cipher::aes_ctr::Aes128Ctr::new( + key.as_array(), + &nonce_and_counter.as_block_array(), + )) + } + + #[allow(clippy::expect_used)] + fn apply_keystream(&mut self, data: &mut [u8]) { + assert!(data.len() < i32::MAX as usize); + self.0.apply_keystream(data).expect("Data length should fit inside of a i32") + } +} + +/// BoringSSL implementation of AES-CTR 256. +pub struct AesCtr256(bssl_crypto::cipher::aes_ctr::Aes256Ctr); + +impl crypto_provider::aes::ctr::AesCtr for AesCtr256 { + type Key = Aes256Key; + + fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self { + Self(bssl_crypto::cipher::aes_ctr::Aes256Ctr::new( + key.as_array(), + &nonce_and_counter.as_block_array(), + )) + } + + #[allow(clippy::expect_used)] + fn apply_keystream(&mut self, data: &mut [u8]) { + assert!(data.len() < i32::MAX as usize); + self.0.apply_keystream(data).expect("Data length should fit inside of a i32") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::marker::PhantomData; + use crypto_provider_test::aes::ctr::*; + use crypto_provider_test::aes::*; + + #[apply(aes_128_ctr_test_cases)] + fn aes_128_ctr_test(testcase: CryptoProviderTestCase<AesCtr128>) { + testcase(PhantomData); + } + + #[apply(aes_256_ctr_test_cases)] + fn aes_256_ctr_test(testcase: CryptoProviderTestCase<AesCtr256>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs similarity index 97% rename from nearby/crypto/crypto_provider_boringssl/src/aes.rs rename to nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs index 88bfeac..d5cbe58 100644 --- a/nearby/crypto/crypto_provider_boringssl/src/aes.rs +++ b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
@@ -17,6 +17,12 @@ Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey, }; +/// AES_CTR implementations. +pub mod ctr; + +/// AES_CBC implementations. +pub mod cbc; + /// BoringSSL AES-128 operations pub struct Aes128; impl Aes for Aes128 {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs index 7c671ae..b5c374a 100644 --- a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs +++ b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
@@ -13,7 +13,8 @@ // limitations under the License. use crypto_provider::ed25519::{ - InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError, + InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _, + SignatureError, }; pub struct Ed25519; @@ -30,11 +31,11 @@ type PublicKey = PublicKey; type Signature = Signature; - fn private_key(&self) -> RawPrivateKey { + fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey { self.0.to_seed() } - fn from_private_key(bytes: &RawPrivateKey) -> Self + fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self where Self: Sized, {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/lib.rs index 3b78898..75758b6 100644 --- a/nearby/crypto/crypto_provider_boringssl/src/lib.rs +++ b/nearby/crypto/crypto_provider_boringssl/src/lib.rs
@@ -21,14 +21,12 @@ clippy::expect_used )] -//! Crate which provides impls for CryptoProvider backed by BoringSSL. - -use bssl_crypto::digest::{Sha256, Sha512}; +//! Crate which provides impls for CryptoProvider backed by BoringSSL +//! use bssl_crypto::rand::rand_bytes; use crypto_provider::{CryptoProvider, CryptoRng}; -use crypto_provider_stubs::*; -/// Implementation of `crypto_provider::aes` types using BoringSSL. +/// Implementation of `crypto_provider::aes` types using BoringSSL pub mod aes; /// Implementations of crypto_provider::hkdf traits backed by BoringSSL @@ -40,31 +38,45 @@ /// Implementations of crypto_provider::ed25519 traits backed by BoringSSL mod ed25519; +/// Implementations of crypto_provider::aead traits backed by BoringSSL +mod aead; + +/// Implementations of crypto_provider::p256 traits backed by BoringSSL +mod p256; + +/// Implementations of crypto_provider::x25519 traits backed by BoringSSL +mod x25519; + +/// Implementations of crypto_provider::sha2 traits backed by BoringSSL +mod sha2; + /// The BoringSSL backed struct which implements CryptoProvider #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct Boringssl; impl CryptoProvider for Boringssl { - type HkdfSha256 = hkdf::Hkdf<Sha256>; + type HkdfSha256 = hkdf::Hkdf<bssl_crypto::digest::Sha256>; type HmacSha256 = hmac::HmacSha256; - type HkdfSha512 = hkdf::Hkdf<Sha512>; + type HkdfSha512 = hkdf::Hkdf<bssl_crypto::digest::Sha512>; type HmacSha512 = hmac::HmacSha512; - type AesCbcPkcs7Padded = AesCbcPkcs7PaddedStubs; - type X25519 = X25519Stubs; - type P256 = P256Stubs; - type Sha256 = Sha2Stubs; - type Sha512 = Sha2Stubs; + type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded; + type X25519 = x25519::X25519Ecdh; + type P256 = p256::P256Ecdh; + type Sha256 = sha2::Sha256; + type Sha512 = sha2::Sha512; type Aes128 = aes::Aes128; type Aes256 = aes::Aes256; - type AesCtr128 = Aes128Stubs; - type AesCtr256 = Aes256Stubs; + type AesCtr128 = aes::ctr::AesCtr128; + type AesCtr256 = aes::ctr::AesCtr256; type Ed25519 = ed25519::Ed25519; - type Aes128GcmSiv = Aes128Stubs; - type Aes256GcmSiv = Aes256Stubs; + type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv; + type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv; + type Aes128Gcm = aead::aes_gcm::AesGcm; + type Aes256Gcm = aead::aes_gcm::AesGcm; type CryptoRng = BoringSslRng; - fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool { - unimplemented!() + fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { + bssl_crypto::mem::crypto_memcmp(a, b) } } @@ -86,3 +98,17 @@ rand_bytes(dest) } } + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + use crypto_provider_test::prelude::*; + use crypto_provider_test::sha2::*; + + use crate::Boringssl; + + #[apply(sha2_test_cases)] + fn sha2_tests(testcase: CryptoProviderTestCase<Boringssl>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/p256.rs b/nearby/crypto/crypto_provider_boringssl/src/p256.rs new file mode 100644 index 0000000..7fefecd --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/p256.rs
@@ -0,0 +1,117 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate alloc; + +use bssl_crypto::ecdh::{PrivateKey, PublicKey}; +use crypto_provider::{ + elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey as _}, + p256::{PointCompression, P256}, + tinyvec::ArrayVec, +}; + +/// Implementation of NIST-P256 using bssl-crypto crate. +pub struct P256Ecdh; + +impl EcdhProvider<P256> for P256Ecdh { + type PublicKey = P256PublicKey; + type EphemeralSecret = P256EphemeralSecret; + type SharedSecret = [u8; 32]; +} + +/// A NIST-P256 public key. +#[derive(Debug, PartialEq, Eq)] +pub struct P256PublicKey(PublicKey<bssl_crypto::ecdh::P256>); +impl crypto_provider::p256::P256PublicKey for P256PublicKey { + type Error = bssl_crypto::ecdh::Error; + + fn from_sec1_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { + PublicKey::try_from(bytes).map(Self) + } + + fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> { + match point_compression { + PointCompression::Compressed => unimplemented!(), + PointCompression::Uncompressed => { + let mut bytes = ArrayVec::<[u8; 65]>::new(); + bytes.extend_from_slice(&self.0.to_vec()); + bytes + } + } + } + + #[allow(clippy::expect_used)] + fn to_affine_coordinates(&self) -> Result<([u8; 32], [u8; 32]), Self::Error> { + Ok(self.0.to_affine_coordinates()) + } + fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> { + PublicKey::<bssl_crypto::ecdh::P256>::from_affine_coordinates(x, y).map(Self) + } +} + +/// Ephemeral secrect for use in a P256 Diffie-Hellman +pub struct P256EphemeralSecret { + secret: PrivateKey<bssl_crypto::ecdh::P256>, +} + +impl EphemeralSecret<P256> for P256EphemeralSecret { + type Impl = P256Ecdh; + type Error = bssl_crypto::ecdh::Error; + type Rng = (); + type EncodedPublicKey = + <P256PublicKey as crypto_provider::elliptic_curve::PublicKey<P256>>::EncodedPublicKey; + + fn generate_random(_rng: &mut Self::Rng) -> Self { + Self { secret: PrivateKey::generate() } + } + + fn public_key_bytes(&self) -> Self::EncodedPublicKey { + P256PublicKey((&self.secret).into()).to_bytes() + } + + fn diffie_hellman( + self, + other_pub: &P256PublicKey, + ) -> Result<<Self::Impl as EcdhProvider<P256>>::SharedSecret, Self::Error> { + let shared_secret = self.secret.diffie_hellman(&other_pub.0)?; + let bytes: <Self::Impl as EcdhProvider<P256>>::SharedSecret = shared_secret.to_bytes(); + Ok(bytes) + } +} + +#[cfg(test)] +impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret { + #[allow(clippy::unwrap_used)] + fn from_private_components( + private_bytes: &[u8; 32], + _public_key: &P256PublicKey, + ) -> Result<Self, Self::Error> { + Ok(Self { secret: PrivateKey::from_private_bytes(private_bytes).unwrap() }) + } +} + +#[cfg(test)] +mod tests { + use super::P256Ecdh; + use core::marker::PhantomData; + use crypto_provider_test::p256::*; + + #[apply(p256_test_cases)] + fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, name: &str) { + if name == "to_bytes_compressed" { + return; // EC point compression not supported by bssl-crypto yet + } + testcase(PhantomData::<P256Ecdh>) + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/sha2.rs b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs new file mode 100644 index 0000000..d95294d --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs
@@ -0,0 +1,35 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// BoringSSL implementation for SHA256 +pub struct Sha256; + +impl crypto_provider::sha2::Sha256 for Sha256 { + fn sha256(input: &[u8]) -> [u8; 32] { + let mut digest = bssl_crypto::digest::Sha256::new_digest(); + digest.update(input); + digest.finalize() + } +} + +/// BoringSSL implementation for SHA512 +pub struct Sha512; + +impl crypto_provider::sha2::Sha512 for Sha512 { + fn sha512(input: &[u8]) -> [u8; 64] { + let mut digest = bssl_crypto::digest::Sha512::new_digest(); + digest.update(input); + digest.finalize() + } +}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/x25519.rs b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs new file mode 100644 index 0000000..58d040b --- /dev/null +++ b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs
@@ -0,0 +1,102 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}; +use crypto_provider::x25519::X25519; + +/// The BoringSSL implementation of X25519 ECDH. +pub struct X25519Ecdh; + +impl EcdhProvider<X25519> for X25519Ecdh { + type PublicKey = X25519PublicKey; + type EphemeralSecret = X25519EphemeralSecret; + type SharedSecret = [u8; 32]; +} + +/// A X25519 ephemeral secret used for Diffie-Hellman. +pub struct X25519EphemeralSecret { + secret: bssl_crypto::x25519::PrivateKey, +} + +impl EphemeralSecret<X25519> for X25519EphemeralSecret { + type Impl = X25519Ecdh; + type Error = bssl_crypto::x25519::DiffieHellmanError; + type Rng = (); + type EncodedPublicKey = [u8; 32]; + + fn generate_random(_rng: &mut Self::Rng) -> Self { + Self { secret: bssl_crypto::x25519::PrivateKey::generate() } + } + + fn public_key_bytes(&self) -> Self::EncodedPublicKey { + let pubkey: bssl_crypto::x25519::PublicKey = (&self.secret).into(); + pubkey.to_bytes() + } + + fn diffie_hellman( + self, + other_pub: &<X25519Ecdh as EcdhProvider<X25519>>::PublicKey, + ) -> Result<<X25519Ecdh as EcdhProvider<X25519>>::SharedSecret, Self::Error> { + self.secret.diffie_hellman(&other_pub.0).map(|secret| secret.to_bytes()) + } +} + +#[cfg(test)] +impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<X25519> + for X25519EphemeralSecret +{ + fn from_private_components( + private_bytes: &[u8; 32], + _public_key: &X25519PublicKey, + ) -> Result<Self, Self::Error> { + Ok(Self { secret: bssl_crypto::x25519::PrivateKey::from_private_bytes(private_bytes) }) + } +} + +/// A X25519 public key. +#[derive(Debug, PartialEq, Eq)] +pub struct X25519PublicKey(bssl_crypto::x25519::PublicKey); + +impl PublicKey<X25519> for X25519PublicKey { + type Error = Error; + type EncodedPublicKey = [u8; 32]; + + fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { + let byte_arr: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?; + Ok(Self(bssl_crypto::x25519::PublicKey::from(&byte_arr))) + } + + fn to_bytes(&self) -> Self::EncodedPublicKey { + self.0.to_bytes() + } +} + +/// Error type for the BoringSSL implementation of x25519. +#[derive(Debug)] +pub enum Error { + /// Unexpected size for the given input. + WrongSize, +} + +#[cfg(test)] +mod tests { + use super::X25519Ecdh; + use core::marker::PhantomData; + use crypto_provider_test::x25519::*; + + #[apply(x25519_test_cases)] + fn x25519_tests(testcase: CryptoProviderTestCase<X25519Ecdh>) { + testcase(PhantomData::<X25519Ecdh>) + } +}
diff --git a/nearby/crypto/crypto_provider_default/src/lib.rs b/nearby/crypto/crypto_provider_default/src/lib.rs index 4b1b01e..b880ba8 100644 --- a/nearby/crypto/crypto_provider_default/src/lib.rs +++ b/nearby/crypto/crypto_provider_default/src/lib.rs
@@ -15,6 +15,10 @@ //! Provides multiple implementations of CryptoProvider through the same struct, configurable by //! feature flag. +#![no_std] +#![forbid(unsafe_code)] +#![deny(missing_docs)] + cfg_if::cfg_if! { if #[cfg(feature = "rustcrypto")] { pub use crypto_provider_rustcrypto::RustCrypto as CryptoProviderImpl;
diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs index f5d5b00..c2ac5b1 100644 --- a/nearby/crypto/crypto_provider_openssl/src/aes.rs +++ b/nearby/crypto/crypto_provider_openssl/src/aes.rs
@@ -26,10 +26,13 @@ use openssl::symm::{Cipher, Crypter, Mode}; -use crypto_provider::aes::{ - cbc::{AesCbcIv, DecryptionError}, - ctr::NonceAndCounter, - Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey, +use crypto_provider::{ + aes::{ + cbc::{AesCbcIv, DecryptionError, EncryptionError}, + ctr::NonceAndCounter, + Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey, + }, + tinyvec::SliceVec, }; /// Uber struct which contains impls for AES-128 fns @@ -126,7 +129,23 @@ impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for OpenSslAesCbcPkcs7 { fn encrypt(key: &crypto_provider::aes::Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> { openssl::symm::encrypt(Cipher::aes_256_cbc(), key.as_slice(), Some(iv.as_slice()), message) - .unwrap() + // The output buffer is allocated by the openssl crate and guarantees to have enough + // space to hold the output value and does not overlap with the input slice. + .expect("encrypt should always succeed") + } + + fn encrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError> { + let encrypted = Self::encrypt(key, iv, message); + if encrypted.len() > message.capacity() { + return Err(EncryptionError::PaddingFailed); + } + message.clear(); + message.extend_from_slice(&encrypted); + Ok(()) } fn decrypt( @@ -142,12 +161,22 @@ ) .map_err(|_| DecryptionError::BadPadding) } + + fn decrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError> { + Self::decrypt(key, iv, ciphertext).map(|result| { + ciphertext.clear(); + ciphertext.extend_from_slice(&result); + }) + } } /// OpenSSL implementation of AES-CTR-128 pub struct OpenSslAesCtr128 { enc_cipher: Crypter, - dec_cipher: Crypter, } impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr128 { @@ -161,33 +190,19 @@ Some(&nonce_and_counter.as_block_array()), ) .unwrap(), - dec_cipher: Crypter::new( - Cipher::aes_128_ctr(), - Mode::Decrypt, - key.as_slice(), - Some(&nonce_and_counter.as_block_array()), - ) - .unwrap(), } } - fn encrypt(&mut self, data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { let mut in_slice = vec![0u8; data.len()]; in_slice.copy_from_slice(data); let _ = self.enc_cipher.update(&in_slice, data); } - - fn decrypt(&mut self, data: &mut [u8]) { - let mut in_slice = vec![0u8; data.len()]; - in_slice.copy_from_slice(data); - let _ = self.dec_cipher.update(&in_slice, data); - } } /// OpenSSL implementation of AES-CTR-256 pub struct OpenSslAesCtr256 { enc_cipher: Crypter, - dec_cipher: Crypter, } impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr256 { @@ -201,27 +216,14 @@ Some(&nonce_and_counter.as_block_array()), ) .unwrap(), - dec_cipher: Crypter::new( - Cipher::aes_256_ctr(), - Mode::Decrypt, - key.as_slice(), - Some(&nonce_and_counter.as_block_array()), - ) - .unwrap(), } } - fn encrypt(&mut self, data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { let mut in_slice = vec![0u8; data.len()]; in_slice.copy_from_slice(data); let _ = self.enc_cipher.update(&in_slice, data); } - - fn decrypt(&mut self, data: &mut [u8]) { - let mut in_slice = vec![0u8; data.len()]; - in_slice.copy_from_slice(data); - let _ = self.dec_cipher.update(&in_slice, data); - } } #[cfg(test)]
diff --git a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs index 110daf2..111348a 100644 --- a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs +++ b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
@@ -13,7 +13,8 @@ // limitations under the License. use crypto_provider::ed25519::{ - InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError, + InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _, + SignatureError, }; use openssl::pkey::{Id, PKey, Private}; use openssl::sign::{Signer, Verifier}; @@ -32,7 +33,7 @@ type PublicKey = PublicKey; type Signature = Signature; - fn private_key(&self) -> RawPrivateKey { + fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey { let private_key = self.0.raw_private_key().unwrap(); let mut public_key = self.0.raw_public_key().unwrap(); let mut result = private_key; @@ -40,7 +41,7 @@ result.try_into().unwrap() } - fn from_private_key(bytes: &RawPrivateKey) -> Self { + fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self { Self(PKey::private_key_from_raw_bytes(bytes, Id::ED25519).unwrap()) }
diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs index d8157d2..8d1db1d 100644 --- a/nearby/crypto/crypto_provider_openssl/src/lib.rs +++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs
@@ -82,6 +82,8 @@ type Ed25519 = ed25519::Ed25519; type Aes128GcmSiv = crypto_provider_stubs::Aes128Stubs; type Aes256GcmSiv = crypto_provider_stubs::Aes256Stubs; + type Aes128Gcm = crypto_provider_stubs::Aes128Stubs; + type Aes256Gcm = crypto_provider_stubs::Aes256Stubs; type CryptoRng = OpenSslRng; fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { @@ -113,7 +115,7 @@ use core::marker::PhantomData; use crypto_provider_test::sha2::*; - use crypto_provider_test::*; + use crypto_provider_test::{prelude::*, *}; use crate::Openssl;
diff --git a/nearby/crypto/crypto_provider_openssl/src/p256.rs b/nearby/crypto/crypto_provider_openssl/src/p256.rs index 2f5b0b1..17dd6ce 100644 --- a/nearby/crypto/crypto_provider_openssl/src/p256.rs +++ b/nearby/crypto/crypto_provider_openssl/src/p256.rs
@@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret}; -use crypto_provider::p256::P256; +use crypto_provider::{ + elliptic_curve::{EcdhProvider, EphemeralSecret}, + p256::{PointCompression, P256}, + tinyvec::ArrayVec, +}; use openssl::bn::{BigNum, BigNumContext}; use openssl::derive::Deriver; use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm}; @@ -65,15 +68,24 @@ Ok(Self(eckey.try_into()?)) } - fn to_sec1_bytes(&self) -> Vec<u8> { + fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> { + let point_conversion_form = match point_compression { + PointCompression::Compressed => PointConversionForm::COMPRESSED, + PointCompression::Uncompressed => PointConversionForm::UNCOMPRESSED, + }; let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); let mut bncontext = BigNumContext::new().unwrap(); - self.0 - .ec_key() - .unwrap() - .public_key() - .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext) - .unwrap() + let mut bytes = ArrayVec::<[u8; 65]>::new(); + bytes.extend_from_slice( + &self + .0 + .ec_key() + .unwrap() + .public_key() + .to_bytes(&ecgroup, point_conversion_form, &mut bncontext) + .unwrap(), + ); + bytes } fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> { @@ -115,6 +127,7 @@ type Impl = P256Ecdh; type Error = Error; type Rng = (); + type EncodedPublicKey = ArrayVec<[u8; 65]>; fn generate_random(_rng: &mut Self::Rng) -> Self { let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); @@ -122,15 +135,20 @@ Self(eckey.try_into().unwrap()) } - fn public_key_bytes(&self) -> Vec<u8> { + fn public_key_bytes(&self) -> Self::EncodedPublicKey { let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); let mut bncontext = BigNumContext::new().unwrap(); - self.0 - .ec_key() - .unwrap() - .public_key() - .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext) - .unwrap() + let mut bytes = Self::EncodedPublicKey::new(); + bytes.extend_from_slice( + &self + .0 + .ec_key() + .unwrap() + .public_key() + .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext) + .unwrap(), + ); + bytes } fn diffie_hellman( @@ -178,7 +196,7 @@ use crypto_provider_test::p256::*; #[apply(p256_test_cases)] - fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>) { - testcase(PhantomData::<P256Ecdh>) + fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, _name: &str) { + testcase(PhantomData) } }
diff --git a/nearby/crypto/crypto_provider_openssl/src/x25519.rs b/nearby/crypto/crypto_provider_openssl/src/x25519.rs index ff6f3b0..2cb4cab 100644 --- a/nearby/crypto/crypto_provider_openssl/src/x25519.rs +++ b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
@@ -30,14 +30,15 @@ impl PublicKey<X25519> for X25519PublicKey { type Error = ErrorStack; + type EncodedPublicKey = [u8; 32]; fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { let key = PKey::public_key_from_raw_bytes(bytes, Id::X25519)?; Ok(X25519PublicKey(key)) } - fn to_bytes(&self) -> Vec<u8> { - self.0.raw_public_key().unwrap() + fn to_bytes(&self) -> Self::EncodedPublicKey { + self.0.raw_public_key().unwrap().try_into().unwrap() } } @@ -48,14 +49,15 @@ type Impl = X25519Ecdh; type Error = ErrorStack; type Rng = (); + type EncodedPublicKey = [u8; 32]; fn generate_random(_rng: &mut Self::Rng) -> Self { let private_key = openssl::pkey::PKey::generate_x25519().unwrap(); Self(private_key) } - fn public_key_bytes(&self) -> Vec<u8> { - self.0.raw_public_key().unwrap() + fn public_key_bytes(&self) -> Self::EncodedPublicKey { + self.0.raw_public_key().unwrap().try_into().unwrap() } fn diffie_hellman(
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml index e0bf534..a0638ff 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml +++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -6,15 +6,22 @@ [dependencies] aead = "0.5.1" -aes-gcm-siv = { version = "0.11.1", features = ["aes"] } -crypto_provider.workspace = true +aes-gcm-siv = { version = "0.11.1", features = [ + "aes", +], default-features = false } +aes-gcm = { version = "0.10.3", features = [ + "aes", +], default-features = false } +crypto_provider = { workspace = true } hmac.workspace = true hkdf.workspace = true sha2.workspace = true x25519-dalek.workspace = true p256 = { workspace = true, features = ["ecdh"], default-features = false } sec1.workspace = true -ed25519-dalek = { workspace = true, default-features = false, features = ["rand_core"] } +ed25519-dalek = { workspace = true, default-features = false, features = [ + "rand_core", +] } rand = { workspace = true, default-features = false } rand_core.workspace = true subtle.workspace = true @@ -31,5 +38,12 @@ [features] default = ["alloc", "rand_chacha"] -std = ["ed25519-dalek/default", "rand/std", "rand/std_rng", "crypto_provider/std", "crypto_provider/alloc"] -alloc = ["aead/bytes"] +std = [ + "alloc", + "ed25519-dalek/default", + "rand/std", + "rand/std_rng", + "crypto_provider/std", + "crypto_provider/alloc", +] +alloc = ["aead/bytes", "aead/alloc", "cbc/alloc", "crypto_provider/alloc"]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs new file mode 100644 index 0000000..f1d9e0f --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs
@@ -0,0 +1,123 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use aead::Payload; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +// RustCrypto defined traits and types +use aes::cipher::typenum::consts::{U12, U16}; +use aes::cipher::BlockCipher; +use aes::cipher::BlockEncrypt; +#[cfg(feature = "alloc")] +use aes_gcm::aead::Aead as _; +use aes_gcm::aead::KeyInit; +use aes_gcm::AeadInPlace as _; + +// CryptoProvider traits and types +use crypto_provider::aead::{Aead, AeadError, AeadInit}; + +pub struct AesGcm<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>( + aes_gcm::AesGcm<A, U12>, +); + +impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcm + for AesGcm<A> +{ +} + +impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> + AeadInit<K> for AesGcm<A> +{ + fn new(key: &K) -> Self { + Self(aes_gcm::AesGcm::<A, U12>::new(key.as_slice().into())) + } +} + +impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcm<A> { + const TAG_SIZE: usize = 16; + type Nonce = [u8; 12]; + type Tag = [u8; 16]; + + #[cfg(feature = "alloc")] + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0 + .encrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad }) + .map_err(|_| AeadError) + } + + fn encrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { + self.0 + .encrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg) + .map(|arr| arr.into()) + .map_err(|_| AeadError) + } + + #[cfg(feature = "alloc")] + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0 + .decrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad }) + .map_err(|_| AeadError) + } + + fn decrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + tag: &Self::Tag, + ) -> Result<(), AeadError> { + self.0 + .decrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg, tag.into()) + .map_err(|_| AeadError) + } +} + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use crypto_provider_test::aead::aes_gcm::*; + use crypto_provider_test::aes::*; + + use super::*; + + #[apply(aes_128_gcm_test_cases)] + fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) { + testcase(PhantomData); + } + + #[apply(aes_128_gcm_test_cases_detached)] + fn aes_128_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_test_cases)] + fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_test_cases_detached)] + fn aes_256_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs index 402c2ed..408017f 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
@@ -12,74 +12,118 @@ // See the License for the specific language governing permissions and // limitations under the License. -use aes_gcm_siv::{AeadInPlace, Aes128GcmSiv, Aes256GcmSiv, KeyInit, Nonce}; +#[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "alloc")] +use aead::Payload; +#[cfg(feature = "alloc")] use alloc::vec::Vec; -use crypto_provider::aead::{Aead, AeadError}; -use crypto_provider::aead::aes_gcm_siv::AesGcmSiv; -use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey}; +// RustCrypto defined traits and types +use aes::cipher::typenum::consts::U16; +use aes::cipher::BlockCipher; +use aes::cipher::BlockEncrypt; +#[cfg(feature = "alloc")] +use aes_gcm_siv::aead::Aead as _; +use aes_gcm_siv::aead::KeyInit; +use aes_gcm_siv::AeadInPlace as _; -pub struct AesGcmSiv128(Aes128GcmSiv); +// CryptoProvider traits and types +use crypto_provider::aead::{Aead, AeadError, AeadInit}; -impl AesGcmSiv for AesGcmSiv128 {} +pub struct AesGcmSiv<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>( + aes_gcm_siv::AesGcmSiv<A>, +); -impl Aead for AesGcmSiv128 { - const TAG_SIZE: usize = 16; - type Nonce = [u8; 12]; - type Key = Aes128Key; +impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcmSiv + for AesGcmSiv<A> +{ +} - fn new(key: &Self::Key) -> Self { - Self(Aes128GcmSiv::new(key.as_slice().into())) - } - - fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { - self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError) - } - - fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { - self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError) +impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> + AeadInit<K> for AesGcmSiv<A> +{ + fn new(key: &K) -> Self { + Self(aes_gcm_siv::AesGcmSiv::<A>::new(key.as_slice().into())) } } -pub struct AesGcmSiv256(Aes256GcmSiv); - -impl AesGcmSiv for AesGcmSiv256 {} - -impl Aead for AesGcmSiv256 { +impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcmSiv<A> { const TAG_SIZE: usize = 16; type Nonce = [u8; 12]; - type Key = Aes256Key; + type Tag = [u8; 16]; - fn new(key: &Self::Key) -> Self { - Self(Aes256GcmSiv::new(key.as_slice().into())) + #[cfg(feature = "alloc")] + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0 + .encrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad }) + .map_err(|_| AeadError) } - fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { - self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError) + fn encrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { + self.0 + .encrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg) + .map(|arr| arr.into()) + .map_err(|_| AeadError) } - fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { - self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError) + #[cfg(feature = "alloc")] + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> { + self.0 + .decrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad }) + .map_err(|_| AeadError) + } + + fn decrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + tag: &Self::Tag, + ) -> Result<(), AeadError> { + self.0 + .decrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg, tag.into()) + .map_err(|_| AeadError) } } #[cfg(test)] mod tests { use core::marker::PhantomData; - use crypto_provider_test::aead::aes_gcm_siv::*; use crypto_provider_test::aes::*; - - use super::*; + use crypto_provider_test::prelude::apply; #[apply(aes_128_gcm_siv_test_cases)] - fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv128>) { + fn aes_gcm_siv_128_test( + testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>, + ) { + testcase(PhantomData); + } + + #[apply(aes_128_gcm_siv_test_cases_detached)] + fn aes_gcm_siv_128_test_detached( + testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>, + ) { testcase(PhantomData); } #[apply(aes_256_gcm_siv_test_cases)] - fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv256>) { + fn aes_gcm_siv_256_test( + testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>, + ) { + testcase(PhantomData); + } + + #[apply(aes_256_gcm_siv_test_cases_detached)] + fn aes_gcm_siv_256_test_detached( + testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>, + ) { testcase(PhantomData); } }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs index 7fc561b..e4c2c60 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
@@ -13,3 +13,5 @@ // limitations under the License. pub(crate) mod aes_gcm_siv; + +pub(crate) mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs deleted file mode 100644 index 06d7224..0000000 --- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs +++ /dev/null
@@ -1,53 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -extern crate alloc; -use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; -use aes::Aes256; -use alloc::vec::Vec; -use crypto_provider::aes::{ - cbc::{AesCbcIv, DecryptionError}, - Aes256Key, AesKey, -}; - -/// RustCrypto implementation of AES-CBC with PKCS7 padding -pub enum AesCbcPkcs7Padded {} -impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded { - fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> { - let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into()); - encryptor.encrypt_padded_vec_mut::<Pkcs7>(message) - } - - fn decrypt( - key: &Aes256Key, - iv: &AesCbcIv, - ciphertext: &[u8], - ) -> Result<Vec<u8>, DecryptionError> { - cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into()) - .decrypt_padded_vec_mut::<Pkcs7>(ciphertext) - .map_err(|_| DecryptionError::BadPadding) - } -} - -#[cfg(test)] -mod tests { - use super::AesCbcPkcs7Padded; - use core::marker::PhantomData; - use crypto_provider_test::aes::cbc::*; - - #[apply(aes_256_cbc_test_cases)] - fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) { - testcase(PhantomData); - } -}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs new file mode 100644 index 0000000..c6d74f0 --- /dev/null +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs
@@ -0,0 +1,102 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "alloc")] +extern crate alloc; + +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; +use crypto_provider::{ + aes::{ + cbc::{AesCbcIv, DecryptionError, EncryptionError}, + Aes256Key, AesKey, + }, + tinyvec::SliceVec, +}; + +/// RustCrypto implementation of AES-CBC with PKCS7 padding +pub enum AesCbcPkcs7Padded {} +impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded { + #[cfg(feature = "alloc")] + fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> { + let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into()); + encryptor.encrypt_padded_vec_mut::<Pkcs7>(message) + } + + fn encrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError> { + let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into()); + let message_len = message.len(); + // Set the length so encrypt_padded_mut can write using the full capacity + // (Unlike `Vec.set_len`, `SliceVec.set_len` is safe and won't panic if len <= capacity) + message.set_len(message.capacity()); + encryptor + .encrypt_padded_mut::<Pkcs7>(message, message_len) + .map(|result| result.len()) + // `SliceVec.set_len` is safe, and won't panic because `encrypt_padded_mut` never + // returns a slice longer than the given buffer. + .map(|new_len| message.set_len(new_len)) + .map_err(|_| { + message.set_len(message_len); // Set the buffer back to its original length + EncryptionError::PaddingFailed + }) + } + + #[cfg(feature = "alloc")] + fn decrypt( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &[u8], + ) -> Result<Vec<u8>, DecryptionError> { + cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into()) + .decrypt_padded_vec_mut::<Pkcs7>(ciphertext) + .map_err(|_| DecryptionError::BadPadding) + } + + fn decrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError> { + // Decrypted size is always smaller than the input size because of padding, so we don't need + // to set the length to the full capacity. + cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into()) + .decrypt_padded_mut::<Pkcs7>(ciphertext) + .map(|result| result.len()) + // `SliceVec.set_len` is safe, and won't panic because decrypted result length is always + // smaller than the input size. + .map(|new_len| ciphertext.set_len(new_len)) + .map_err(|_| { + ciphertext.as_mut().fill(0); + DecryptionError::BadPadding + }) + } +} + +#[cfg(test)] +mod tests { + use super::AesCbcPkcs7Padded; + use core::marker::PhantomData; + use crypto_provider_test::aes::cbc::*; + + #[apply(aes_256_cbc_test_cases)] + fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) { + testcase(PhantomData); + } +}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs similarity index 88% rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs index 85cdac6..09ae557 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
@@ -33,11 +33,7 @@ } } - fn encrypt(&mut self, data: &mut [u8]) { - self.cipher.apply_keystream(data); - } - - fn decrypt(&mut self, data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { self.cipher.apply_keystream(data); } } @@ -59,11 +55,7 @@ } } - fn encrypt(&mut self, data: &mut [u8]) { - self.cipher.apply_keystream(data); - } - - fn decrypt(&mut self, data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { self.cipher.apply_keystream(data); } }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs similarity index 98% rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs index c71f2ec..4c04bf5 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
@@ -22,7 +22,6 @@ }; /// Module implementing AES-CBC. -#[cfg(feature = "alloc")] pub(crate) mod cbc; pub(crate) mod ctr;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs index d11a5ea..ce8089e 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
@@ -15,8 +15,8 @@ use ed25519_dalek::Signer; use crypto_provider::ed25519::{ - InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError, - PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, + InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _, + SignatureError, PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH, }; pub struct Ed25519; @@ -33,11 +33,11 @@ type PublicKey = PublicKey; type Signature = Signature; - fn private_key(&self) -> [u8; PRIVATE_KEY_LENGTH] { + fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> [u8; PRIVATE_KEY_LENGTH] { self.0.to_bytes() } - fn from_private_key(bytes: &RawPrivateKey) -> Self { + fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self { Self(ed25519_dalek::SigningKey::from_bytes(bytes)) }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs similarity index 86% rename from nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs rename to nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs index aba0d4a..48380fa 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
@@ -22,7 +22,7 @@ use hmac::digest::{HashMarker, OutputSizeUser}; /// RustCrypto based hkdf implementation -pub struct Hkdf<D> +pub struct Hkdf<D>(hkdf::Hkdf<D>) where D: OutputSizeUser, D: CoreProxy, @@ -33,10 +33,7 @@ + Default + Clone, <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>, - Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, -{ - hkdf_impl: hkdf::Hkdf<D>, -} + Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero; impl<D> crypto_provider::hkdf::Hkdf for Hkdf<D> where @@ -52,7 +49,7 @@ Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, { fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self { - Hkdf { hkdf_impl: hkdf::Hkdf::new(salt, ikm) } + Hkdf(hkdf::Hkdf::new(salt, ikm)) } fn expand_multi_info( @@ -60,11 +57,11 @@ info_components: &[&[u8]], okm: &mut [u8], ) -> Result<(), InvalidLength> { - self.hkdf_impl.expand_multi_info(info_components, okm).map_err(|_| InvalidLength) + self.0.expand_multi_info(info_components, okm).map_err(|_| InvalidLength) } fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> { - self.hkdf_impl.expand(info, okm).map_err(|_| InvalidLength) + self.0.expand(info, okm).map_err(|_| InvalidLength) } }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs similarity index 72% rename from nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs rename to nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs index dfda208..d8cef33 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
@@ -23,7 +23,7 @@ use hmac::Mac; /// RustCrypto based hmac implementation -pub struct Hmac<D> +pub struct Hmac<D>(hmac::Hmac<D>) where D: OutputSizeUser, D: CoreProxy, @@ -34,43 +34,38 @@ + Default + Clone, <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>, - Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero, -{ - hmac_impl: hmac::Hmac<D>, -} + Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero; impl crypto_provider::hmac::Hmac<32> for Hmac<sha2::Sha256> { #[allow(clippy::expect_used)] fn new_from_key(key: [u8; 32]) -> Self { hmac::Hmac::new_from_slice(&key) - .map(|hmac| Self { hmac_impl: hmac }) + .map(Self) .expect("length will always be valid because input key is of fixed size") } fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { - hmac::Hmac::new_from_slice(key) - .map(|hmac| Self { hmac_impl: hmac }) - .map_err(|_| InvalidLength) + hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength) } fn update(&mut self, data: &[u8]) { - self.hmac_impl.update(data); + self.0.update(data); } fn finalize(self) -> [u8; 32] { - self.hmac_impl.finalize().into_bytes().into() + self.0.finalize().into_bytes().into() } fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { - self.hmac_impl.verify_slice(tag).map_err(|_| MacError) + self.0.verify_slice(tag).map_err(|_| MacError) } fn verify(self, tag: [u8; 32]) -> Result<(), MacError> { - self.hmac_impl.verify(&tag.into()).map_err(|_| MacError) + self.0.verify(&tag.into()).map_err(|_| MacError) } fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { - self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError) + self.0.verify_truncated_left(tag).map_err(|_| MacError) } } @@ -78,34 +73,32 @@ #[allow(clippy::expect_used)] fn new_from_key(key: [u8; 64]) -> Self { hmac::Hmac::new_from_slice(&key) - .map(|hmac| Self { hmac_impl: hmac }) + .map(Self) .expect("length will always be valid because input key is of fixed size") } fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> { - hmac::Hmac::new_from_slice(key) - .map(|hmac| Self { hmac_impl: hmac }) - .map_err(|_| InvalidLength) + hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength) } fn update(&mut self, data: &[u8]) { - self.hmac_impl.update(data); + self.0.update(data); } fn finalize(self) -> [u8; 64] { - self.hmac_impl.finalize().into_bytes().into() + self.0.finalize().into_bytes().into() } fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> { - self.hmac_impl.verify_slice(tag).map_err(|_| MacError) + self.0.verify_slice(tag).map_err(|_| MacError) } fn verify(self, tag: [u8; 64]) -> Result<(), MacError> { - self.hmac_impl.verify(&tag.into()).map_err(|_| MacError) + self.0.verify(&tag.into()).map_err(|_| MacError) } fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> { - self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError) + self.0.verify_truncated_left(tag).map_err(|_| MacError) } }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs index e95712c..628f679 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -25,6 +25,7 @@ use core::{fmt::Debug, marker::PhantomData}; +pub use aes; use cfg_if::cfg_if; pub use hkdf; pub use hmac; @@ -35,17 +36,17 @@ /// Contains the RustCrypto backed impls for AES-GCM-SIV operations mod aead; /// Contains the RustCrypto backed AES impl for CryptoProvider -pub mod aes; +pub mod aes_cp; /// Contains the RustCrypto backed impl for ed25519 key generation, signing, and verification mod ed25519; /// Contains the RustCrypto backed hkdf impl for CryptoProvider -mod hkdf_rc; +mod hkdf_cp; /// Contains the RustCrypto backed hmac impl for CryptoProvider -mod hmac_rc; +mod hmac_cp; /// Contains the RustCrypto backed P256 impl for CryptoProvider mod p256; /// Contains the RustCrypto backed SHA2 impl for CryptoProvider -mod sha2_rc; +mod sha2_cp; /// Contains the RustCrypto backed X25519 impl for CryptoProvider mod x25519; @@ -54,9 +55,11 @@ /// Providing a type alias for compatibility with existing usage of RustCrypto /// by default we use StdRng for the underlying csprng pub type RustCrypto = RustCryptoImpl<rand::rngs::StdRng>; - } else { + } else if #[cfg(feature = "rand_chacha")] { /// A no_std compatible implementation of CryptoProvider backed by RustCrypto crates pub type RustCrypto = RustCryptoImpl<rand_chacha::ChaCha20Rng>; + } else { + compile_error!("Must specify either --features std or --features rand_chacha"); } } @@ -76,23 +79,24 @@ impl<R: CryptoRng + SeedableRng + RngCore + Eq + PartialEq + Debug + Clone + Send> crypto_provider::CryptoProvider for RustCryptoImpl<R> { - type HkdfSha256 = hkdf_rc::Hkdf<sha2::Sha256>; - type HmacSha256 = hmac_rc::Hmac<sha2::Sha256>; - type HkdfSha512 = hkdf_rc::Hkdf<sha2::Sha512>; - type HmacSha512 = hmac_rc::Hmac<sha2::Sha512>; - #[cfg(feature = "alloc")] - type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded; + type HkdfSha256 = hkdf_cp::Hkdf<sha2::Sha256>; + type HmacSha256 = hmac_cp::Hmac<sha2::Sha256>; + type HkdfSha512 = hkdf_cp::Hkdf<sha2::Sha512>; + type HmacSha512 = hmac_cp::Hmac<sha2::Sha512>; + type AesCbcPkcs7Padded = aes_cp::cbc::AesCbcPkcs7Padded; type X25519 = x25519::X25519Ecdh<R>; type P256 = p256::P256Ecdh<R>; - type Sha256 = sha2_rc::RustCryptoSha256; - type Sha512 = sha2_rc::RustCryptoSha512; - type Aes128 = aes::Aes128; - type Aes256 = aes::Aes256; - type AesCtr128 = aes::ctr::AesCtr128; - type AesCtr256 = aes::ctr::AesCtr256; + type Sha256 = sha2_cp::RustCryptoSha256; + type Sha512 = sha2_cp::RustCryptoSha512; + type Aes128 = aes_cp::Aes128; + type Aes256 = aes_cp::Aes256; + type AesCtr128 = aes_cp::ctr::AesCtr128; + type AesCtr256 = aes_cp::ctr::AesCtr256; type Ed25519 = ed25519::Ed25519; - type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv128; - type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv256; + type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>; + type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>; + type Aes128Gcm = aead::aes_gcm::AesGcm<aes::Aes128>; + type Aes256Gcm = aead::aes_gcm::AesGcm<aes::Aes256>; type CryptoRng = RcRng<R>; fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { @@ -124,6 +128,7 @@ mod tests { use core::marker::PhantomData; + use crypto_provider_test::prelude::*; use crypto_provider_test::sha2::*; use crate::RustCrypto;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs index 539ccc0..98a6cd4 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
@@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate alloc; - use crate::RcRng; -use alloc::vec::Vec; use core::marker::PhantomData; use crypto_provider::{ elliptic_curve::{EcdhProvider, EphemeralSecret}, - p256::P256, + p256::{PointCompression, P256}, + tinyvec::ArrayVec, }; use p256::{ elliptic_curve, @@ -52,8 +50,12 @@ p256::PublicKey::from_sec1_bytes(bytes).map(Self) } - fn to_sec1_bytes(&self) -> Vec<u8> { - self.0.to_encoded_point(true).as_bytes().to_vec() + fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> { + let mut bytes = ArrayVec::<[u8; 65]>::new(); + bytes.extend_from_slice( + self.0.to_encoded_point(point_compression == PointCompression::Compressed).as_bytes(), + ); + bytes } #[allow(clippy::expect_used)] @@ -87,6 +89,7 @@ type Impl = P256Ecdh<R>; type Error = sec1::Error; type Rng = RcRng<R>; + type EncodedPublicKey = ArrayVec<[u8; 65]>; fn generate_random(rng: &mut Self::Rng) -> Self { Self { @@ -95,8 +98,10 @@ } } - fn public_key_bytes(&self) -> Vec<u8> { - self.secret.public_key().to_encoded_point(false).as_bytes().into() + fn public_key_bytes(&self) -> Self::EncodedPublicKey { + let mut bytes = Self::EncodedPublicKey::new(); + bytes.extend_from_slice(self.secret.public_key().to_encoded_point(false).as_bytes()); + bytes } fn diffie_hellman( @@ -136,7 +141,7 @@ use rand::rngs::StdRng; #[apply(p256_test_cases)] - fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>) { + fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>, _name: &str) { testcase(PhantomData::<P256Ecdh<StdRng>>) } }
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs similarity index 100% rename from nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs rename to nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs index 445e858..ad67777 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
@@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate alloc; - use crate::RcRng; -use alloc::vec::Vec; use core::marker::PhantomData; use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}; use crypto_provider::x25519::X25519; @@ -45,6 +42,7 @@ type Impl = X25519Ecdh<R>; type Error = Error; type Rng = RcRng<R>; + type EncodedPublicKey = [u8; 32]; fn generate_random(rng: &mut Self::Rng) -> Self { Self { @@ -53,9 +51,9 @@ } } - fn public_key_bytes(&self) -> Vec<u8> { + fn public_key_bytes(&self) -> Self::EncodedPublicKey { let pubkey: x25519_dalek::PublicKey = (&self.secret).into(); - pubkey.to_bytes().into() + pubkey.to_bytes() } fn diffie_hellman( @@ -90,14 +88,15 @@ impl PublicKey<X25519> for X25519PublicKey { type Error = Error; + type EncodedPublicKey = [u8; 32]; fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> { let byte_sized: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?; Ok(Self(byte_sized.into())) } - fn to_bytes(&self) -> Vec<u8> { - self.0.as_bytes().to_vec() + fn to_bytes(&self) -> Self::EncodedPublicKey { + self.0.to_bytes() } }
diff --git a/nearby/crypto/crypto_provider_stubs/Cargo.toml b/nearby/crypto/crypto_provider_stubs/Cargo.toml index d7c4d45..4e8bdec 100644 --- a/nearby/crypto/crypto_provider_stubs/Cargo.toml +++ b/nearby/crypto/crypto_provider_stubs/Cargo.toml
@@ -5,4 +5,4 @@ publish.workspace = true [dependencies] -crypto_provider = {workspace = true, features = ["std", "alloc"] } \ No newline at end of file +crypto_provider = {workspace = true, features = ["std", "alloc"] }
diff --git a/nearby/crypto/crypto_provider_stubs/src/lib.rs b/nearby/crypto/crypto_provider_stubs/src/lib.rs index 5d54b72..32c9381 100644 --- a/nearby/crypto/crypto_provider_stubs/src/lib.rs +++ b/nearby/crypto/crypto_provider_stubs/src/lib.rs
@@ -20,21 +20,23 @@ use std::fmt::Debug; -use crypto_provider::ed25519::{RawPrivateKey, RawPublicKey, RawSignature}; +use crypto_provider::aead::AeadInit; use crypto_provider::{ - aead::aes_gcm_siv::AesGcmSiv, - aead::{Aead, AeadError}, + aead::{Aead, AeadError, AesGcm, AesGcmSiv}, aes::{ - cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError}, + cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError}, ctr::{AesCtr, NonceAndCounter}, Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, }, - ed25519, - ed25519::{Ed25519Provider, InvalidBytes, KeyPair, Signature, SignatureError}, + ed25519::{ + self, Ed25519Provider, InvalidBytes, KeyPair, RawPrivateKey, RawPrivateKeyPermit, + RawPublicKey, RawSignature, Signature, SignatureError, + }, elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}, hkdf::{Hkdf, InvalidLength}, hmac::{Hmac, MacError}, - p256::{P256PublicKey, P256}, + p256::{P256PublicKey, PointCompression, P256}, + tinyvec::{ArrayVec, SliceVec}, x25519::X25519, }; @@ -58,6 +60,8 @@ type Ed25519 = Ed25519Stubs; type Aes128GcmSiv = Aes128Stubs; type Aes256GcmSiv = Aes256Stubs; + type Aes128Gcm = Aes128Stubs; + type Aes256Gcm = Aes256Stubs; type CryptoRng = (); fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool { @@ -171,6 +175,14 @@ unimplemented!() } + fn encrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + message: &mut SliceVec<u8>, + ) -> Result<(), EncryptionError> { + unimplemented!() + } + fn decrypt( _key: &Aes256Key, _iv: &AesCbcIv, @@ -178,6 +190,14 @@ ) -> Result<Vec<u8>, DecryptionError> { unimplemented!() } + + fn decrypt_in_place( + key: &Aes256Key, + iv: &AesCbcIv, + ciphertext: &mut SliceVec<u8>, + ) -> Result<(), DecryptionError> { + unimplemented!() + } } pub struct X25519Stubs; @@ -194,12 +214,13 @@ type Impl = X25519Stubs; type Error = (); type Rng = (); + type EncodedPublicKey = [u8; 32]; fn generate_random(_rng: &mut Self::Rng) -> Self { unimplemented!() } - fn public_key_bytes(&self) -> Vec<u8> { + fn public_key_bytes(&self) -> Self::EncodedPublicKey { unimplemented!() } @@ -215,12 +236,13 @@ type Impl = P256Stubs; type Error = (); type Rng = (); + type EncodedPublicKey = ArrayVec<[u8; 65]>; fn generate_random(_rng: &mut Self::Rng) -> Self { unimplemented!() } - fn public_key_bytes(&self) -> Vec<u8> { + fn public_key_bytes(&self) -> Self::EncodedPublicKey { unimplemented!() } @@ -237,12 +259,13 @@ impl PublicKey<X25519> for EcdhPubKey { type Error = (); + type EncodedPublicKey = [u8; 32]; fn from_bytes(_bytes: &[u8]) -> Result<Self, Self::Error> { unimplemented!() } - fn to_bytes(&self) -> Vec<u8> { + fn to_bytes(&self) -> Self::EncodedPublicKey { unimplemented!() } } @@ -257,7 +280,7 @@ unimplemented!() } - fn to_sec1_bytes(&self) -> Vec<u8> { + fn to_sec1_bytes(&self, _point_compression: PointCompression) -> ArrayVec<[u8; 65]> { unimplemented!() } @@ -294,6 +317,12 @@ pub struct Aes128Stubs; +impl AeadInit<Aes128Key> for Aes128Stubs { + fn new(key: &Aes128Key) -> Self { + unimplemented!() + } +} + impl AesCipher for Aes128Stubs { type Key = Aes128Key; @@ -321,11 +350,7 @@ unimplemented!() } - fn encrypt(&mut self, _data: &mut [u8]) { - unimplemented!() - } - - fn decrypt(&mut self, _data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { unimplemented!() } } @@ -333,25 +358,48 @@ impl Aead for Aes128Stubs { const TAG_SIZE: usize = 16; type Nonce = [u8; 12]; - type Key = Aes128Key; + type Tag = [u8; 16]; - fn new(key: &Self::Key) -> Self { + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> { unimplemented!() } - fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { + fn encrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { unimplemented!() } - fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> { + unimplemented!() + } + + fn decrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + tag: &Self::Tag, + ) -> Result<(), AeadError> { unimplemented!() } } impl AesGcmSiv for Aes128Stubs {} +impl AesGcm for Aes128Stubs {} + pub struct Aes256Stubs; +impl AeadInit<Aes256Key> for Aes256Stubs { + fn new(key: &Aes256Key) -> Self { + unimplemented!() + } +} + impl AesCipher for Aes256Stubs { type Key = Aes256Key; @@ -379,11 +427,7 @@ unimplemented!() } - fn encrypt(&mut self, _data: &mut [u8]) { - unimplemented!() - } - - fn decrypt(&mut self, _data: &mut [u8]) { + fn apply_keystream(&mut self, data: &mut [u8]) { unimplemented!() } } @@ -391,23 +435,40 @@ impl Aead for Aes256Stubs { const TAG_SIZE: usize = 16; type Nonce = [u8; 12]; - type Key = Aes256Key; + type Tag = [u8; 16]; - fn new(key: &Self::Key) -> Self { + fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> { unimplemented!() } - fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { + fn encrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + ) -> Result<Self::Tag, AeadError> { unimplemented!() } - fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> { + fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> { + unimplemented!() + } + + fn decrypt_detached( + &self, + msg: &mut [u8], + aad: &[u8], + nonce: &Self::Nonce, + tag: &Self::Tag, + ) -> Result<(), AeadError> { unimplemented!() } } impl AesGcmSiv for Aes256Stubs {} +impl AesGcm for Aes256Stubs {} + pub struct Ed25519Stubs; impl Ed25519Provider for Ed25519Stubs { @@ -457,11 +518,11 @@ type PublicKey = PublicKeyStubs; type Signature = SignatureStubs; - fn private_key(&self) -> RawPrivateKey { + fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey { unimplemented!() } - fn from_private_key(_bytes: &RawPrivateKey) -> Self + fn from_raw_private_key(_bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self where Self: Sized, {
diff --git a/nearby/crypto/crypto_provider_test/Cargo.toml b/nearby/crypto/crypto_provider_test/Cargo.toml index a4d92ec..4064330 100644 --- a/nearby/crypto/crypto_provider_test/Cargo.toml +++ b/nearby/crypto/crypto_provider_test/Cargo.toml
@@ -5,7 +5,7 @@ publish.workspace = true [dependencies] -crypto_provider = { workspace = true, features = ["test_vectors"] } +crypto_provider = { workspace = true, features = ["raw_private_key_permit", "test_vectors", "alloc"] } rand_ext.workspace = true test_helper.workspace = true @@ -14,4 +14,4 @@ rstest.workspace = true rstest_reuse.workspace = true wycheproof.workspace = true -hex.workspace = true \ No newline at end of file +hex.workspace = true
diff --git a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock index 9e1d140..bd8f6f9 100644 --- a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock +++ b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -165,6 +179,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_default" @@ -193,6 +210,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -239,9 +257,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -307,9 +325,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -390,6 +408,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -732,6 +760,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -773,9 +807,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs new file mode 100644 index 0000000..88eaf32 --- /dev/null +++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs
@@ -0,0 +1,316 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use alloc::vec::Vec; +use core::marker; + +use hex_literal::hex; +use rstest_reuse::template; + +pub use crate::prelude; +use crypto_provider::aead::{AeadInit, AesGcm}; + +/// Test AES-GCM-128 encryption +pub fn aes_128_gcm_test_encrypt<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC4 + let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d"); + let nonce = hex!("438a547a94ea88dce46c6c85"); + let aes = A::new(&test_key.into()); + let msg = hex!(""); + let tag = hex!("960247ba5cde02e41a313c4c0136edc3"); + let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed"); + assert_eq!(&result[..], &tag); +} + +pub fn aes_128_gcm_test_encrypt_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC4 + let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d"); + let nonce = hex!("438a547a94ea88dce46c6c85"); + let aes = A::new(&test_key.into()); + let msg = hex!(""); + let tag = hex!("960247ba5cde02e41a313c4c0136edc3"); + let mut buf = Vec::from(msg.as_slice()); + let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &[0_u8; 0]); + assert_eq!(&actual_tag, &tag); +} + +/// Test AES-GCM-128 decryption +pub fn aes_128_gcm_test_decrypt<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC2 + let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9"); + let nonce = hex!("921d2507fa8007b7bd067d34"); + let aes = A::new(&test_key.into()); + let msg = hex!("001d0c231287c1182784554ca3a21908"); + let ct = hex!("49d8b9783e911913d87094d1f63cc765"); + let ad = hex!("00112233445566778899aabbccddeeff"); + let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92"); + let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed"); + assert_eq!(&result[..16], &ct); + assert_eq!(&result[16..], &tag); + assert_eq!(A::TAG_SIZE, result[16..].len()); + let result = aes.decrypt(&result[..], &ad, &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-GCM-128 decryption +pub fn aes_128_gcm_test_decrypt_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC2 + let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9"); + let nonce = hex!("921d2507fa8007b7bd067d34"); + let aes = A::new(&test_key.into()); + let msg = hex!("001d0c231287c1182784554ca3a21908"); + let ct = hex!("49d8b9783e911913d87094d1f63cc765"); + let ad = hex!("00112233445566778899aabbccddeeff"); + let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92"); + let mut buf = Vec::from(msg.as_slice()); + let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(actual_tag, tag); + assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &tag).is_ok()); + assert_eq!(&buf[..], &msg); +} + +/// Test AES-GCM-128 decryption +pub fn aes_128_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC23 + let test_key = hex!("000102030405060708090a0b0c0d0e0f"); + let nonce = hex!("505152535455565758595a5b"); + let aes = A::new(&test_key.into()); + let ct = hex!("eb156d081ed6b6b55f4612f021d87b39"); + let mut buf = Vec::from(ct.as_slice()); + let bad_tag = hex!("d9847dbc326a06e988c77ad3863e6083"); + aes.decrypt_detached(&mut buf, b"", &nonce, &bad_tag) + .expect_err("decryption tag verification should fail"); + // assert that the buffer does not change if tag verification fails + assert_eq!(buf, ct); +} + +/// Test AES-256-GCM encryption/decryption +pub fn aes_256_gcm_test_tc74<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC74 + let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123"); + let nonce = hex!("00112233445566778899aabb"); + let aes = A::new(&test_key.into()); + let msg = hex!(""); + let ad = hex!("aabbccddeeff"); + let tag = hex!("2a7d77fa526b8250cb296078926b5020"); + let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed"); + assert_eq!(&result[..], &tag); + assert_eq!(A::TAG_SIZE, result.len()); + let result = aes.decrypt(&result, &ad, &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-256-GCM encryption/decryption +pub fn aes_256_gcm_test_tc74_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC74 + let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123"); + let nonce = hex!("00112233445566778899aabb"); + let aes = A::new(&test_key.into()); + let msg = hex!(""); + let ad = hex!("aabbccddeeff"); + let ct = hex!(""); + let tag = hex!("2a7d77fa526b8250cb296078926b5020"); + let mut buf = Vec::new(); + buf.extend_from_slice(&msg); + let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(&actual_tag, &tag); + assert_eq!(A::TAG_SIZE, tag.len()); + assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &actual_tag).is_ok()); + assert_eq!(&buf, &msg); +} + +/// Test AES-256-GCM encryption/decryption +pub fn aes_256_gcm_test_tc79<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC79 + let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a"); + let nonce = hex!("2fcb1b38a99e71b84740ad9b"); + let aes = A::new(&test_key.into()); + let msg = hex!("549b365af913f3b081131ccb6b825588"); + let ct = hex!("f58c16690122d75356907fd96b570fca"); + let tag = hex!("28752c20153092818faba2a334640d6e"); + let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed"); + assert_eq!(&result[..16], &ct); + assert_eq!(&result[16..], &tag); + let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-256-GCM encryption/decryption +pub fn aes_256_gcm_test_tc79_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC79 + let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a"); + let nonce = hex!("2fcb1b38a99e71b84740ad9b"); + let aes = A::new(&test_key.into()); + let msg = hex!("549b365af913f3b081131ccb6b825588"); + let ct = hex!("f58c16690122d75356907fd96b570fca"); + let tag = hex!("28752c20153092818faba2a334640d6e"); + let mut buf = Vec::from(msg.as_slice()); + let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(&actual_tag, &tag); + assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok()); + assert_eq!(&buf, &msg); +} + +/// Test AES-256-GCM encryption/decryption where the tag given to decryption doesn't match. +pub fn aes_256_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>) +where + A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json + // TC94 + let test_key = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + let nonce = hex!("505152535455565758595a5b"); + let aes = A::new(&test_key.into()); + let aad = hex!(""); + let ct = hex!("b2061457c0759fc1749f174ee1ccadfa"); + let bad_tag = hex!("9de8fef6d8ab1bf1bf887232eab590dd"); + let mut buf = Vec::from(ct.as_slice()); + aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag) + .expect_err("Decrypting with bad tag should fail"); + // assert that the buffer does not change if tag verification fails + assert_eq!(buf, ct); +} + +/// Generates the test cases to validate the AES-128-GCM implementation. +/// For example, to test `MyAesGcm128Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm::testing::*; +/// +/// mod tests { +/// #[apply(aes_128_gcm_test_cases)] +/// fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) { +/// testcase(MyAesGcm128Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::encrypt(aes_128_gcm_test_encrypt)] +#[case::decrypt(aes_128_gcm_test_decrypt)] +fn aes_128_gcm_test_cases<F: AesGcmFactory<Key = Aes128Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +} + +/// Generates the test cases to validate the AES-128-GCM implementation. +/// For example, to test `MyAesGcm128Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm::testing::*; +/// +/// mod tests { +/// #[apply(aes_128_gcm_test_cases_detached)] +/// fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) { +/// testcase(MyAesGcm128Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::encrypt_detached(aes_128_gcm_test_encrypt_detached)] +#[case::decrypt_detached(aes_128_gcm_test_decrypt_detached)] +#[case::decrypt_detached_bad_tag(aes_128_gcm_test_decrypt_detached_bad_tag)] +fn aes_128_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes128Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +} + +/// Generates the test cases to validate the AES-256-GCM implementation. +/// For example, to test `MyAesGcm256Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm::testing::*; +/// +/// mod tests { +/// #[apply(aes_256_gcm_test_cases)] +/// fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) { +/// testcase(MyAesGcm256Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::tc74(aes_256_gcm_test_tc74)] +#[case::tc79(aes_256_gcm_test_tc79)] +fn aes_256_gcm_test_cases<F: AesGcmFactory<Key = Aes256Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +} + +/// Generates the test cases to validate the AES-256-GCM implementation. +/// For example, to test `MyAesGcm256Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm::testing::*; +/// +/// mod tests { +/// #[apply(aes_256_gcm_test_cases_detached)] +/// fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) { +/// testcase(MyAesGcm256Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::tc74_detached(aes_256_gcm_test_tc74_detached)] +#[case::tc79_detached(aes_256_gcm_test_tc79_detached)] +#[case::decrypt_detached_bad_tag(aes_256_gcm_test_decrypt_detached_bad_tag)] +fn aes_256_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes256Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs index 893fba2..56d2215 100644 --- a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs +++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
@@ -18,12 +18,28 @@ use rstest_reuse::template; pub use crate::prelude; -use crypto_provider::aes::{Aes128Key, Aes256Key}; +use crypto_provider::aead::{AeadInit, AesGcmSiv}; -use crypto_provider::aead::aes_gcm_siv::AesGcmSiv; +/// Test AES-GCM-SIV-128 encryption +pub fn aes_128_gcm_siv_test_encrypt<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC1 + let test_key = hex!("01000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); + let msg = hex!(""); + let tag = hex!("dc20e2d83f25705bb49e439eca56de25"); + let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed"); + assert_eq!(&result[..], &tag); +} -/// Test AES-GCM-SIV-128 encryption/decryption -pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::PhantomData<A>) { +pub fn aes_128_gcm_siv_test_encrypt_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json // TC1 let test_key = hex!("01000000000000000000000000000000"); @@ -32,23 +48,102 @@ let msg = hex!(""); let mut buf = Vec::from(msg.as_slice()); let tag = hex!("dc20e2d83f25705bb49e439eca56de25"); - assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..], &tag); + let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &[0_u8; 0]); + assert_eq!(&actual_tag, &tag); +} + +/// Test AES-GCM-SIV-128 decryption +pub fn aes_128_gcm_siv_test_decrypt<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json // TC2 + let test_key = hex!("01000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); + let msg = hex!("0100000000000000"); + let ct = hex!("b5d839330ac7b786"); + let tag = hex!("578782fff6013b815b287c22493a364c"); + let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed"); + assert_eq!(&result[..8], &ct); + assert_eq!(&result[8..], &tag); + assert_eq!(A::TAG_SIZE, result[8..].len()); + let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-GCM-SIV-128 decryption +pub fn aes_128_gcm_siv_test_decrypt_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC2 + let test_key = hex!("01000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); let msg = hex!("0100000000000000"); let ct = hex!("b5d839330ac7b786"); let tag = hex!("578782fff6013b815b287c22493a364c"); let mut buf = Vec::from(msg.as_slice()); - assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..8], &ct); - assert_eq!(&buf[8..], &tag); - assert_eq!(A::TAG_SIZE, buf[8..].len()); - assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok()); + let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(actual_tag, tag); + assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok()); assert_eq!(&buf[..], &msg); } +/// Test AES-GCM-SIV-128 decryption where the tag given to decryption doesn't match +pub fn aes_128_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC45 + let test_key = hex!("00112233445566778899aabbccddeeff"); + let nonce = hex!("000000000000000000000000"); + let aad = hex!("9ea3371e258288d5a01b15384e2c99ee"); + let aes = A::new(&test_key.into()); + // Use a longer ciphertext as the test case to make sure it's not unchanged only for the most + // recent block. + let ct = hex!( + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" + "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" + ); + let bad_tag = hex!("13a1883272188b4c8d2727178198fe95"); + let mut buf = Vec::from(ct.as_slice()); + aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag).expect_err("Decryption should fail"); + assert_eq!(&buf, &ct); // Buffer should be unchanged if decryption failed +} + /// Test AES-256-GCM-SIV encryption/decryption -pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) { +pub fn aes_256_gcm_siv_test_tc77<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC77 + let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); + let msg = hex!("0100000000000000"); + let ct = hex!("c2ef328e5c71c83b"); + let tag = hex!("843122130f7364b761e0b97427e3df28"); + let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed"); + assert_eq!(&result[..8], &ct); + assert_eq!(&result[8..], &tag); + assert_eq!(A::TAG_SIZE, result[8..].len()); + let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-256-GCM-SIV encryption/decryption +pub fn aes_256_gcm_siv_test_tc77_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json // TC77 let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000"); @@ -59,22 +154,72 @@ buf.extend_from_slice(&msg); let ct = hex!("c2ef328e5c71c83b"); let tag = hex!("843122130f7364b761e0b97427e3df28"); - assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..8], &ct); - assert_eq!(&buf[8..], &tag); - assert_eq!(A::TAG_SIZE, buf[8..].len()); - assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..], &msg); + let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(&actual_tag, &tag); + assert_eq!(A::TAG_SIZE, tag.len()); + assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &actual_tag).is_ok()); + assert_eq!(&buf, &msg); +} + +/// Test AES-256-GCM-SIV encryption/decryption +pub fn aes_256_gcm_siv_test_tc78<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json // TC78 + let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); + let msg = hex!("010000000000000000000000"); + let ct = hex!("9aab2aeb3faa0a34aea8e2b1"); + let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e"); + let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed"); + assert_eq!(&result[..12], &ct); + assert_eq!(&result[12..], &tag); + let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed"); + assert_eq!(&result[..], &msg); +} + +/// Test AES-256-GCM-SIV encryption/decryption +pub fn aes_256_gcm_siv_test_tc78_detached<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC78 + let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000"); + let nonce = hex!("030000000000000000000000"); + let aes = A::new(&test_key.into()); let msg = hex!("010000000000000000000000"); let ct = hex!("9aab2aeb3faa0a34aea8e2b1"); let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e"); let mut buf = Vec::from(msg.as_slice()); - assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..12], &ct); - assert_eq!(&buf[12..], &tag); - assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok()); - assert_eq!(&buf[..], &msg); + let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap(); + assert_eq!(&buf, &ct); + assert_eq!(&actual_tag, &tag); + assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok()); + assert_eq!(&buf, &msg); +} + +/// Test AES-256-GCM-SIV encryption/decryption where the tag given to decryption doesn't match. +pub fn aes_256_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>) +where + A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>, +{ + // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json + // TC122 + let test_key = hex!("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"); + let nonce = hex!("000000000000000000000000"); + let aes = A::new(&test_key.into()); + let aad = hex!("0289eaa93eb084107d2088435ef2a0cd"); + let ct = hex!("ffffffffffffffff"); + let bad_tag = hex!("ffffffffffffffffffffffffffffffff"); + let mut buf = Vec::from(ct.as_slice()); + aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag) + .expect_err("Decrypting with bad tag should fail"); + assert_eq!(&buf, &ct); // The buffer should be unchanged if the decryption failed } /// Generates the test cases to validate the AES-128-GCM-SIV implementation. @@ -93,13 +238,37 @@ #[template] #[export] #[rstest] -#[case::encrypt(aes_128_gcm_siv_test)] -#[case::decrypt(aes_128_gcm_siv_test)] +#[case::encrypt(aes_128_gcm_siv_test_encrypt)] +#[case::decrypt(aes_128_gcm_siv_test_decrypt)] fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>( #[case] testcase: CryptoProviderTestCase<F>, ) { } +/// Generates the test cases to validate the AES-128-GCM-SIV implementation. +/// For example, to test `MyAesGcmSiv128Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm_siv::testing::*; +/// +/// mod tests { +/// #[apply(aes_128_gcm_siv_test_cases_detached)] +/// fn aes_128_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSivImpl>) { +/// testcase(MyAesGcmSiv128Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::encrypt_detached(aes_128_gcm_siv_test_encrypt_detached)] +#[case::decrypt_detached(aes_128_gcm_siv_test_decrypt_detached)] +#[case::decrypt_detached_bad_tag(aes_128_gcm_siv_test_decrypt_detached_bad_tag)] +fn aes_128_gcm_siv_test_cases_detached<F: AesGcmSivFactory<Key = Aes128Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +} + /// Generates the test cases to validate the AES-256-GCM-SIV implementation. /// For example, to test `MyAesGcmSiv256Impl`: /// @@ -116,9 +285,33 @@ #[template] #[export] #[rstest] -#[case::encrypt(aes_256_gcm_siv_test)] -#[case::decrypt(aes_256_gcm_siv_test)] +#[case::tc77(aes_256_gcm_siv_test_tc77)] +#[case::tc78(aes_256_gcm_siv_test_tc78)] fn aes_256_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes256Key>>( #[case] testcase: CryptoProviderTestCase<F>, ) { } + +/// Generates the test cases to validate the AES-256-GCM-SIV implementation. +/// For example, to test `MyAesGcmSiv256Impl`: +/// +/// ``` +/// use crypto_provider::aes::aes_gcm_siv::testing::*; +/// +/// mod tests { +/// #[apply(aes_256_gcm_siv_test_cases_detached)] +/// fn aes_256_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSiv256Impl>) { +/// testcase(MyAesGcmSiv256Impl); +/// } +/// } +/// ``` +#[template] +#[export] +#[rstest] +#[case::tc77_detached(aes_256_gcm_siv_test_tc77_detached)] +#[case::tc78_detached(aes_256_gcm_siv_test_tc78_detached)] +#[case::decrypt_detached_bad_tag(aes_256_gcm_siv_test_decrypt_detached_bad_tag)] +fn aes_256_gcm_siv_test_cases_detached<F: AesGcmSivFactory<Key = Aes256Key>>( + #[case] testcase: CryptoProviderTestCase<F>, +) { +}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/mod.rs b/nearby/crypto/crypto_provider_test/src/aead/mod.rs index 962aa49..3fe857c 100644 --- a/nearby/crypto/crypto_provider_test/src/aead/mod.rs +++ b/nearby/crypto/crypto_provider_test/src/aead/mod.rs
@@ -14,3 +14,6 @@ /// Contains test cases for aes_gcm_siv implementations. pub mod aes_gcm_siv; + +/// Contains test cases for aes_gcm implementations. +pub mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs index b22c828..46cbebe 100644 --- a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs +++ b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
@@ -15,13 +15,17 @@ use crate::aes::Aes256Key; pub use crate::prelude::*; use core::marker::PhantomData; -use crypto_provider::aes::cbc::{AesCbcIv, AesCbcPkcs7Padded}; +use crypto_provider::{ + aes::cbc::{AesCbcIv, AesCbcPkcs7Padded}, + tinyvec::SliceVec, +}; use hex_literal::hex; use rstest_reuse::template; /// Tests for AES-256-CBC encryption pub fn aes_256_cbc_test_encrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { - // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632 + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492 + // tcId: 132 let key: Aes256Key = hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into(); let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb"); @@ -30,9 +34,47 @@ assert_eq!(A::encrypt(&key, &iv, &msg), expected_ciphertext); } +/// Tests for AES-256-CBC in-place encryption +pub fn aes_256_cbc_test_encrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492 + // tcId: 132 + let key: Aes256Key = + hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into(); + let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb"); + let msg = hex!("3f56935def3f"); + let expected_ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48"); + let mut msg_buffer_backing = [0_u8; 16]; + let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0); + msg_buffer.extend_from_slice(&msg); + A::encrypt_in_place(&key, &iv, &mut msg_buffer).unwrap(); + assert_eq!(msg_buffer.as_slice(), &expected_ciphertext); +} + +/// Tests for AES-256-CBC encryption, where the given buffer `SliceVec` is too short to contain the +/// output. +pub fn aes_256_cbc_test_encrypt_in_place_too_short<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1612 + // tcId: 144 + let key: Aes256Key = + hex!("4f097858a1aec62cf18f0966b2b120783aa4ae9149d3213109740506ae47adfe").into(); + let iv: AesCbcIv = hex!("400aab92803bcbb44a96ef789655b34e"); + let msg = hex!("ee53d8e5039e82d9fcca114e375a014febfea117a7e709d9008d43858e3660"); + let mut msg_buffer_backing = [0_u8; 31]; + let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0); + msg_buffer.extend_from_slice(&msg); + A::encrypt_in_place(&key, &iv, &mut msg_buffer) + .expect_err("Encrypting AES with 15-byte buffer should fail"); + // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data + assert!( + msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == msg, + "Unrecognized content in buffer after decryption failure" + ) +} + /// Tests for AES-256-CBC decryption pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { - // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632 + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492 + // tcId: 132 let key: Aes256Key = hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into(); let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb"); @@ -41,6 +83,52 @@ assert_eq!(A::decrypt(&key, &iv, &ciphertext).unwrap(), expected_msg); } +/// Tests for AES-256-CBC decryption with bad padding +pub fn aes_256_cbc_test_decrypt_bad_padding<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690 + // tcId: 151 + let key: Aes256Key = + hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into(); + let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70"); + let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf"); + A::decrypt(&key, &iv, &ciphertext).expect_err("Decryption with bad padding should fail"); +} + +/// Tests for AES-256-CBC in-place decryption +pub fn aes_256_cbc_test_decrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492 + // tcId: 132 + let key: Aes256Key = + hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into(); + let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb"); + let mut ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48"); + let expected_msg = hex!("3f56935def3f"); + let mut msg_buffer = SliceVec::from(&mut ciphertext); + A::decrypt_in_place(&key, &iv, &mut msg_buffer).unwrap(); + assert_eq!(msg_buffer.as_slice(), expected_msg); +} + +/// Tests for AES-256-CBC in-place decryption with bad padding +pub fn aes_256_cbc_test_decrypt_in_place_bad_padding<A: AesCbcPkcs7Padded>( + _marker: PhantomData<A>, +) { + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690 + // tcId: 151 + let key: Aes256Key = + hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into(); + let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70"); + let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf"); + let mut msg_buffer_backing = ciphertext; + let mut msg_buffer = SliceVec::from(&mut msg_buffer_backing); + A::decrypt_in_place(&key, &iv, &mut msg_buffer) + .expect_err("Decryption with bad padding should fail"); + // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data + assert!( + msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == ciphertext, + "Unrecognized content in buffer after decryption failure" + ) +} + /// Generates the test cases to validate the AES-256-CBC implementation. /// For example, to test `MyAesCbc256Impl`: /// @@ -59,5 +147,9 @@ #[export] #[rstest] #[case::encrypt(aes_256_cbc_test_encrypt)] +#[case::encrypt_in_place(aes_256_cbc_test_encrypt_in_place)] +#[case::encrypt_in_place_too_short(aes_256_cbc_test_encrypt_in_place_too_short)] #[case::decrypt(aes_256_cbc_test_decrypt)] +#[case::decrypt_bad_padding(aes_256_cbc_test_decrypt_bad_padding)] +#[case::decrypt_in_place_bad_padding(aes_256_cbc_test_decrypt_in_place_bad_padding)] fn aes_256_cbc_test_cases<A: AesCbcPkcs7Padded>(#[case] testcase: CryptoProviderTestCases<F>) {}
diff --git a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs index b1d6b8b..de71740 100644 --- a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs +++ b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
@@ -27,22 +27,22 @@ let mut cipher = A::new(&key, NonceAndCounter::from_block(iv)); block = hex!("6bc1bee22e409f96e93d7e117393172a"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_1 = hex!("874d6191b620e3261bef6864990db6ce"); assert_eq!(expected_ciphertext_1, block); block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_2 = hex!("9806f66b7970fdff8617187bb9fffdff"); assert_eq!(expected_ciphertext_2, block); block = hex!("30c81c46a35ce411e5fbc1191a0a52ef"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_3 = hex!("5ae4df3edbd5d35e5b4f09020db03eab"); assert_eq!(expected_ciphertext_3, block); block = hex!("f69f2445df4f9b17ad2b417be66c3710"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_3 = hex!("1e031dda2fbe03d1792170a0f3009cee"); assert_eq!(expected_ciphertext_3, block); } @@ -56,22 +56,22 @@ let mut cipher = A::new(&key, NonceAndCounter::from_block(iv)); block = hex!("874d6191b620e3261bef6864990db6ce"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a"); assert_eq!(expected_plaintext_1, block); block = hex!("9806f66b7970fdff8617187bb9fffdff"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51"); assert_eq!(expected_plaintext_2, block); block = hex!("5ae4df3edbd5d35e5b4f09020db03eab"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef"); assert_eq!(expected_plaintext_3, block); block = hex!("1e031dda2fbe03d1792170a0f3009cee"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710"); assert_eq!(expected_plaintext_3, block); } @@ -86,22 +86,22 @@ let mut cipher = A::new(&key, NonceAndCounter::from_block(iv)); block = hex!("6bc1bee22e409f96e93d7e117393172a"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_1 = hex!("601ec313775789a5b7a7f504bbf3d228"); assert_eq!(expected_ciphertext_1, block); block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_2 = hex!("f443e3ca4d62b59aca84e990cacaf5c5"); assert_eq!(expected_ciphertext_2, block); block = hex!("30c81c46a35ce411e5fbc1191a0a52ef"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_3 = hex!("2b0930daa23de94ce87017ba2d84988d"); assert_eq!(expected_ciphertext_3, block); block = hex!("f69f2445df4f9b17ad2b417be66c3710"); - cipher.encrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_ciphertext_3 = hex!("dfc9c58db67aada613c2dd08457941a6"); assert_eq!(expected_ciphertext_3, block); } @@ -116,22 +116,22 @@ let mut cipher = A::new(&key, NonceAndCounter::from_block(iv)); block = hex!("601ec313775789a5b7a7f504bbf3d228"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a"); assert_eq!(expected_plaintext_1, block); block = hex!("f443e3ca4d62b59aca84e990cacaf5c5"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51"); assert_eq!(expected_plaintext_2, block); block = hex!("2b0930daa23de94ce87017ba2d84988d"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef"); assert_eq!(expected_plaintext_3, block); block = hex!("dfc9c58db67aada613c2dd08457941a6"); - cipher.decrypt(&mut block); + cipher.apply_keystream(&mut block); let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710"); assert_eq!(expected_plaintext_3, block); }
diff --git a/nearby/crypto/crypto_provider_test/src/ed25519.rs b/nearby/crypto/crypto_provider_test/src/ed25519.rs index d99605c..0fdb484 100644 --- a/nearby/crypto/crypto_provider_test/src/ed25519.rs +++ b/nearby/crypto/crypto_provider_test/src/ed25519.rs
@@ -18,7 +18,9 @@ use alloc::borrow::ToOwned; use alloc::string::String; use alloc::vec::Vec; -use crypto_provider::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature}; +use crypto_provider::ed25519::{ + Ed25519Provider, KeyPair, PublicKey, RawPrivateKeyPermit, RawSignature, Signature, +}; use wycheproof::TestResult; // These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced @@ -27,7 +29,7 @@ // also used for test cases in the above RFC: // https://dev.gnupg.org/source/libgcrypt/browse/master/tests/t-ed25519.inp const PATH_TO_RFC_VECTORS_FILE: &str = - "crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt"; + "crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt"; /// Runs set of Ed25519 wycheproof test vectors against a provided ed25519 implementation /// Tests vectors from Project Wycheproof: <https://github.com/google/wycheproof> @@ -39,10 +41,7 @@ .expect("should be able to load test set"); for test_group in test_set.test_groups { - let key_pair = test_group.key; - let public_key = key_pair.pk; - let secret_key = key_pair.sk; - + let public_key = test_group.key.pk.to_vec(); for test in test_group.tests { let tc_id = test.tc_id; let comment = test.comment; @@ -53,8 +52,7 @@ TestResult::Invalid => false, TestResult::Valid | TestResult::Acceptable => true, }; - let result = - run_test::<E>(public_key.clone(), secret_key.clone(), sig.clone(), msg.clone()); + let result = run_wycheproof_test::<E>(public_key.to_vec(), sig.to_vec(), msg.to_vec()); if valid { if let Err(desc) = result { panic!( @@ -73,6 +71,20 @@ } } +fn run_wycheproof_test<E>(pub_key: Vec<u8>, sig: Vec<u8>, msg: Vec<u8>) -> Result<(), &'static str> +where + E: Ed25519Provider, +{ + let pub_key = E::PublicKey::from_bytes(pub_key.as_slice().try_into().unwrap()) + .map_err(|_| "Invalid public key bytes")?; + + let raw_sig: RawSignature = + sig.as_slice().try_into().map_err(|_| "Invalid length signature")?; + let signature = E::Signature::from_bytes(&raw_sig); + + pub_key.verify_strict(msg.as_slice(), &signature).map_err(|_| "Signature verification failed") +} + /// Runs the RFC specified test vectors against an Ed25519 implementation pub fn run_rfc_test_vectors<E>() where @@ -126,7 +138,10 @@ { let private_key_bytes: [u8; 32] = private_key.as_slice().try_into().expect("Secret key is the wrong length"); - let kp = E::KeyPair::from_private_key(&private_key_bytes); + + // Permits: Test-only code, not a production leak of the private key + let permit = RawPrivateKeyPermit::default(); + let kp = E::KeyPair::from_raw_private_key(&private_key_bytes, &permit); let sig_result = kp.sign(msg.as_slice()); (sig.as_slice() == sig_result.to_bytes()).then_some(()).ok_or("sig not matching expected")?;
diff --git a/nearby/crypto/crypto_provider_test/src/lib.rs b/nearby/crypto/crypto_provider_test/src/lib.rs index 8e63f44..2209a3e 100644 --- a/nearby/crypto/crypto_provider_test/src/lib.rs +++ b/nearby/crypto/crypto_provider_test/src/lib.rs
@@ -36,9 +36,11 @@ /// Common items that needs to be imported to use these test cases pub mod prelude { pub use super::CryptoProviderTestCase; + pub use ::rstest; pub use rstest::rstest; pub use rstest_reuse; pub use rstest_reuse::apply; + pub extern crate std; } /// A test case for Crypto Provider. A test case is a function that panics if the test fails.
diff --git a/nearby/crypto/crypto_provider_test/src/p256.rs b/nearby/crypto/crypto_provider_test/src/p256.rs index 9869c9e..5ebbd50 100644 --- a/nearby/crypto/crypto_provider_test/src/p256.rs +++ b/nearby/crypto/crypto_provider_test/src/p256.rs
@@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate std; use crate::elliptic_curve::EphemeralSecretForTesting; pub use crate::prelude::*; use crate::TestError; use core::marker::PhantomData; -use crypto_provider::p256::{P256PublicKey, P256}; +use core::ops::Deref; +use crypto_provider::p256::{P256PublicKey, PointCompression, P256}; use crypto_provider::{ elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}, CryptoRng, @@ -62,9 +62,35 @@ 8bbe76c6dc1643088107636deff8aa79e8002a157b92" ); let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap(); + // Not part of the API contract, but `to_bytes()` should prefer to use uncompressed + // representation since support for compressed point is optional. + let key_bytes = key.to_bytes(); + assert_eq!(sec1_bytes.to_vec(), key_bytes.deref()); +} + +/// Test for P256PublicKey::to_sec1_bytes(Compressed). Support for compressed representation is +/// optional. +pub fn to_bytes_compressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { + let sec1_bytes = hex!( + "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022 + 8bbe76c6dc1643088107636deff8aa79e8002a157b92" + ); + let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap(); + let key_bytes = key.to_sec1_bytes(PointCompression::Compressed); let sec1_bytes_compressed = hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09"); - assert_eq!(sec1_bytes_compressed.to_vec(), key.to_bytes()); + assert_eq!(sec1_bytes_compressed.to_vec(), key_bytes.deref()); +} + +/// Test for P256PublicKey::to_sec1_bytes(Uncompressed) +pub fn to_bytes_uncompressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { + let sec1_bytes = hex!( + "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022 + 8bbe76c6dc1643088107636deff8aa79e8002a157b92" + ); + let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap(); + let key_bytes = key.to_sec1_bytes(PointCompression::Uncompressed); + assert_eq!(sec1_bytes.to_vec(), key_bytes.deref()); } /// Random test for P256PublicKey::to_bytes @@ -75,7 +101,7 @@ P256, >>::Rng::new()) .public_key_bytes(); - let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap(); + let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap(); assert_eq!( E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(), public_key, @@ -192,7 +218,7 @@ /// Test for P256 Diffie-Hellman key exchange. pub fn p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { // From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1 - // http://google3/third_party/wycheproof/testvectors/ecdh_secp256r1_ecpoint_test.json;l=22;rcl=375894991 + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/ecdh_secp256r1_ecpoint_test.json#L22 // sec1 public key manually extracted from the ASN encoded test data let public_key_sec1 = hex!( "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f @@ -230,20 +256,24 @@ }; let result = p256_ecdh_test_impl::<E>( &test.public_key, - &test.private_key.try_into().expect("Private key should be 32 bytes long"), + &test + .private_key + .as_slice() + .try_into() + .expect("Private key should be 32 bytes long"), ); match test.result { wycheproof::TestResult::Valid => { let shared_secret = result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id)); - assert_eq!(test.shared_secret, shared_secret.into()); + assert_eq!(test.shared_secret.as_slice(), shared_secret.into()); } wycheproof::TestResult::Invalid => { result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id)); } wycheproof::TestResult::Acceptable => { if let Ok(shared_secret) = result { - assert_eq!(test.shared_secret, shared_secret.into()); + assert_eq!(test.shared_secret.as_slice(), shared_secret.into()); } // Test passes if `result` is an error because this test is "acceptable" } @@ -268,22 +298,43 @@ #[template] #[export] #[rstest] -#[case::to_bytes(to_bytes_test)] -#[case::to_bytes_random(to_bytes_random_test)] -#[case::from_sec1_bytes_not_on_curve(from_sec1_bytes_not_on_curve_test)] -#[case::from_sec1_bytes_not_on_infinity(from_sec1_bytes_at_infinity_test)] -#[case::from_affine_coordinates(from_affine_coordinates_test)] -#[case::from_affine_coordinates_not_on_curve(from_affine_coordinates_not_on_curve_test)] -#[case::public_key_to_affine_coordinates(public_key_to_affine_coordinates_test)] +#[case::to_bytes(to_bytes_test, "to_bytes")] +#[case::to_bytes_compressed(to_bytes_compressed_test, "to_bytes_compressed")] +#[case::to_bytes_uncompressed(to_bytes_uncompressed_test, "to_bytes_uncompressed")] +#[case::to_bytes_random(to_bytes_random_test, "to_bytes_random")] +#[case::from_sec1_bytes_not_on_curve( + from_sec1_bytes_not_on_curve_test, + "from_sec1_bytes_not_on_curve" +)] +#[case::from_sec1_bytes_not_on_infinity( + from_sec1_bytes_at_infinity_test, + "from_sec1_bytes_not_on_infinity" +)] +#[case::from_affine_coordinates(from_affine_coordinates_test, "from_affine_coordinates")] +#[case::from_affine_coordinates_not_on_curve( + from_affine_coordinates_not_on_curve_test, + "from_affine_coordinates_not_on_curve" +)] +#[case::public_key_to_affine_coordinates( + public_key_to_affine_coordinates_test, + "public_key_to_affine_coordinates" +)] #[case::public_key_to_affine_coordinates_compressed02( - public_key_to_affine_coordinates_compressed02_test + public_key_to_affine_coordinates_compressed02_test, + "public_key_to_affine_coordinates_compressed02" )] #[case::public_key_to_affine_coordinates_compressed03( - public_key_to_affine_coordinates_compressed03_test + public_key_to_affine_coordinates_compressed03_test, + "public_key_to_affine_coordinates_compressed03" )] #[case::public_key_to_affine_coordinates_zero_top_byte( - public_key_to_affine_coordinates_zero_top_byte_test + public_key_to_affine_coordinates_zero_top_byte_test, + "public_key_to_affine_coordinates_zero_top_byte" )] -#[case::p256_ecdh(p256_ecdh_test)] -#[case::wycheproof_p256(wycheproof_p256_test)] -fn p256_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {} +#[case::p256_ecdh(p256_ecdh_test, "p256_ecdh")] +#[case::wycheproof_p256(wycheproof_p256_test, "wycheproof_p256")] +fn p256_test_cases<C: CryptoProvider>( + #[case] testcase: CryptoProviderTestCase<C>, + #[case] name: &str, +) { +}
diff --git a/nearby/crypto/crypto_provider_test/src/sha2.rs b/nearby/crypto/crypto_provider_test/src/sha2.rs index 330f284..66a3025 100644 --- a/nearby/crypto/crypto_provider_test/src/sha2.rs +++ b/nearby/crypto/crypto_provider_test/src/sha2.rs
@@ -14,7 +14,7 @@ extern crate alloc; extern crate std; -pub use crate::prelude::*; +use crate::prelude::*; use crate::CryptoProvider; use alloc::vec::Vec; use core::{marker::PhantomData, str::FromStr};
diff --git a/nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt b/nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt similarity index 100% rename from nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt rename to nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt
diff --git a/nearby/crypto/crypto_provider_test/src/x25519.rs b/nearby/crypto/crypto_provider_test/src/x25519.rs index 0fcaa12..1fe2019 100644 --- a/nearby/crypto/crypto_provider_test/src/x25519.rs +++ b/nearby/crypto/crypto_provider_test/src/x25519.rs
@@ -58,7 +58,7 @@ pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) { let public_key_bytes = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap(); - assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes()); + assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes().as_ref()); } /// Random test for `PublicKey<X25519>::to_bytes` @@ -69,9 +69,9 @@ X25519, >>::Rng::new()) .public_key_bytes(); - let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap(); + let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap(); assert_eq!( - E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(), + E::PublicKey::from_bytes(public_key.to_bytes().as_ref()).unwrap(), public_key, "from_bytes should return the same key for `{public_key_bytes:?}`", ); @@ -81,7 +81,7 @@ /// Test for X25519 Diffie-Hellman key exchange. pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) { // From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1 - // http://google3/third_party/wycheproof/testvectors/ecdh_secx25519r1_ecpoint_test.json;l=22;rcl=375894991 + // https://github.com/google/wycheproof/blob/b063b4a/testvectors/x25519_test.json#L23 // sec1 public key manually extracted from the ASN encoded test data let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829"); let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475"); @@ -109,20 +109,24 @@ for test in test_group.tests { let result = x25519_ecdh_test_impl::<E>( &test.public_key, - &test.private_key.try_into().expect("Private keys should be 32 bytes long"), + &test + .private_key + .as_slice() + .try_into() + .expect("Private keys should be 32 bytes long"), ); match test.result { wycheproof::TestResult::Valid => { let shared_secret = result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id)); - assert_eq!(&test.shared_secret, &shared_secret.into()); + assert_eq!(&test.shared_secret.as_slice(), &shared_secret.into()); } wycheproof::TestResult::Invalid => { result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id)); } wycheproof::TestResult::Acceptable => { if let Ok(shared_secret) = result { - assert_eq!(test.shared_secret, shared_secret.into()); + assert_eq!(test.shared_secret.as_slice(), shared_secret.into()); } // Test passes if `result` is an error because this test is "acceptable" }
diff --git a/nearby/presence/CMakeLists.txt b/nearby/presence/CMakeLists.txt index 2b4b66e..d2a251b 100644 --- a/nearby/presence/CMakeLists.txt +++ b/nearby/presence/CMakeLists.txt
@@ -32,15 +32,6 @@ set(CMAKE_CXX_FLAGS_DEBUG "-g ${CMAKE_C_FLAGS_DEBUG}") endif () -if (UNIX) - add_compile_options(-Wall -Wextra -Wimplicit-fallthrough -Wextra-semi - -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi - -Wshadow - -Wsign-compare) -elseif (MSVC) - add_compile_options(-W4 -O1) -endif () - find_package(OpenSSL REQUIRED) if (OPENSSL_FOUND) message(STATUS "OpenSSL Found: ${OPENSSL_VERSION}") @@ -48,6 +39,10 @@ message(STATUS "OpenSSL Libraries: ${OPENSSL_LIBRARIES}") endif () +if (MSVC) + add_compile_options(-W4 -O1 -MD) +endif () + if (ENABLE_TESTS) message(STATUS "Enabling workspace wide tests") @@ -62,8 +57,8 @@ enable_testing() include(GoogleTest) - # Find GoogleBenchmark - find_package(benchmark REQUIRED) + # Include google benchmark + add_subdirectory(${THIRD_PARTY_DIR}/benchmark ${THIRD_PARTY_DIR}/benchmark/build) # Setup jsoncpp set(JSONCPP_DIR ${THIRD_PARTY_DIR}/jsoncpp) @@ -74,6 +69,15 @@ ) endif () +# Add these compile options for our own code, not needed for the above deps +if (UNIX) + add_compile_options(-Wall -Wextra -Wimplicit-fallthrough -Wextra-semi + -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi + -Wshadow + -Wsign-compare) +endif() + + add_subdirectory(np_cpp_ffi) add_subdirectory(ldt_np_c_sample)
diff --git a/nearby/presence/array_ref/README.md b/nearby/presence/array_ref/README.md new file mode 100644 index 0000000..2ff0748 --- /dev/null +++ b/nearby/presence/array_ref/README.md
@@ -0,0 +1,4 @@ +This crate was originally forked from crates.io: https://github.com/droundy/arrayref + +This fork removes unsafe code which is no longer needed in stable rust while +keeping the behavior the same. The owner of the upstream repo has been unresponsive with regards to landing these changes upstream ie: https://github.com/droundy/arrayref/pull/24 \ No newline at end of file
diff --git a/nearby/presence/handle_map/src/lib.rs b/nearby/presence/handle_map/src/lib.rs deleted file mode 100644 index 745aec4..0000000 --- a/nearby/presence/handle_map/src/lib.rs +++ /dev/null
@@ -1,608 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//! A thread-safe implementation of a map for managing object handles, -//! a safer alternative to raw pointers for FFI interop. -#![cfg_attr(not(test), no_std)] -#![forbid(unsafe_code)] -#![deny(missing_docs)] -extern crate alloc; -extern crate core; - -use alloc::boxed::Box; -use alloc::vec::Vec; -use core::ops::{Deref, DerefMut}; -use core::sync::atomic::Ordering; -use hashbrown::hash_map::EntryRef; -use portable_atomic::{AtomicU32, AtomicU64}; -use spin::lock_api::*; - -#[cfg(test)] -mod tests; - -/// A RAII read lock guard for an object in a [`HandleMap`] -/// pointed-to by a given [`Handle`]. When this struct is -/// dropped, the underlying read lock on the associated -/// shard will be dropped. -pub struct ObjectReadGuard<'a, T> { - guard: lock_api::MappedRwLockReadGuard<'a, spin::RwLock<()>, T>, -} - -impl<'a, T> Deref for ObjectReadGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.guard.deref() - } -} - -/// A RAII read-write lock guard for an object in a [`HandleMap`] -/// pointed-to by a given [`Handle`]. When this struct is -/// dropped, the underlying read-write lock on the associated -/// shard will be dropped. -pub struct ObjectReadWriteGuard<'a, T> { - guard: lock_api::MappedRwLockWriteGuard<'a, spin::RwLock<()>, T>, -} - -impl<'a, T> Deref for ObjectReadWriteGuard<'a, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.guard.deref() - } -} - -impl<'a, T> DerefMut for ObjectReadWriteGuard<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.guard.deref_mut() - } -} - -/// FFI-transmissible structure expressing the dimensions -/// (max # of allocatable slots, number of shards) of a handle-map -/// to be used upon initialization. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct HandleMapDimensions { - /// The number of shards which are employed - /// by the associated handle-map. - pub num_shards: u8, - /// The maximum number of active handles which may be - /// stored within the associated handle-map. - pub max_active_handles: u32, -} - -/// Trait for marker structs containing information about a -/// given [`SingletonHandleMap`], including the underlying -/// object type and the dimensions of the map. -/// Used primarily to enforce type-level constraints -/// on `SingletoneHandleMap`s, including -/// ensuring that they are truly singletons. -pub trait SingletonHandleMapInfo { - /// The underlying kind of object pointed-to by handles - /// derived from the associated handle-map. - type Object: Send + Sync; - - /// Gets the dimensions of the corresponding handle-map. - fn dimensions(&self) -> HandleMapDimensions; - - /// Gets a static reference to the global handle-map - fn get_handle_map() -> &'static HandleMap<Self::Object>; -} - -/// An individual handle to be given out by a [`HandleMap`]. -/// This representation is untyped, and just a wrapper -/// around a handle-id, in contrast to implementors of `HandleLike`. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct Handle { - handle_id: u64, -} - -impl From<&Handle> for Handle { - fn from(handle: &Handle) -> Self { - *handle - } -} - -impl Handle { - /// Constructs a handle wrapping the given ID. - /// - /// No validity checks are done on the wrapped ID - /// to ensure that the given ID is active in - /// any specific handle-map, and the type - /// of the handle is not represented. - /// - /// As a result, this method is only useful for - /// allowing access to handles from an FFI layer. - pub fn from_id(handle_id: u64) -> Self { - Self { handle_id } - } - /// Gets the ID for this handle. - /// - /// Since the underlying handle is un-typed,` - /// this method is only suitable for - /// transmitting handles across an FFI layer. - pub fn get_id(&self) -> u64 { - self.handle_id - } - /// Derives the shard index from the handle id - fn get_shard_index(&self, num_shards: u8) -> usize { - (self.handle_id % (num_shards as u64)) as usize - } -} - -/// Error raised when attempting to allocate into a full handle-map. -#[derive(Debug)] -pub struct HandleMapFullError; - -/// Internal error enum for failed allocations into a given shard. -enum ShardAllocationError<T, F: FnOnce() -> T> { - /// Error for when the entry for the handle is occupied, - /// in which case we spit out the object-provider to try again - /// with a new handle-id. - EntryOccupied(F), - /// Error for when we would exceed the maximum number of allocations. - ExceedsAllocationLimit, -} - -/// Error raised when the entry for a given [`Handle`] doesn't exist. -#[derive(Debug)] -pub struct HandleNotPresentError; - -/// Wrapper around a `HandleMap` which is specifically tailored for maintaining -/// a global singleton `'static` reference to a handle-map. -/// The wrapper handles initialization of the singleton in a const-context -/// and subsequent accesses to ensure that the underlying map is always -/// initialized upon attempts to access it. -pub struct SingletonHandleMap<I: SingletonHandleMapInfo> { - info: I, - wrapped: spin::once::Once<HandleMap<I::Object>, spin::relax::Spin>, -} - -impl<I: SingletonHandleMapInfo> SingletonHandleMap<I> { - /// Constructs a new global handle-map using the given - /// singleton handle-map info. This method is callable - /// from a `const`-context to allow it to be used to initialize - /// `static` variables. - pub const fn with_info(info: I) -> Self { - Self { info, wrapped: spin::once::Once::new() } - } - /// Initialize the handle-map if it's not already initialized, - /// and return a reference to the result. - pub fn get(&self) -> &HandleMap<I::Object> { - self.wrapped.call_once(|| { - let dimensions = self.info.dimensions(); - HandleMap::with_dimensions(dimensions) - }) - } -} - -/// A thread-safe mapping from "handle"s [like pointers, but safer] -/// to underlying structures, supporting allocations, reads, writes, -/// and deallocations of objects behind handles. -pub struct HandleMap<T: Send + Sync> { - /// The dimensions of this handle-map - dimensions: HandleMapDimensions, - - /// The individually-lockable "shards" of the handle-map, - /// among which the keys will be roughly uniformly-distributed. - handle_map_shards: Box<[HandleMapShard<T>]>, - - /// An atomically-incrementing counter which tracks the - /// next handle ID which allocations will attempt to use. - new_handle_id_counter: AtomicU64, - - /// An atomic integer roughly tracking the number of - /// currently-outstanding allocated entries in this - /// handle-map among all [`HandleMapShard`]s. - outstanding_allocations_counter: AtomicU32, -} - -impl<T: Send + Sync> HandleMap<T> { - /// Creates a new handle-map with the given `HandleMapDimensions`. - pub fn with_dimensions(dimensions: HandleMapDimensions) -> Self { - let mut handle_map_shards = Vec::with_capacity(dimensions.num_shards as usize); - for _ in 0..dimensions.num_shards { - handle_map_shards.push(HandleMapShard::default()); - } - let handle_map_shards = handle_map_shards.into_boxed_slice(); - Self { - dimensions, - handle_map_shards, - new_handle_id_counter: AtomicU64::new(0), - outstanding_allocations_counter: AtomicU32::new(0), - } - } -} - -impl<T: Send + Sync> HandleMap<T> { - /// Allocates a new object within the given handle-map, returning - /// a handle to the location it was stored at. This operation - /// may fail if attempting to allocate over the `dimensions.max_active_handles` - /// limit imposed on the handle-map, in which case this method - /// will return a `HandleMapFullError`. - pub fn allocate( - &self, - initial_value_provider: impl FnOnce() -> T, - ) -> Result<Handle, HandleMapFullError> { - let mut initial_value_provider = initial_value_provider; - loop { - // Increment the new-handle-ID counter using relaxed memory ordering, - // since the only invariant that we want to enforce is that concurrently-running - // threads always get distinct new handle-ids. - let new_handle_id = self.new_handle_id_counter.fetch_add(1, Ordering::Relaxed); - let new_handle = Handle::from_id(new_handle_id); - let shard_index = new_handle.get_shard_index(self.dimensions.num_shards); - - // Now, check the shard to see if we can actually allocate into it. - let shard_allocate_result = self.handle_map_shards[shard_index].try_allocate( - new_handle, - initial_value_provider, - &self.outstanding_allocations_counter, - self.dimensions.max_active_handles, - ); - match shard_allocate_result { - Ok(_) => { - return Ok(new_handle); - } - Err(ShardAllocationError::ExceedsAllocationLimit) => { - return Err(HandleMapFullError); - } - Err(ShardAllocationError::EntryOccupied(thrown_back_provider)) => { - // We need to do the whole thing again with a new ID - initial_value_provider = thrown_back_provider; - } - } - } - } - - /// Gets a read-only reference to an object within the given handle-map, - /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`]. - pub fn get(&self, handle: Handle) -> Result<ObjectReadGuard<T>, HandleNotPresentError> { - let shard_index = handle.get_shard_index(self.dimensions.num_shards); - self.handle_map_shards[shard_index].get(handle) - } - - /// Gets a read+write reference to an object within the given handle-map, - /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`]. - pub fn get_mut( - &self, - handle: Handle, - ) -> Result<ObjectReadWriteGuard<T>, HandleNotPresentError> { - let shard_index = handle.get_shard_index(self.dimensions.num_shards); - self.handle_map_shards[shard_index].get_mut(handle) - } - - /// Removes the object pointed to by the given handle in - /// the handle-map, returning the removed object if it - /// exists. Otherwise, returns [`HandleNotPresentError`]. - pub fn deallocate(&self, handle: Handle) -> Result<T, HandleNotPresentError> { - let shard_index = handle.get_shard_index(self.dimensions.num_shards); - self.handle_map_shards[shard_index] - .deallocate(handle, &self.outstanding_allocations_counter) - } - - /// Gets the actual number of elements stored in the entire map. - /// Only suitable for single-threaded sections of tests. - #[cfg(test)] - pub(crate) fn len(&self) -> usize { - self.handle_map_shards.iter().map(|s| s.len()).sum() - } - - /// Sets the new-handle-id counter to the given value. - /// Only suitable for tests. - #[cfg(test)] - pub(crate) fn set_new_handle_id_counter(&mut self, value: u64) { - self.new_handle_id_counter = AtomicU64::new(value); - } -} - -// Bunch o' type aliases to make talking about them much easier in the shard code. -type ShardMapType<T> = hashbrown::HashMap<Handle, T>; -type ShardReadWriteLock<T> = RwLock<ShardMapType<T>>; -type ShardReadGuard<'a, T> = RwLockReadGuard<'a, ShardMapType<T>>; -type ShardUpgradableReadGuard<'a, T> = RwLockUpgradableReadGuard<'a, ShardMapType<T>>; -type ShardReadWriteGuard<'a, T> = RwLockWriteGuard<'a, ShardMapType<T>>; - -/// An individual handle-map shard, which is ultimately -/// just a hash-map behind a lock. -pub(crate) struct HandleMapShard<T: Send + Sync> { - data: RwLock<ShardMapType<T>>, -} - -impl<T: Send + Sync> Default for HandleMapShard<T> { - fn default() -> Self { - Self { data: RwLock::new(hashbrown::HashMap::new()) } - } -} - -impl<T: Send + Sync> HandleMapShard<T> { - fn get(&self, handle: Handle) -> Result<ObjectReadGuard<T>, HandleNotPresentError> { - let map_read_guard = ShardReadWriteLock::<T>::read(&self.data); - let read_only_map_ref = map_read_guard.deref(); - if read_only_map_ref.contains_key(&handle) { - let object_read_guard = ShardReadGuard::<T>::map(map_read_guard, move |map_ref| { - // We know that the entry exists, since we've locked the - // shard and already checked that it exists prior to - // handing out this new, mapped read-lock. - map_ref.get(&handle).unwrap() - }); - Ok(ObjectReadGuard { guard: object_read_guard }) - } else { - // Auto-drop the read guard, and return an error - Err(HandleNotPresentError) - } - } - /// Gets a read-write guard on the entire shard map if an entry for the given - /// handle exists, but if not, yield [`HandleNotPresentError`]. - fn get_read_write_guard_if_entry_exists( - &self, - handle: Handle, - ) -> Result<ShardReadWriteGuard<T>, HandleNotPresentError> { - // Start with an upgradable read lock and then upgrade to a write lock. - // By doing this, we prevent new readers from entering (see `spin` documentation) - let map_upgradable_read_guard = ShardReadWriteLock::<T>::upgradable_read(&self.data); - let read_only_map_ref = map_upgradable_read_guard.deref(); - if read_only_map_ref.contains_key(&handle) { - // If we know that the entry exists, and we're currently - // holding a read-lock, we know that we're safe to request - // an upgrade to a write lock, since only one write or - // upgradable read lock can be outstanding at any one time. - let map_read_write_guard = - ShardUpgradableReadGuard::<T>::upgrade(map_upgradable_read_guard); - Ok(map_read_write_guard) - } else { - // Auto-drop the read guard, we don't need to allow a write. - Err(HandleNotPresentError) - } - } - - fn get_mut(&self, handle: Handle) -> Result<ObjectReadWriteGuard<T>, HandleNotPresentError> { - let map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?; - // Expose only the pointed-to object with a mapped read-write guard - let object_read_write_guard = - ShardReadWriteGuard::<T>::map(map_read_write_guard, move |map_ref| { - // Already checked that the entry exists while holding the lock - map_ref.get_mut(&handle).unwrap() - }); - Ok(ObjectReadWriteGuard { guard: object_read_write_guard }) - } - fn deallocate( - &self, - handle: Handle, - outstanding_allocations_counter: &AtomicU32, - ) -> Result<T, HandleNotPresentError> { - let mut map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?; - // We don't need to worry about double-decrements, since the above call - // got us an upgradable read guard for our read, which means it's the only - // outstanding upgradeable guard on the shard. See `spin` documentation. - // Remove the pointed-to object from the map, and return it, - // releasing the lock when the guard goes out of scope. - let removed_object = map_read_write_guard.deref_mut().remove(&handle).unwrap(); - // Decrement the allocations counter. Release ordering because we want - // to ensure that clearing the map entry never gets re-ordered to after when - // this counter gets decremented. - outstanding_allocations_counter.sub(1, Ordering::Release); - Ok(removed_object) - } - - fn try_allocate<F>( - &self, - handle: Handle, - object_provider: F, - outstanding_allocations_counter: &AtomicU32, - max_active_handles: u32, - ) -> Result<(), ShardAllocationError<T, F>> - where - F: FnOnce() -> T, - { - // Use an upgradeable read guard -> write guard to provide greater fairness - // toward writers (see `spin` documentation) - let map_upgradable_read_guard = ShardReadWriteLock::<T>::upgradable_read(&self.data); - let mut map_read_write_guard = - ShardUpgradableReadGuard::<T>::upgrade(map_upgradable_read_guard); - match map_read_write_guard.entry_ref(&handle) { - EntryRef::Occupied(_) => { - // We've already allocated for that handle-id, so yield - // the object provider back to the caller. - Err(ShardAllocationError::EntryOccupied(object_provider)) - } - EntryRef::Vacant(vacant_entry) => { - // An entry is open, but we haven't yet checked the allocations count. - // Try to increment the total allocations count atomically. - // Use acquire ordering on a successful bump, because we don't want - // to invoke the allocation closure before we have a guaranteed slot. - // On the other hand, upon failure, we don't care about ordering - // of surrounding operations, and so we use a relaxed ordering there. - let allocation_count_bump_result = outstanding_allocations_counter.fetch_update( - Ordering::Acquire, - Ordering::Relaxed, - |old_total_allocations| { - if old_total_allocations >= max_active_handles { - None - } else { - Some(old_total_allocations + 1) - } - }, - ); - match allocation_count_bump_result { - Ok(_) => { - // We're good to actually allocate - let object = object_provider(); - vacant_entry.insert(object); - Ok(()) - } - Err(_) => { - // The allocation would cause us to exceed the allowed allocations, - // so release all locks and error. - Err(ShardAllocationError::ExceedsAllocationLimit) - } - } - } - } - } - /// Gets the actual number of elements stored in this shard. - /// Only suitable for single-threaded sections of tests. - #[cfg(test)] - fn len(&self) -> usize { - let guard = ShardReadWriteLock::<T>::read(&self.data); - guard.deref().len() - } -} - -/// Externally-facing trait for things which behave like handle-map handles -/// with a globally-defined handle-map for the type. -pub trait HandleLike: Sized { - /// The underlying object type pointed-to by this handle - type Object: Send + Sync; - - /// Tries to allocate a new handle using the given provider - /// to construct the underlying stored object as a new - /// entry into the global handle table for this type. - fn allocate( - initial_value_provider: impl FnOnce() -> Self::Object, - ) -> Result<Self, HandleMapFullError>; - - /// Gets a RAII read-guard on the contents behind this handle. - fn get(&self) -> Result<ObjectReadGuard<Self::Object>, HandleNotPresentError>; - - /// Gets a RAII read-write guard on the contents behind this handle. - fn get_mut(&self) -> Result<ObjectReadWriteGuard<Self::Object>, HandleNotPresentError>; - - /// Deallocates the contents behind this handle. - fn deallocate(self) -> Result<Self::Object, HandleNotPresentError>; -} - -#[macro_export] -/// `declare_handle_map! { handle_module_name, handle_type_name, wrapped_type, -/// map_dimension_provider }` -/// -/// Declares a new public module with name `handle_module_name` which includes a new type -/// `handle_type_name` which is `#[repr(C)]` and represents FFI-accessible handles -/// to values of type `wrapped_type`. -/// -/// Internal to the generated module, a new static `SingletonHandleMap` is created, where the -/// maximum number of active handles and the number of shards are given by -/// the dimensions returned by evaluation of the `map_dimension_provider` expression. -/// -/// Note: `map_dimension_provider` will be evaluated within the defined module's scope, -/// so you will likely need to use `super` to refer to definitions in the enclosing scope. -/// -/// # Example -/// The following code defines an FFI-safe type `StringHandle` which references -/// the `String` data-type, and uses it to define a (contrived) -/// function `sample` which will print "Hello World". -/// -/// ``` -/// mod sample { -/// use core::ops::Deref; -/// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike}; -/// -/// fn get_string_handle_map_dimensions() -> HandleMapDimensions { -/// HandleMapDimensions { -/// num_shards: 8, -/// max_active_handles: 100, -/// } -/// } -/// -/// declare_handle_map! { string_handle, StringHandle, String, -/// super::get_string_handle_map_dimensions() } -/// -/// use string_handle::StringHandle; -/// -/// fn sample() { -/// // Note: this method could panic if there are -/// // more than 99 outstanding handles. -/// -/// // Allocate a new string-handle pointing to the string "Hello" -/// let handle = StringHandle::allocate(|| { "Hello".to_string() }).unwrap(); -/// { -/// // Obtain a write-guard on the contents of our handle -/// let mut handle_write_guard = handle.get_mut().unwrap(); -/// handle_write_guard.push_str(" World"); -/// // Write guard is auto-dropped at the end of this block. -/// } -/// { -/// // Obtain a read-guard on the contents of our handle. -/// // Note that we had to ensure that the write-guard was -/// // dropped prior to doing this, or else execution -/// // could potentially hang. -/// let handle_read_guard = handle.get().unwrap(); -/// println!("{}", handle_read_guard.deref()); -/// } -/// // Clean up the data behind the created handle -/// handle.deallocate().unwrap(); -/// } -/// } -/// ``` -macro_rules! declare_handle_map { - ($handle_module_name:ident, $handle_type_name:ident, $wrapped_type:ty, - $map_dimension_provider:expr) => { - #[doc = concat!("Macro-generated (via `handle_map::declare_handle_map!`) module", - " which defines the `", stringify!($handle_module_name), "::", - stringify!($handle_type_name), "` FFI-transmissible handle type ", - " which references values of type `", stringify!($wrapped_type), "`.")] - pub mod $handle_module_name { - use $crate::SingletonHandleMapInfo; - - struct SingletonInfo; - - static GLOBAL_HANDLE_MAP: $crate::SingletonHandleMap<SingletonInfo> = - $crate::SingletonHandleMap::with_info(SingletonInfo); - - impl $crate::SingletonHandleMapInfo for SingletonInfo { - type Object = $wrapped_type; - - fn dimensions(&self) -> $crate::HandleMapDimensions { - $map_dimension_provider - } - fn get_handle_map() -> &'static $crate::HandleMap<$wrapped_type> { - GLOBAL_HANDLE_MAP.get() - } - } - - #[doc = concat!("A `#[repr(C)]` handle to a value of type `", - stringify!($wrapped_type), "`.")] - #[repr(C)] - #[derive(Clone, Copy, PartialEq, Eq)] - pub struct $handle_type_name { - handle_id: u64, - } - - impl $handle_type_name { - fn get_as_handle(&self) -> $crate::Handle { - $crate::Handle::from_id(self.handle_id) - } - } - impl $crate::HandleLike for $handle_type_name { - type Object = $wrapped_type; - fn allocate(initial_value_provider: impl FnOnce() -> $wrapped_type, - ) -> Result<Self, $crate::HandleMapFullError> { - SingletonInfo::get_handle_map().allocate(initial_value_provider) - .map(|derived_handle| Self { - handle_id: derived_handle.get_id(), - }) - } - fn get(&self) -> Result<$crate::ObjectReadGuard<$wrapped_type>, $crate::HandleNotPresentError> { - SingletonInfo::get_handle_map().get(self.get_as_handle()) - } - fn get_mut(&self) -> Result<$crate::ObjectReadWriteGuard<$wrapped_type>, $crate::HandleNotPresentError> { - SingletonInfo::get_handle_map().get_mut(self.get_as_handle()) - } - fn deallocate(self) -> Result<$wrapped_type, $crate::HandleNotPresentError> { - SingletonInfo::get_handle_map().deallocate(self.get_as_handle()) - } - } - } - } -}
diff --git a/nearby/presence/ldt/Cargo.toml b/nearby/presence/ldt/Cargo.toml index c4456cc..2ee7b4c 100644 --- a/nearby/presence/ldt/Cargo.toml +++ b/nearby/presence/ldt/Cargo.toml
@@ -13,7 +13,7 @@ ldt_tbc.workspace = true [dev-dependencies] -crypto_provider_default = {workspace = true} +crypto_provider_default = {workspace = true, features = ["rustcrypto"]} rand_ext.workspace = true test_helper.workspace = true xts_aes.workspace = true
diff --git a/nearby/presence/ldt/fuzz/Cargo.lock b/nearby/presence/ldt/fuzz/Cargo.lock index 767108f..419d9e9 100644 --- a/nearby/presence/ldt/fuzz/Cargo.lock +++ b/nearby/presence/ldt/fuzz/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -157,6 +171,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_rustcrypto" @@ -164,6 +181,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -193,9 +211,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -261,9 +279,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -329,6 +347,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -583,6 +611,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -618,9 +652,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/presence/ldt/src/lib.rs b/nearby/presence/ldt/src/lib.rs index f05c14b..9065e12 100644 --- a/nearby/presence/ldt/src/lib.rs +++ b/nearby/presence/ldt/src/lib.rs
@@ -31,7 +31,6 @@ /// `B` is the block size. /// `T` is the provided implementation of a Tweakable Block Cipher /// `M` is the implementation of a [pure mix function](https://eprint.iacr.org/2017/841.pdf) -#[repr(C)] pub struct LdtEncryptCipher<const B: usize, T: TweakableBlockCipher<B>, M: Mix> { cipher_1: T::EncryptionCipher, cipher_2: T::EncryptionCipher, @@ -124,7 +123,7 @@ // Encrypt or decrypt in place with a tweak O: Fn(&C, T::Tweak, &mut [u8; B]), // Mix a/b into block-sized chunks - X: Fn(&[u8], &[u8]) -> ([u8; B], [u8; B]), + X: for<'a, 'b> Fn(&'a [u8], &'b [u8]) -> (&'b [u8], &'a [u8]), P: Padder<B, T>, { if data.len() < B || data.len() >= B * 2 { @@ -147,20 +146,23 @@ // |z| = B - s, |m3| = s let (z, m3) = m1_ciphertext.split_at(B - s); debug_assert_eq!(s, m3.len()); + // c3 and c2 are the last s bytes of their size-B arrays, respectively - let (mut c3, c2) = mix(m3, m2); + let (c3, c2) = mix(m3, m2); + let c1 = { - // constructing z || c3 is easy since c3 is already the last s bytes - c3[0..(B - s)].copy_from_slice(z); - let mut z_c3 = c3; - let tweak = padder.pad_tweak(&c2[B - s..]); + let mut z_c3 = [0; B]; + z_c3[(B - s)..].copy_from_slice(c3); + z_c3[0..(B - s)].copy_from_slice(z); + + let tweak = padder.pad_tweak(c2); cipher_op(second_cipher, tweak, &mut z_c3); z_c3 }; - let len = data.len(); - data.get_mut(0..B).ok_or(LdtError::InvalidLength(len))?.copy_from_slice(&c1); - data.get_mut(B..).ok_or(LdtError::InvalidLength(len))?.copy_from_slice(&c2[B - s..]); + let (left, right) = data.split_at_mut(B); + left.copy_from_slice(&c1); + right.copy_from_slice(c2); Ok(()) } @@ -219,31 +221,23 @@ /// Mix `a` and `b`, writing into the last `s` bytes of the output arrays. /// `a` and `b` must be the same length `s`, and no longer than the block size `B`. /// Must be the inverse of [Mix::backwards]. - fn forwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]); + fn forwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]); /// Mix `a` and `b`, writing into the last `s` bytes of the output arrays. /// `a` and `b` must be the same length, and no longer than the block size `B`. /// Must be the inverse of [Mix::forwards]. - fn backwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]); + fn backwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]); } /// Per section 2.4, swapping `a` and `b` is a valid mix function pub struct Swap {} impl Mix for Swap { - fn forwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]) { + fn forwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]) { debug_assert_eq!(a.len(), b.len()); - // implies b length as well - debug_assert!(a.len() <= B); - let mut out1 = [0; B]; - let mut out2 = [0; B]; - - let start = B - a.len(); - out1[start..].copy_from_slice(b); - out2[start..].copy_from_slice(a); - (out1, out2) + (b, a) } - fn backwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]) { + fn backwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]) { // backwards is the same as forwards. Self::forwards(a, b) } @@ -262,7 +256,7 @@ fn pad_tweak(&self, data: &[u8]) -> T::Tweak; } -/// The default padding algorithm per section 2 of LDT paper. +/// The default padding algorithm per section 2 of [LDT paper](https://eprint.iacr.org/2017/841.pdf) #[derive(Default)] pub struct DefaultPadder;
diff --git a/nearby/presence/ldt_np_adv/Cargo.toml b/nearby/presence/ldt_np_adv/Cargo.toml index 02592ce..522e97b 100644 --- a/nearby/presence/ldt_np_adv/Cargo.toml +++ b/nearby/presence/ldt_np_adv/Cargo.toml
@@ -17,14 +17,14 @@ ldt_tbc.workspace = true [dev-dependencies] -crypto_provider_default.workspace = true +crypto_provider_default = { workspace = true, features = ["rustcrypto", "std"] } crypto_provider_openssl.workspace = true rand_ext.workspace = true test_helper.workspace = true rand.workspace = true base64.workspace = true -serde_json = {workspace = true, features=["std"]} +serde_json = { workspace = true, features = ["std"] } hex.workspace = true anyhow.workspace = true criterion.workspace = true
diff --git a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock index 5050ad0..6b6ef4b 100644 --- a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock +++ b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -161,6 +175,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_rustcrypto" @@ -168,6 +185,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -197,9 +215,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -265,9 +283,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -333,6 +351,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -610,6 +638,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -645,9 +679,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/presence/ldt_np_adv/src/lib.rs b/nearby/presence/ldt_np_adv/src/lib.rs index 3785797..31aa50d 100644 --- a/nearby/presence/ldt_np_adv/src/lib.rs +++ b/nearby/presence/ldt_np_adv/src/lib.rs
@@ -41,10 +41,13 @@ /// Max LDT-XTS-AES data size: `(2 * AES block size) - 1` pub const LDT_XTS_AES_MAX_LEN: usize = 31; -/// Legacy (v0) format uses 14-byte metadata key + +/// Legacy (v0) format uses a 14-byte metadata key pub const NP_LEGACY_METADATA_KEY_LEN: usize = 14; -/// The salt included in an NP advertisement +/// The salt included in an NP advertisement. +/// LDT does not use an IV but can instead incorporate the 2 byte, regularly rotated, +/// salt from the advertisement payload and XOR it with the padded tweak data. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LegacySalt { /// Salt bytes extracted from the incoming NP advertisement @@ -104,6 +107,20 @@ /// Decrypts and validates a NP legacy format advertisement encrypted with LDT. /// +/// A NP legacy advertisement will always be in the format of: +/// +/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) | repeated +/// { DE header | DE payload } +/// +/// Example: +/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) | +/// Tx power DE header (1 byte) | Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes) +/// +/// The ciphertext bytes will always start with the Identity through the end of the +/// advertisement, for example in the above [ Identity (14 bytes) | Tx power DE header (1 byte) | +/// Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes) ] will be the ciphertext section +/// passed as the input to `decrypt_and_verify` +/// /// `B` is the underlying block cipher block size. /// `O` is the max output size (must be 2 * B - 1). /// `T` is the tweakable block cipher used by LDT. @@ -130,6 +147,10 @@ /// /// If the plaintext's metadata key matches this item's MAC, return the plaintext, otherwise `None`. /// + /// NOTE: because LDT acts as a PRP over the entire message, tampering with any bit scrambles + /// the whole message, so we can leverage the MAC on just the metadata key to ensure integrity + /// for the whole message. + /// /// # Errors /// - If `payload` has a length outside of `[B, B * 2)`. /// - If the decrypted plaintext fails its HMAC validation
diff --git a/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml b/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml deleted file mode 100644 index 5a4a047..0000000 --- a/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml +++ /dev/null
@@ -1,3 +0,0 @@ -paths = [ - "../../../boringssl-build/boringssl/rust/bssl-crypto", -]
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.lock b/nearby/presence/ldt_np_adv_ffi/Cargo.lock index 1d13887..3f7e2a7 100644 --- a/nearby/presence/ldt_np_adv_ffi/Cargo.lock +++ b/nearby/presence/ldt_np_adv_ffi/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -98,6 +112,13 @@ [[package]] name = "bssl-crypto" version = "0.1.0" +dependencies = [ + "bssl-sys", +] + +[[package]] +name = "bssl-sys" +version = "0.1.0" [[package]] name = "bytes" @@ -177,6 +198,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_boringssl" @@ -184,7 +208,6 @@ dependencies = [ "bssl-crypto", "crypto_provider", - "crypto_provider_stubs", ] [[package]] @@ -204,6 +227,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -240,9 +264,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -299,9 +323,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -384,6 +408,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -841,6 +875,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -882,9 +922,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.toml b/nearby/presence/ldt_np_adv_ffi/Cargo.toml index d53809f..829f932 100644 --- a/nearby/presence/ldt_np_adv_ffi/Cargo.toml +++ b/nearby/presence/ldt_np_adv_ffi/Cargo.toml
@@ -6,20 +6,20 @@ [dependencies] crypto_provider = { path = "../../crypto/crypto_provider" } -crypto_provider_openssl = {path = "../../crypto/crypto_provider_openssl", optional = true} -crypto_provider_rustcrypto = {path = "../../crypto/crypto_provider_rustcrypto", optional = true} -crypto_provider_boringssl = {path = "../../crypto/crypto_provider_boringssl", optional = true} +crypto_provider_openssl = { path = "../../crypto/crypto_provider_openssl", optional = true } +crypto_provider_rustcrypto = { path = "../../crypto/crypto_provider_rustcrypto", optional = true } +crypto_provider_boringssl = { path = "../../crypto/crypto_provider_boringssl", optional = true } ldt = { path = "../ldt" } ldt_np_adv = { path = "../ldt_np_adv" } -np_hkdf = {path = "../np_hkdf"} +np_hkdf = { path = "../np_hkdf" } cfg-if = "1.0.0" -lazy_static = { version = "1.4.0"} +lazy_static = { version = "1.4.0" } # no_std only deps -libc_alloc = {version = "1.0.4", optional = true } +libc_alloc = { version = "1.0.4", optional = true } panic-abort = { version = "0.3.2", optional = true } -spin = {version = "0.9.8", optional = true } +spin = { version = "0.9.8", optional = true } [dev-dependencies] rand = "0.8.5"
diff --git a/nearby/presence/ldt_np_adv_ffi/src/lib.rs b/nearby/presence/ldt_np_adv_ffi/src/lib.rs index 2ed8d95..1534857 100644 --- a/nearby/presence/ldt_np_adv_ffi/src/lib.rs +++ b/nearby/presence/ldt_np_adv_ffi/src/lib.rs
@@ -20,6 +20,8 @@ clippy::panic, clippy::expect_used )] +#![allow(internal_features)] +// TODO: Remove usage of `lang_items` when ffi is no longer alloc // These features are needed to support no_std + alloc #![feature(lang_items)] @@ -128,7 +130,7 @@ get_enc_handle_map() .remove(&handle.handle) .ok_or(CloseCipherError::InvalidHandle) - .map(|_| 0) + .map(|_| SUCCESS) }) } @@ -138,14 +140,11 @@ get_dec_handle_map() .remove(&handle.handle) .ok_or(CloseCipherError::InvalidHandle) - .map(|_| 0) + .map(|_| SUCCESS) }) } #[no_mangle] -// continue to use LdtAdvDecrypter::encrypt() for now, but we should expose a higher level API -// and get rid of this. -#[allow(deprecated)] extern "C" fn NpLdtEncrypt( handle: NpLdtEncryptHandle, buffer: *mut u8,
diff --git a/nearby/presence/ldt_np_c_sample/CMakeLists.txt b/nearby/presence/ldt_np_c_sample/CMakeLists.txt index 588078a..aefc0ba 100644 --- a/nearby/presence/ldt_np_c_sample/CMakeLists.txt +++ b/nearby/presence/ldt_np_c_sample/CMakeLists.txt
@@ -31,7 +31,6 @@ ldt_c_sample optimized "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}" debug "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/debug/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}" - OpenSSL::SSL ) if(UNIX)
diff --git a/nearby/presence/ldt_np_c_sample/main.c b/nearby/presence/ldt_np_c_sample/main.c index 1ae9244..9348a70 100644 --- a/nearby/presence/ldt_np_c_sample/main.c +++ b/nearby/presence/ldt_np_c_sample/main.c
@@ -14,12 +14,12 @@ * limitations under the License. */ +#include "np_ldt.h" + #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "np_ldt.h" - static const uint8_t KEY_SEED_BYTES[] = {204, 219, 36, 137, 233, 252, 172, 66, 179, 147, 72, 184, 148, 30, 209, 154, 29, 54, 14, 117, 224, 152, 200, 193, 94, 107, 28, 194, 182, 32, 205, 57}; static const uint8_t KNOWN_HMAC_BYTES[] = {223, 185, 10, 31, 155, 31, 226, 141, 24, 187, 204, 165, 34, 64, 181, 204, 44, 203, 95, 141, 82, 137, 163, 203, 100, 235, 53, 65, 202, 97, 75, 180}; static const uint8_t TEST_DATA_BYTES[] = {205, 104, 63, 225, 161, 209, 248, 70, 84, 61, 10, 19, 212, 174, 164, 0, 64, 200, 214, 123};
diff --git a/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt b/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt index 6edfd63..141563d 100644 --- a/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt +++ b/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt
@@ -25,7 +25,6 @@ "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}" jsoncpp GTest::gtest_main - OpenSSL::SSL ) if(UNIX) target_link_libraries( @@ -51,7 +50,6 @@ ldt_benchmarks "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}" benchmark::benchmark - OpenSSL::SSL ) if(UNIX)
diff --git a/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc b/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc index d0cfb67..2559954 100644 --- a/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc +++ b/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc
@@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <benchmark/benchmark.h> +#include "np_ldt.h" + #include <ctime> -#include "np_ldt.h" +#include "benchmark/benchmark.h" using std::vector; using std::tuple;
diff --git a/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc b/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc index e66af65..7cd1fb3 100644 --- a/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc +++ b/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc
@@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <gtest/gtest.h> -#include <json/json.h> - extern "C" { #include "np_ldt.h" } @@ -27,6 +24,9 @@ #include <pthread.h> #endif +#include "gtest/gtest.h" +#include "json/json.h" + #ifdef LDT_TEST_VECTORS static const char *PATH_TO_DATA_FILE = LDT_TEST_VECTORS; #else
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml b/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml new file mode 100644 index 0000000..978460f --- /dev/null +++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml
@@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2022 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.nearby.LdtNpJni"> + <uses-sdk android:minSdkVersion="19" /> +</manifest> \ No newline at end of file
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts b/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts index 3cc0692..0f0991e 100644 --- a/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts +++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts
@@ -15,24 +15,35 @@ */ plugins { - id("java") - kotlin("jvm") version "1.8.0" + id("java") + kotlin("jvm") version "1.9.0" +} + +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of("17")) + } } group = "com.google.android.gms.nearby.presence.hazmat" version = "1.0-SNAPSHOT" repositories { - mavenCentral() + mavenCentral() + google() } dependencies { - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") - implementation(kotlin("stdlib")) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(kotlin("stdlib")) + implementation("androidx.annotation:annotation:1.6.0") } tasks.getByName<Test>("test") { - useJUnitPlatform() - jvmArgs = mutableListOf("-Djava.library.path=../../../../target/debug") + useJUnitPlatform() + jvmArgs = mutableListOf("-Djava.library.path=../../../../target/debug") + testLogging { + events("passed", "skipped", "failed") + } } \ No newline at end of file
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java deleted file mode 100644 index ecd0a85..0000000 --- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java +++ /dev/null
@@ -1,162 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.gms.nearby.presence.hazmat; - -/** - * LDT-XTS-AES128 implementation using the default "swap" mix function. It is suitable for - * encryption or decryption of individual Nearby Presence BLE 4.2 legacy format encrypted payloads. - * - * <p>To avoid leaking resources, call close() once an instance is no longer needed. - * - * <p>This class is not thread safe. - */ -public class LdtNpCipher implements AutoCloseable { - - /** - * Size in bytes of key seed used to derive further keys used for in np ldt operations - */ - private static final int KEY_SEED_SIZE = 32; - /** - * Size in bytes of the metadata keys calculated hmac tag - */ - private static final int TAG_SIZE = 32; - /** Block size of AES. */ - private static final int BLOCK_SIZE = 16; - - private final long ldtHandle; - private boolean closed = false; - - private LdtNpCipher(long ldtHandle) { - this.ldtHandle = ldtHandle; - } - - /** - * Create a new Ldt instance using LDT-XTS-AES128 with the "swap" mix function. - * - * @param key_seed 64-byte key material from the credential for the identity used to broadcast. The - * supplied byte[] can be zeroed out once this method returns, as the contents are copied. - * @return an instance configured with the supplied key - * @throws LdtException if the key is the wrong size - * @throws LdtException if the tag is the wrong size - * @throws LdtException if creating the instance fails - */ - public static LdtNpCipher fromKey(byte[] key_seed, byte[] metadata_key_tag) throws LdtException { - if (key_seed.length != KEY_SEED_SIZE) { - throw new LdtException("Key must be " + KEY_SEED_SIZE + " bytes"); - } - if (metadata_key_tag.length != TAG_SIZE) { - throw new LdtException("Tag must be " + TAG_SIZE + " bytes"); - } - - long handle = LdtNpJni.createLdtCipher(key_seed, metadata_key_tag); - if (handle == 0) { - throw new LdtException("Creating Ldt native resources failed"); - } - - return new LdtNpCipher(handle); - } - - /** - * Encode a 2 byte salt as a big-endian char. - * - * @return a char with b1 in the high bits and b2 in the low bits - */ - public static char saltAsChar(byte b1, byte b2) { - // byte widening conversion to int sign-extends - int highBits = b1 << 8; - int lowBits = b2 & 0xFF; - // narrowing conversion truncates to low 16 bits - return (char) (highBits | lowBits); - } - - /** - * Encrypt data in place, XORing bytes derived from the salt into the LDT tweaks. - * - * @param salt the salt that will be used in the advertisement with this encrypted payload. See - * {@link LdtNpCipher#saltAsChar(byte, byte)} for constructing the char - * representation. - * @param data plaintext to encrypt in place: the metadata key followed by the data elements to be - * encrypted. The length must be in [16, 31). - * @throws IllegalStateException if this instance has already been closed - * @throws IllegalArgumentException if data is the wrong length - * @throws LdtException if encryption fails - */ - public void encrypt(char salt, byte[] data) throws LdtException { - checkPreconditions(data); - - int res = LdtNpJni.encrypt(ldtHandle, salt, data); - if (res < 0) { - // TODO is it possible for this to fail if the length is correct? - throw new LdtException("Could not encrypt: error code " + res); - } - } - - /** - * Decrypt the data in place, XORing the LDT tweak with the provided bytes. - * - * @param salt the salt extracted from the advertisement that contained this payload. See {@link - * LdtNpCipher#saltAsChar(byte, byte)} for constructing the char representation. - * @param data ciphertext to decrypt in place: the metadata key followed by the data elements to - * be decrypted. The length must be in [16, 31). - * @throws IllegalStateException if this instance has already been closed - * @throws IllegalArgumentException if data is the wrong length - * @throws LdtException if decryption fails - */ - public void decrypt_and_verify(char salt, byte[] data) throws LdtException { - checkPreconditions(data); - - int res = LdtNpJni.decrypt_and_verify(ldtHandle, salt, data); - if (res < 0) { - // TODO is it possible for this to fail if the length is correct? - throw new LdtException("Could not decrypt: error code " + res); - } - } - - private void checkPreconditions(byte[] data) { - if (closed) { - throw new IllegalStateException("Instance has been closed"); - } - if (data.length < BLOCK_SIZE || data.length >= BLOCK_SIZE * 2) { - throw new IllegalArgumentException( - "Data must be at least " + BLOCK_SIZE + " and less than " + BLOCK_SIZE * 2 + " bytes"); - } - } - - /** - * Releases native resources. - * - * <p>Once closed, an Ldt instance cannot be used further. - */ - @Override - public void close() { - if (closed) { - return; - } - closed = true; - - int res = LdtNpJni.closeLdtCipher(ldtHandle); - if (res < 0) { - throw new RuntimeException("Could not close Ldt: error code " + res); - } - } - - public static class LdtException extends Exception { - LdtException(String message) { - super(message); - } - } -}
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java index bbb0067..6af362a 100644 --- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java +++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java
@@ -15,48 +15,90 @@ */ package com.google.android.gms.nearby.presence.hazmat; -/** JNI for LDT-XTS-AES128 with the "swap" mix function. */ +import androidx.annotation.IntDef; + + +/** JNI for a Nearby Presence LDT-XTS-AES128 cipher with the "swap" mix function. */ class LdtNpJni { + /** Error codes which map to return values on the native side. */ + @IntDef({DecryptErrorCode.DATA_LEN_ERROR, DecryptErrorCode.JNI_OP_ERROR, DecryptErrorCode.MAC_MISMATCH} ) + public @interface DecryptErrorCode { + int DATA_LEN_ERROR = -1; + int JNI_OP_ERROR = -2; + int MAC_MISMATCH = -3; + } + + /** Error codes which map to return values on the native side. */ + @IntDef({EncryptErrorCode.DATA_LEN_ERROR, EncryptErrorCode.JNI_OP_ERROR} ) + public @interface EncryptErrorCode { + int DATA_LEN_ERROR = -1; + int JNI_OP_ERROR = -2; + } + static { System.loadLibrary("ldt_np_jni"); } /** - * Create an instance of LDT-XTS-AES-128 using the Swap mix function. + * Create a new LDT-XTS-AES128 Encryption cipher using the "swap" mix function. * - * @param key key bytes, must be 4x AES key size = 64 bytes - * @return 0 on error, and any other value for success + * @param keySeed is the 32-byte key material from the Nearby Presence credential from which + * the LDT key will be derived. + * @return 0 on error, or a non-zero handle on success. */ - static native long createLdtCipher(byte[] key_seed, byte[] metadata_key_hmac_tag); + static native long createEncryptionCipher(byte[] keySeed); /** - * Close the native resources for an Ldt instance. + * Create a new LDT-XTS-AES128 Decryption cipher using the "swap" mix function. * - * @param ldtHandle An ldt handle returned from {@link LdtNpJni#createLdtCipher}. - * @return 0 on success, <0 for any error + * @param keySeed is the 32-byte key material from the Nearby Presence credential from which + * the LDT key will be derived. + * @param hmacTag is the hmac auth tag calculated on the metadata key used to verify + * decryption was successful. + * @return 0 on error, or a non-zero handle on success. */ - static native int closeLdtCipher(long ldtHandle); + static native long createDecryptionCipher(byte[] keySeed, byte[] hmacTag); /** - * Encrypt the data in place. - * - * @param ldtHandle An ldt handle returned from {@link LdtNpJni#createLdtCipher}. - * @param salt big-endian salt to be expanded into bytes XORd into the LDT tweaks - * @param data size must be between 16 and 31 bytes - * @return 0 on success, -1 if the data size is wrong, or another negative number for any other - * error + * Close the native resources for an LdtEncryptCipher instance. + * @param ldtEncryptHandle a ldt handle returned from {@link LdtNpJni#createEncryptionCipher}. */ - static native int encrypt(long ldtHandle, char salt, byte[] data); + static native void closeEncryptCipher(long ldtEncryptHandle); /** - * Decrypt the data in place using the default LDT tweak padding scheme. + * Close the native resources for an LdtDecryptCipher instance. * - * @param ldtHandle An ldt address returned from {@link LdtNpJni#createLdtCipher}. - * @param salt big-endian salt to be expanded into bytes XORd into the LDT tweaks - * @param data size must be between 16 and 31 bytes - * @return 0 on success, -1 if the data size is wrong, -2 if the calculated hmac - * does not match the provided tag or another negative number for any other error + * @param ldtDecryptHandle a ldt handle returned from {@link LdtNpJni#createDecryptionCipher}. */ - static native int decrypt_and_verify(long ldtHandle, char salt, byte[] data); + static native void closeDecryptCipher(long ldtDecryptHandle); + + /** + * Encrypt a 16-31 byte buffer in-place. + * + * @param ldtEncryptHandle a ldt encryption handle returned from {@link LdtNpJni#createEncryptionCipher}. + * @param salt is the big-endian 2 byte salt that will be used in the Nearby + * Presence advertisement, which will be incorporated into the tweaks LDT uses + * while encrypting. + * @param data 16-31 bytes of plaintext data to be encrypted + * @return 0 on success, in which case `buffer` will now contain ciphertext or a non-zero + * an error code on failure + */ + @EncryptErrorCode + static native int encrypt(long ldtEncryptHandle, char salt, byte[] data); + + /** + * Decrypt a 16-31 byte buffer in-place and verify the plaintext metadata key matches + * this item's MAC, if not the buffer will not be decrypted. + * + * @param ldtDecryptHandle a ldt encryption handle returned from {@link LdtNpJni#createDecryptionCipher}. + * @param salt is the big-endian 2 byte salt that will be used in the Nearby + * Presence advertisement, which will be incorporated into the tweaks LDT uses + * while encrypting. + * @param data 16-31 bytes of ciphertext data to be decrypted + * @return 0 on success, in which case `buffer` will now contain ciphertext or a non-zero + * an error code on failure, in which case data will remain unchanged. + */ + @DecryptErrorCode + static native int decryptAndVerify(long ldtDecryptHandle, char salt, byte[] data); }
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt new file mode 100644 index 0000000..8f080e8 --- /dev/null +++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt
@@ -0,0 +1,210 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.gms.nearby.presence.hazmat + +import androidx.annotation.IntDef +import com.google.android.gms.nearby.presence.hazmat.LdtNpJni.DecryptErrorCode +import com.google.android.gms.nearby.presence.hazmat.LdtNpJni.EncryptErrorCode + +private const val KEY_SEED_SIZE = 32 +private const val TAG_SIZE = 32 +private const val BLOCK_SIZE = 16 + +// Error return value for create operations +private const val CREATE_HANDLE_ERROR = 0L + +// Status code returned on successful cipher operations +private const val SUCCESS = 0 + +/** + * A 2 byte salt that will be used in the advertisement with this encrypted payload. + */ +class Salt(b1: Byte, b2: Byte) { + private val saltBytes: Char + + // Returns the bytes of the salt represented as a 2 byte Char type + internal fun getBytesAsChar(): Char { + return saltBytes + } + + init { + // byte widening conversion to int sign-extends + val highBits = b1.toInt() shl 8 + val lowBits = b2.toInt() and 0xFF + // narrowing conversion truncates to low 16 bits + saltBytes = (highBits or lowBits).toChar() + } +} + +/** A runtime exception thrown if something fails during a Ldt jni call*/ +class LdtJniException internal constructor(message: String?) : RuntimeException(message) + +/** + * A checked exception which occurs if the calculated hmac tag does not match the expected hmac + * tag during decrypt operations + */ +class MacMismatchException internal constructor(message: String?) : Exception(message) + +/** + * Create a new LdtEncryptionCipher instance using LDT-XTS-AES128 with the "swap" mix function. + * + * @constructor Creates a new instance from the provided keySeed + * @param keySeed is the key material from the Nearby Presence credential from which + * the LDT key will be derived. + * @return an instance configured with the supplied key seed + * @throws IllegalArgumentException if the keySeed is the wrong size + * @throws LdtJniException if creating the instance fails + */ +class LdtEncryptionCipher @Throws( + LdtJniException::class, + IllegalArgumentException::class +) constructor(keySeed: ByteArray) : + AutoCloseable { + @Volatile + private var closed = false + private val handle: Long + + init { + require(keySeed.size == KEY_SEED_SIZE) + handle = LdtNpJni.createEncryptionCipher(keySeed) + if (handle == CREATE_HANDLE_ERROR) { + throw LdtJniException("Creating ldt encryption cipher native resources failed") + } + } + + /** + * Encrypt a 16-31 byte buffer in-place. + * + * @param salt the salt that will be used in the advertisement with this encrypted payload. + * @param data plaintext to encrypt in place: the metadata key followed by the data elements to be + * encrypted. The length must be in [16, 31). + * @throws IllegalStateException if this instance has already been closed + * @throws IllegalArgumentException if data is the wrong length + * @throws LdtJniException if encryption fails + */ + @Throws(LdtJniException::class, IllegalArgumentException::class, IllegalStateException::class) + fun encrypt(salt: Salt, data: ByteArray) { + check(!closed) { "Use after free! This instance has already been closed" } + requireValidSizeData(data.size) + + when (val res = LdtNpJni.encrypt(handle, salt.getBytesAsChar(), data)) { + EncryptErrorCode.JNI_OP_ERROR -> throw LdtJniException("Error during jni encrypt operation: error code $res"); + EncryptErrorCode.DATA_LEN_ERROR -> check(false) // this will never happen, lengths checked above + else -> check(res == SUCCESS) + } + } + + /** + * Releases native resources. + * + * <p>Once closed, a Ldt instance cannot be used further. + */ + override fun close() { + if (!closed) { + closed = true + LdtNpJni.closeEncryptCipher(handle) + } + } +} + +/** + * A LdtDecryptionCipher instance which uses LDT-XTS-AES128 with the "swap" mix function. + * + * @constructor Creates a new instance from the provided keySeed and hmacTag + * @param keySeed is the key material from the Nearby Presence credential from which + * the LDT key will be derived. + * @param hmacTag is the hmac auth tag calculated on the metadata key used to verify + * decryption was successful. + * @return an instance configured with the supplied key seed + * @throws IllegalArgumentException if the keySeed is the wrong size + * @throws IllegalArgumentException if the hmacTag is the wrong size + * @throws LdtJniException if creating the instance fails + */ +class LdtDecryptionCipher @Throws( + LdtJniException::class, + IllegalArgumentException::class +) constructor(keySeed: ByteArray, hmacTag: ByteArray) : AutoCloseable { + @Volatile + private var closed = false + private val handle: Long + + init { + require(keySeed.size == KEY_SEED_SIZE) + require(hmacTag.size == TAG_SIZE) + handle = LdtNpJni.createDecryptionCipher(keySeed, hmacTag) + if (handle == CREATE_HANDLE_ERROR) { + throw LdtJniException("Creating ldt decryption cipher native resources failed") + } + } + + /** Error codes which map to return values on the native side. */ + @IntDef(DecryptAndVerifyResultCode.SUCCESS, DecryptAndVerifyResultCode.MAC_MISMATCH) + annotation class DecryptAndVerifyResultCode { + companion object { + const val SUCCESS = 0 + const val MAC_MISMATCH = -1 + } + } + + /** + * Decrypt a 16-31 byte buffer in-place and verify the plaintext metadata key matches + * this item's MAC, if not the buffer will not be decrypted. + * + * @param salt the salt extracted from the advertisement that contained this payload. + * @param data ciphertext to decrypt in place: the metadata key followed by the data elements to + * be decrypted. The length must be in [16, 31). + * @return a [DecryptAndVerifyResultCode] indicating of the decrypt operation failed or succeeded. + * In the case of a failed decrypt, the provided plaintext will not change. + * @throws IllegalStateException if this instance has already been closed + * @throws IllegalArgumentException if data is the wrong length + * @throws LdtJniException if decryption fails + */ + @Throws( + IllegalStateException::class, + IllegalArgumentException::class, + LdtJniException::class + ) + @DecryptAndVerifyResultCode + fun decryptAndVerify(salt: Salt, data: ByteArray): Int { + check(!closed) { "Double free! Close should only ever be called once" } + requireValidSizeData(data.size) + + when (val res = LdtNpJni.decryptAndVerify(handle, salt.getBytesAsChar(), data)) { + DecryptErrorCode.MAC_MISMATCH -> return DecryptAndVerifyResultCode.MAC_MISMATCH + DecryptErrorCode.DATA_LEN_ERROR -> check(false); // This condition is impossible, we validated data length above + DecryptErrorCode.JNI_OP_ERROR -> throw LdtJniException("Error occurred during jni encrypt operation") + else -> check(res == SUCCESS) + } + return DecryptAndVerifyResultCode.SUCCESS + } + + /** + * Releases native resources. + * + * <p>Once closed, a Ldt instance cannot be used further. + */ + override fun close() { + if (!closed) { + closed = true + LdtNpJni.closeEncryptCipher(handle) + } + } +} + +private fun requireValidSizeData(size: Int) { + require(size >= BLOCK_SIZE && size < BLOCK_SIZE * 2) { "Invalid size data: $size" } +} +
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt index dc861c6..07d29a3 100644 --- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt +++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt
@@ -16,27 +16,143 @@ package com.google.android.gms.nearby.presence.hazmat -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +const val KEY_SEED = "CCDB2489E9FCAC42B39348B8941ED19A1D360E75E098C8C15E6B1CC2B620CD39" +const val HMAC_TAG = "DFB90A1F9B1FE28D18BBCCA52240B5CC2CCB5F8D5289A3CB64EB3541CA614BB4" +const val PLAINTEXT = "CD683FE1A1D1F846543D0A13D4AEA40040C8D67B" +const val SALT_BYTES = "32EE" +const val EXPECTED_CIPHER_TEXT = "04344411F1E57C841FE0F7150636BC782455059A" class LdtNpJniTests { @Test - fun ldtRoundTripTest() { + fun roundTripTest() { // Data taken from first test case in ldt_np_adv/resources/test/np_adv_test_vectors.json - val key_seed = "CCDB2489E9FCAC42B39348B8941ED19A1D360E75E098C8C15E6B1CC2B620CD39".decodeHex() - val hmac_tag = "DFB90A1F9B1FE28D18BBCCA52240B5CC2CCB5F8D5289A3CB64EB3541CA614BB4".decodeHex() - val plaintext = "CD683FE1A1D1F846543D0A13D4AEA40040C8D67B".decodeHex() - val salt_bytes = "32EE".decodeHex() - val expected_ciphertext = "04344411F1E57C841FE0F7150636BC782455059A".decodeHex() - val salt = LdtNpCipher.saltAsChar(salt_bytes[0], salt_bytes[1]) + val keySeed = KEY_SEED.decodeHex() + val hmacTag = HMAC_TAG.decodeHex() + val plaintext = PLAINTEXT.decodeHex() + val saltBytes = SALT_BYTES.decodeHex() + val expectedCiphertext = EXPECTED_CIPHER_TEXT.decodeHex() + val salt = Salt(saltBytes[0], saltBytes[1]) val data = plaintext.copyOf() - val LdtCipher = LdtNpCipher.fromKey(key_seed, hmac_tag) - LdtCipher.encrypt(salt, data) - Assertions.assertArrayEquals(expected_ciphertext, data) + val encryptionCipher = LdtEncryptionCipher(keySeed) + encryptionCipher.encrypt(salt, data) + assertArrayEquals(expectedCiphertext, data) + encryptionCipher.close() - LdtCipher.decrypt_and_verify(salt, data) - Assertions.assertArrayEquals(plaintext, data) + + val decryptionCipher = LdtDecryptionCipher(keySeed, hmacTag) + val result = decryptionCipher.decryptAndVerify(salt, data) + assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.SUCCESS, result) + assertArrayEquals(plaintext, data) + decryptionCipher.close() + } + + @Test + fun createEncryptionCipherInvalidLength() { + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(31) + LdtEncryptionCipher(keySeed) + } + + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(33) + LdtEncryptionCipher(keySeed) + } + } + + @Test + fun encryptInvalidLengthData() { + val keySeed = KEY_SEED.decodeHex() + val cipher = LdtEncryptionCipher(keySeed) + assertThrows<IllegalArgumentException> { + var data = ByteArray(15) + cipher.encrypt(Salt(0x0, 0x0), data) + } + assertThrows<IllegalArgumentException> { + var data = ByteArray(32) + cipher.encrypt(Salt(0x0, 0x0), data) + } + } + + @Test + fun encryptUseAfterClose() { + val keySeed = KEY_SEED.decodeHex() + val cipher = LdtEncryptionCipher(keySeed) + val data = ByteArray(20) + cipher.close() + assertThrows<IllegalStateException> { cipher.encrypt(Salt(0x0, 0x0), data) } + } + + @Test + fun createDecryptionCipherInvalidLengths() { + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(31) + val hmacTag = ByteArray(31) + LdtDecryptionCipher(keySeed, hmacTag) + } + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(33) + val hmacTag = ByteArray(33) + LdtDecryptionCipher(keySeed, hmacTag) + } + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(32) + val hmacTag = ByteArray(33) + LdtDecryptionCipher(keySeed, hmacTag) + } + assertThrows<IllegalArgumentException> { + val keySeed = ByteArray(33) + val hmacTag = ByteArray(32) + LdtDecryptionCipher(keySeed, hmacTag) + } + } + + @Test + fun decryptInvalidLengthData() { + val keySeed = KEY_SEED.decodeHex() + val hmacTag = HMAC_TAG.decodeHex() + val cipher = LdtDecryptionCipher(keySeed, hmacTag) + assertThrows<IllegalArgumentException> { + var data = ByteArray(15) + cipher.decryptAndVerify(Salt(0x0, 0x0), data) + } + assertThrows<IllegalArgumentException> { + var data = ByteArray(32) + cipher.decryptAndVerify(Salt(0x0, 0x0), data) + } + } + + @Test + fun decryptMacMismatch() { + val keySeed = KEY_SEED.decodeHex() + val hmacTag = HMAC_TAG.decodeHex() + + // alter first byte in the hmac tag + hmacTag[0] = 0x00 + val cipher = LdtDecryptionCipher(keySeed, hmacTag) + + val cipherText = EXPECTED_CIPHER_TEXT.decodeHex() + val saltBytes = SALT_BYTES.decodeHex() + val salt = Salt(saltBytes[0], saltBytes[1]) + + val result = cipher.decryptAndVerify(salt, cipherText); + assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.MAC_MISMATCH, result) + } + + @Test + fun decryptUseAfterClose() { + val keySeed = KEY_SEED.decodeHex() + val hmacTag = HMAC_TAG.decodeHex() + val cipher = LdtDecryptionCipher(keySeed, hmacTag) + cipher.close() + + val data = ByteArray(20) + assertThrows<IllegalStateException> { cipher.decryptAndVerify(Salt(0x0, 0x0), data) } } }
diff --git a/nearby/presence/ldt_np_jni/src/lib.rs b/nearby/presence/ldt_np_jni/src/lib.rs index 8f7958e..538a44e 100644 --- a/nearby/presence/ldt_np_jni/src/lib.rs +++ b/nearby/presence/ldt_np_jni/src/lib.rs
@@ -20,6 +20,8 @@ //! - <https://www.iitk.ac.in/esc101/05Aug/tutorial/native1.1/index.html> //! - <https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html> +// We are not actually no_std because the jni crate is pulling it in, but at least this enforces +// that this lib isn't using anything from the std lib #![no_std] #![deny(missing_docs)] @@ -28,10 +30,9 @@ use alloc::boxed::Box; -use jni::objects::JByteArray; use jni::{ - objects::JClass, - sys::{jbyte, jbyteArray, jchar, jint, jlong}, + objects::{JByteArray, JClass}, + sys::{jbyte, jchar, jint, jlong}, JNIEnv, }; @@ -45,149 +46,199 @@ const MIN_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE; const MAX_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE * 2 - 1; -/// Error return value for creating handles +/// Required size constraints of input parameters +const KEY_SEED_SIZE: usize = 32; +const TAG_SIZE: usize = 32; + +/// Error return value for create operations const CREATE_ERROR: jlong = 0; -// TODO: don't allow panics to cross FFI boundary -// TODO: JNI null checks? (only if jni crate isn't doing them already). +/// Status code returned on successful cipher operations +const SUCCESS: jint = 0; -// TODO: split this into separate APIs for encrypt and decrypt -struct Ldt { - ldt_enc: LdtEncrypterXtsAes128<CryptoProviderImpl>, - ldt_dec: LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>, -} +type LdtAdvDecrypter = LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>; +type LdtAdvEncrypter = LdtEncrypterXtsAes128<CryptoProviderImpl>; -/// Create an LDT cipher. +/// Marker trait to ensure above types are thread safe +trait JniThreadSafe: Send + Sync {} + +impl JniThreadSafe for LdtAdvDecrypter {} + +impl JniThreadSafe for LdtAdvEncrypter {} + +/// Create a LDT Encryption cipher. /// -/// Returns 0 for error, or the pointer as a jlong/i64. -/// Safety: We know the key pointer is safe as it is coming directly from the JVM. +/// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success. #[no_mangle] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createLdtCipher( +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createEncryptionCipher( env: JNIEnv, _class: JClass, - key_seed: jbyteArray, - metadata_key_hmac_tag: jbyteArray, + java_key_seed: JByteArray, ) -> jlong { - env.get_array_length(unsafe { &JByteArray::from_raw(key_seed) }) - .map_err(|_| CREATE_ERROR) - // check length - .and_then(|len| if len as usize != 32 { Err(CREATE_ERROR) } else { Ok(len) }) - // extract u8 array - .and_then(|len| { - let mut jbyte_buf = [jbyte::default(); 32]; - env.get_byte_array_region( - unsafe { &JByteArray::from_raw(key_seed) }, - 0, - &mut jbyte_buf[..], - ) - .map_err(|_| CREATE_ERROR) - .map(|_| (len, jbyte_array_to_u8_array(jbyte_buf))) - }) - // initialize ldt -- we already know the key is the right length - .and_then(|(_len, key_seed_buf)| { - let hkdf_key_seed = NpKeySeedHkdf::new(&key_seed_buf); - let ldt_enc = ldt_np_adv::LdtEncrypterXtsAes128::<CryptoProviderImpl>::new( - &hkdf_key_seed.legacy_ldt_key(), - ); + create_map_to_error(|| { + let key_seed = + env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| { + if seed.len() != KEY_SEED_SIZE { + Err(CREATE_ERROR) + } else { + Ok(seed) + } + })?; - let mut tag_buff = [jbyte::default(); 32]; - let tag = env - .get_byte_array_region( - unsafe { &JByteArray::from_raw(metadata_key_hmac_tag) }, - 0, - &mut tag_buff[..], - ) - .map_err(|_| CREATE_ERROR) - .map(|_| jbyte_array_to_u8_array(tag_buff)) - .unwrap(); - // TODO: Error handling + let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new( + key_seed.as_slice().try_into().expect("Length is checked above"), + ); - let ldt_dec = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>( - &hkdf_key_seed, - tag, - ); - box_to_handle(Ldt { ldt_enc, ldt_dec }).map_err(|_| CREATE_ERROR) - }) - .unwrap_or_else(|e| e) + let cipher = LdtAdvEncrypter::new(&hkdf_key_seed.legacy_ldt_key()); + box_to_handle(cipher).map_err(|_| CREATE_ERROR) + }) } -/// Close an LDT cipher. +/// Create a LDT Decryption cipher. +/// +/// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success. #[no_mangle] -pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeLdtCipher( +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createDecryptionCipher( + env: JNIEnv, + _class: JClass, + java_key_seed: JByteArray, + java_hmac_tag: JByteArray, +) -> jlong { + create_map_to_error(|| { + let key_seed = + env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| { + if seed.len() != KEY_SEED_SIZE { + Err(CREATE_ERROR) + } else { + Ok(seed) + } + })?; + let hmac_tag = + env.convert_byte_array(&java_hmac_tag).map_err(|_| CREATE_ERROR).and_then(|tag| { + if tag.len() != TAG_SIZE { + Err(CREATE_ERROR) + } else { + Ok(tag) + } + })?; + let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new( + key_seed.as_slice().try_into().expect("Length is checked above"), + ); + + let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>( + &hkdf_key_seed, + hmac_tag.as_slice().try_into().expect("Length is checked above"), + ); + box_to_handle(cipher).map_err(|_| CREATE_ERROR) + }) +} + +fn create_map_to_error<F: Fn() -> Result<jlong, jlong>>(f: F) -> jlong { + f().unwrap_or_else(|e| e) +} + +/// Close an LDT Encryption Cipher +#[no_mangle] +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeEncryptCipher( _env: JNIEnv, _class: JClass, - ldt_handle: jlong, -) -> jint { + handle: jlong, +) { // create the box, let it be dropped - let _ = boxed_from_handle::<Ldt>(ldt_handle); - // success -- are there any meaningful error condtions we can even detect? - 0 + let _ = boxed_from_handle::<LdtAdvEncrypter>(handle); +} + +/// Close an LDT Decryption Cipher +#[no_mangle] +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeDecryptCipher( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + // create the box, let it be dropped + let _ = boxed_from_handle::<LdtAdvDecrypter>(handle); } /// Encrypt a buffer in place. -/// Safety: We know the data jArray pointer is safe because it is coming directly from the JVM. #[no_mangle] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt( +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt( env: JNIEnv, _class: JClass, - ldt_handle: jlong, + handle: jlong, salt: jchar, - data: jbyteArray, + data: JByteArray, ) -> jint { - jbyte_cipher_data_as_u8_array(&env, data) - .and_then(|(len, mut data_u8)| { - with_handle::<Ldt, _, _>(ldt_handle, |ldt| { - ldt.ldt_enc.encrypt(&mut data_u8[..len], &expand_np_salt_to_padder(salt)).map_err( - |err| match err { - ldt::LdtError::InvalidLength(_) => CipherOpError::DataLen, - }, - )?; - env.set_byte_array_region( - unsafe { &JByteArray::from_raw(data) }, - 0, - &u8_slice_to_jbyte_array(data_u8)[..len], - ) - .map_err(|_| CipherOpError::JniOp) - .map(|_| 0) // success - }) + map_to_error_code(|| { + let mut buffer = + env.convert_byte_array(&data).map_err(|_| EncryptError::JniOp).and_then(|data| { + if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) { + Err(EncryptError::DataLen) + } else { + Ok(data) + } + })?; + + with_handle::<LdtAdvEncrypter, _, _>(handle, |cipher| { + cipher.encrypt(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt)).map_err( + |err| match err { + ldt::LdtError::InvalidLength(_) => EncryptError::DataLen, + }, + )?; + + // Avoid a copy since transmuting from a &[u8] to a &[i8] is safe + // Safety: + // - u8 and jbyte/i8 are the same size have the same alignment + let jbyte_buffer = bytes_to_jbytes(buffer.as_slice()); + + env.set_byte_array_region(&data, 0, jbyte_buffer) + .map_err(|_| EncryptError::JniOp) + .map(|_| SUCCESS) }) - .unwrap_or_else(|e| e.to_jni_error_code()) + }) } /// Decrypt a buffer in place. /// Safety: We know the data pointer is safe because it is coming directly from the JVM. #[no_mangle] -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decrypt_1and_1verify( +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decryptAndVerify( env: JNIEnv, _class: JClass, - ldt_handle: jlong, + handle: jlong, salt: jchar, - data: jbyteArray, + data: JByteArray, ) -> jint { - jbyte_cipher_data_as_u8_array(&env, data) - .and_then(|(len, mut data_u8)| { - with_handle::<Ldt, _, _>(ldt_handle, |ldt| { - let result = ldt - .ldt_dec - .decrypt_and_verify(&data_u8[..len], &expand_np_salt_to_padder(salt)) - .map_err(|err| match err { - LdtAdvDecryptError::InvalidLength(_) => CipherOpError::DataLen, - LdtAdvDecryptError::MacMismatch => CipherOpError::MacMisMatch, - })?; - data_u8[..result.len()].copy_from_slice(result.as_slice()); - env.set_byte_array_region( - unsafe { &JByteArray::from_raw(data) }, - 0, - &u8_slice_to_jbyte_array(data_u8)[..len], - ) - .map_err(|_| CipherOpError::JniOp) - .map(|_| 0) // success - }) + map_to_error_code(|| { + let mut buffer = + env.convert_byte_array(&data).map_err(|_| DecryptError::JniOp).and_then(|data| { + if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) { + Err(DecryptError::DataLen) + } else { + Ok(data) + } + })?; + + with_handle::<LdtAdvDecrypter, _, _>(handle, |cipher| { + let result = cipher + .decrypt_and_verify(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt)) + .map_err(|err| match err { + LdtAdvDecryptError::InvalidLength(_) => DecryptError::DataLen, + LdtAdvDecryptError::MacMismatch => DecryptError::MacMisMatch, + })?; + + let jbyte_buffer = bytes_to_jbytes(result.as_slice()); + + env.set_byte_array_region(&data, 0, jbyte_buffer) + .map_err(|_| DecryptError::JniOp) + .map(|_| SUCCESS) }) - .unwrap_or_else(|e| e.to_jni_error_code()) + }) +} + +/// A zero-copy conversion from a u8 slice to a jbyte slice +fn bytes_to_jbytes(bytes: &[u8]) -> &[jbyte] { + // Safety: + // - u8 and jbyte/i8 are the same size have the same alignment + unsafe { alloc::slice::from_raw_parts(bytes.as_ptr() as *const jbyte, bytes.len()) } } /// Reconstruct a `Box<T>` from `handle`, and invoke `f` with the resulting `&T`. @@ -202,7 +253,6 @@ // don't consume the box -- need to keep the handle alive Box::leak(boxed); - ret } @@ -243,52 +293,6 @@ .map(|ptr_64: u64| ptr_64 as jlong) } -/// Extract data suitable for Ldt128 cipher ops from a JNI jbyteArray. -/// -/// Returns `(data len in buffer, buffer)`, or `Err` if any JNI ops fail. -fn jbyte_cipher_data_as_u8_array( - env: &JNIEnv, - cipher_data: jbyteArray, -) -> Result<(usize, [u8; MAX_DATA_LEN]), CipherOpError> { - let data_len = env - .get_array_length(unsafe { &JByteArray::from_raw(cipher_data) }) - .map_err(|_| CipherOpError::JniOp)? as usize; - if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data_len) { - return Err(CipherOpError::DataLen); - } - - let mut buf = [jbyte::default(); MAX_DATA_LEN]; - env.get_byte_array_region( - unsafe { &JByteArray::from_raw(cipher_data) }, - 0, - &mut buf[0..data_len], - ) - .map_err(|_| CipherOpError::JniOp)?; - - Ok((data_len, jbyte_array_to_u8_array(buf))) -} - -/// Convert a jbyte array to a u8 array -fn jbyte_array_to_u8_array<const N: usize>(src: [jbyte; N]) -> [u8; N] { - let mut dest = [0_u8; N]; - for i in 0..N { - // numeric cast doesn't alter bits, which is what we want - // https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics - dest[i] = src[i] as u8; - } - dest -} - -fn u8_slice_to_jbyte_array<const N: usize>(src: [u8; N]) -> [jbyte; N] { - let mut dest = [0_i8; N]; - for i in 0..N { - // numeric cast doesn't alter bits, which is what we want - // https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics - dest[i] = src[i] as jbyte; - } - dest -} - /// Expand the NP salt to the size needed to be an LDT XorPadder. /// /// Returns a XorPadder containing the HKDF of the salt. @@ -297,23 +301,48 @@ ldt_np_adv::salt_padder::<16, CryptoProviderImpl>(salt_bytes.into()) } +fn map_to_error_code<E: JniError, F: Fn() -> Result<jint, E>>(f: F) -> jint { + f().unwrap_or_else(|e| e.to_jni_error_code()) +} + +trait JniError { + fn to_jni_error_code(&self) -> jint; +} + #[derive(Debug)] -enum CipherOpError { - /// The mac did not match the provided tag - MacMisMatch, +enum EncryptError { /// Data is the wrong length DataLen, /// JNI op failed JniOp, } -impl CipherOpError { +impl JniError for EncryptError { + fn to_jni_error_code(&self) -> jint { + match self { + Self::DataLen => -1, + Self::JniOp => -2, + } + } +} + +#[derive(Debug)] +enum DecryptError { + /// Data is the wrong length + DataLen, + /// The mac did not match the provided tag + MacMisMatch, + /// JNI op failed + JniOp, +} + +impl JniError for DecryptError { /// Returns an error code suitable for returning from Ldt encrypt/decrypt JNI calls. fn to_jni_error_code(&self) -> jint { match self { - CipherOpError::DataLen => -1, - CipherOpError::MacMisMatch => -2, - CipherOpError::JniOp => -3, + Self::DataLen => -1, + Self::JniOp => -2, + Self::MacMisMatch => -3, } } }
diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml index 4c8aff5..5123737 100644 --- a/nearby/presence/np_adv/Cargo.toml +++ b/nearby/presence/np_adv/Cargo.toml
@@ -14,29 +14,43 @@ crypto_provider.workspace = true strum.workspace = true strum_macros.workspace = true -nom = { version = "7.1.1", features = ["alloc"] } +nom = { version = "7.1.3", default-features = false, features = ["alloc"] } lazy_static.workspace = true sink.workspace = true tinyvec.workspace = true -rand.workspace = true [features] default = [] devtools = [] testing = [] +alloc = ["crypto_provider/alloc"] [dev-dependencies] hex.workspace = true +rand.workspace = true rand_ext = { path = "../rand_ext" } init_with = "1.1.0" -serde_json.workspace = true +serde_json = {workspace = true, features = ["std"]} +serde.workspace = true anyhow.workspace = true test_helper = { path = "../test_helper" } criterion.workspace = true -crypto_provider_default = {workspace = true, features = ["std", "rustcrypto"]} -np_ed25519 = {workspace = true, features = ["std"]} -sink = {workspace = true, features = ["std"]} +crypto_provider_default = { workspace = true, features = ["std", "rustcrypto"] } +np_ed25519 = { workspace = true, features = ["std"] } +rmp-serde = "1.1.2" +sink = { workspace = true, features = ["std"] } [[bench]] name = "deser_adv" harness = false +required-features = ["alloc", "devtools"] + +[[test]] +name = "examples_v0" +path = "tests/examples_v0.rs" +required-features = ["alloc"] + +[[test]] +name = "examples_v1" +path = "tests/examples_v1.rs" +required-features = ["alloc"]
diff --git a/nearby/presence/np_adv/benches/deser_adv.rs b/nearby/presence/np_adv/benches/deser_adv.rs index 9699ebf..e998047 100644 --- a/nearby/presence/np_adv/benches/deser_adv.rs +++ b/nearby/presence/np_adv/benches/deser_adv.rs
@@ -16,26 +16,29 @@ use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; -use ldt_np_adv::{LdtEncrypterXtsAes128, LegacySalt}; +use ldt_np_adv::LegacySalt; +use np_adv::deserialization_arena; +use np_adv::extended::serialize::AdvertisementType; use np_adv::{ - credential::{simple::*, source::*, v0::*, v1::*}, + credential::{book::*, v0::*, v1::*, *}, de_type::EncryptedIdentityDataElementType, - deserialize_advertisement, deserialize_v0_advertisement, deserialize_v1_advertisement, + deserialize_advertisement, extended::{ data_elements::{GenericDataElement, TxPowerDataElement}, deserialize::VerificationMode, serialize::{ - AdvBuilder as ExtendedAdvBuilder, MicEncrypted, SectionBuilder, SectionIdentity, - SignedEncrypted, + AdvBuilder as ExtendedAdvBuilder, MicEncryptedSectionEncoder, PublicSectionEncoder, + SectionBuilder, SectionEncoder, SignedEncryptedSectionEncoder, }, }, legacy::{ actions::{ActionBits, ActionsDataElement}, serialize::{AdvBuilder as LegacyAdvBuilder, LdtIdentity}, + ShortMetadataKey, }, shared_data::{ContextSyncSeqNum, TxPower}, - PublicIdentity, + MetadataKey, PublicIdentity, }; use rand::{Rng as _, SeedableRng as _}; use strum::IntoEnumIterator; @@ -58,21 +61,22 @@ .map(|_| V1Identity::random(&mut crypto_rng)) .collect::<Vec<_>>(); - let mut adv_builder = ExtendedAdvBuilder::new(); + let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Encrypted); // take the first n identities, one section per identity for identity in identities.iter().take(num_sections) { - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new( - &identity.key_seed, + let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new( + identity.key_seed, + identity.extended_metadata_key, + identity.key_pair.private_key(), ); match identity_type { VerificationMode::Mic => { let mut sb = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Private, - &identity.extended_metadata_key, - &hkdf, + &broadcast_cm, )) .unwrap(); @@ -81,12 +85,10 @@ } VerificationMode::Signature => { let mut sb = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Private, - &identity.extended_metadata_key, - &identity.key_pair, - &hkdf, + &broadcast_cm, )) .unwrap(); @@ -98,22 +100,11 @@ let adv = adv_builder.into_advertisement(); - match crypto_type { - CryptoMaterialType::MinFootprint => run_with_v1_creds::< - MinimumFootprintV1CryptoMaterial, - CryptoProviderImpl, - >( - b, identities, adv.as_slice() - ), - CryptoMaterialType::Precalculated => { - run_with_v1_creds::< - PrecalculatedV1CryptoMaterial, - CryptoProviderImpl, - >( - b, identities, adv.as_slice() - ) - } - } + run_with_v1_creds::< + CryptoProviderImpl + >( + b, crypto_type, identities, adv.as_slice() + ) }, ); } @@ -123,31 +114,23 @@ } pub fn deser_adv_v1_plaintext(c: &mut Criterion) { - let _rng = rand::rngs::StdRng::from_entropy(); + c.bench_function("Deser V1 plaintext: sections=1", |b| { + let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Plaintext); - for &num_sections in &[1, 2, 5, 8] { - // measure worst-case performance -- the correct identities will be the last - // num_sections of the identities to be tried - c.bench_function(&format!("Deser V1 plaintext: sections={num_sections}"), |b| { - let mut adv_builder = ExtendedAdvBuilder::new(); + let mut sb = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); - // take the first n identities, one section per identity - for _ in 0..num_sections { - let mut sb = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + add_des(&mut sb); + sb.add_to_advertisement(); - add_des(&mut sb); - sb.add_to_advertisement(); - } + let adv = adv_builder.into_advertisement(); - let adv = adv_builder.into_advertisement(); - - run_with_v1_creds::<MinimumFootprintV1CryptoMaterial, CryptoProviderImpl>( - b, - vec![], - adv.as_slice(), - ) - }); - } + run_with_v1_creds::<CryptoProviderImpl>( + b, + CryptoMaterialType::MinFootprint, + vec![], + adv.as_slice(), + ) + }); } pub fn deser_adv_v0_encrypted(c: &mut Criterion) { @@ -164,17 +147,17 @@ .collect::<Vec<_>>(); let identity = &identities[0]; - let hkdf = - np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&identity.key_seed); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new( + identity.key_seed, + identity.legacy_metadata_key, + ); let mut adv_builder = LegacyAdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( EncryptedIdentityDataElementType::Private, LegacySalt::from(rng.gen::<[u8; 2]>()), - identity.legacy_metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new( - &hkdf.legacy_ldt_key(), - ), + &broadcast_cm, )); let mut action_bits = ActionBits::default(); @@ -183,46 +166,31 @@ let adv = adv_builder.into_advertisement().unwrap(); - match crypto_type { - CryptoMaterialType::MinFootprint => run_with_v0_creds::< - MinimumFootprintV0CryptoMaterial, - CryptoProviderImpl, - >( - b, identities, adv.as_slice() - ), - CryptoMaterialType::Precalculated => run_with_v0_creds::< - PrecalculatedV0CryptoMaterial, - CryptoProviderImpl, - >( - b, identities, adv.as_slice() - ), - } + run_with_v0_creds::<CryptoProviderImpl>( + b, + crypto_type, + identities, + adv.as_slice(), + ) }, ); } } } -type DefaultV0Credential = np_adv::credential::simple::SimpleV0Credential< - np_adv::credential::v0::MinimumFootprintV0CryptoMaterial, - (), ->; -type DefaultV1Credential = np_adv::credential::simple::SimpleV1Credential< - np_adv::credential::v1::MinimumFootprintV1CryptoMaterial, - (), ->; -type DefaultBothCredentialSource = - np_adv::credential::source::OwnedBothCredentialSource<DefaultV0Credential, DefaultV1Credential>; - pub fn deser_adv_v0_plaintext(c: &mut Criterion) { - let mut adv_builder = LegacyAdvBuilder::new(PublicIdentity::default()); + let mut adv_builder = LegacyAdvBuilder::new(PublicIdentity); let mut action_bits = ActionBits::default(); action_bits.set_action(ContextSyncSeqNum::try_from(3).unwrap()); adv_builder.add_data_element(ActionsDataElement::from(action_bits)).unwrap(); let adv = adv_builder.into_advertisement().unwrap(); - let cred_source: DefaultBothCredentialSource = DefaultBothCredentialSource::new_empty(); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); for &num_advs in &[1, 10, 100, 1000] { c.bench_function( @@ -231,9 +199,10 @@ b.iter(|| { for _ in 0..num_advs { black_box( - deserialize_advertisement::<_, _, _, _, CryptoProviderImpl>( + deserialize_advertisement::<_, CryptoProviderImpl>( + deserialization_arena!(), black_box(adv.as_slice()), - black_box(&cred_source), + black_box(&cred_book), ) .expect("Should succeed"), ); @@ -246,22 +215,20 @@ /// Benchmark decrypting a V0 advertisement with credentials built from the reversed list of /// identities -fn run_with_v0_creds<M, C>(b: &mut Bencher, identities: Vec<V0Identity<C>>, adv: &[u8]) -where - M: V0CryptoMaterialExt, +fn run_with_v0_creds<C>( + b: &mut Bencher, + crypto_material_type: CryptoMaterialType, + identities: Vec<V0Identity<C>>, + adv: &[u8], +) where C: CryptoProvider, { let mut creds = identities .into_iter() - .map(|i| { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&i.key_seed); - SimpleV0Credential::new( - M::build_cred::<C>( - i.key_seed, - hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&i.legacy_metadata_key), - ), - i.key_seed, - ) + .map(|identity| identity.into_discovery_credential()) + .map(|discovery_credential| MatchableCredential { + discovery_credential, + match_data: EmptyMatchedCredential, }) .collect::<Vec<_>>(); @@ -269,34 +236,55 @@ // cred source for predictably bad performance creds.reverse(); - let cred_source = SliceCredentialSource::new(&creds); - b.iter(|| { - black_box(deserialize_v0_advertisement::<_, _, C>(adv, &cred_source).map(|_| 0_u8).unwrap()) - }); + match crypto_material_type { + CryptoMaterialType::MinFootprint => { + // Cache size of 0 => only min-footprint creds + let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&creds, &[]); + + b.iter(|| { + black_box( + deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book) + .map(|_| 0_u8) + .unwrap(), + ) + }); + } + CryptoMaterialType::Precalculated => { + let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>( + creds, + core::iter::empty(), + ); + b.iter(|| { + black_box( + deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book) + .map(|_| 0_u8) + .unwrap(), + ) + }); + } + } } /// Benchmark decrypting a V1 advertisement with credentials built from the reversed list of /// identities -fn run_with_v1_creds<M, C>(b: &mut Bencher, identities: Vec<V1Identity<C>>, adv: &[u8]) -where - M: V1CryptoMaterialExt, +fn run_with_v1_creds<C>( + b: &mut Bencher, + crypto_material_type: CryptoMaterialType, + identities: Vec<V1Identity<C>>, + adv: &[u8], +) where C: CryptoProvider, { let mut creds = identities .into_iter() - .map(|i| { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&i.key_seed); - SimpleV1Credential::new( - M::build_cred::<C>( - i.key_seed, - hkdf.extended_unsigned_metadata_key_hmac_key() - .calculate_hmac(&i.extended_metadata_key), - hkdf.extended_signed_metadata_key_hmac_key() - .calculate_hmac(&i.extended_metadata_key), - i.key_pair.public(), - ), - i.key_seed, - ) + .map(|identity| identity.into_discovery_credential()) + .map(|discovery_credential| MatchableCredential { + discovery_credential, + match_data: EmptyMatchedCredential, }) .collect::<Vec<_>>(); @@ -304,12 +292,39 @@ // cred source for predictably bad performance creds.reverse(); - let cred_source = SliceCredentialSource::new(&creds); - b.iter(|| { - black_box(deserialize_v1_advertisement::<_, _, C>(adv, &cred_source).map(|_| 0_u8).unwrap()) - }); + match crypto_material_type { + CryptoMaterialType::MinFootprint => { + // Cache size of 0 => only min-footprint creds + let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &creds); + + b.iter(|| { + black_box( + deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book) + .map(|_| 0_u8) + .unwrap(), + ) + }); + } + CryptoMaterialType::Precalculated => { + let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>( + core::iter::empty(), + creds, + ); + b.iter(|| { + black_box( + deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book) + .map(|_| 0_u8) + .unwrap(), + ) + }); + } + } } -fn add_des<I: SectionIdentity>(sb: &mut SectionBuilder<I>) { +fn add_des<I: SectionEncoder>(sb: &mut SectionBuilder<I>) { sb.add_de_res(|_| TxPower::try_from(17).map(TxPowerDataElement::from)).unwrap(); sb.add_de_res(|_| GenericDataElement::try_from(100_u32.into(), &[0; 10])).unwrap(); } @@ -324,20 +339,29 @@ struct V0Identity<C: CryptoProvider> { key_seed: [u8; 32], - legacy_metadata_key: [u8; 14], + legacy_metadata_key: ShortMetadataKey, _marker: PhantomData<C>, } impl<C: CryptoProvider> V0Identity<C> { /// Generate a new identity with random crypto material fn random<R: rand::Rng + rand::CryptoRng>(rng: &mut R) -> Self { - Self { key_seed: rng.gen(), legacy_metadata_key: rng.gen(), _marker: PhantomData } + Self { + key_seed: rng.gen(), + legacy_metadata_key: ShortMetadataKey(rng.gen()), + _marker: PhantomData, + } + } + /// Convert this `V0Identity` into a V0 discovery credential. + fn into_discovery_credential(self) -> V0DiscoveryCredential { + SimpleBroadcastCryptoMaterial::<V0>::new(self.key_seed, self.legacy_metadata_key) + .derive_v0_discovery_credential::<C>() } } struct V1Identity<C: CryptoProvider> { key_seed: [u8; 32], - extended_metadata_key: [u8; 16], + extended_metadata_key: MetadataKey, key_pair: np_ed25519::KeyPair<C>, } @@ -346,77 +370,23 @@ fn random(rng: &mut C::CryptoRng) -> Self { Self { key_seed: rng.gen(), - extended_metadata_key: rng.gen(), + extended_metadata_key: MetadataKey(rng.gen()), key_pair: np_ed25519::KeyPair::<C>::generate(), } } + /// Convert this `V1Identity` into a `V1DiscoveryCredential`. + fn into_discovery_credential(self) -> V1DiscoveryCredential { + SimpleSignedBroadcastCryptoMaterial::new( + self.key_seed, + self.extended_metadata_key, + self.key_pair.private_key(), + ) + .derive_v1_discovery_credential::<C>() + } } -#[derive(strum_macros::EnumIter, Debug)] +#[derive(strum_macros::EnumIter, Clone, Copy, Debug)] enum CryptoMaterialType { MinFootprint, Precalculated, } - -// if we get confident this is a valid shared way, could move this to the main trait -trait V0CryptoMaterialExt: V0CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - legacy_metadata_key_hmac: [u8; 32], - ) -> Self; -} - -trait V1CryptoMaterialExt: V1CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - unsigned_metadata_key_hmac: [u8; 32], - signed_metadata_key_hmac: [u8; 32], - pub_key: np_ed25519::PublicKey<C>, - ) -> Self; -} - -impl V0CryptoMaterialExt for MinimumFootprintV0CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - legacy_metadata_key_hmac: [u8; 32], - ) -> Self { - Self::new(key_seed, legacy_metadata_key_hmac) - } -} - -impl V0CryptoMaterialExt for PrecalculatedV0CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - legacy_metadata_key_hmac: [u8; 32], - ) -> Self { - Self::new::<C>(&key_seed, legacy_metadata_key_hmac) - } -} - -impl V1CryptoMaterialExt for MinimumFootprintV1CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - unsigned_metadata_key_hmac: [u8; 32], - signed_metadata_key_hmac: [u8; 32], - pub_key: np_ed25519::PublicKey<C>, - ) -> Self { - Self::new(key_seed, unsigned_metadata_key_hmac, signed_metadata_key_hmac, pub_key) - } -} - -impl V1CryptoMaterialExt for PrecalculatedV1CryptoMaterial { - fn build_cred<C: CryptoProvider>( - key_seed: [u8; 32], - unsigned_metadata_key_hmac: [u8; 32], - signed_metadata_key_hmac: [u8; 32], - pub_key: np_ed25519::PublicKey<C>, - ) -> Self { - let min_foot = MinimumFootprintV1CryptoMaterial::new( - key_seed, - unsigned_metadata_key_hmac, - signed_metadata_key_hmac, - pub_key, - ); - min_foot.to_precalculated::<C>() - } -}
diff --git a/nearby/presence/np_adv/src/credential/book.rs b/nearby/presence/np_adv/src/credential/book.rs new file mode 100644 index 0000000..9e6361d --- /dev/null +++ b/nearby/presence/np_adv/src/credential/book.rs
@@ -0,0 +1,542 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits defining credential books. These are used in the deserialization path to provide +//! the credentials to try for either advertisement version. +//! See [`CredentialBookBuilder`] for batteries-included implementations. + +use crate::credential::source::{ + CredentialSource, DiscoveryCredentialSource, SliceCredentialSource, +}; +use crate::credential::v0::{V0DiscoveryCryptoMaterial, V0}; +use crate::credential::v1::{ + SignedSectionIdentityResolutionMaterial, SignedSectionVerificationMaterial, + UnsignedSectionIdentityResolutionMaterial, UnsignedSectionVerificationMaterial, + V1DiscoveryCryptoMaterial, V1, +}; +#[cfg(feature = "alloc")] +use crate::credential::ReferencedMatchedCredential; +use crate::credential::{ + DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion, +}; +use core::borrow::Borrow; +use core::marker::PhantomData; +use crypto_provider::CryptoProvider; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// A collection of credentials to try when attempting to deserialize +/// advertisements of either advertisement version which is +/// valid for the given lifetime. +pub trait CredentialBook<'a> +where + Self: 'a, +{ + /// The type of the matched credentials for the credentials + /// held within this credential-book. May lend from the credential-book. + type Matched: MatchedCredential; + + /// The type of V0 crypto-materials yielded by this credential-book. + type V0Crypto: V0DiscoveryCryptoMaterial; + + /// The type of V1 crypto-materials yielded by this credential-book. + type V1Crypto: V1DiscoveryCryptoMaterial; + + /// The iterator type returned from [`CredentialBook#v0_iter()`], + /// which yields `(crypto-material, match_data)` pairs. + /// This is a lending iterator which may borrow things from `self`. + type V0Iterator: Iterator<Item = (Self::V0Crypto, Self::Matched)>; + + /// The iterator type returned from [`CredentialBook#v1_iter()`], + /// which yields `(crypto-material, match_data)` pairs. + /// This is a lending iterator which may borrow things from `self`. + type V1Iterator: Iterator<Item = (Self::V1Crypto, Self::Matched)>; + + /// Returns an iterator over V0 credentials in this credential book. + fn v0_iter(&'a self) -> Self::V0Iterator; + + /// Returns an iterator over V1 credentials in this credential book. + fn v1_iter(&'a self) -> Self::V1Iterator; +} + +/// Convenient marker struct for building credential-books with +/// some given match-data type. +pub struct CredentialBookBuilder<M: MatchedCredential> { + _marker: PhantomData<M>, +} + +impl<M: MatchedCredential> CredentialBookBuilder<M> { + /// Constructs a [`CachedSliceCredentialBook`] from the given slices of discovery credentials, + /// populating its internal buffers of cached credential crypto-materials for up to the + /// given number of credentials in each version (up to `N0` credentials cached in V0, + /// and up to `N1` credentials cached in `V1`.) + pub fn build_cached_slice_book<'a, const N0: usize, const N1: usize, P: CryptoProvider>( + v0_creds: &'a [MatchableCredential<V0, M>], + v1_creds: &'a [MatchableCredential<V1, M>], + ) -> CachedSliceCredentialBook<'a, M, N0, N1> { + let v0_source = SliceCredentialSource::new(v0_creds); + let v0_cache = init_cache_from_source::<V0, _, N0, P>(&v0_source); + let v0_source = CachedCredentialSource::<_, _, N0>::new(v0_source, v0_cache); + + let v1_source = SliceCredentialSource::new(v1_creds); + let v1_cache = init_cache_from_source::<V1, _, N1, P>(&v1_source); + let v1_source = CachedCredentialSource::<_, _, N1>::new(v1_source, v1_cache); + + CredentialBookFromSources::new(v0_source, v1_source) + } + + #[cfg(feature = "alloc")] + /// Constructs a new credential-book which owns all of the given credentials, + /// and maintains pre-calculated cryptographic information about them + /// for speedy advertisement deserialization. + pub fn build_precalculated_owned_book<P: CryptoProvider>( + v0_iter: impl IntoIterator<Item = MatchableCredential<V0, M>>, + v1_iter: impl IntoIterator<Item = MatchableCredential<V1, M>>, + ) -> PrecalculatedOwnedCredentialBook<M> { + let v0_source = PrecalculatedOwnedCredentialSource::<V0, M>::new::<P>(v0_iter); + let v1_source = PrecalculatedOwnedCredentialSource::<V1, M>::new::<P>(v1_iter); + CredentialBookFromSources::new(v0_source, v1_source) + } +} + +// Now, for the implementation details. External implementors still need +// to be able to reference these types (since the returned types from +// [`CredentialBookBuilder`]'s convenience methods are just type-aliases), +// and if they want, they can use them as building-blocks to construct +// their own [`CredentialBook`]s, but they're largely just scaffolding. + +/// A structure bundling both V0 and V1 credential-sources to define +/// a credential-book which owns both of these independent sources. +pub struct CredentialBookFromSources<S0, S1> { + /// The source for V0 credentials. + v0_source: S0, + /// The source for V1 credentials. + v1_source: S1, +} + +impl<'a, S0, S1> CredentialBookFromSources<S0, S1> +where + Self: 'a, + S0: CredentialSource<'a, V0>, + S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>, + <S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial, + <S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial, +{ + /// Constructs a new [`CredentialBook`] out of the two given + /// credential-sources, one for v0 and one for v1. The match-data + /// of both credential sources must be the same. + pub fn new(v0_source: S0, v1_source: S1) -> Self { + Self { v0_source, v1_source } + } +} + +impl<'a, S0, S1> CredentialBook<'a> for CredentialBookFromSources<S0, S1> +where + Self: 'a, + S0: CredentialSource<'a, V0>, + S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>, + <S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial, + <S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial, +{ + type Matched = <S0 as CredentialSource<'a, V0>>::Matched; + type V0Crypto = <S0 as CredentialSource<'a, V0>>::Crypto; + type V1Crypto = <S1 as CredentialSource<'a, V1>>::Crypto; + type V0Iterator = <S0 as CredentialSource<'a, V0>>::Iterator; + type V1Iterator = <S1 as CredentialSource<'a, V1>>::Iterator; + + fn v0_iter(&'a self) -> Self::V0Iterator { + self.v0_source.iter() + } + fn v1_iter(&'a self) -> Self::V1Iterator { + self.v1_source.iter() + } +} + +/// Type-level function used internally +/// by [`CachedCredentialSource`] in order to uniformly +/// refer to the "precalculated" crypto-material variants +/// for each protocol version. +pub(crate) mod precalculated_for_version { + use crate::credential::v0::{ + PrecalculatedV0DiscoveryCryptoMaterial, V0DiscoveryCredential, V0, + }; + use crate::credential::v1::{ + PrecalculatedV1DiscoveryCryptoMaterial, V1DiscoveryCredential, V1, + }; + use crate::credential::{DiscoveryCryptoMaterial, ProtocolVersion}; + use crypto_provider::CryptoProvider; + + pub trait MappingTrait<V: ProtocolVersion> { + type Output: DiscoveryCryptoMaterial<V>; + /// Provides pre-calculated crypto-material for the given + /// discovery credential. + fn precalculate<C: CryptoProvider>( + discovery_credential: &V::DiscoveryCredential, + ) -> Self::Output; + } + pub enum Marker {} + impl MappingTrait<V0> for Marker { + type Output = PrecalculatedV0DiscoveryCryptoMaterial; + fn precalculate<C: CryptoProvider>( + discovery_credential: &V0DiscoveryCredential, + ) -> PrecalculatedV0DiscoveryCryptoMaterial { + PrecalculatedV0DiscoveryCryptoMaterial::new::<C>(discovery_credential) + } + } + impl MappingTrait<V1> for Marker { + type Output = PrecalculatedV1DiscoveryCryptoMaterial; + fn precalculate<C: CryptoProvider>( + discovery_credential: &V1DiscoveryCredential, + ) -> PrecalculatedV1DiscoveryCryptoMaterial { + discovery_credential.to_precalculated::<C>() + } + } +} + +type PrecalculatedCryptoForProtocolVersion<V> = + <precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::Output; + +fn precalculate_crypto_material<V: ProtocolVersion, C: CryptoProvider>( + discovery_credential: &V::DiscoveryCredential, +) -> PrecalculatedCryptoForProtocolVersion<V> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + <precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::precalculate::< + C, + >(discovery_credential) +} + +/// Iterator type for [`CachedCredentialSource`]. +pub struct CachedCredentialSourceIterator< + 'a, + V: ProtocolVersion + 'a, + S: DiscoveryCredentialSource<'a, V> + 'a, + const N: usize, +> where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + /// The current index of the (enumerated) elements that we're iterating over. + /// Always counts up at every iteration, and may lie outside of the range + /// of the `cache` slice for uncached elements. + current_index: usize, + /// The cache of pre-calculated crypto-materials from the original source. + /// + /// The [`self.source_iterator`]'s (up-to) first `N` elements + /// must match (up-to) the first `N` elements in the cache, + /// and they must both appear in the same order. + cache: &'a [Option<PrecalculatedCryptoForProtocolVersion<V>>; N], + /// The iterator over the credentials in the original source + source_iterator: S::Iterator, +} + +impl<'a, V: ProtocolVersion + 'a, S: DiscoveryCredentialSource<'a, V> + 'a, const N: usize> Iterator + for CachedCredentialSourceIterator<'a, V, S, N> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + type Item = (PossiblyCachedDiscoveryCryptoMaterial<'a, V>, S::Matched); + fn next(&mut self) -> Option<Self::Item> { + // Regardless of what we're going to do with the crypto-materials, we still need + // to advance the underlying iterator, and bail if it runs out. + let (discovery_credential, match_data) = self.source_iterator.next()?; + // Check whether/not we have cached crypto-material for the current index + let crypto = match self.cache.get(self.current_index) { + Some(precalculated_crypto_entry) => { + let precalculated_crypto = precalculated_crypto_entry + .as_ref() + .expect("iterator still going, but cache is not full?"); + PossiblyCachedDiscoveryCryptoMaterial::from_precalculated(precalculated_crypto) + } + None => PossiblyCachedDiscoveryCryptoMaterial::from_discovery_credential( + discovery_credential.borrow().clone(), + ), + }; + // Advance the index forward to point to the next item in the cache. + self.current_index += 1; + Some((crypto, match_data)) + } +} + +/// A [`CredentialSource`] which augments the externally-provided [`DiscoveryCredentialSource`] with +/// a cache containing up to the specified number of pre-calculated credentials. +pub struct CachedCredentialSource<V: ProtocolVersion, S, const N: usize> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + wrapped: S, + cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N], +} + +/// Helper function to construct a cache for a [`CachedCredentialSource`] from +/// a reference to some source of discovery-credentials. +/// +/// This function needs to be kept separate from [`CachedCredentialSource#new`] +/// to get around otherwise-tricky lifetime issues around ephemerally-borrowing +/// from a moved object within the body of a function. +pub(crate) fn init_cache_from_source<'a, V: ProtocolVersion, S, const N: usize, P: CryptoProvider>( + original: &'a S, +) -> [Option<PrecalculatedCryptoForProtocolVersion<V>>; N] +where + S: DiscoveryCredentialSource<'a, V> + 'a, + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + let mut cache = [0u8; N].map(|_| None); + for (cache_entry, source_credential) in cache.iter_mut().zip(original.iter()) { + let (discovery_credential, _) = source_credential; + let precalculated_crypto_material = + precalculate_crypto_material::<V, P>(discovery_credential.borrow()); + let _ = cache_entry.insert(precalculated_crypto_material); + } + cache +} + +impl<'a, V: ProtocolVersion, S, const N: usize> CachedCredentialSource<V, S, N> +where + S: DiscoveryCredentialSource<'a, V> + 'a, + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + /// Constructs a [`CachedCredentialSource`] from the given [`DiscoveryCredentialSource`] + /// and the given (initial) cache contents, as constructed via the + /// [`init_cache_from_source`] helper function. + pub(crate) fn new( + wrapped: S, + cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N], + ) -> Self { + Self { wrapped, cache } + } +} + +/// Internal implementation of [`PossiblyCachedDiscoveryCryptoMaterial`] to hide +/// what crypto-material variants we're actually storing in cached +/// credential-books. +pub(crate) enum PossiblyCachedDiscoveryCryptoMaterialKind<'a, V: ProtocolVersion> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + Discovery(V::DiscoveryCredential), + Precalculated(&'a PrecalculatedCryptoForProtocolVersion<V>), +} + +/// Crypto-materials that are potentially references to +/// already-cached precomputed variants, or raw discovery +/// credentials. +pub struct PossiblyCachedDiscoveryCryptoMaterial<'a, V: ProtocolVersion> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + pub(crate) wrapped: PossiblyCachedDiscoveryCryptoMaterialKind<'a, V>, +} + +impl<'a, V: ProtocolVersion> PossiblyCachedDiscoveryCryptoMaterial<'a, V> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + fn from_discovery_credential(discovery_credential: V::DiscoveryCredential) -> Self { + Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(discovery_credential) } + } + fn from_precalculated(precalculated: &'a PrecalculatedCryptoForProtocolVersion<V>) -> Self { + Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(precalculated) } + } +} + +impl<'a, V: ProtocolVersion> DiscoveryCryptoMaterial<V> + for PossiblyCachedDiscoveryCryptoMaterial<'a, V> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.metadata_nonce::<C>(), + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.metadata_nonce::<C>(), + } + } +} + +impl<'a> V0DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V0> { + fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.ldt_adv_cipher::<C>(), + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.ldt_adv_cipher::<C>(), + } + } +} + +impl<'a> V1DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V1> { + fn signed_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> SignedSectionIdentityResolutionMaterial { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => { + x.signed_identity_resolution_material::<C>() + } + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => { + x.signed_identity_resolution_material::<C>() + } + } + } + + fn unsigned_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionIdentityResolutionMaterial { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => { + x.unsigned_identity_resolution_material::<C>() + } + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => { + x.unsigned_identity_resolution_material::<C>() + } + } + } + + fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => { + x.signed_verification_material::<C>() + } + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => { + x.signed_verification_material::<C>() + } + } + } + + fn unsigned_verification_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionVerificationMaterial { + match &self.wrapped { + PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => { + x.unsigned_verification_material::<C>() + } + PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => { + x.unsigned_verification_material::<C>() + } + } + } +} + +impl<'a, V: ProtocolVersion, S, const N: usize> CredentialSource<'a, V> + for CachedCredentialSource<V, S, N> +where + Self: 'a, + S: DiscoveryCredentialSource<'a, V> + 'a, + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, + PossiblyCachedDiscoveryCryptoMaterial<'a, V>: DiscoveryCryptoMaterial<V>, +{ + type Matched = <S as DiscoveryCredentialSource<'a, V>>::Matched; + type Crypto = PossiblyCachedDiscoveryCryptoMaterial<'a, V>; + type Iterator = CachedCredentialSourceIterator<'a, V, S, N>; + + fn iter(&'a self) -> Self::Iterator { + CachedCredentialSourceIterator { + current_index: 0, + cache: &self.cache, + source_iterator: self.wrapped.iter(), + } + } +} + +/// A simple credentials source for environments which are, +/// for all practical purposes, not space-constrained, and hence +/// can store an arbitrary amount of pre-calculated crypto-materials. +/// +/// Requires `alloc` as a result of internally leveraging a `Vec`. +#[cfg(feature = "alloc")] +pub struct PrecalculatedOwnedCredentialSource<V: ProtocolVersion, M: MatchedCredential> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + credentials: Vec<(PrecalculatedCryptoForProtocolVersion<V>, M)>, +} + +#[cfg(feature = "alloc")] +impl<'a, V: ProtocolVersion + 'a, M: MatchedCredential + 'a> + PrecalculatedOwnedCredentialSource<V, M> +where + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, +{ + /// Pre-calculates crypto material for the given credentials, and constructs a + /// credentials source which holds owned copies of this crypto-material. + pub fn new<P: CryptoProvider>( + credential_iter: impl IntoIterator<Item = MatchableCredential<V, M>>, + ) -> Self { + let credentials = credential_iter + .into_iter() + .map(|credential| { + ( + precalculate_crypto_material::<V, P>(&credential.discovery_credential), + credential.match_data, + ) + }) + .collect(); + Self { credentials } + } +} + +#[cfg(feature = "alloc")] +fn reference_crypto_and_match_data<C, M: MatchedCredential>( + pair_ref: &(C, M), +) -> (&C, ReferencedMatchedCredential<M>) { + let (c, m) = pair_ref; + (c, m.into()) +} + +#[cfg(feature = "alloc")] +impl<'a, V: ProtocolVersion, M: MatchedCredential> CredentialSource<'a, V> + for PrecalculatedOwnedCredentialSource<V, M> +where + Self: 'a, + precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>, + &'a PrecalculatedCryptoForProtocolVersion<V>: DiscoveryCryptoMaterial<V>, +{ + type Matched = ReferencedMatchedCredential<'a, M>; + type Crypto = &'a PrecalculatedCryptoForProtocolVersion<V>; + type Iterator = core::iter::Map< + core::slice::Iter<'a, (PrecalculatedCryptoForProtocolVersion<V>, M)>, + fn( + &'a (PrecalculatedCryptoForProtocolVersion<V>, M), + ) + -> (&'a PrecalculatedCryptoForProtocolVersion<V>, ReferencedMatchedCredential<'a, M>), + >; + + fn iter(&'a self) -> Self::Iterator { + self.credentials.iter().map(reference_crypto_and_match_data) + } +} + +/// Type-alias for credential sources which are provided via slice credential-sources +/// with a pre-calculated credential cache layered on top. +pub type CachedSliceCredentialSource<'a, V, M, const N: usize> = + CachedCredentialSource<V, SliceCredentialSource<'a, V, M>, N>; + +/// A [`CredentialBook`] whose sources for V0 and V1 credentials come from the given slices +/// of discovery credentials, with crypto-materials for up to the given number of credentials +/// from the beginning of each slice kept in an in-memory cache. +pub type CachedSliceCredentialBook<'a, M, const N0: usize, const N1: usize> = + CredentialBookFromSources< + CachedSliceCredentialSource<'a, V0, M, N0>, + CachedSliceCredentialSource<'a, V1, M, N1>, + >; + +#[cfg(feature = "alloc")] +/// A credential-book which owns all of its (non-matched) credential data, +/// and maintains pre-calculated cryptographic information about all +/// stored credentials for speedy advertisement deserialization. +/// +/// Use this credential book if memory usage is not terribly tight, +/// and you're operating in an environment with an allocator. +pub type PrecalculatedOwnedCredentialBook<M> = CredentialBookFromSources< + PrecalculatedOwnedCredentialSource<V0, M>, + PrecalculatedOwnedCredentialSource<V1, M>, +>;
diff --git a/nearby/presence/np_adv/src/credential/mod.rs b/nearby/presence/np_adv/src/credential/mod.rs index a138f0b..3cab51e 100644 --- a/nearby/presence/np_adv/src/credential/mod.rs +++ b/nearby/presence/np_adv/src/credential/mod.rs
@@ -18,68 +18,375 @@ //! efficiency gains with implementations tailored to suit (e.g. caching a few hot credentials //! rather than reading from disk every time, etc). +use crate::MetadataKey; + +use crate::credential::v0::{V0DiscoveryCredential, V0ProtocolVersion}; + +use core::convert::Infallible; use core::fmt::Debug; -use crypto_provider::CryptoProvider; +use crypto_provider::{CryptoProvider, CryptoRng}; -use self::{ - simple::{SimpleV0Credential, SimpleV1Credential}, - source::OwnedBothCredentialSource, - v0::MinimumFootprintV0CryptoMaterial, - v1::MinimumFootprintV1CryptoMaterial, -}; - -pub mod simple; +pub mod book; pub mod source; +#[cfg(test)] +pub mod tests; pub mod v0; pub mod v1; -/// A credential which has an associated [`MatchedCredential`] -pub trait MatchableCredential { - /// The [MatchedCredential] provided by this [`MatchableCredential`]. - type Matched<'m>: MatchedCredential<'m> - where - Self: 'm; - - /// Returns the subset of credential data that should be associated with a successfully - /// decrypted advertisement or advertisement section. - fn matched(&self) -> Self::Matched<'_>; +/// Information about a credential as supplied by the caller. +#[derive(Clone)] +pub struct MatchableCredential<V: ProtocolVersion, M: MatchedCredential> { + /// The discovery credential/cryptographic information associated + /// with this particular credential which is used for discovering + /// advertisements/advertisement sections generated via the + /// paired sender credential. + pub discovery_credential: V::DiscoveryCredential, + /// The data which will be yielded back to the caller upon a successful + /// identity-match against this credential. + pub match_data: M, } -/// Convenient type-level function for referring to the match data for a [`MatchableCredential`]. -pub type MatchedCredFromCred<'s, C> = <C as MatchableCredential>::Matched<'s>; +impl<V: ProtocolVersion, M: MatchedCredential> MatchableCredential<V, M> { + /// De-structures this credential into the pairing of a discovery + /// credential and some matched credential data. + pub fn into_pair(self) -> (V::DiscoveryCredential, M) { + (self.discovery_credential, self.match_data) + } + /// Views this credential as a (borrowed) discovery-credential + /// combined with some matched credential data + /// (which is copied - see documentation on [`MatchedCredential`]) + pub fn as_pair(&self) -> (&V::DiscoveryCredential, ReferencedMatchedCredential<M>) { + (&self.discovery_credential, ReferencedMatchedCredential::from(&self.match_data)) + } +} /// The portion of a credential's data to be bundled with the advertisement content it was used to -/// decrypt. +/// decrypt. At a minimum, this includes any encrypted identity-specific metadata. /// -/// As it is `Debug` and `Eq`, implementors should not hold any cryptographic material to avoid +/// As it is `Debug` and `Eq`, implementors should not hold any cryptographic secrets to avoid /// accidental logging, timing side channels on comparison, etc, or should use custom impls of /// those traits rather than deriving them. -pub trait MatchedCredential<'m>: Debug + PartialEq + Eq {} +/// +/// Instances of `MatchedCredential` may be cloned whenever advertisement content is +/// successfully associated with a credential (see [`crate::WithMatchedCredential`]). As a +/// result, it's recommended to use matched-credentials which reference +/// some underlying match-data, but don't necessarily own it. +/// See [`ReferencedMatchedCredential`] for the most common case of shared references. +pub trait MatchedCredential: Debug + PartialEq + Eq + Clone { + /// The type returned for successful calls to [`Self::fetch_encrypted_metadata`]. + type EncryptedMetadata: AsRef<[u8]>; -/// A V0 credential containing some [`v0::V0CryptoMaterial`] -pub trait V0Credential: MatchableCredential { - /// The [v0::V0CryptoMaterial] provided by this V0Credential impl. - type CryptoMaterial: v0::V0CryptoMaterial; + /// The type of errors for [`Self::fetch_encrypted_metadata`]. + type EncryptedMetadataFetchError: Debug; - /// Returns the crypto material associated with the credential. + /// Attempts to obtain the (AES-GCM)-encrypted metadata bytes for the credential, + /// with possible failure based on the availability of the underlying data (i.e: + /// failing disk reads.) /// - /// Used to decrypted encrypted advertisement content. - fn crypto_material(&self) -> &Self::CryptoMaterial; + /// If your implementation does not maintain any encrypted metadata for each credential, + /// you may simply return an empty byte-array from this method. + /// + /// If your method for obtaining metadata cannot fail, use + /// the `core::convert::Infallible` type for the error type + /// [`Self::EncryptedMetadataFetchError`]. + fn fetch_encrypted_metadata( + &self, + ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError>; } -/// A V1 credential containing some [`v1::V1CryptoMaterial`] -pub trait V1Credential: MatchableCredential { - /// The [v1::V1CryptoMaterial] provided by this Credential impl. - type CryptoMaterial: v1::V1CryptoMaterial; - - /// Returns the crypto material associated with the credential. - /// - /// Used to decrypt encrypted advertisement content. - fn crypto_material(&self) -> &Self::CryptoMaterial; +/// [`MatchedCredential`] wrapper around a shared reference to a [`MatchedCredential`]. +/// This is done instead of providing a blanket impl of [`MatchedCredential`] for +/// reference types to allow for downstream crates to impl [`MatchedCredential`] on +/// specific reference types. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ReferencedMatchedCredential<'a, M: MatchedCredential> { + wrapped: &'a M, } -/// An owned credential store that contains minimum footprint crypto materials -pub type MinimumFootprintCredentialSource<T> = OwnedBothCredentialSource< - SimpleV0Credential<MinimumFootprintV0CryptoMaterial, T>, - SimpleV1Credential<MinimumFootprintV1CryptoMaterial, T>, ->; +impl<'a, M: MatchedCredential> From<&'a M> for ReferencedMatchedCredential<'a, M> { + fn from(wrapped: &'a M) -> Self { + Self { wrapped } + } +} + +impl<'a, M: MatchedCredential> AsRef<M> for ReferencedMatchedCredential<'a, M> { + fn as_ref(&self) -> &M { + self.wrapped + } +} + +impl<'a, M: MatchedCredential> MatchedCredential for ReferencedMatchedCredential<'a, M> { + type EncryptedMetadata = <M as MatchedCredential>::EncryptedMetadata; + type EncryptedMetadataFetchError = <M as MatchedCredential>::EncryptedMetadataFetchError; + fn fetch_encrypted_metadata( + &self, + ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> { + self.wrapped.fetch_encrypted_metadata() + } +} + +/// A simple implementation of [`MatchedCredential`] where all match-data +/// is contained in the encrypted metadata byte-field. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct MetadataMatchedCredential<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> { + encrypted_metadata: A, +} + +#[cfg(any(test, feature = "alloc"))] +impl MetadataMatchedCredential<alloc::vec::Vec<u8>> { + /// Builds a [`MetadataMatchedCredential`] whose contents are given + /// as plaintext to be encrypted using AES-GCM against the given + /// broadcast crypto-material. + pub fn encrypt_from_plaintext<V, B, C>(broadcast_cm: &B, plaintext_metadata: &[u8]) -> Self + where + V: ProtocolVersion, + B: BroadcastCryptoMaterial<V>, + C: CryptoProvider, + { + let encrypted_metadata = broadcast_cm.encrypt_metadata::<C>(plaintext_metadata); + Self { encrypted_metadata } + } +} + +impl<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> MetadataMatchedCredential<A> { + /// Builds a new [`MetadataMatchedCredential`] with the given + /// encrypted metadata. + pub fn new(encrypted_metadata: A) -> Self { + Self { encrypted_metadata } + } +} + +impl<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> MatchedCredential + for MetadataMatchedCredential<A> +{ + type EncryptedMetadata = A; + type EncryptedMetadataFetchError = Infallible; + fn fetch_encrypted_metadata(&self) -> Result<Self::EncryptedMetadata, Infallible> { + Ok(self.encrypted_metadata.clone()) + } +} + +/// Trivial implementation of [`MatchedCredential`] which consists of no match-data. +/// Suitable for usage scenarios where the decoded advertisement contents matter, +/// but not necessarily which devices generated the contents. +/// +/// Attempting to obtain the encrypted metadata from this type of credential +/// will always yield an empty byte-array. +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub struct EmptyMatchedCredential; + +impl MatchedCredential for EmptyMatchedCredential { + type EncryptedMetadata = [u8; 0]; + type EncryptedMetadataFetchError = Infallible; + fn fetch_encrypted_metadata( + &self, + ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> { + Ok([0u8; 0]) + } +} + +#[cfg(any(test, feature = "devtools"))] +/// A [`MatchedCredential`] which consists only of the `key_seed` in the crypto-material +/// for the credential. Note that this is unique per-credential by construction, +/// and so this provides natural match-data for credentials in settings where +/// there may not be any other information available. +/// +/// Since this matched-credential type contains cryptographic information mirroring +/// a credential's crypto-material, this structure is not suitable for production +/// usage outside of unit tests and dev-tools. +/// +/// Additionally, note that the metadata on this particular kind of matched credential +/// is deliberately made inaccessible. This is done because a key-seed representation +/// is only suitable in very limited circumstances where no other meaningful +/// identifying information is available, such as that which is contained in metadata. +/// Attempting to obtain the encrypted metadata from this type of matched credential +/// will always yield an empty byte-array. +#[derive(Default, Debug, PartialEq, Eq, Clone)] +pub struct KeySeedMatchedCredential { + key_seed: [u8; 32], +} + +#[cfg(any(test, feature = "devtools"))] +impl From<[u8; 32]> for KeySeedMatchedCredential { + fn from(key_seed: [u8; 32]) -> Self { + Self { key_seed } + } +} +#[cfg(any(test, feature = "devtools"))] +impl From<KeySeedMatchedCredential> for [u8; 32] { + fn from(matched: KeySeedMatchedCredential) -> Self { + matched.key_seed + } +} + +#[cfg(any(test, feature = "devtools"))] +impl MatchedCredential for KeySeedMatchedCredential { + type EncryptedMetadata = [u8; 0]; + type EncryptedMetadataFetchError = Infallible; + fn fetch_encrypted_metadata( + &self, + ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> { + Ok([0u8; 0]) + } +} + +/// Error returned when metadata decryption fails. +#[derive(Debug, Eq, PartialEq)] +pub struct MetadataDecryptionError; + +/// Seal for protocol versions to enforce totality. +pub(crate) mod protocol_version_seal { + /// Internal-only supertrait of protocol versions + /// for the purpose of sealing the trait. + pub trait ProtocolVersionSeal: Clone {} +} + +/// Marker trait for protocol versions (V0/V1) +/// and associated data about them. +pub trait ProtocolVersion: protocol_version_seal::ProtocolVersionSeal { + /// The discovery credential type for this protocol version, which + /// is the minimal amount of cryptographic materials that we need + /// in order to discover advertisements/sections which make + /// use of the sender-paired version of the credential. + type DiscoveryCredential: DiscoveryCryptoMaterial<Self> + Clone; + + /// The native-length metadata key for this protocol version + /// [i.e: if V0, a 14-byte metadata key, or if V1, a 16-byte + /// metadata key.] + type MetadataKey: Clone + AsRef<[u8]>; + + /// Computes the metadata nonce for this version from the given key-seed. + fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12]; + + /// Expands the passed metadata key (if needed) to a 16-byte metadata key + /// which may be used for metadata encryption/decryption + fn expand_metadata_key<C: CryptoProvider>(metadata_key: Self::MetadataKey) -> MetadataKey; + + /// Generates a random metadata key using the given cryptographically-secure Rng + fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> Self::MetadataKey; + + #[cfg(any(test, feature = "alloc"))] + /// Decrypt the given metadata using the given metadata nonce and version-specific + /// metadata key. Returns [`MetadataDecryptionError`] in the case that + /// the decryption operation failed. + fn decrypt_metadata<C: CryptoProvider>( + metadata_nonce: [u8; 12], + metadata_key: Self::MetadataKey, + encrypted_metadata: &[u8], + ) -> Result<alloc::vec::Vec<u8>, MetadataDecryptionError> { + use crypto_provider::{ + aead::{Aead, AeadInit}, + aes::Aes128Key, + }; + + let metadata_key = Self::expand_metadata_key::<C>(metadata_key); + let metadata_key = Aes128Key::from(metadata_key.0); + let aead = <<C as CryptoProvider>::Aes128Gcm as AeadInit<Aes128Key>>::new(&metadata_key); + // No additional authenticated data for encrypted metadata. + aead.decrypt(encrypted_metadata, &[], &metadata_nonce).map_err(|_| MetadataDecryptionError) + } +} + +/// Trait for structures which provide cryptographic +/// materials for discovery in a particular protocol version. +/// See [`crate::credential::v0::V0DiscoveryCryptoMaterial`] +/// and [`crate::credential::v1::V1DiscoveryCryptoMaterial`] +/// for V0 and V1 specializations. +pub trait DiscoveryCryptoMaterial<V: ProtocolVersion> { + /// Constructs or copies the metadata nonce used for decryption of associated credential + /// metadata for the identity represented via this crypto material. + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12]; +} + +/// Cryptographic materials necessary for broadcasting encrypted +/// advertisement contents with the given protocol version. +pub trait BroadcastCryptoMaterial<V: ProtocolVersion> { + /// Yields a copy of the key seed to be used to derive other key materials used + /// in the encryption of broadcasted advertisement contents. + fn key_seed(&self) -> [u8; 32]; + + /// Yields a copy of the metadata-key (size dependent on protocol version) + /// to tag advertisement contents sent with this broadcast crypto-material. + fn metadata_key(&self) -> V::MetadataKey; + + /// Yields the 16-byte expanded metadata key, suitable for metadata encryption. + fn expanded_metadata_key<C: CryptoProvider>(&self) -> MetadataKey { + V::expand_metadata_key::<C>(self.metadata_key()) + } + + /// Constructs the metadata nonce used for encryption of associated credential + /// metadata for the identity represented via this crypto material. + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + V::metadata_nonce_from_key_seed::<C>(&self.key_seed()) + } + + /// Derives a V0 discovery credential from this V0 broadcast crypto-material + /// which may be used to discover v0 advertisements broadcasted with this credential.` + fn derive_v0_discovery_credential<C: CryptoProvider>(&self) -> V0DiscoveryCredential + where + V: V0ProtocolVersion, + { + let key_seed = self.key_seed(); + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed); + let metadata_key_hmac = + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(self.metadata_key().as_ref()); + V0DiscoveryCredential::new(key_seed, metadata_key_hmac) + } + + #[cfg(any(test, feature = "alloc"))] + /// Encrypts the given plaintext metadata bytes to allow that metadata + /// to be shared with receiving devices. + fn encrypt_metadata<C: CryptoProvider>( + &self, + plaintext_metadata: &[u8], + ) -> alloc::vec::Vec<u8> { + use crypto_provider::{ + aead::{Aead, AeadInit}, + aes::Aes128Key, + }; + let plaintext_metadata_key = self.expanded_metadata_key::<C>(); + let plaintext_metadata_key = Aes128Key::from(plaintext_metadata_key.0); + + let aead = + <<C as CryptoProvider>::Aes128Gcm as AeadInit<Aes128Key>>::new(&plaintext_metadata_key); + // No additional authenticated data for encrypted metadata. + aead.encrypt(plaintext_metadata, &[], &self.metadata_nonce::<C>()) + .expect("Metadata encryption should be infallible") + } +} + +/// Concrete implementation of [`BroadcastCryptoMaterial<V>`] for +/// a particular protocol version which keeps the key seed +/// and the metadata key contiguous in memory. +/// +/// Broadcast crypto-material specified in this way will only +/// be usable for (unsigned) advertisement content broadcasts +/// in the given protocol version. +/// +/// For more flexible expression of broadcast credentials, +/// feel free to directly implement one or more of the +/// [`BroadcastCryptoMaterial`] and/or +/// [`crate::credential::v1::SignedBroadcastCryptoMaterial`] +/// traits on your own struct, dependent on the details +/// of your own broadcast credentials. +pub struct SimpleBroadcastCryptoMaterial<V: ProtocolVersion> { + key_seed: [u8; 32], + metadata_key: V::MetadataKey, +} + +impl<V: ProtocolVersion> SimpleBroadcastCryptoMaterial<V> { + /// Builds some simple broadcast crypto-materials out of + /// the provided key-seed and version-specific metadata-key. + pub fn new(key_seed: [u8; 32], metadata_key: V::MetadataKey) -> Self { + Self { key_seed, metadata_key } + } +} + +impl<V: ProtocolVersion> BroadcastCryptoMaterial<V> for SimpleBroadcastCryptoMaterial<V> { + fn key_seed(&self) -> [u8; 32] { + self.key_seed + } + fn metadata_key(&self) -> V::MetadataKey { + self.metadata_key.clone() + } +}
diff --git a/nearby/presence/np_adv/src/credential/simple.rs b/nearby/presence/np_adv/src/credential/simple.rs deleted file mode 100644 index 6ea2436..0000000 --- a/nearby/presence/np_adv/src/credential/simple.rs +++ /dev/null
@@ -1,148 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Simple implementations of credentials. These can be combined with the provided crypto material -//! implementations to have a working credential type. -//! -//! ```rust -//! use np_adv::credential::{ -//! simple::SimpleV0Credential, -//! v0::MinimumFootprintV0CryptoMaterial, -//! }; -//! type MyV0Credential = SimpleV0Credential<MinimumFootprintV0CryptoMaterial, ()>; -//! ``` - -use core::fmt::Debug; - -use super::*; -use super::{v0::*, v1::*}; - -/// A simple implementation of [`V0Credential`] that wraps a [`V0CryptoMaterial`] and some `T` data that -/// will be exposed via the [`MatchedCredential`]. -pub struct SimpleV0Credential<C, T> -where - C: V0CryptoMaterial, - T: Debug + Eq, -{ - material: C, - match_data: T, -} - -impl<C, T> SimpleV0Credential<C, T> -where - C: V0CryptoMaterial, - T: Debug + Eq, -{ - /// Construct a new credential. - /// - /// `material` will be returned by [V0Credential::crypto_material]. - /// `match_data` will be returned by [SimpleV0Credential::matched], wrapped in [SimpleMatchedCredential]. - pub fn new(material: C, match_data: T) -> Self { - Self { material, match_data } - } -} - -impl<C, T> MatchableCredential for SimpleV0Credential<C, T> -where - C: V0CryptoMaterial, - T: Debug + Eq, -{ - type Matched<'m> = SimpleMatchedCredential<'m, T> where Self: 'm; - - fn matched(&'_ self) -> Self::Matched<'_> { - SimpleMatchedCredential { data: &self.match_data } - } -} - -impl<C, T> V0Credential for SimpleV0Credential<C, T> -where - C: V0CryptoMaterial, - T: Debug + Eq, -{ - type CryptoMaterial = C; - - fn crypto_material(&self) -> &Self::CryptoMaterial { - &self.material - } -} - -/// A simple implementation of [V1Credential] that wraps a [V1CryptoMaterial] and some `T` data that -/// will be exposed via the [MatchedCredential]. -pub struct SimpleV1Credential<C, T> -where - C: V1CryptoMaterial, - T: Debug + Eq, -{ - material: C, - match_data: T, -} - -impl<C, T> SimpleV1Credential<C, T> -where - C: V1CryptoMaterial, - T: Debug + Eq, -{ - /// Construct a new credential. - /// - /// `material` will be returned by [V1Credential::crypto_material]. - /// `match_data` will be returned by [SimpleV1Credential::matched], wrapped in [SimpleMatchedCredential]. - pub fn new(material: C, match_data: T) -> Self { - Self { material, match_data } - } -} - -impl<C, T> MatchableCredential for SimpleV1Credential<C, T> -where - C: V1CryptoMaterial, - T: Debug + Eq, -{ - type Matched<'m> = SimpleMatchedCredential<'m, T> where Self: 'm; - - fn matched(&'_ self) -> Self::Matched<'_> { - SimpleMatchedCredential { data: &self.match_data } - } -} - -impl<C, T> V1Credential for SimpleV1Credential<C, T> -where - C: V1CryptoMaterial, - T: Debug + Eq, -{ - type CryptoMaterial = C; - - fn crypto_material(&self) -> &Self::CryptoMaterial { - &self.material - } -} - -/// The [MatchedCredential] used by [SimpleV0Credential] -/// and by [SimpleV1Credential]. -#[derive(Debug, PartialEq, Eq)] -pub struct SimpleMatchedCredential<'m, T: Debug + PartialEq + Eq> { - data: &'m T, -} - -impl<'m, T: Debug + PartialEq + Eq> SimpleMatchedCredential<'m, T> { - /// Construct a new instance that wraps `data`. - pub fn new(data: &'m T) -> Self { - Self { data } - } - - /// Returns the underlying matched credential data. - pub fn matched_data(&self) -> &'m T { - self.data - } -} - -impl<'m, T: Debug + PartialEq + Eq> MatchedCredential<'m> for SimpleMatchedCredential<'m, T> {}
diff --git a/nearby/presence/np_adv/src/credential/source.rs b/nearby/presence/np_adv/src/credential/source.rs index e102b7e..709bbd6 100644 --- a/nearby/presence/np_adv/src/credential/source.rs +++ b/nearby/presence/np_adv/src/credential/source.rs
@@ -12,139 +12,135 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits defining sources for credentials. These are used in the deserialization path to provide -//! the credentials to try. [`SliceCredentialSource`] and [`OwnedCredentialSource`] implementations -//! are also defined in this module. +//! Definitions of traits for structures which supply +//! credentials for discovering advertisements/advertisement +//! sections for a _particular_ protocol version. -use super::*; -use alloc::vec::Vec; +use crate::credential::{ + DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion, + ReferencedMatchedCredential, +}; +use core::borrow::Borrow; -/// A source of credentials to try when decrypting advertisements, -/// which really just wraps an iterator over a given credential type. -pub trait CredentialSource<C: MatchableCredential> { - /// The iterator type produced that emits credentials - type Iterator<'a>: Iterator<Item = &'a C> - where - Self: 'a, - C: 'a; +/// Specialized version of the [`CredentialSource`] trait for +/// credential-sources which provide discovery credentials +/// for a specific protocol version. +/// +/// If you want ready-made structures which can provide +/// credentials for both V0 and V1 protocol versions, +/// see [`crate::credential::book::CredentialBook`] +/// and [`crate::credential::book::CredentialBookBuilder`] instead. +/// +/// It's preferred to use this kind of credential-source +/// in client code, if possible, and then lift to a +/// [`CredentialSource`] using [`AsCredentialSource`] +/// instead of implementing [`CredentialSource`] directly, +/// since it's better to trust this crate to handle +/// the details of what's in [`DiscoveryCryptoMaterial`]s +/// for specific protocol versions. +pub trait DiscoveryCredentialSource<'a, V: ProtocolVersion> +where + Self: 'a, +{ + /// The kind of data yielded to the caller upon a successful + /// identity-match. + type Matched: MatchedCredential; + + /// The kind of crypto-material yielded from the wrapped + /// iterator, which allows borrowing a discovery credential. + type Crypto: DiscoveryCryptoMaterial<V> + Borrow<V::DiscoveryCredential>; + + /// The iterator type produced which emits credentials. + /// This is a lending iterator which may borrow things from `self`. + type Iterator: Iterator<Item = (Self::Crypto, Self::Matched)>; /// Iterate over the available credentials - fn iter(&self) -> Self::Iterator<'_>; + fn iter(&'a self) -> Self::Iterator; } -/// Trait for combined credential sources able to yield credential sources for both V0 and V1. -pub trait BothCredentialSource<C0, C1> +/// A source of credentials for a particular protocol version, +/// utilizing any [`DiscoveryCryptoMaterial`] which is usable +/// for discovering advertisements in that protocol version. +/// +/// This trait is largely leveraged as a tool for building +/// new kinds of [`crate::credential::book::CredentialBook`]s +/// via the [`crate::credential::book::CredentialBookFromSources`] +/// wrapper. It differs from the [`DiscoveryCredentialSource`] +/// trait in that the crypto-materials do not have to be +/// discovery credentials, and can instead be some pre-calculated +/// crypto-materials. +/// +/// See [`crate::credential::book::CachedCredentialSource`] +/// for an example of this pattern. +pub trait CredentialSource<'a, V: ProtocolVersion> where - C0: V0Credential, - C1: V1Credential, + Self: 'a, { - /// The type of the underlying credential-source for v0 credentials - type V0Source: CredentialSource<C0>; - /// The type of the underlying credential-source for v1 credentials - type V1Source: CredentialSource<C1>; + /// The kind of data yielded to the caller upon a successful + /// identity-match. + type Matched: MatchedCredential; - /// Gets a source for v0 credentials maintained by this `BothCredentialSource`. - fn v0(&self) -> &Self::V0Source; + /// The kind of crypto-material yielded from the wrapped + /// iterator. + type Crypto: DiscoveryCryptoMaterial<V>; - /// Gets a source for v1 credentials maintained by this `BothCredentialSource`. - fn v1(&self) -> &Self::V1Source; + /// The iterator type produced which emits credentials. + /// This is a lending iterator which may borrow things from `self`. + type Iterator: Iterator<Item = (Self::Crypto, Self::Matched)>; - /// Convenient function alias to [`self.v0().iter()`] for iterating - /// over v0 credentials. - fn iter_v0(&self) -> <Self::V0Source as CredentialSource<C0>>::Iterator<'_> { - self.v0().iter() - } + /// Iterate over the available credentials + fn iter(&'a self) -> Self::Iterator; +} - /// Convenient function alias to the [`CredentialSource<C1>#iter()`] for iterating - /// over v0 credentials. - fn iter_v1(&self) -> <Self::V1Source as CredentialSource<C1>>::Iterator<'_> { - self.v1().iter() +// Note: This is needed to get around coherence problems +// with the [`CredentialSource`] trait's relationship +// with [`DiscoveryCredentialSource`] if it were declared +// as a sub-trait (i.e: conflicting impls) +/// Wrapper which turns any [`DiscoveryCredentialSource`] +/// into a [`CredentialSource`]. +pub struct AsCredentialSource<S>(pub S); + +impl<'a, V: ProtocolVersion, S: DiscoveryCredentialSource<'a, V>> CredentialSource<'a, V> + for AsCredentialSource<S> +{ + type Matched = <S as DiscoveryCredentialSource<'a, V>>::Matched; + type Crypto = <S as DiscoveryCredentialSource<'a, V>>::Crypto; + type Iterator = <S as DiscoveryCredentialSource<'a, V>>::Iterator; + + fn iter(&'a self) -> Self::Iterator { + self.0.iter() } } -/// A simple [CredentialSource] that just iterates over a provided slice of credentials -pub struct SliceCredentialSource<'c, C: MatchableCredential> { - credentials: &'c [C], +/// A simple [`DiscoveryCredentialSource`] which iterates over a provided slice of credentials +pub struct SliceCredentialSource<'c, V: ProtocolVersion, M: MatchedCredential> { + credentials: &'c [MatchableCredential<V, M>], } -impl<'c, C: MatchableCredential> SliceCredentialSource<'c, C> { - /// Construct the credential source from the provided credentials. - pub fn new(credentials: &'c [C]) -> Self { +impl<'c, V: ProtocolVersion, M: MatchedCredential> SliceCredentialSource<'c, V, M> { + /// Construct the credential supplier from the provided slice of credentials. + pub fn new(credentials: &'c [MatchableCredential<V, M>]) -> Self { Self { credentials } } } -impl<'c, C: MatchableCredential> CredentialSource<C> for SliceCredentialSource<'c, C> { - type Iterator<'i> = core::slice::Iter<'i, C> - where Self: 'i; - - fn iter(&'_ self) -> Self::Iterator<'_> { - self.credentials.iter() - } -} - -/// A simple credential source which owns all of its credentials. -pub struct OwnedCredentialSource<C: MatchableCredential> { - credentials: Vec<C>, -} - -impl<C: MatchableCredential> OwnedCredentialSource<C> { - /// Constructs an owned credential source from the given credentials - pub fn new(credentials: Vec<C>) -> Self { - Self { credentials } - } -} - -impl<C: MatchableCredential> CredentialSource<C> for OwnedCredentialSource<C> { - type Iterator<'i> = core::slice::Iter<'i, C> - where Self: 'i; - - fn iter(&'_ self) -> Self::Iterator<'_> { - self.credentials.iter() - } -} - -/// An owned credential source for both v0 and v1 credentials, -pub struct OwnedBothCredentialSource<C0, C1> +impl<'a, 'b, V: ProtocolVersion, M: MatchedCredential> DiscoveryCredentialSource<'a, V> + for SliceCredentialSource<'b, V, M> where - C0: V0Credential, - C1: V1Credential, + 'b: 'a, + Self: 'b, + &'a <V as ProtocolVersion>::DiscoveryCredential: DiscoveryCryptoMaterial<V>, { - v0_source: OwnedCredentialSource<C0>, - v1_source: OwnedCredentialSource<C1>, -} + type Matched = ReferencedMatchedCredential<'a, M>; + type Crypto = &'a V::DiscoveryCredential; + type Iterator = core::iter::Map< + core::slice::Iter<'a, MatchableCredential<V, M>>, + fn( + &'a MatchableCredential<V, M>, + ) -> (&'a V::DiscoveryCredential, ReferencedMatchedCredential<M>), + >; -impl<C0, C1> OwnedBothCredentialSource<C0, C1> -where - C0: V0Credential, - C1: V1Credential, -{ - /// Creates a new `OwnedBothCredentialSource` from credential-lists - /// for both V0 and V1 - pub fn new(v0_credentials: Vec<C0>, v1_credentials: Vec<C1>) -> Self { - let v0_source = OwnedCredentialSource::new(v0_credentials); - let v1_source = OwnedCredentialSource::new(v1_credentials); - Self { v0_source, v1_source } - } - - /// Creates a new credential source that is empty. - pub fn new_empty() -> Self { - Self::new(Vec::new(), Vec::new()) - } -} - -impl<C0, C1> BothCredentialSource<C0, C1> for OwnedBothCredentialSource<C0, C1> -where - C0: V0Credential, - C1: V1Credential, -{ - type V0Source = OwnedCredentialSource<C0>; - type V1Source = OwnedCredentialSource<C1>; - - fn v0(&self) -> &Self::V0Source { - &self.v0_source - } - fn v1(&self) -> &Self::V1Source { - &self.v1_source + fn iter(&'a self) -> Self::Iterator { + self.credentials.iter().map(MatchableCredential::<V, M>::as_pair) } }
diff --git a/nearby/presence/np_adv/src/credential/tests.rs b/nearby/presence/np_adv/src/credential/tests.rs new file mode 100644 index 0000000..297fdb5 --- /dev/null +++ b/nearby/presence/np_adv/src/credential/tests.rs
@@ -0,0 +1,206 @@ +// Copyright 2023 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests of functionality related to credentials, credential-views, and credential suppliers. + +use crate::credential::{ + book::{ + init_cache_from_source, CachedCredentialSource, PossiblyCachedDiscoveryCryptoMaterialKind, + }, + source::{CredentialSource, SliceCredentialSource}, + v0::{V0DiscoveryCredential, V0}, + v1::{ + SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial, V1DiscoveryCredential, + V1DiscoveryCryptoMaterial, V1, + }, + BroadcastCryptoMaterial, EmptyMatchedCredential, KeySeedMatchedCredential, MatchableCredential, + MetadataDecryptionError, ProtocolVersion, ReferencedMatchedCredential, + SimpleBroadcastCryptoMaterial, +}; +use crate::legacy::ShortMetadataKey; +use crate::MetadataKey; +use alloc::{vec, vec::Vec}; +use crypto_provider_default::CryptoProviderImpl; + +fn get_zeroed_v0_discovery_credential() -> V0DiscoveryCredential { + V0DiscoveryCredential::new([0u8; 32], [0u8; 32]) +} + +fn get_constant_packed_v1_discovery_credential(value: u8) -> V1DiscoveryCredential { + let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate(); + SimpleSignedBroadcastCryptoMaterial::new( + [value; 32], + MetadataKey([value; 16]), + // NOTE: This winds up being unused in these test cases + key_pair.private_key(), + ) + .derive_v1_discovery_credential::<CryptoProviderImpl>() +} + +#[test] +fn cached_credential_source_keeps_same_entries_as_original() { + let creds: [MatchableCredential<V1, KeySeedMatchedCredential>; 5] = + [0u8, 1, 2, 3, 4].map(|x| { + let match_data = KeySeedMatchedCredential::from([x; 32]); + MatchableCredential { + discovery_credential: get_constant_packed_v1_discovery_credential(x), + match_data, + } + }); + let supplier = SliceCredentialSource::new(&creds); + let cache = init_cache_from_source::<_, _, 3, CryptoProviderImpl>(&supplier); + let cached = CachedCredentialSource::new(supplier, cache); + let cached_view = &cached; + assert_eq!(cached_view.iter().count(), 5); + // Now we're going to check that the pairings between the match-data + // and the MIC hmac key wind up being the same between the original + // creds list and what's provided by the cached source. + let expected: Vec<_> = creds + .iter() + .map(|cred| { + ( + cred.discovery_credential + .unsigned_verification_material::<CryptoProviderImpl>() + .mic_hmac_key, + ReferencedMatchedCredential::from(&cred.match_data), + ) + }) + .collect(); + let actual: Vec<_> = cached_view + .iter() + .map(|(crypto_material, match_data)| { + ( + crypto_material.unsigned_verification_material::<CryptoProviderImpl>().mic_hmac_key, + match_data, + ) + }) + .collect(); + assert_eq!(actual, expected); +} + +#[test] +fn cached_credential_source_has_requested_cache_size() { + let creds: [MatchableCredential<V0, EmptyMatchedCredential>; 10] = + [0u8; 10].map(|_| MatchableCredential { + discovery_credential: get_zeroed_v0_discovery_credential(), + match_data: EmptyMatchedCredential, + }); + let supplier = SliceCredentialSource::new(&creds); + let cache = init_cache_from_source::<_, _, 5, CryptoProviderImpl>(&supplier); + let cached = CachedCredentialSource::new(supplier, cache); + let cached_view = &cached; + assert_eq!(cached_view.iter().count(), 10); + for (i, (cred, _)) in cached_view.iter().enumerate() { + if i < 5 { + // Should be cached + if let PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(_) = cred.wrapped { + } else { + panic!("Credential #{} was not cached", i); + } + } else { + // Should be discovery credentials + if let PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(_) = cred.wrapped { + } else { + panic!("Credential #{} was not supposed to be cached", i); + } + } + } +} + +#[test] +fn v0_metadata_decryption_works_same_metadata_key() { + let key_seed = [3u8; 32]; + let metadata_key = ShortMetadataKey([5u8; 14]); + + let metadata = vec![7u8; 42]; + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); + + let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata); + + let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>(); + + let decryption_result = V0::decrypt_metadata::<CryptoProviderImpl>( + metadata_nonce, + metadata_key, + &encrypted_metadata, + ); + assert_eq!(decryption_result, Ok(metadata)) +} + +#[test] +fn v1_metadata_decryption_works_same_metadata_key() { + let key_seed = [9u8; 32]; + let metadata_key = MetadataKey([2u8; 16]); + + let metadata = vec![6u8; 51]; + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + + let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata); + + let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>(); + + let decryption_result = V1::decrypt_metadata::<CryptoProviderImpl>( + metadata_nonce, + metadata_key, + &encrypted_metadata, + ); + assert_eq!(decryption_result, Ok(metadata)) +} + +#[test] +fn v0_metadata_decryption_fails_different_metadata_key() { + let key_seed = [3u8; 32]; + let encrypting_metadata_key = ShortMetadataKey([5u8; 14]); + + let metadata = vec![7u8; 42]; + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, encrypting_metadata_key); + + let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata); + + let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>(); + + let decrypting_metadata_key = ShortMetadataKey([6u8; 14]); + + let decryption_result = V0::decrypt_metadata::<CryptoProviderImpl>( + metadata_nonce, + decrypting_metadata_key, + &encrypted_metadata, + ); + assert_eq!(decryption_result, Err(MetadataDecryptionError)) +} + +#[test] +fn v1_metadata_decryption_fails_different_metadata_key() { + let key_seed = [251u8; 32]; + let encrypting_metadata_key = MetadataKey([127u8; 16]); + + let metadata = vec![255u8; 42]; + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, encrypting_metadata_key); + + let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata); + + let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>(); + + let decrypting_metadata_key = MetadataKey([249u8; 16]); + + let decryption_result = V1::decrypt_metadata::<CryptoProviderImpl>( + metadata_nonce, + decrypting_metadata_key, + &encrypted_metadata, + ); + assert_eq!(decryption_result, Err(MetadataDecryptionError)) +}
diff --git a/nearby/presence/np_adv/src/credential/v0.rs b/nearby/presence/np_adv/src/credential/v0.rs index e2d1b5d..c89909d 100644 --- a/nearby/presence/np_adv/src/credential/v0.rs +++ b/nearby/presence/np_adv/src/credential/v0.rs
@@ -13,32 +13,33 @@ // limitations under the License. //! Cryptographic materials for v0 advertisement-format credentials. +use crate::credential::{protocol_version_seal, DiscoveryCryptoMaterial, ProtocolVersion}; +use crate::legacy::ShortMetadataKey; +use crate::MetadataKey; +use crypto_provider::{CryptoProvider, CryptoRng}; -use super::*; - -/// Cryptographic material for an individual NP credential used to decrypt v0 advertisements. -// Space-time tradeoffs: -// - LDT keys (64b) take about 1.4us. -pub trait V0CryptoMaterial { - /// Returns an LDT NP advertisement cipher built with the provided `Aes` - fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>; -} - -/// A [`V0CryptoMaterial`] that minimizes memory footprint at the expense of CPU time when -/// providing derived key material -pub struct MinimumFootprintV0CryptoMaterial { +/// Cryptographic information about a particular V0 discovery credential +/// necessary to match and decrypt encrypted V0 advertisements. +#[derive(Clone)] +pub struct V0DiscoveryCredential { key_seed: [u8; 32], legacy_metadata_key_hmac: [u8; 32], } -impl MinimumFootprintV0CryptoMaterial { - /// Construct an [MinimumFootprintV0CryptoMaterial] from the provided identity data. +impl V0DiscoveryCredential { + /// Construct an [V0DiscoveryCredential] from the provided identity data. pub fn new(key_seed: [u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self { Self { key_seed, legacy_metadata_key_hmac } } } -impl V0CryptoMaterial for MinimumFootprintV0CryptoMaterial { +impl DiscoveryCryptoMaterial<V0> for V0DiscoveryCredential { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + V0::metadata_nonce_from_key_seed::<C>(&self.key_seed) + } +} + +impl V0DiscoveryCryptoMaterial for V0DiscoveryCredential { fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> { let hkdf = np_hkdf::NpKeySeedHkdf::new(&self.key_seed); ldt_np_adv::build_np_adv_decrypter( @@ -49,27 +50,73 @@ } } -/// [`V0CryptoMaterial`] that minimizes CPU time when providing key material at +/// Type-level identifier for the V0 protocol version. +#[derive(Debug, Clone)] +pub enum V0 {} + +impl protocol_version_seal::ProtocolVersionSeal for V0 {} + +impl ProtocolVersion for V0 { + type DiscoveryCredential = V0DiscoveryCredential; + type MetadataKey = ShortMetadataKey; + + fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12] { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed); + hkdf.legacy_metadata_nonce() + } + fn expand_metadata_key<C: CryptoProvider>(metadata_key: ShortMetadataKey) -> MetadataKey { + metadata_key.expand::<C>() + } + fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> ShortMetadataKey { + ShortMetadataKey(rng.gen()) + } +} + +/// Trait which exists purely to be able to restrict the protocol +/// version of certain type-bounds to V0. +pub trait V0ProtocolVersion: ProtocolVersion {} + +impl V0ProtocolVersion for V0 {} + +/// Cryptographic material for an individual NP credential used to decrypt v0 advertisements. +/// Unlike [`V0DiscoveryCredential`], derived information about cryptographic materials may +/// be stored in implementors of this trait. +// Space-time tradeoffs: +// - LDT keys (64b) take about 1.4us. +pub trait V0DiscoveryCryptoMaterial: DiscoveryCryptoMaterial<V0> { + /// Returns an LDT NP advertisement cipher built with the provided `Aes` + fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>; +} + +/// [`V0DiscoveryCryptoMaterial`] that minimizes CPU time when providing key material at /// the expense of occupied memory. -pub struct PrecalculatedV0CryptoMaterial { +pub struct PrecalculatedV0DiscoveryCryptoMaterial { pub(crate) legacy_ldt_key: ldt::LdtKey<xts_aes::XtsAes128Key>, pub(crate) legacy_metadata_key_hmac: [u8; 32], pub(crate) legacy_metadata_key_hmac_key: [u8; 32], + pub(crate) metadata_nonce: [u8; 12], } -impl PrecalculatedV0CryptoMaterial { +impl PrecalculatedV0DiscoveryCryptoMaterial { /// Construct a new instance from the provided credential material. - pub fn new<C: CryptoProvider>(key_seed: &[u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed); + pub(crate) fn new<C: CryptoProvider>(discovery_credential: &V0DiscoveryCredential) -> Self { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&discovery_credential.key_seed); Self { legacy_ldt_key: hkdf.legacy_ldt_key(), - legacy_metadata_key_hmac, + legacy_metadata_key_hmac: discovery_credential.legacy_metadata_key_hmac, legacy_metadata_key_hmac_key: *hkdf.legacy_metadata_key_hmac_key().as_bytes(), + metadata_nonce: hkdf.legacy_metadata_nonce(), } } } -impl V0CryptoMaterial for PrecalculatedV0CryptoMaterial { +impl DiscoveryCryptoMaterial<V0> for PrecalculatedV0DiscoveryCryptoMaterial { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + self.metadata_nonce + } +} + +impl V0DiscoveryCryptoMaterial for PrecalculatedV0DiscoveryCryptoMaterial { fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> { ldt_np_adv::build_np_adv_decrypter( &self.legacy_ldt_key, @@ -78,3 +125,30 @@ ) } } + +// Implementations for reference types -- we don't provide a blanket impl for references +// due to the potential to conflict with downstream crates' implementations. + +impl<'a> DiscoveryCryptoMaterial<V0> for &'a V0DiscoveryCredential { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + (*self).metadata_nonce::<C>() + } +} + +impl<'a> V0DiscoveryCryptoMaterial for &'a V0DiscoveryCredential { + fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> { + (*self).ldt_adv_cipher::<C>() + } +} + +impl<'a> DiscoveryCryptoMaterial<V0> for &'a PrecalculatedV0DiscoveryCryptoMaterial { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + (*self).metadata_nonce::<C>() + } +} + +impl<'a> V0DiscoveryCryptoMaterial for &'a PrecalculatedV0DiscoveryCryptoMaterial { + fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> { + (*self).ldt_adv_cipher::<C>() + } +}
diff --git a/nearby/presence/np_adv/src/credential/v1.rs b/nearby/presence/np_adv/src/credential/v1.rs index 4b134e4..7d060f2 100644 --- a/nearby/presence/np_adv/src/credential/v1.rs +++ b/nearby/presence/np_adv/src/credential/v1.rs
@@ -14,17 +14,133 @@ //! Cryptographic materials for v1 advertisement-format credentials. -use core::borrow::Borrow; -use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider}; +use crate::credential::{ + protocol_version_seal, BroadcastCryptoMaterial, DiscoveryCryptoMaterial, ProtocolVersion, +}; +use crate::MetadataKey; +use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider, CryptoRng}; use np_hkdf::UnsignedSectionKeys; +/// Cryptographic information about a particular V1 discovery credential +/// necessary to match and decrypt encrypted V1 sections. +#[derive(Clone)] +pub struct V1DiscoveryCredential { + key_seed: [u8; 32], + expected_unsigned_metadata_key_hmac: [u8; 32], + expected_signed_metadata_key_hmac: [u8; 32], + pub_key: ed25519::RawPublicKey, +} +impl V1DiscoveryCredential { + /// Construct a V1 discovery credential from the provided identity data. + pub fn new<C: CryptoProvider>( + key_seed: [u8; 32], + expected_unsigned_metadata_key_hmac: [u8; 32], + expected_signed_metadata_key_hmac: [u8; 32], + pub_key: np_ed25519::PublicKey<C>, + ) -> Self { + Self { + key_seed, + expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, + pub_key: pub_key.to_bytes(), + } + } + + /// Constructs pre-calculated crypto material from this discovery credential. + pub(crate) fn to_precalculated<C: CryptoProvider>( + &self, + ) -> PrecalculatedV1DiscoveryCryptoMaterial { + let signed_identity_resolution_material = self.signed_identity_resolution_material::<C>(); + let unsigned_identity_resolution_material = + self.unsigned_identity_resolution_material::<C>(); + let signed_verification_material = self.signed_verification_material::<C>(); + let unsigned_verification_material = self.unsigned_verification_material::<C>(); + let metadata_nonce = self.metadata_nonce::<C>(); + PrecalculatedV1DiscoveryCryptoMaterial { + signed_identity_resolution_material, + unsigned_identity_resolution_material, + signed_verification_material, + unsigned_verification_material, + metadata_nonce, + } + } +} + +impl DiscoveryCryptoMaterial<V1> for V1DiscoveryCredential { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + V1::metadata_nonce_from_key_seed::<C>(&self.key_seed) + } +} + +impl V1DiscoveryCryptoMaterial for V1DiscoveryCredential { + fn signed_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> SignedSectionIdentityResolutionMaterial { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); + SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac( + &hkdf, + self.expected_signed_metadata_key_hmac, + ) + } + + fn unsigned_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionIdentityResolutionMaterial { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); + UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac( + &hkdf, + self.expected_unsigned_metadata_key_hmac, + ) + } + + fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { + SignedSectionVerificationMaterial { pub_key: self.pub_key } + } + + fn unsigned_verification_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionVerificationMaterial { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); + let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&hkdf).as_bytes(); + UnsignedSectionVerificationMaterial { mic_hmac_key } + } +} + +/// Type-level identifier for the V1 protocol version. +#[derive(Debug, Clone)] +pub enum V1 {} + +impl protocol_version_seal::ProtocolVersionSeal for V1 {} + +impl ProtocolVersion for V1 { + type DiscoveryCredential = V1DiscoveryCredential; + type MetadataKey = MetadataKey; + + fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12] { + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed); + hkdf.extended_metadata_nonce() + } + fn expand_metadata_key<C: CryptoProvider>(metadata_key: MetadataKey) -> MetadataKey { + metadata_key + } + fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> MetadataKey { + MetadataKey(rng.gen()) + } +} + +/// Trait which exists purely to be able to restrict the protocol +/// version of certain type-bounds to V1. +pub trait V1ProtocolVersion: ProtocolVersion {} + +impl V1ProtocolVersion for V1 {} + /// Cryptographic materials necessary for determining whether or not /// a given V1 advertisement section matches an identity. /// Per the construction of the V1 specification, this is also /// the information necessary to decrypt the raw byte contents /// of an encrypted V1 section. #[derive(Clone)] -pub struct SectionIdentityResolutionMaterial { +pub(crate) struct SectionIdentityResolutionMaterial { /// The AES key for decrypting section ciphertext pub(crate) aes_key: Aes128Key, /// The metadata key HMAC key for deriving and verifying the identity metadata @@ -44,9 +160,21 @@ pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self { Self(raw) } + /// Extracts the underlying section-identity resolution material carried around + /// within this wrapper for resolution of signed sections. + pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial { + self.0 + } + #[cfg(any(test, feature = "devtools"))] + /// Gets the underlying section-identity resolution material carried around + /// within this wrapper for resolution of signed sections. pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial { &self.0 } + + /// Constructs identity-resolution material for a signed section whose + /// discovery credential leverages the provided HKDF and has the given + /// expected metadata-key HMAC. pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>( hkdf: &np_hkdf::NpKeySeedHkdf<C>, expected_metadata_key_hmac: [u8; 32], @@ -69,9 +197,20 @@ pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self { Self(raw) } + /// Extracts the underlying section-identity resolution material carried around + /// within this wrapper for resolution of unsigned sections. + pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial { + self.0 + } + /// Gets the underlying section-identity resolution material carried around + /// within this wrapper for resolution of unsigned sections. + #[cfg(any(test, feature = "devtools"))] pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial { &self.0 } + /// Constructs identity-resolution material for an unsigned (MIC) section whose + /// discovery credential leverages the provided HKDF and has the given + /// expected metadata-key HMAC. pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>( hkdf: &np_hkdf::NpKeySeedHkdf<C>, expected_metadata_key_hmac: [u8; 32], @@ -129,88 +268,54 @@ // is only used on the matching identity, not all identities. /// Cryptographic material for an individual NP credential used to decrypt and verify v1 sections. -pub trait V1CryptoMaterial { - /// The return type of `Self::signed_identity_resolution_material`, which is some - /// data-type which allows borrowing `SignedSectionIdentityResolutionMaterial` - type SignedIdentityResolverReference<'a>: Borrow<SignedSectionIdentityResolutionMaterial> - where - Self: 'a; - /// The return type of `Self::unsigned_identity_resolution_material`, which is some - /// data-type which allows borrowing `UnsignedSectionIdentityResolutionMaterial` - type UnsignedIdentityResolverReference<'a>: Borrow<UnsignedSectionIdentityResolutionMaterial> - where - Self: 'a; - - /// Constructs or references the identity resolution material for signed sections +pub trait V1DiscoveryCryptoMaterial: DiscoveryCryptoMaterial<V1> { + /// Constructs or copies the identity resolution material for signed sections fn signed_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::SignedIdentityResolverReference<'_>; + ) -> SignedSectionIdentityResolutionMaterial; - /// Constructs or references the identity resolution material for unsigned sections + /// Constructs or copies the identity resolution material for unsigned sections fn unsigned_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::UnsignedIdentityResolverReference<'_>; + ) -> UnsignedSectionIdentityResolutionMaterial; /// Constructs or copies non-identity-resolution deserialization material for signed /// sections. - /// - /// Note: We mandate "copies" here due to the relatively small size of verification-only crypto - /// materials (32 bytes). fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial; /// Constructs or copies non-identity-resolution deserialization material for unsigned /// sections. - /// - /// Note: We mandate "copies" here due to the relatively small size of verification-only crypto - /// materials (32 bytes). fn unsigned_verification_material<C: CryptoProvider>( &self, ) -> UnsignedSectionVerificationMaterial; - - /// Constructs pre-calculated crypto material out of this crypto-material. - fn to_precalculated<C: CryptoProvider>(self) -> PrecalculatedV1CryptoMaterial - where - Self: Sized, - { - let signed_identity_resolution_material = - self.signed_identity_resolution_material::<C>().borrow().clone(); - let unsigned_identity_resolution_material = - self.unsigned_identity_resolution_material::<C>().borrow().clone(); - let signed_verification_material = self.signed_verification_material::<C>(); - let unsigned_verification_material = self.unsigned_verification_material::<C>(); - PrecalculatedV1CryptoMaterial { - signed_identity_resolution_material, - unsigned_identity_resolution_material, - signed_verification_material, - unsigned_verification_material, - } - } } -/// [`V1CryptoMaterial`] that minimizes CPU time when providing key material at +/// V1 [`DiscoveryCryptoMaterial`] that minimizes CPU time when providing key material at /// the expense of occupied memory -pub struct PrecalculatedV1CryptoMaterial { +pub struct PrecalculatedV1DiscoveryCryptoMaterial { pub(crate) signed_identity_resolution_material: SignedSectionIdentityResolutionMaterial, pub(crate) unsigned_identity_resolution_material: UnsignedSectionIdentityResolutionMaterial, pub(crate) signed_verification_material: SignedSectionVerificationMaterial, pub(crate) unsigned_verification_material: UnsignedSectionVerificationMaterial, + pub(crate) metadata_nonce: [u8; 12], } -impl V1CryptoMaterial for PrecalculatedV1CryptoMaterial { - type SignedIdentityResolverReference<'a> = &'a SignedSectionIdentityResolutionMaterial - where Self: 'a; - type UnsignedIdentityResolverReference<'a> = &'a UnsignedSectionIdentityResolutionMaterial - where Self: 'a; +impl DiscoveryCryptoMaterial<V1> for PrecalculatedV1DiscoveryCryptoMaterial { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + self.metadata_nonce + } +} +impl V1DiscoveryCryptoMaterial for PrecalculatedV1DiscoveryCryptoMaterial { fn signed_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::SignedIdentityResolverReference<'_> { - &self.signed_identity_resolution_material + ) -> SignedSectionIdentityResolutionMaterial { + self.signed_identity_resolution_material.clone() } fn unsigned_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::UnsignedIdentityResolverReference<'_> { - &self.unsigned_identity_resolution_material + ) -> UnsignedSectionIdentityResolutionMaterial { + self.unsigned_identity_resolution_material.clone() } fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { self.signed_verification_material.clone() @@ -222,67 +327,126 @@ } } -/// [`V1CryptoMaterial`] that minimizes memory footprint at the expense of CPU -/// time when providing derived key material. -pub struct MinimumFootprintV1CryptoMaterial { - key_seed: [u8; 32], - expected_unsigned_metadata_key_hmac: [u8; 32], - expected_signed_metadata_key_hmac: [u8; 32], - pub_key: ed25519::RawPublicKey, -} +// Implementations for reference types -- we don't provide a blanket impl for references +// due to the potential to conflict with downstream crates' implementations. -impl MinimumFootprintV1CryptoMaterial { - /// Construct an [MinimumFootprintV1CryptoMaterial] from the provided identity data. - pub fn new<C: CryptoProvider>( - key_seed: [u8; 32], - expected_unsigned_metadata_key_hmac: [u8; 32], - expected_signed_metadata_key_hmac: [u8; 32], - pub_key: np_ed25519::PublicKey<C>, - ) -> Self { - Self { - key_seed, - expected_unsigned_metadata_key_hmac, - expected_signed_metadata_key_hmac, - pub_key: pub_key.to_bytes(), - } +impl<'a> DiscoveryCryptoMaterial<V1> for &'a V1DiscoveryCredential { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + (*self).metadata_nonce::<C>() } } -impl V1CryptoMaterial for MinimumFootprintV1CryptoMaterial { - type SignedIdentityResolverReference<'a> = SignedSectionIdentityResolutionMaterial - where Self: 'a; - type UnsignedIdentityResolverReference<'a> = UnsignedSectionIdentityResolutionMaterial - where Self: 'a; - +impl<'a> V1DiscoveryCryptoMaterial for &'a V1DiscoveryCredential { fn signed_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::SignedIdentityResolverReference<'_> { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); - SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac( - &hkdf, - self.expected_signed_metadata_key_hmac, - ) + ) -> SignedSectionIdentityResolutionMaterial { + (*self).signed_identity_resolution_material::<C>() } - fn unsigned_identity_resolution_material<C: CryptoProvider>( &self, - ) -> Self::UnsignedIdentityResolverReference<'_> { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); - UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac( - &hkdf, - self.expected_unsigned_metadata_key_hmac, - ) + ) -> UnsignedSectionIdentityResolutionMaterial { + (*self).unsigned_identity_resolution_material::<C>() } - fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { - SignedSectionVerificationMaterial { pub_key: self.pub_key } + (*self).signed_verification_material::<C>() } - fn unsigned_verification_material<C: CryptoProvider>( &self, ) -> UnsignedSectionVerificationMaterial { - let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed); - let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&hkdf).as_bytes(); - UnsignedSectionVerificationMaterial { mic_hmac_key } + (*self).unsigned_verification_material::<C>() + } +} + +impl<'a> DiscoveryCryptoMaterial<V1> for &'a PrecalculatedV1DiscoveryCryptoMaterial { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + (*self).metadata_nonce::<C>() + } +} + +impl<'a> V1DiscoveryCryptoMaterial for &'a PrecalculatedV1DiscoveryCryptoMaterial { + fn signed_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> SignedSectionIdentityResolutionMaterial { + (*self).signed_identity_resolution_material::<C>() + } + fn unsigned_identity_resolution_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionIdentityResolutionMaterial { + (*self).unsigned_identity_resolution_material::<C>() + } + fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { + (*self).signed_verification_material::<C>() + } + fn unsigned_verification_material<C: CryptoProvider>( + &self, + ) -> UnsignedSectionVerificationMaterial { + (*self).unsigned_verification_material::<C>() + } +} + +/// Extension of [`BroadcastCryptoMaterial`] for `V1` to add +/// crypto-materials which are necessary to sign V1 sections. +pub trait SignedBroadcastCryptoMaterial: BroadcastCryptoMaterial<V1> { + /// Gets the advertisement-signing private key for constructing + /// signature-verified V1 sections. + /// + /// The private key is returned in an opaque, crypto-provider-independent + /// form to provide a safeguard against leaking the bytes of the key. + fn signing_key(&self) -> ed25519::PrivateKey; + + /// Constructs the V1 discovery credential which may be used to discover + /// V1 advertisement sections broadcasted using this broadcast crypto-material + fn derive_v1_discovery_credential<C: CryptoProvider>(&self) -> V1DiscoveryCredential { + let key_seed = self.key_seed(); + let metadata_key = self.metadata_key(); + let pub_key = self.signing_key().derive_public_key::<C::Ed25519>(); + let pub_key = np_ed25519::PublicKey::<C>::new(pub_key); + + let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed); + let unsigned = hkdf + .extended_unsigned_metadata_key_hmac_key() + .calculate_hmac(metadata_key.0.as_slice()); + let signed = + hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(metadata_key.0.as_slice()); + V1DiscoveryCredential::new(key_seed, unsigned, signed, pub_key) + } +} + +/// Concrete implementation of a [`SignedBroadcastCryptoMaterial`] which keeps the key +/// seed, the V1 metadata key, and the signing key contiguous in memory. +/// +/// For more flexible expression of broadcast +/// credentials, feel free to directly implement [`SignedBroadcastCryptoMaterial`] +/// for your own broadcast-credential-storing data-type. +pub struct SimpleSignedBroadcastCryptoMaterial { + key_seed: [u8; 32], + metadata_key: MetadataKey, + signing_key: ed25519::PrivateKey, +} + +impl SimpleSignedBroadcastCryptoMaterial { + /// Builds some simple V1 signed broadcast crypto-materials out of + /// the provided key-seed, metadata-key, and signing key. + pub fn new( + key_seed: [u8; 32], + metadata_key: MetadataKey, + signing_key: ed25519::PrivateKey, + ) -> Self { + Self { key_seed, metadata_key, signing_key } + } +} + +impl BroadcastCryptoMaterial<V1> for SimpleSignedBroadcastCryptoMaterial { + fn key_seed(&self) -> [u8; 32] { + self.key_seed + } + fn metadata_key(&self) -> MetadataKey { + self.metadata_key + } +} + +impl SignedBroadcastCryptoMaterial for SimpleSignedBroadcastCryptoMaterial { + fn signing_key(&self) -> ed25519::PrivateKey { + self.signing_key.clone() } }
diff --git a/nearby/presence/np_adv/src/de_type.rs b/nearby/presence/np_adv/src/de_type.rs index 90c86e1..4966ed0 100644 --- a/nearby/presence/np_adv/src/de_type.rs +++ b/nearby/presence/np_adv/src/de_type.rs
@@ -29,7 +29,7 @@ /// /// Shared between V0 and V1. /// -/// Must not overlap with [PlainDataElementType]. +/// Must not overlap with [`PlainDataElementType`](crate::legacy::de_type::PlainDataElementType). #[derive(strum_macros::EnumIter, Debug, Clone, Copy, PartialEq, Eq)] #[allow(clippy::enum_variant_names)] pub(crate) enum IdentityDataElementType {
diff --git a/nearby/presence/np_adv/src/deser_v0_tests.rs b/nearby/presence/np_adv/src/deser_v0_tests.rs index fa94b89..2fb6f38 100644 --- a/nearby/presence/np_adv/src/deser_v0_tests.rs +++ b/nearby/presence/np_adv/src/deser_v0_tests.rs
@@ -18,28 +18,30 @@ use crate::{ credential::{ - simple::{SimpleMatchedCredential, SimpleV0Credential}, - source::SliceCredentialSource, - v0::MinimumFootprintV0CryptoMaterial, + book::{CredentialBook, CredentialBookBuilder}, + v0::{V0DiscoveryCredential, V0}, + EmptyMatchedCredential, MatchableCredential, MatchedCredential, + SimpleBroadcastCryptoMaterial, }, de_type::EncryptedIdentityDataElementType, - deserialize_v0_advertisement, + deserialization_arena, + deserialization_arena::DeserializationArena, + deserialize_advertisement, legacy::{ actions::{ActionBits, ActionsDataElement, ToActionElement}, data_elements::DataElement, deserialize::PlainDataElement, serialize::{AdvBuilder, Identity, LdtIdentity}, - BLE_ADV_SVC_CONTENT_LEN, + ShortMetadataKey, BLE_ADV_SVC_CONTENT_LEN, }, shared_data::ContextSyncSeqNum, - CredentialSource, NoIdentity, PlaintextIdentityMode, PublicIdentity, V0AdvContents, - V0Credential, + HasIdentityMatch, PlaintextIdentityMode, PublicIdentity, V0AdvertisementContents, }; use array_view::ArrayView; -use core::marker::PhantomData; +use core::{borrow::BorrowMut, marker::PhantomData}; use crypto_provider::CryptoProvider; use crypto_provider_default::CryptoProviderImpl; -use ldt_np_adv::{LdtEncrypterXtsAes128, LegacySalt}; +use ldt_np_adv::LegacySalt; use std::{prelude::rust_2021::*, vec}; use strum::IntoEnumIterator as _; @@ -51,10 +53,18 @@ let (adv, adv_config) = adv_random_identity(&mut rng, &identities); - let creds = identities.iter().map(|i| i.credential()).collect::<Vec<_>>(); - let cred_source = SliceCredentialSource::new(&creds); + let creds = identities + .iter() + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) + .collect::<Vec<_>>(); - let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice()); + let mut arena = deserialization_arena!(); + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]); + let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book); assert_adv_equals(&adv_config, &contents); } @@ -68,24 +78,31 @@ let (adv, adv_config) = adv_random_identity(&mut rng, &identities); - let creds = identities + let credentials = identities .iter() .filter(|i| { // remove identity used, if any !adv_config.identity.map(|sci| sci.key_seed == i.key_seed).unwrap_or(false) }) - .map(|i| i.credential()) + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) .collect::<Vec<_>>(); - let cred_source = SliceCredentialSource::new(&creds); - let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice()); + let mut arena = deserialization_arena!(); + let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>( + &credentials, + &[], + ); + let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book); match adv_config.identity { // we ended up generating plaintext, so it's fine None => assert_adv_equals(&adv_config, &contents), Some(_) => { // we generated an encrypted adv, but didn't include the credential - assert_eq!(V0AdvContents::NoMatchingCredentials, contents); + assert_eq!(V0AdvertisementContents::NoMatchingCredentials, contents); } } } @@ -99,29 +116,34 @@ let (adv, adv_config) = adv_random_identity(&mut rng, &identities); - let creds = Vec::<SimpleV0Credential<MinimumFootprintV0CryptoMaterial, [u8; 32]>>::new(); - let cred_source = SliceCredentialSource::new(&creds); + let creds = Vec::<MatchableCredential<V0, EmptyMatchedCredential>>::new(); - let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice()); + let mut arena = deserialization_arena!(); + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]); + let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book); match adv_config.identity { // we ended up generating plaintext, so it's fine None => assert_adv_equals(&adv_config, &contents), Some(_) => { // we generated an encrypted adv, but didn't include the credential - assert_eq!(V0AdvContents::NoMatchingCredentials, contents); + assert_eq!(V0AdvertisementContents::NoMatchingCredentials, contents); } } } } -fn assert_adv_equals<'m>( +/// Short-hand for asserting that the contents of two V0 advertisements +/// are the same for tests where we only ever have 0-1 broadcasting +/// identities in play. +fn assert_adv_equals<M: MatchedCredential + AsRef<EmptyMatchedCredential>>( adv_config: &AdvConfig, - adv: &V0AdvContents<'m, SimpleMatchedCredential<'m, [u8; 32]>>, + adv: &V0AdvertisementContents<M>, ) { match adv_config.identity { None => match adv { - V0AdvContents::Plaintext(p) => { + V0AdvertisementContents::Plaintext(p) => { let mut action_bits = ActionBits::default(); action_bits.set_action(ContextSyncSeqNum::try_from(3).unwrap()); let de = ActionsDataElement::from(action_bits); @@ -135,7 +157,7 @@ _ => panic!("should be a plaintext adv"), }, Some(_) => match adv { - V0AdvContents::Decrypted(wmc) => { + V0AdvertisementContents::Decrypted(wmc) => { assert!(adv_config.plaintext_mode.is_none()); // different generic type param, so can't re-use the DE from above @@ -152,7 +174,7 @@ wmc.contents().identity_type() ); assert_eq!( - &adv_config.identity.unwrap().legacy_metadata_key, + adv_config.identity.unwrap().legacy_metadata_key, wmc.contents().metadata_key() ); } @@ -161,16 +183,19 @@ } } -fn deser_v0<'s, C, S, P>( - cred_source: &'s S, - adv: &[u8], -) -> V0AdvContents<'s, SimpleMatchedCredential<'s, [u8; 32]>> +fn deser_v0<'adv, B, P>( + arena: impl BorrowMut<DeserializationArena<'adv>>, + adv: &'adv [u8], + cred_book: &'adv B, +) -> V0AdvertisementContents<B::Matched> where - C: V0Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's, - S: CredentialSource<C>, + B: CredentialBook<'adv>, P: CryptoProvider, { - deserialize_v0_advertisement::<C, S, P>(adv, cred_source).unwrap() + deserialize_advertisement::<_, P>(arena, adv, cred_book) + .expect("Should be a valid advertisement") + .into_v0() + .expect("Should be V0") } /// Populate an advertisement with a randomly chosen identity and a DE @@ -179,38 +204,27 @@ identities: &'a Vec<TestIdentity<CryptoProviderImpl>>, ) -> (ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, AdvConfig<'a>) { let identity = identities.choose(&mut rng).unwrap(); - match rng.gen_range(0_u8..=2) { - 0 => { - let mut adv_builder = AdvBuilder::new(NoIdentity::default()); - add_de(&mut adv_builder); + if rng.gen_bool(0.5) { + let mut adv_builder = AdvBuilder::new(PublicIdentity); + add_de(&mut adv_builder); - ( - adv_builder.into_advertisement().unwrap(), - AdvConfig::new(None, Some(PlaintextIdentityMode::None)), - ) - } - 1 => { - let mut adv_builder = AdvBuilder::new(PublicIdentity::default()); - add_de(&mut adv_builder); + ( + adv_builder.into_advertisement().unwrap(), + AdvConfig::new(None, Some(PlaintextIdentityMode::Public)), + ) + } else { + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new( + identity.key_seed, + identity.legacy_metadata_key, + ); + let mut adv_builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( + identity.identity_type, + LegacySalt::from(rng.gen::<[u8; 2]>()), + &broadcast_cm, + )); + add_de(&mut adv_builder); - ( - adv_builder.into_advertisement().unwrap(), - AdvConfig::new(None, Some(PlaintextIdentityMode::Public)), - ) - } - 2 => { - let mut adv_builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( - identity.identity_type, - LegacySalt::from(rng.gen::<[u8; 2]>()), - identity.legacy_metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&identity.hkdf().legacy_ldt_key()), - )); - add_de(&mut adv_builder); - - (adv_builder.into_advertisement().unwrap(), AdvConfig::new(Some(identity), None)) - } - - _ => unreachable!(), + (adv_builder.into_advertisement().unwrap(), AdvConfig::new(Some(identity), None)) } } @@ -229,7 +243,7 @@ struct TestIdentity<C: CryptoProvider> { identity_type: EncryptedIdentityDataElementType, key_seed: [u8; 32], - legacy_metadata_key: [u8; 14], + legacy_metadata_key: ShortMetadataKey, _marker: PhantomData<C>, } @@ -242,20 +256,17 @@ .choose(rng) .unwrap(), key_seed: rng.gen(), - legacy_metadata_key: rng.gen(), + legacy_metadata_key: ShortMetadataKey(rng.gen()), _marker: PhantomData, } } - /// Returns a credential using crypto material from this identity - fn credential(&self) -> SimpleV0Credential<MinimumFootprintV0CryptoMaterial, [u8; 32]> { + /// Returns a discovery-credential using crypto material from this identity + fn discovery_credential(&self) -> V0DiscoveryCredential { let hkdf = self.hkdf(); - SimpleV0Credential::new( - MinimumFootprintV0CryptoMaterial::new( - self.key_seed, - hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&self.legacy_metadata_key), - ), + V0DiscoveryCredential::new( self.key_seed, + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&self.legacy_metadata_key.0), ) }
diff --git a/nearby/presence/np_adv/src/deser_v1_tests.rs b/nearby/presence/np_adv/src/deser_v1_tests.rs index b59b18b..80f646e 100644 --- a/nearby/presence/np_adv/src/deser_v1_tests.rs +++ b/nearby/presence/np_adv/src/deser_v1_tests.rs
@@ -19,79 +19,133 @@ use crate::{ credential::{ - simple::{SimpleMatchedCredential, SimpleV1Credential}, - source::SliceCredentialSource, - v1::MinimumFootprintV1CryptoMaterial, + book::{CredentialBook, CredentialBookBuilder}, + v1::{ + SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial, + V1DiscoveryCredential, + }, + EmptyMatchedCredential, MatchableCredential, MatchedCredential, }, de_type::EncryptedIdentityDataElementType, - deserialize_v1_advertisement, + deserialization_arena, + deserialization_arena::DeserializationArena, + deserialize_advertisement, extended::{ data_elements::GenericDataElement, deserialize::VerificationMode, serialize::{ - AdvBuilder, EncodedAdvertisement, MicEncrypted, SectionBuilder, SectionIdentity, - SignedEncrypted, + AdvBuilder, AdvertisementType, EncodedAdvertisement, MicEncryptedSectionEncoder, + PublicSectionEncoder, SectionBuilder, SectionEncoder, SignedEncryptedSectionEncoder, }, }, - AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, CredentialSource, - PlaintextIdentityMode, PublicIdentity, Section, V1AdvContents, V1Credential, - V1DeserializedSection, + AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, HasIdentityMatch, MetadataKey, + PlaintextIdentityMode, Section, V1AdvertisementContents, V1DeserializedSection, }; use crypto_provider::{CryptoProvider, CryptoRng}; use std::{collections, prelude::rust_2021::*, vec}; use strum::IntoEnumIterator as _; +use crate::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT; use crypto_provider_default::CryptoProviderImpl; #[test] -fn v1_all_identities_resolvable_mix_plaintext_ciphertext() { +fn v1_plaintext() { let mut rng = StdRng::from_entropy(); for _ in 0..100 { let identities = (0..100) .map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng)) .collect::<Vec<_>>(); - let mut adv_builder = AdvBuilder::new(); - let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let section_configs: Vec<SectionConfig<CryptoProviderImpl>> = + fill_plaintext_adv(&mut rng, &mut adv_builder); let adv = adv_builder.into_advertisement(); - let creds = identities.iter().map(|i| i.credential()).collect::<Vec<_>>(); - let cred_source = SliceCredentialSource::new(&creds); - // check if the section header would be 0 or if the section is empty - match section_configs.is_empty() { - true => { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv); - assert_eq!( - v1_error, - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError - } - ); //assert a adv deserialization error - } - false => { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv); - assert_eq!(0, v1_contents.invalid_sections_count()); - assert_eq!(section_configs.len(), v1_contents.sections.len()); - for (section_config, section) in - section_configs.iter().zip(v1_contents.sections.iter()) - { - assert_section_equals(section_config, section); + let creds = identities + .iter() + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) + .collect::<Vec<_>>(); + + let mut arena = deserialization_arena!(); + // check if the section is empty or there is more than one public section + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); + if section_configs.is_empty() { + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!( + v1_error, + AdvDeserializationError::ParseError { + details_hazmat: + AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError } + ); //assert a adv deserialization error + } else { + let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!(0, v1_contents.invalid_sections_count()); + assert_eq!(section_configs.len(), v1_contents.sections.len()); + for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter()) + { + assert_section_equals(section_config, section); } } } } #[test] -fn v1_only_non_matching_identities_available_mix_plaintext_ciphertext() { +fn v1_all_identities_resolvable_ciphertext() { let mut rng = StdRng::from_entropy(); for _ in 0..100 { let identities = (0..100) .map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng)) .collect::<Vec<_>>(); - let mut adv_builder = AdvBuilder::new(); - let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder); + let adv = adv_builder.into_advertisement(); + let creds = identities + .iter() + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) + .collect::<Vec<_>>(); + let mut arena = deserialization_arena!(); + // check if the section header would be 0 or if the section is empty + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); + if section_configs.is_empty() { + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!( + v1_error, + AdvDeserializationError::ParseError { + details_hazmat: + AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError + } + ); //assert a adv deserialization error + } else { + let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!(0, v1_contents.invalid_sections_count()); + assert_eq!(section_configs.len(), v1_contents.sections.len()); + for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter()) + { + assert_section_equals(section_config, section); + } + } + } +} + +#[test] +fn v1_only_non_matching_identities_available_ciphertext() { + let mut rng = StdRng::from_entropy(); + for _ in 0..100 { + let identities = (0..100) + .map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng)) + .collect::<Vec<_>>(); + + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder); let adv = adv_builder.into_advertisement(); let creds = identities .iter() @@ -101,104 +155,103 @@ .iter() .any(|sc| sc.identity.map(|sci| sci.key_seed == i.key_seed).unwrap_or(false)) }) - .map(|i| i.credential()) + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) .collect::<Vec<_>>(); - let cred_source = SliceCredentialSource::new(&creds); + + let mut arena = deserialization_arena!(); // check if the section header would be 0 - match section_configs.is_empty() { - true => { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv); - assert_eq!( - v1_error, - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError - } - ); //assert a adv deserialization error - } - false => { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv); - // all encrypted identity sections are invalid - let encrypted_section_count = - section_configs.iter().filter(|sc| sc.identity.is_some()).count(); - assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count()); - assert_eq!( - section_configs.len() - encrypted_section_count, - v1_contents.sections.len() - ); - for (section_config, section) in section_configs - .iter() - // skip encrypted sections - .filter(|sc| sc.identity.is_none()) - .zip(v1_contents.sections.iter()) - { - assert_section_equals(section_config, section); + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); + if section_configs.is_empty() { + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!( + v1_error, + AdvDeserializationError::ParseError { + details_hazmat: + AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError } + ); //assert a adv deserialization error + } else { + let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + // all encrypted identity sections are invalid + let encrypted_section_count = + section_configs.iter().filter(|sc| sc.identity.is_some()).count(); + assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count()); + assert_eq!(section_configs.len() - encrypted_section_count, v1_contents.sections.len()); + for (section_config, section) in section_configs + .iter() + // skip encrypted sections + .filter(|sc| sc.identity.is_none()) + .zip(v1_contents.sections.iter()) + { + assert_section_equals(section_config, section); } } } } #[test] -fn v1_no_creds_available_mix_plaintext_ciphertext() { +fn v1_no_creds_available_ciphertext() { let mut rng = StdRng::from_entropy(); for _ in 0..100 { let identities = (0..100).map(|_| TestIdentity::random(&mut rng)).collect::<Vec<_>>(); - let mut adv_builder = AdvBuilder::new(); - let section_configs = - fill_adv::<StdRng, CryptoProviderImpl>(&mut rng, &identities, &mut adv_builder); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + let section_configs = fill_encrypted_adv::<StdRng, CryptoProviderImpl>( + &mut rng, + &identities, + &mut adv_builder, + ); let adv = adv_builder.into_advertisement(); - let cred_source: SliceCredentialSource< - '_, - SimpleV1Credential<MinimumFootprintV1CryptoMaterial, [u8; 32]>, - > = SliceCredentialSource::new(&[]); + let mut arena = deserialization_arena!(); // check if the section header would be 0 - match section_configs.is_empty() { - true => { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv); - assert_eq!( - v1_error, - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError - } - ); //assert a adv deserialization error - } - false => { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv); - // all encrypted identity sections are invalid - let encrypted_section_count = - section_configs.iter().filter(|sc| sc.identity.is_some()).count(); - assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count()); - assert_eq!( - section_configs.len() - encrypted_section_count, - v1_contents.sections.len() - ); - - for (section_config, section) in section_configs - .iter() - // skip encrypted sections - .filter(|sc| sc.identity.is_none()) - .zip(v1_contents.sections.iter()) - { - assert_section_equals(section_config, section); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + if section_configs.is_empty() { + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!( + v1_error, + AdvDeserializationError::ParseError { + details_hazmat: + AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError } + ); //assert a adv deserialization error + } else { + let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + // all encrypted identity sections are invalid + let encrypted_section_count = + section_configs.iter().filter(|sc| sc.identity.is_some()).count(); + assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count()); + assert_eq!(section_configs.len() - encrypted_section_count, v1_contents.sections.len()); + + for (section_config, section) in section_configs + .iter() + // skip encrypted sections + .filter(|sc| sc.identity.is_none()) + .zip(v1_contents.sections.iter()) + { + assert_section_equals(section_config, section); } } } } #[test] -fn v1_only_some_matching_identities_available_mix_plaintext_ciphertext() { +fn v1_only_some_matching_identities_available_ciphertext() { let mut rng = StdRng::from_entropy(); for _ in 0..100 { let identities = (0..100) .map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng)) .collect::<Vec<_>>(); - let mut adv_builder = AdvBuilder::new(); - let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder); let adv = adv_builder.into_advertisement(); // identities used in sections, which may be used in multiple sections too let identities_to_remove: collections::HashSet<_> = identities @@ -217,59 +270,59 @@ let creds = identities .iter() .filter(|i| !identities_to_remove.contains(&i.key_seed)) - .map(|i| i.credential()) + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) .collect::<Vec<_>>(); - let cred_source = SliceCredentialSource::new(&creds); + + let mut arena = deserialization_arena!(); + + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); + // check if the section header would be 0 - match section_configs.is_empty() { - true => { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv); - assert_eq!( - v1_error, - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError - } - ); //assert a adv deserialization error - } - false => { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv); - - let affected_sections: Vec<_> = section_configs - .iter() - .filter(|sc| { - sc.identity - .map(|sci| identities_to_remove.iter().any(|ks| &sci.key_seed == ks)) - .unwrap_or(false) - }) - .collect(); - - assert_eq!(affected_sections.len(), v1_contents.invalid_sections_count()); - assert_eq!( - section_configs.len() - affected_sections.len(), - v1_contents.sections.len() - ); - - for (section_config, section) in section_configs - .iter() - // skip sections w/ removed identities - .filter(|sc| { - sc.identity - .map(|i| !identities_to_remove.contains(&i.key_seed)) - .unwrap_or(true) - }) - .zip(v1_contents.sections.iter()) - { - assert_section_equals(section_config, section); + if section_configs.is_empty() { + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + assert_eq!( + v1_error, + AdvDeserializationError::ParseError { + details_hazmat: + AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError } + ); //assert a adv deserialization error + } else { + let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book); + + let affected_sections: Vec<_> = section_configs + .iter() + .filter(|sc| { + sc.identity + .map(|sci| identities_to_remove.iter().any(|ks| &sci.key_seed == ks)) + .unwrap_or(false) + }) + .collect(); + + assert_eq!(affected_sections.len(), v1_contents.invalid_sections_count()); + assert_eq!(section_configs.len() - affected_sections.len(), v1_contents.sections.len()); + + for (section_config, section) in section_configs + .iter() + // skip sections w/ removed identities + .filter(|sc| { + sc.identity.map(|i| !identities_to_remove.contains(&i.key_seed)).unwrap_or(true) + }) + .zip(v1_contents.sections.iter()) + { + assert_section_equals(section_config, section); } } } } -fn assert_section_equals<'m, C: CryptoProvider>( +fn assert_section_equals<M: MatchedCredential, C: CryptoProvider>( section_config: &SectionConfig<C>, - section: &V1DeserializedSection<'m, SimpleMatchedCredential<'m, [u8; 32]>>, + section: &V1DeserializedSection<M>, ) { match section_config.identity { None => match section { @@ -279,7 +332,7 @@ assert_eq!(section_config.plaintext_mode.unwrap(), p.identity()); assert_eq!( section_config.data_elements, - p.data_elements().map(|de| de.into()).collect::<Vec<_>>() + p.iter_data_elements().map(|de| (&de.unwrap()).into()).collect::<Vec<_>>() ) } V1DeserializedSection::Decrypted(_) => panic!("no id, but decrypted section"), @@ -291,14 +344,17 @@ assert_eq!( section_config.data_elements, - wmc.contents().data_elements().map(|de| de.into()).collect::<Vec<_>>() + wmc.contents() + .iter_data_elements() + .map(|de| (&de.unwrap()).into()) + .collect::<Vec<GenericDataElement>>() ); assert_eq!( section_config.identity.unwrap().identity_type, wmc.contents().identity_type() ); assert_eq!( - §ion_config.identity.unwrap().extended_metadata_key, + section_config.identity.unwrap().extended_metadata_key, wmc.contents().metadata_key() ); assert_eq!( @@ -310,35 +366,66 @@ } } -fn deser_v1_error<'s, C, S, P>( - cred_source: &'s S, - adv: EncodedAdvertisement, +fn deser_v1_error<'a, B, P>( + arena: &'a mut DeserializationArena<'a>, + adv: &'a EncodedAdvertisement, + cred_book: &'a B, ) -> AdvDeserializationError where - C: V1Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's, - S: CredentialSource<C>, + B: CredentialBook<'a>, P: CryptoProvider, { - let v1_contents = match deserialize_v1_advertisement::<C, S, P>(adv.as_slice(), cred_source) { + let v1_contents = match deserialize_advertisement::<_, P>(arena, adv.as_slice(), cred_book) { Err(e) => e, _ => panic!("Expecting an error!"), }; v1_contents } -fn deser_v1<'s, C, S, P>( - cred_source: &'s S, - adv: EncodedAdvertisement, -) -> V1AdvContents<'s, SimpleMatchedCredential<'s, [u8; 32]>> +fn deser_v1<'adv, B, P>( + arena: &'adv mut DeserializationArena<'adv>, + adv: &'adv EncodedAdvertisement, + cred_book: &'adv B, +) -> V1AdvertisementContents<'adv, B::Matched> where - C: V1Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's, - S: CredentialSource<C>, + B: CredentialBook<'adv>, P: CryptoProvider, { - deserialize_v1_advertisement::<C, S, P>(adv.as_slice(), cred_source).unwrap() + deserialize_advertisement::<_, P>(arena, adv.as_slice(), cred_book) + .expect("Should be a valid advertisement") + .into_v1() + .expect("Should be V1") } + /// Populate a random number of sections with randomly chosen identities and random DEs -fn fill_adv<'a, R: rand::Rng, C: CryptoProvider>( +fn fill_plaintext_adv<'a, R: rand::Rng, C: CryptoProvider>( + mut rng: &mut R, + adv_builder: &mut AdvBuilder, +) -> Vec<SectionConfig<'a, C>> { + let mut expected = Vec::new(); + // build sections + for _ in 0..rng.gen_range(0..=NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT) { + let res = adv_builder.section_builder(PublicSectionEncoder::default()).map(|s| { + SectionConfig::new( + None, + Some(PlaintextIdentityMode::Public), + None, + add_des(s, &mut rng), + ) + }); + match res { + Ok(tuple) => expected.push(tuple), + Err(_) => { + // couldn't fit that section; maybe another smaller section will fit + continue; + } + } + } + expected +} + +/// Populate a random number of sections with randomly chosen identities and random DEs +fn fill_encrypted_adv<'a, R: rand::Rng, C: CryptoProvider>( mut rng: &mut R, identities: &'a Vec<TestIdentity<C>>, adv_builder: &mut AdvBuilder, @@ -349,21 +436,15 @@ for _ in 0..rng.gen_range(0..=6) { let chosen_index = rng.gen_range(0..identities.len()); let identity = &identities[chosen_index]; - let res = match rng.gen_range(1_u8..=3) { - 1 => adv_builder.section_builder(PublicIdentity::default()).map(|s| { - SectionConfig::new( - None, - Some(PlaintextIdentityMode::Public), - None, - add_des(s, &mut rng), - ) - }), - 2 => adv_builder - .section_builder(MicEncrypted::new_random_salt( + + let broadcast_cm = identity.broadcast_credential(); + + let res = if rng.gen_bool(0.5) { + adv_builder + .section_builder(MicEncryptedSectionEncoder::<C>::new_random_salt( &mut salt_rng, identity.identity_type, - &identity.extended_metadata_key, - &identity.hkdf(), + &broadcast_cm, )) .map(|s| { SectionConfig::new( @@ -372,14 +453,13 @@ Some(VerificationMode::Mic), add_des(s, &mut rng), ) - }), - 3 => adv_builder - .section_builder(SignedEncrypted::new_random_salt( + }) + } else { + adv_builder + .section_builder(SignedEncryptedSectionEncoder::<C>::new_random_salt( &mut salt_rng, identity.identity_type, - &identity.extended_metadata_key, - &identity.key_pair, - &identity.hkdf(), + &broadcast_cm, )) .map(|s| { SectionConfig::new( @@ -388,8 +468,7 @@ Some(VerificationMode::Signature), add_des(s, &mut rng), ) - }), - _ => unreachable!(), + }) }; match res { Ok(tuple) => expected.push(tuple), @@ -401,10 +480,11 @@ } expected } + struct TestIdentity<C: CryptoProvider> { identity_type: EncryptedIdentityDataElementType, key_seed: [u8; 32], - extended_metadata_key: [u8; 16], + extended_metadata_key: MetadataKey, key_pair: np_ed25519::KeyPair<C>, marker: PhantomData<C>, } @@ -417,32 +497,26 @@ .choose(rng) .unwrap(), key_seed: rng.gen(), - extended_metadata_key: rng.gen(), + extended_metadata_key: MetadataKey(rng.gen()), key_pair: np_ed25519::KeyPair::<C>::generate(), marker: PhantomData, } } - /// Returns a credential using crypto material from this identity - fn credential(&self) -> SimpleV1Credential<MinimumFootprintV1CryptoMaterial, [u8; 32]> { - let hkdf = self.hkdf(); - SimpleV1Credential::new( - MinimumFootprintV1CryptoMaterial::new( - self.key_seed, - hkdf.extended_unsigned_metadata_key_hmac_key() - .calculate_hmac(&self.extended_metadata_key), - hkdf.extended_signed_metadata_key_hmac_key() - .calculate_hmac(&self.extended_metadata_key), - self.key_pair.public(), - ), + /// Returns a (simple, signed) broadcast credential using crypto material from this identity + fn broadcast_credential(&self) -> SimpleSignedBroadcastCryptoMaterial { + SimpleSignedBroadcastCryptoMaterial::new( self.key_seed, + self.extended_metadata_key, + self.key_pair.private_key(), ) } - fn hkdf(&self) -> np_hkdf::NpKeySeedHkdf<C> { - np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed) + /// Returns a discovery credential using crypto material from this identity + fn discovery_credential(&self) -> V1DiscoveryCredential { + self.broadcast_credential().derive_v1_discovery_credential::<C>() } } /// Add several DEs with random types and contents -fn add_des<I: SectionIdentity, R: rand::Rng>( +fn add_des<I: SectionEncoder, R: rand::Rng>( mut sb: SectionBuilder<I>, rng: &mut R, ) -> Vec<GenericDataElement> {
diff --git a/nearby/presence/np_adv/src/deserialization_arena.rs b/nearby/presence/np_adv/src/deserialization_arena.rs new file mode 100644 index 0000000..6b5f651 --- /dev/null +++ b/nearby/presence/np_adv/src/deserialization_arena.rs
@@ -0,0 +1,102 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types for creating arenas used in deserialization of np_adv. This implementation is purpose-made +//! for deserializing in `np_adv` and is not intended for general use as an arena. + +use crate::extended::BLE_ADV_SVC_CONTENT_LEN; + +/// Create a [`DeserializationArena`] suitable for use with deserializing an advertisement. +#[macro_export] +macro_rules! deserialization_arena { + // Trick borrowed from `pin!`: In an argument to a braced constructor, if we take a reference to + // a temporary value, that value will be upgraded to live for the scope of the enclosing block, + // avoiding another `let` binding for the buffer which is normally required to keep the buffer + // on the stack. + // Reference: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension + () => { + $crate::deserialization_arena::DeserializationArena { + slice: &mut $crate::deserialization_arena::DeserializationArena::buffer(), + } + }; +} + +/// A simple allocator that simply keeps splitting the given slice on allocation. Use the +/// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance. +pub struct DeserializationArena<'a> { + #[doc(hidden)] // Exposed for use by `deserialization_arena!` only. + pub slice: &'a mut [u8], +} + +impl<'a> DeserializationArena<'a> { + #[doc(hidden)] // Exposed for use by `deserialization_arena!` only. + pub fn buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] { + [0; BLE_ADV_SVC_CONTENT_LEN] + } + + /// Allocates `len` bytes from the slice given upon construction. In the expected use case, the + /// allocated slice should be written to with actual data, overwriting what's contained in + /// there. While reading from the allocated slice without first writing to it is safe in the + /// Rust memory-safety sense, the returned slice contains "garbage" data from the slice given + /// during construction. + /// + /// Returns an error with [`ArenaOutOfSpace`] if the remaining slice is not long enough to + /// allocate `len` bytes. + pub fn allocate(&mut self, len: u8) -> Result<&'a mut [u8], ArenaOutOfSpace> { + if usize::from(len) > self.slice.len() { + return Err(ArenaOutOfSpace); + } + let (allocated, remaining) = core::mem::take(&mut self.slice).split_at_mut(len.into()); + self.slice = remaining; + // Note: the returned data is logically garbage, but in practice it's all zeroes assuming + // `deserialization_arena!` was used to create this allocator. + Ok(allocated) + } +} + +/// Error indicating that the arena has ran out of space, and deserialization cannot proceed. This +/// should never happen if the arena is created with [`crate::deserialization_arena!`], since the +/// total size of decrypted sections should be less than the size of the incoming BLE advertisement. +#[derive(Debug, PartialEq, Eq)] +pub struct ArenaOutOfSpace; + +#[cfg(test)] +mod test { + use crate::{deserialization_arena::ArenaOutOfSpace, extended::BLE_ADV_SVC_CONTENT_LEN}; + + #[test] + fn test_creation() { + assert_eq!(BLE_ADV_SVC_CONTENT_LEN, deserialization_arena!().slice.len()); + } + + #[test] + fn test_allocation() { + let mut arena = deserialization_arena!(); + assert_eq!(Ok(&mut [0_u8; 100][..]), arena.allocate(100)); + assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, arena.slice.len()); + } + + #[test] + fn test_allocation_overflow() { + let mut arena = deserialization_arena!(); + assert_eq!(Err(ArenaOutOfSpace), arena.allocate(u8::MAX)); + } + + #[test] + fn test_allocation_twice_overflow() { + let mut arena = deserialization_arena!(); + assert_eq!(Ok(&mut [0_u8; 128][..]), arena.allocate(128)); + assert_eq!(Err(ArenaOutOfSpace), arena.allocate(128)); + } +}
diff --git a/nearby/presence/np_adv/src/extended/data_elements/mod.rs b/nearby/presence/np_adv/src/extended/data_elements/mod.rs index 2058be1..4e6e481 100644 --- a/nearby/presence/np_adv/src/extended/data_elements/mod.rs +++ b/nearby/presence/np_adv/src/extended/data_elements/mod.rs
@@ -67,8 +67,8 @@ } /// Convert a deserialized DE into one you can serialize -impl<'a> From<DataElement<'a>> for GenericDataElement { - fn from(de: DataElement<'a>) -> Self { +impl<'a> From<&'a DataElement<'a>> for GenericDataElement { + fn from(de: &'a DataElement<'a>) -> Self { Self::try_from(de.de_type(), de.contents()) .expect("Deserialized DE must have a valid length") }
diff --git a/nearby/presence/np_adv/src/extended/data_elements/tests.rs b/nearby/presence/np_adv/src/extended/data_elements/tests.rs index a8938b0..a0f9a2f 100644 --- a/nearby/presence/np_adv/src/extended/data_elements/tests.rs +++ b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
@@ -15,16 +15,16 @@ extern crate std; use super::*; +use crate::extended::serialize::AdvertisementType; use crate::{ - extended::serialize::{section_tests::SectionBuilderExt, AdvBuilder}, + extended::serialize::{section_tests::SectionBuilderExt, AdvBuilder, PublicSectionEncoder}, shared_data::TxPower, - PublicIdentity, }; #[test] fn serialize_tx_power_de() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder.add_de_res(|_| TxPower::try_from(3_i8).map(TxPowerDataElement::from)).unwrap(); @@ -41,8 +41,8 @@ #[test] fn serialize_actions_de_empty() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder.add_de_res(|_| ActionsDataElement::try_from_actions(&[])).unwrap(); @@ -58,8 +58,8 @@ #[test] fn serialize_actions_de_non_empty() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de_res(|_| ActionsDataElement::try_from_actions(&[1, 1, 2, 3, 5, 8])) @@ -78,8 +78,8 @@ #[test] fn serialize_context_sync_seq_num_de() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de_res(|_| ContextSyncSeqNum::try_from(3).map(ContextSyncSeqNumDataElement::from)) @@ -98,8 +98,8 @@ #[test] fn serialize_connectivity_info_de_bluetooth() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder.add_de(|_| ConnectivityInfoDataElement::bluetooth([1; 4], [2; 6])).unwrap(); @@ -118,8 +118,8 @@ #[test] fn serialize_connectivity_info_de_mdns() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder.add_de(|_| ConnectivityInfoDataElement::mdns([1; 4], 2)).unwrap(); @@ -138,8 +138,8 @@ #[test] fn serialize_connectivity_info_de_wifi_direct() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de(|_| ConnectivityInfoDataElement::wifi_direct([1; 10], [2; 10], [3; 2], 4)) @@ -162,8 +162,8 @@ #[test] fn serialize_connectivity_capabilities_de_wifi_direct() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de(|_| ConnectivityCapabilityDataElement::wifi_direct([1; 3], [2; 3]))
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs index d112ead..dfab7ce 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
@@ -15,20 +15,25 @@ extern crate std; use super::*; +use crate::deserialization_arena; use crate::extended::data_elements::TxPowerDataElement; -use crate::extended::serialize::{SingleTypeDataElement, WriteDataElement}; +use crate::extended::deserialize::DataElementParseError; +use crate::extended::serialize::{AdvertisementType, SingleTypeDataElement, WriteDataElement}; use crate::shared_data::TxPower; use crate::{ - credential::{simple::SimpleV1Credential, v1::MinimumFootprintV1CryptoMaterial}, + credential::{v1::V1, SimpleBroadcastCryptoMaterial}, de_type::EncryptedIdentityDataElementType, extended::{ deserialize::{ - encrypted_section::MicEncryptedSectionDeserializationError, + encrypted_section::{ + EncryptedSectionContents, IdentityResolutionOrDeserializationError, + MicVerificationError, + }, parse_sections, - test_stubs::{HkdfCryptoMaterial, IntermediateSectionExt}, - CiphertextSection, OffsetDataElement, RawV1Salt, + test_stubs::IntermediateSectionExt, + CiphertextSection, DataElement, RawV1Salt, }, - serialize::{AdvBuilder, CapacityLimitedVec, MicEncrypted}, + serialize::{AdvBuilder, CapacityLimitedVec, MicEncryptedSectionEncoder}, }, parse_adv_header, AdvHeader, Section, }; @@ -39,20 +44,20 @@ #[test] fn deserialize_mic_encrypted_correct_keys() { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let raw_salt = RawV1Salt([3; 16]); let section_salt = V1Salt::<CryptoProviderImpl>::from(raw_salt); let identity_type = EncryptedIdentityDataElementType::Private; - let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_for_testing( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -70,24 +75,38 @@ panic!("incorrect header"); }; - let sections = parse_sections(&adv_header, remaining).unwrap(); + let sections = parse_sections(adv_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); let enc_section = section.as_ciphertext().unwrap(); + + let contents = if let CiphertextSection::MicEncryptedIdentity(contents) = &enc_section { + contents + } else { + panic!("incorrect flavor"); + }; + let keypair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate(); // deserializing to Section works - let credential = SimpleV1Credential::new( - MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( - key_seed, - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), - keypair.public(), - ), - (), - ); - let section = enc_section.try_deserialize::<_, CryptoProviderImpl>(&credential).unwrap(); + let discovery_credential = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, keypair.private_key()) + .derive_v1_discovery_credential::<CryptoProviderImpl>(); + + let identity_resolution_material = + discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>(); + let verification_material = + discovery_credential.unsigned_verification_material::<CryptoProviderImpl>(); + + let mut arena = deserialization_arena!(); + let section = contents + .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut arena, + &identity_resolution_material, + &verification_material, + ) + .unwrap(); assert_eq!( DecryptedSection::new( @@ -97,37 +116,32 @@ raw_salt, SectionContents { section_header: 19 // encryption info de - + 2 // de header - + 16 // metadata key - + 2 // de contents - + 16, // mic hmac tag + + 2 // de header + + 16 // metadata key + + 2 // de contents + + 16, // mic hmac tag // battery DE - de_data: ArrayView::try_from_slice(&[5]).unwrap(), - data_elements: vec![OffsetDataElement { - offset: 2.into(), - de_type: 0x05_u8.into(), - start_of_contents: 0, - contents_len: 1 - }] + data_element_start_offset: 2, + de_region_excl_identity: &[txpower_de.de_header().serialize().as_slice(), &[5],] + .concat(), } ), section ); + let data_elements = section.collect_data_elements().unwrap(); + assert_eq!( + data_elements, + &[DataElement { offset: 2.into(), de_type: 0x05_u8.into(), contents: &[5] }] + ); assert_eq!( - vec![(DataElementOffset::from(2_usize), TxPowerDataElement::DE_TYPE, vec![5_u8])], - section - .data_elements() + vec![(DataElementOffset::from(2_u8), TxPowerDataElement::DE_TYPE, vec![5_u8])], + data_elements + .into_iter() .map(|de| (de.offset(), de.de_type(), de.contents().to_vec())) .collect::<Vec<_>>() ); - let contents = if let CiphertextSection::MicEncryptedIdentity(contents) = &enc_section { - contents - } else { - panic!("incorrect flavor"); - }; - let mut encryption_info_bytes = [0_u8; 19]; encryption_info_bytes[0..2].copy_from_slice(&[0x91, 0x10]); encryption_info_bytes[2] = 0x00; @@ -136,19 +150,21 @@ let ciphertext_end = adv.as_slice().len() - 16; assert_eq!( &MicEncryptedSection { - section_header: 19 // encryption info de + contents: EncryptedSectionContents { + section_header: 19 // encryption info de + 2 // de header + 16 // metadata key + 2 // de contents + 16, // mic hmac tag - adv_header: &adv_header, - encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x1], - offset: 1.into(), - identity_type: EncryptedIdentityDataElementType::Private, + adv_header, + encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x1], + offset: 1.into(), + identity_type: EncryptedIdentityDataElementType::Private, + }, + all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..ciphertext_end], }, - all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..ciphertext_end], mic: SectionMic { mic: adv.as_slice()[ciphertext_end..].try_into().unwrap() } }, contents @@ -156,30 +172,26 @@ // plaintext is correct { - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( - key_seed, - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), - keypair.public(), - ); - let identity_resolution_material = - crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>(); - let verification_material = - crypto_material.unsigned_verification_material::<CryptoProviderImpl>(); - - let decrypted = contents - .try_decrypt::<CryptoProviderImpl>( - &identity_resolution_material, - &verification_material, + let identity_resolution_contents = + contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>(); + let identity_match = identity_resolution_contents + .try_match::<CryptoProviderImpl>( + &identity_resolution_material.into_raw_resolution_material(), ) .unwrap(); + let mut arena = deserialization_arena!(); + let decrypted = contents + .contents + .decrypt_ciphertext::<CryptoProviderImpl>(&mut arena, identity_match) + .unwrap(); - let mut expected = metadata_key.as_slice().to_vec(); + let mut expected = Vec::new(); // battery de expected.extend_from_slice(txpower_de.clone().de_header().serialize().as_slice()); txpower_de.write_de_contents(&mut expected); - assert_eq!(&expected, decrypted.as_slice()); + assert_eq!(metadata_key, decrypted.metadata_key_plaintext); + assert_eq!(&expected, decrypted.plaintext_contents); } } @@ -187,7 +199,7 @@ fn deserialize_mic_encrypted_incorrect_aes_key_error() { // bad aes key -> bad metadata key plaintext do_bad_deserialize_params::<CryptoProviderImpl>( - MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, Some([0xFF; 16].into()), None, None, @@ -199,7 +211,7 @@ fn deserialize_mic_encrypted_incorrect_metadata_key_hmac_key_error() { // bad metadata key hmac key -> bad calculated metadata key mac do_bad_deserialize_params::<CryptoProviderImpl>( - MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, None, Some([0xFF; 32].into()), None, @@ -211,7 +223,7 @@ fn deserialize_mic_encrypted_incorrect_mic_hmac_key_error() { // bad mic hmac key -> bad calculated mic do_bad_deserialize_params::<CryptoProviderImpl>( - MicEncryptedSectionDeserializationError::MicMismatch, + MicVerificationError::MicMismatch.into(), None, None, Some([0xFF; 32].into()), @@ -223,7 +235,7 @@ fn deserialize_mic_encrypted_incorrect_expected_metadata_key_hmac_error() { // bad expected metadata key mac do_bad_deserialize_params::<CryptoProviderImpl>( - MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, None, None, None, @@ -235,7 +247,9 @@ fn deserialize_mic_encrypted_incorrect_salt_error() { // bad salt -> bad iv -> bad metadata key plaintext do_bad_deserialize_tampered( - MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError::IdentityMatchingError, + ), |_| {}, |adv| adv[23..39].copy_from_slice(&[0xFF; 16]), ); @@ -245,7 +259,7 @@ fn deserialize_mic_encrypted_de_that_wont_parse() { // add an extra byte to the section, leading it to try to parse a DE that doesn't exist do_bad_deserialize_tampered( - MicEncryptedSectionDeserializationError::DeParseError, + DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd), |sec| sec.try_push(0xFF).unwrap(), |_| {}, ); @@ -255,7 +269,9 @@ fn deserialize_mic_encrypted_tampered_mic_error() { // flip the a bit in the first MIC byte do_bad_deserialize_tampered( - MicEncryptedSectionDeserializationError::MicMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + MicVerificationError::MicMismatch.into(), + ), |_| {}, |adv| { let mic_start = adv.len() - 16; @@ -268,7 +284,9 @@ fn deserialize_mic_encrypted_tampered_payload_error() { // flip the last payload bit do_bad_deserialize_tampered( - MicEncryptedSectionDeserializationError::MicMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + MicVerificationError::MicMismatch.into(), + ), |_| {}, |adv| *adv.last_mut().unwrap() ^= 0x01, ); @@ -277,26 +295,27 @@ /// Attempt a decryption that will fail when using the provided parameters for decryption only. /// `None` means use the correct value for that parameter. fn do_bad_deserialize_params<C: CryptoProvider>( - error: MicEncryptedSectionDeserializationError, + error: IdentityResolutionOrDeserializationError<MicVerificationError>, aes_key: Option<crypto_provider::aes::Aes128Key>, metadata_key_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>, mic_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>, expected_metadata_key_hmac: Option<[u8; 32]>, ) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let section_salt: V1Salt<C> = [3; 16].into(); let identity_type = EncryptedIdentityDataElementType::Private; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_for_testing( + .section_builder(MicEncryptedSectionEncoder::<C>::new( identity_type, section_salt, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -314,7 +333,7 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); @@ -331,7 +350,7 @@ .unwrap_or_else(|| key_seed_hkdf.extended_unsigned_metadata_key_hmac_key()) .as_bytes(), expected_metadata_key_hmac: expected_metadata_key_hmac.unwrap_or_else(|| { - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key) + key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0) }), }; let identity_resolution_material = @@ -346,33 +365,42 @@ assert_eq!( error, contents - .try_deserialize::<C>(&identity_resolution_material, &verification_material) + .try_resolve_identity_and_deserialize::<C>( + &mut deserialization_arena!(), + &identity_resolution_material, + &verification_material, + ) .unwrap_err() ); } -fn do_bad_deserialize_tampered< - S: Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), - A: Fn(&mut Vec<u8>), ->( - expected_error: MicEncryptedSectionDeserializationError, - mangle_section: S, - mangle_adv: A, +#[derive(Debug, PartialEq)] +enum DeserializeError { + IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError<MicVerificationError>, + ), + DataElementParseError(DataElementParseError), +} + +fn do_bad_deserialize_tampered( + expected_error: DeserializeError, + mangle_section: impl Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), + mangle_adv: impl Fn(&mut Vec<u8>), ) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into(); let identity_type = EncryptedIdentityDataElementType::Private; - let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_for_testing( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, section_salt, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -394,7 +422,7 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); @@ -405,22 +433,35 @@ panic!("incorrect flavor"); }; - // generate a random key pair since we need _some_ public key + // generate a random key pair since we need _some_ public key in our discovery + // credential, even if it winds up going unused let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate(); - let crypto_material = HkdfCryptoMaterial::new(&key_seed, &metadata_key, key_pair.public()); - let identity_resolution_material = - crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>(); - let verification_material = - crypto_material.unsigned_verification_material::<CryptoProviderImpl>(); + let discovery_credential = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()) + .derive_v1_discovery_credential::<CryptoProviderImpl>(); - assert_eq!( - expected_error, - contents - .try_deserialize::<CryptoProviderImpl>( - &identity_resolution_material, - &verification_material, - ) - .unwrap_err() - ); + let identity_resolution_material = + discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>(); + let verification_material = + discovery_credential.unsigned_verification_material::<CryptoProviderImpl>(); + + match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut deserialization_arena!(), + &identity_resolution_material, + &verification_material, + ) { + Ok(section) => { + assert_eq!( + expected_error, + DeserializeError::DataElementParseError( + section.collect_data_elements().unwrap_err() + ) + ); + } + Err(e) => assert_eq!( + expected_error, + DeserializeError::IdentityResolutionOrDeserializationError(e), + ), + }; }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs index ad305f5..0afeb08 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
@@ -14,33 +14,124 @@ use crate::{ credential::v1::*, + deserialization_arena::DeserializationArena, extended::{ deserialize::{ - parse_non_identity_des, DecryptedSection, EncryptedIdentityMetadata, EncryptionInfo, - SectionContents, SectionMic, VerificationMode, + DecryptedSection, EncryptedIdentityMetadata, EncryptionInfo, SectionContents, + SectionMic, VerificationMode, }, section_signature_payload::*, - to_array_view, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN, + METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN, }, - V1Header, NP_SVC_UUID, + MetadataKey, V1Header, NP_SVC_UUID, }; + +#[cfg(any(feature = "devtools", test))] +use alloc::vec::Vec; +#[cfg(feature = "devtools")] use array_view::ArrayView; +use core::fmt::Debug; use crypto_provider::{ - aes::ctr::{AesCtr, AesCtrNonce, NonceAndCounter, AES_CTR_NONCE_LEN}, + aes::ctr::{AesCtr, AesCtrNonce, NonceAndCounter}, hmac::Hmac, CryptoProvider, }; use np_hkdf::v1_salt::V1Salt; +use super::ArenaOutOfSpace; + #[cfg(test)] mod mic_decrypt_tests; #[cfg(test)] mod signature_decrypt_tests; +/// Represents the contents of an encrypted section +/// which are directly employed in identity resolution. +/// This does not incorporate any information about credentials. #[derive(PartialEq, Eq, Debug)] -pub(crate) struct SignatureEncryptedSection<'a> { +pub(crate) struct SectionIdentityResolutionContents { + /// The ciphertext for the metadata key + pub(crate) metadata_key_ciphertext: [u8; METADATA_KEY_LEN], + /// The 12-byte cryptographic nonce which is derived from the encryption info + /// and the identity metadata for a particular section. + pub(crate) nonce: AesCtrNonce, +} + +impl SectionIdentityResolutionContents { + /// Decrypt the contained metadata-key ciphertext buffer whose bytes of plaintext are, maybe, the + /// metadata key for an NP identity, as specified via pre-calculated cryptographic materials + /// stored in some [`SectionIdentityResolutionMaterial`]. + /// + /// This method does not decrypt an entire section's ciphertext aside from the metadata key, + /// and so verification (MIC or signature) needs to be done elsewhere. + /// + /// Returns `Some` if decrypting the metadata-key ciphertext produces plaintext whose HMAC + /// matches the expected MAC. Otherwise, returns `None`. + pub(crate) fn try_match<C: CryptoProvider>( + &self, + identity_resolution_material: &SectionIdentityResolutionMaterial, + ) -> Option<IdentityMatch<C>> { + let mut decrypt_buf = self.metadata_key_ciphertext; + let aes_key = &identity_resolution_material.aes_key; + let mut cipher = C::AesCtr128::new(aes_key, NonceAndCounter::from_nonce(self.nonce)); + cipher.apply_keystream(&mut decrypt_buf[..]); + + let metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C> = + identity_resolution_material.metadata_key_hmac_key.into(); + let expected_metadata_key_hmac = identity_resolution_material.expected_metadata_key_hmac; + metadata_key_hmac_key.verify_hmac(&decrypt_buf[..], expected_metadata_key_hmac).ok().map( + move |_| IdentityMatch { + cipher, + metadata_key_plaintext: MetadataKey(decrypt_buf), + nonce: self.nonce, + }, + ) + } +} + +/// Carries data about an identity "match" for a particular section +/// against some particular V1 identity-resolution crypto-materials. +pub(crate) struct IdentityMatch<C: CryptoProvider> { + /// Decrypted metadata key ciphertext + metadata_key_plaintext: MetadataKey, + /// The AES-Ctr nonce to be used in section decryption and verification + nonce: AesCtrNonce, + /// The state of the AES-Ctr cipher after successfully decrypting + /// the metadata key ciphertext. May be used to decrypt the remainder + /// of the section ciphertext. + cipher: C::AesCtr128, +} + +/// Maximum length of a section's contents, after the metadata-key. +#[allow(unused)] +const MAX_SECTION_CONTENTS_LEN: usize = NP_ADV_MAX_SECTION_LEN - METADATA_KEY_LEN; + +/// Bare, decrypted contents from an encrypted section, +/// including the decrypted metadata key and the decrypted section ciphertext. +/// At this point, verification of the plaintext contents has not yet been performed. +pub(crate) struct RawDecryptedSection<'adv> { + pub(crate) metadata_key_plaintext: MetadataKey, + pub(crate) nonce: AesCtrNonce, + pub(crate) plaintext_contents: &'adv [u8], +} + +#[cfg(feature = "devtools")] +impl<'adv> RawDecryptedSection<'adv> { + pub(crate) fn to_raw_bytes(&self) -> ArrayView<u8, NP_ADV_MAX_SECTION_LEN> { + let mut result = Vec::new(); + result.extend_from_slice(&self.metadata_key_plaintext.0); + result.extend_from_slice(self.plaintext_contents); + // Won't panic because of the involved lengths + ArrayView::try_from_slice(&result).unwrap() + } +} + +/// Represents the contents of an encrypted section, +/// independent of the encryption type. +#[derive(PartialEq, Eq, Debug)] +pub(crate) struct EncryptedSectionContents<'a> { pub(crate) section_header: u8, - pub(crate) adv_header: &'a V1Header, + pub(crate) adv_header: V1Header, pub(crate) encryption_info: EncryptionInfo, pub(crate) identity: EncryptedIdentityMetadata, /// All ciphertext (Contents of identity DE + all DEs) @@ -48,83 +139,134 @@ pub(crate) all_ciphertext: &'a [u8], } -impl<'a> SignatureEncryptedSection<'a> { - /// Decrypt the ciphertext and check that the first [METADATA_KEY_LEN] bytes match the expected HMAC. - /// The remaining data is not verified. +impl<'a> EncryptedSectionContents<'a> { + /// Constructs a representation of the contents of an encrypted V1 section + /// from the advertisement header, the section header, information about + /// the encryption used for identity verification, identity metadata, + /// and the entire section contents as an undecrypted ciphertext. /// - /// Returns `Ok` if the section was an identity match. - /// Otherwise, returns `Err`. - pub(crate) fn try_decrypt<P: CryptoProvider>( - &self, - identity_resolution_material: &SignedSectionIdentityResolutionMaterial, - ) -> Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, SignatureEncryptedSectionDeserializationError> - { - let salt: V1Salt<P> = self.encryption_info.salt().into(); - let (mut buf, mut cipher) = try_decrypt_metadata_key_prefix::<P>( - self.all_ciphertext, - salt.derive(Some(self.identity.offset)).expect("AES-CTR nonce is a valid HKDF size"), - identity_resolution_material.as_raw_resolution_material(), - ) - .ok_or(SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch)?; - - // decrypt everything else - cipher.decrypt(&mut buf[METADATA_KEY_LEN..]); - - Ok(to_array_view(buf)) + /// # Panics + /// If `all_ciphertext` is greater than `NP_ADV_MAX_SECTION_LEN` bytes, + /// or less than `METADATA_KEY_LEN` bytes. + pub(crate) fn new( + adv_header: V1Header, + section_header: u8, + encryption_info: EncryptionInfo, + identity: EncryptedIdentityMetadata, + all_ciphertext: &'a [u8], + ) -> Self { + assert!(all_ciphertext.len() >= METADATA_KEY_LEN); + assert!(all_ciphertext.len() <= NP_ADV_MAX_SECTION_LEN); + Self { adv_header, section_header, encryption_info, identity, all_ciphertext } } - /// Try deserializing into a [Section]. - /// - /// Returns an error if the credential is incorrect or if the section data is malformed. + /// Gets the salt for this encrypted section + pub(crate) fn salt<C: CryptoProvider>(&self) -> V1Salt<C> { + self.encryption_info.salt().into() + } + + /// Constructs a cryptographic nonce for this encrypted section + /// based on the contained salt. + pub(crate) fn compute_nonce<C: CryptoProvider>(&self) -> AesCtrNonce { + self.salt::<C>() + .derive(Some(self.identity.offset)) + .expect("AES-CTR nonce is a valid HKDF size") + } + + /// Constructs some cryptographic contents for section identity-resolution + /// out of the entire contents of this encrypted section. + pub(crate) fn compute_identity_resolution_contents<C: CryptoProvider>( + &self, + ) -> SectionIdentityResolutionContents { + let nonce = self.compute_nonce::<C>(); + let metadata_key_ciphertext: [u8; METADATA_KEY_LEN] = + self.all_ciphertext[..METADATA_KEY_LEN].try_into().unwrap(); + + SectionIdentityResolutionContents { nonce, metadata_key_ciphertext } + } + + /// Given an identity-match, decrypts the ciphertext in this encrypted section + /// and returns the raw bytes of the decrypted plaintext. + pub(crate) fn decrypt_ciphertext<C: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + mut identity_match: IdentityMatch<C>, + ) -> Result<RawDecryptedSection<'a>, ArenaOutOfSpace> { + // Fill decrypt_buf with the ciphertext after the metadata key + let decrypt_buf = + arena.allocate(u8::try_from(self.all_ciphertext.len() - METADATA_KEY_LEN).expect( + "all_ciphertext.len() must be in [METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]", + ))?; + decrypt_buf.copy_from_slice(&self.all_ciphertext[METADATA_KEY_LEN..]); + + // Decrypt everything after the metadata key + identity_match.cipher.apply_keystream(decrypt_buf); + + Ok(RawDecryptedSection { + metadata_key_plaintext: identity_match.metadata_key_plaintext, + nonce: identity_match.nonce, + plaintext_contents: decrypt_buf, + }) + } + + /// Try decrypting into some raw bytes given some raw identity-resolution material. + #[cfg(feature = "devtools")] + pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + identity_resolution_material: &SectionIdentityResolutionMaterial, + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { + let identity_resolution_contents = self.compute_identity_resolution_contents::<P>(); + identity_resolution_contents.try_match(identity_resolution_material).map(|identity_match| { + let decrypted_section = self.decrypt_ciphertext::<P>(arena, identity_match)?; + Ok(decrypted_section.to_raw_bytes()) + }) + } +} + +/// An encrypted section which is verified using a ed25519 signature +#[derive(PartialEq, Eq, Debug)] +pub(crate) struct SignatureEncryptedSection<'a> { + pub(crate) contents: EncryptedSectionContents<'a>, +} + +impl<'a> SignatureEncryptedSection<'a> { + /// Try deserializing into a [`DecryptedSection`] given an identity-match + /// with some paired verification material for the matched identity. pub(crate) fn try_deserialize<P>( &self, - identity_resolution_material: &SignedSectionIdentityResolutionMaterial, + arena: &mut DeserializationArena<'a>, + identity_match: IdentityMatch<P>, verification_material: &SignedSectionVerificationMaterial, - ) -> Result<DecryptedSection, SignatureEncryptedSectionDeserializationError> + ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>> where P: CryptoProvider, { - let raw_salt = self.encryption_info.salt(); - let salt: V1Salt<P> = raw_salt.into(); - let plaintext = self.try_decrypt::<P>(identity_resolution_material)?; - - // won't panic: plaintext length is at least METADATA_KEY_LEN - let (metadata_key, remaining) = plaintext.as_slice().split_at(METADATA_KEY_LEN); - let metadata_key = - metadata_key.try_into().expect("slice is the same length as the desired array"); + let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?; + let metadata_key = raw_decrypted.metadata_key_plaintext; + let nonce = raw_decrypted.nonce; + let remaining = raw_decrypted.plaintext_contents; if remaining.len() < crypto_provider::ed25519::SIGNATURE_LENGTH { - return Err(SignatureEncryptedSectionDeserializationError::SignatureMissing); + return Err(SignatureVerificationError::SignatureMissing.into()); } // should not panic due to above check let (non_identity_des, sig) = remaining.split_at(remaining.len() - crypto_provider::ed25519::SIGNATURE_LENGTH); - // de offset 2 because of leading encryption info and identity DEs - let (_, ref_des) = parse_non_identity_des(2)(non_identity_des) - .map_err(|_| SignatureEncryptedSectionDeserializationError::DeParseError)?; - // All implementations only check for 64 bytes, and this will always result in a 64 byte signature. let expected_signature = np_ed25519::Signature::<P>::try_from(sig).expect("Signature is always 64 bytes."); - let nonce = salt - .derive::<{ AES_CTR_NONCE_LEN }>(Some(self.identity.offset)) - .expect("AES-CTR nonce is a valid HKDF length"); - - // all other plaintext, except for the signature. - // Won't panic because we know we just parsed the sig de from this, - // and we're using the as-parsed lengths - let remainder = &plaintext.as_slice()[..plaintext.len() - sig.len()]; - let section_signature_payload = SectionSignaturePayload::from_deserialized_parts( - self.adv_header.header_byte, - self.section_header, - &self.encryption_info.bytes, + self.contents.adv_header.header_byte, + self.contents.section_header, + &self.contents.encryption_info.bytes, &nonce, - self.identity.header_bytes, - remainder, + self.contents.identity.header_bytes, + metadata_key, + non_identity_des, ); let public_key = verification_material.signature_verification_public_key(); @@ -133,161 +275,221 @@ // Length of the payload should fit in the signature verification buffer. debug_assert!(e != np_ed25519::SignatureVerificationError::PayloadTooBig); - SignatureEncryptedSectionDeserializationError::SignatureMismatch + SignatureVerificationError::SignatureMismatch })?; + let salt = self.contents.salt::<P>(); + Ok(DecryptedSection::new( - self.identity.identity_type, + self.contents.identity.identity_type, VerificationMode::Signature, metadata_key, - raw_salt, - SectionContents::new(self.section_header, &ref_des), + salt.into(), + // de offset 2 because of leading encryption info and identity DEs + SectionContents::new(self.contents.section_header, non_identity_des, 2), )) } + + /// Try decrypting into some raw bytes given some raw signed crypto-material. + #[cfg(feature = "devtools")] + pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + identity_resolution_material: &SignedSectionIdentityResolutionMaterial, + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { + self.contents.try_resolve_identity_and_decrypt::<P>( + arena, + identity_resolution_material.as_raw_resolution_material(), + ) + } + + /// Try deserializing into a [Section] given some raw signed crypto-material. + #[cfg(test)] + pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + identity_resolution_material: &SignedSectionIdentityResolutionMaterial, + verification_material: &SignedSectionVerificationMaterial, + ) -> Result< + DecryptedSection, + IdentityResolutionOrDeserializationError<SignatureVerificationError>, + > { + let section_identity_resolution_contents = + self.contents.compute_identity_resolution_contents::<P>(); + match section_identity_resolution_contents + .try_match::<P>(identity_resolution_material.as_raw_resolution_material()) + { + Some(identity_match) => self + .try_deserialize(arena, identity_match, verification_material) + .map_err(|e| e.into()), + None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError), + } + } } -/// Should not be exposed publicly as it's too detailed. +/// An error when attempting to resolve an identity and then +/// attempt to deserialize an encrypted advertisement. +/// +/// This should not be exposed publicly, since it's too +/// detailed. +#[cfg(test)] #[derive(Debug, PartialEq, Eq)] -pub(crate) enum SignatureEncryptedSectionDeserializationError { - /// The decrypted metadata key did not have the right MAC - MetadataKeyMacMismatch, +pub(crate) enum IdentityResolutionOrDeserializationError<V: VerificationError> { + /// Failed to match the encrypted adv to an identity + IdentityMatchingError, + /// Failed to deserialize the encrypted adv after matching the identity + DeserializationError(DeserializationError<V>), +} + +#[cfg(test)] +impl<V: VerificationError> From<DeserializationError<V>> + for IdentityResolutionOrDeserializationError<V> +{ + fn from(deserialization_error: DeserializationError<V>) -> Self { + Self::DeserializationError(deserialization_error) + } +} + +#[cfg(test)] +impl<V: VerificationError> From<V> for IdentityResolutionOrDeserializationError<V> { + fn from(verification_error: V) -> Self { + Self::DeserializationError(DeserializationError::VerificationError(verification_error)) + } +} + +/// An error when attempting to deserialize an encrypted advertisement, +/// assuming that we already have an identity-match. +/// +/// This should not be exposed publicly, since it's too +/// detailed. +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum DeserializationError<V: VerificationError> { + /// Verification failed + VerificationError(V), + /// The given arena ran out of space + ArenaOutOfSpace, +} + +impl<V: VerificationError> From<ArenaOutOfSpace> for DeserializationError<V> { + fn from(_: ArenaOutOfSpace) -> Self { + Self::ArenaOutOfSpace + } +} + +impl<V: VerificationError> From<V> for DeserializationError<V> { + fn from(verification_error: V) -> Self { + Self::VerificationError(verification_error) + } +} + +/// Common trait bound for errors which arise during +/// verification of encrypted section contents. +/// +/// Implementors should not be exposed publicly, since it's too +/// detailed. +pub(crate) trait VerificationError: Debug + PartialEq + Eq {} + +/// An error when attempting to verify a signature +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum SignatureVerificationError { /// The provided signature did not match the calculated signature SignatureMismatch, /// The provided signature is missing SignatureMissing, - /// Decrypted DEs could not be parsed, possibly because the ciphertext was tampered with, as - /// DE parsing has to be done before the signature is verified - DeParseError, } +impl VerificationError for SignatureVerificationError {} + +/// An encrypted section whose contents are verified to match a message integrity code (MIC) #[derive(PartialEq, Eq, Debug)] pub(crate) struct MicEncryptedSection<'a> { - pub(crate) section_header: u8, - pub(crate) adv_header: &'a V1Header, - pub(crate) encryption_info: EncryptionInfo, + pub(crate) contents: EncryptedSectionContents<'a>, pub(crate) mic: SectionMic, - pub(crate) identity: EncryptedIdentityMetadata, - /// All ciphertext (Contents of identity DE + all DEs). - /// Length must be in `[METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]`. - pub(crate) all_ciphertext: &'a [u8], } impl<'a> MicEncryptedSection<'a> { - /// Attempt to decrypt and verify the encrypted portion of the section with the provided - /// precalculated cryptographic material for an MIC-encrypted section. - /// - /// If successful, returns a buffer containing the decrypted plaintext. - pub(crate) fn try_decrypt<P: CryptoProvider>( - &self, - identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, - verification_material: &UnsignedSectionVerificationMaterial, - ) -> Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, MicEncryptedSectionDeserializationError> - { - let salt: V1Salt<P> = self.encryption_info.salt().into(); - let nonce = - salt.derive(Some(self.identity.offset)).expect("AES-CTR nonce is a valid HKDF size"); - - let (mut buf, mut cipher) = try_decrypt_metadata_key_prefix::<P>( - self.all_ciphertext, - nonce, - identity_resolution_material.as_raw_resolution_material(), - ) - .ok_or(MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch)?; - - // if mic is ok, the section was generated by someone holding at least the shared credential - let mut mic_hmac = verification_material.mic_hmac_key::<P>().build_hmac(); - mic_hmac.update(&NP_SVC_UUID); - mic_hmac.update(&[self.adv_header.header_byte]); - mic_hmac.update(&[self.section_header]); - mic_hmac.update(&self.encryption_info.bytes); - mic_hmac.update(&nonce); - mic_hmac.update(&self.identity.header_bytes); - mic_hmac.update(self.all_ciphertext); - mic_hmac - // adv only contains first 16 bytes of HMAC - .verify_truncated_left(&self.mic.mic) - .map_err(|_e| MicEncryptedSectionDeserializationError::MicMismatch)?; - - // decrypt everything else - cipher.decrypt(&mut buf[METADATA_KEY_LEN..]); - - Ok(to_array_view(buf)) - } - - /// Try deserializing into a [Section]. + /// Try deserializing into a [`DecryptedSection`]. /// /// Returns an error if the credential is incorrect or if the section data is malformed. pub(crate) fn try_deserialize<P>( &self, - identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, + arena: &mut DeserializationArena<'a>, + identity_match: IdentityMatch<P>, verification_material: &UnsignedSectionVerificationMaterial, - ) -> Result<DecryptedSection, MicEncryptedSectionDeserializationError> + ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>> where P: CryptoProvider, { - let plaintext = - self.try_decrypt::<P>(identity_resolution_material, verification_material)?; - // won't panic: plaintext is at least METADATA_KEY_LEN long - let (metadata_key, remaining_des) = plaintext.as_slice().split_at(METADATA_KEY_LEN); - let metadata_key = - metadata_key.try_into().expect("slice is the same length as the desired array"); - // offset 2 for encryption info and identity DEs - let (_, ref_des) = parse_non_identity_des(2)(remaining_des) - .map_err(|_| MicEncryptedSectionDeserializationError::DeParseError)?; + let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?; + let metadata_key = raw_decrypted.metadata_key_plaintext; + let nonce = raw_decrypted.nonce; + let remaining_des = raw_decrypted.plaintext_contents; + + // if mic is ok, the section was generated by someone holding at least the shared credential + let mut mic_hmac = verification_material.mic_hmac_key::<P>().build_hmac(); + mic_hmac.update(&NP_SVC_UUID); + mic_hmac.update(&[self.contents.adv_header.header_byte]); + mic_hmac.update(&[self.contents.section_header]); + mic_hmac.update(&self.contents.encryption_info.bytes); + mic_hmac.update(&nonce); + mic_hmac.update(&self.contents.identity.header_bytes); + mic_hmac.update(self.contents.all_ciphertext); + mic_hmac + // adv only contains first 16 bytes of HMAC + .verify_truncated_left(&self.mic.mic) + .map_err(|_e| MicVerificationError::MicMismatch)?; + + let salt = self.contents.salt::<P>(); Ok(DecryptedSection::new( - self.identity.identity_type, + self.contents.identity.identity_type, VerificationMode::Mic, metadata_key, - self.encryption_info.salt(), - SectionContents::new(self.section_header, &ref_des), + salt.into(), + // offset 2 for encryption info and identity DEs + SectionContents::new(self.contents.section_header, remaining_des, 2), )) } + + /// Try decrypting into some raw bytes given some raw unsigned crypto-material. + #[cfg(feature = "devtools")] + pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { + self.contents.try_resolve_identity_and_decrypt::<P>( + arena, + identity_resolution_material.as_raw_resolution_material(), + ) + } + + /// Try deserializing into a [Section] given some raw unsigned crypto-material. + #[cfg(test)] + pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>( + &self, + arena: &mut DeserializationArena<'a>, + identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, + verification_material: &UnsignedSectionVerificationMaterial, + ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>> + { + let section_identity_resolution_contents = + self.contents.compute_identity_resolution_contents::<P>(); + match section_identity_resolution_contents + .try_match::<P>(identity_resolution_material.as_raw_resolution_material()) + { + Some(identity_match) => self + .try_deserialize(arena, identity_match, verification_material) + .map_err(|e| e.into()), + None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError), + } + } } -/// Should not be exposed publicly as it's too detailed. #[derive(Debug, PartialEq, Eq)] -pub(crate) enum MicEncryptedSectionDeserializationError { - /// Plaintext metadata key did not have the expected MAC - MetadataKeyMacMismatch, +pub(crate) enum MicVerificationError { /// Calculated MIC did not match MIC from section MicMismatch, - /// Parsing of decrypted DEs failed - DeParseError, } -/// Decrypt a ciphertext buffer whose first [METADATA_KEY_LEN] bytes of plaintext are, maybe, the -/// metadata key for an NP identity. -/// -/// Returns `Some` if decrypting the first [METADATA_KEY_LEN] bytes produces plaintext whose HMAC -/// matches the expected MAC. Only the first [METADATA_KEY_LEN] bytes are decrypted. The cipher is -/// returned along with the buffer as it is in the appropriate state to continue decrypting the -/// remaining ciphertext. -/// -/// Returns `None` if the plaintext does not match the expected HMAC. -/// -/// # Panics -/// -/// Panics if `ciphertext`'s length is not in `[METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]`. -#[allow(clippy::type_complexity)] -fn try_decrypt_metadata_key_prefix<C: CryptoProvider>( - ciphertext: &[u8], - nonce: AesCtrNonce, - identity_resolution_material: &SectionIdentityResolutionMaterial, -) -> Option<(tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, C::AesCtr128)> { - assert!(ciphertext.len() <= NP_ADV_MAX_SECTION_LEN); - let mut decrypt_buf = tinyvec::ArrayVec::<[u8; NP_ADV_MAX_SECTION_LEN]>::new(); - let aes_key = &identity_resolution_material.aes_key; - let mut cipher = C::AesCtr128::new(aes_key, NonceAndCounter::from_nonce(nonce)); - - decrypt_buf.extend_from_slice(ciphertext); - cipher.decrypt(&mut decrypt_buf[..METADATA_KEY_LEN]); - - let metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C> = - identity_resolution_material.metadata_key_hmac_key.into(); - let expected_metadata_key_hmac = identity_resolution_material.expected_metadata_key_hmac; - metadata_key_hmac_key - .verify_hmac(&decrypt_buf[..METADATA_KEY_LEN], expected_metadata_key_hmac) - .ok() - .map(|_| (decrypt_buf, cipher)) -} +impl VerificationError for MicVerificationError {}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs index b78cad9..10fc46b 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
@@ -14,8 +14,12 @@ extern crate std; +use crate::deserialization_arena; use crate::extended::data_elements::TxPowerDataElement; -use crate::extended::deserialize::{DecryptedSection, EncryptionInfo, RawV1Salt}; +use crate::extended::deserialize::{ + DataElementParseError, DecryptedSection, EncryptionInfo, RawV1Salt, SectionContents, +}; +use crate::extended::serialize::AdvertisementType; use crate::shared_data::TxPower; use crate::{ credential::v1::*, @@ -23,23 +27,22 @@ extended::{ deserialize::{ encrypted_section::{ - SignatureEncryptedSection, SignatureEncryptedSectionDeserializationError, + EncryptedSectionContents, IdentityResolutionOrDeserializationError, + SignatureEncryptedSection, SignatureVerificationError, }, parse_sections, test_stubs::IntermediateSectionExt, - CiphertextSection, EncryptedIdentityMetadata, OffsetDataElement, SectionContents, - VerificationMode, + CiphertextSection, DataElement, EncryptedIdentityMetadata, VerificationMode, }, section_signature_payload::*, serialize::{ - AdvBuilder, CapacityLimitedVec, SignedEncrypted, SingleTypeDataElement, + AdvBuilder, CapacityLimitedVec, SignedEncryptedSectionEncoder, SingleTypeDataElement, WriteDataElement, }, NP_ADV_MAX_SECTION_LEN, }, - parse_adv_header, AdvHeader, Section, + parse_adv_header, AdvHeader, MetadataKey, Section, }; -use array_view::ArrayView; use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider}; use crypto_provider_default::CryptoProviderImpl; use np_hkdf::v1_salt; @@ -51,7 +54,7 @@ #[test] fn deserialize_signature_encrypted_correct_keys() { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let raw_salt = RawV1Salt([3; 16]); let section_salt = V1Salt::<CryptoProviderImpl>::from(raw_salt); @@ -59,15 +62,16 @@ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let key_pair = KeyPair::generate(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -86,7 +90,7 @@ panic!("incorrect header"); }; - let sections = parse_sections(&adv_header, remaining).unwrap(); + let sections = parse_sections(adv_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); @@ -105,36 +109,44 @@ let section_len = 19 + 2 + 16 + 2 + 64; assert_eq!( &SignatureEncryptedSection { - section_header: section_len, - adv_header: &adv_header, - encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x1], - offset: 1.into(), - identity_type: EncryptedIdentityDataElementType::Private, + contents: EncryptedSectionContents { + section_header: section_len, + adv_header, + encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x1], + offset: 1.into(), + identity_type: EncryptedIdentityDataElementType::Private, + }, + // adv header + salt + section header + encryption info + identity header + all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..], }, - // adv header + salt + section header + encryption info + identity header - all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..], }, contents ); // plaintext is correct { - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>( key_seed, - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), + key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), + key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), key_pair.public(), ); let signed_identity_resolution_material = crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>(); - let decrypted = contents - .try_decrypt::<CryptoProviderImpl>(&signed_identity_resolution_material) + let identity_resolution_contents = + contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>(); + let identity_match = identity_resolution_contents + .try_match::<CryptoProviderImpl>( + &signed_identity_resolution_material.into_raw_resolution_material(), + ) .unwrap(); - let mut expected = metadata_key.as_slice().to_vec(); - // battery de + let mut arena = deserialization_arena!(); + let decrypted = contents.contents.decrypt_ciphertext(&mut arena, identity_match).unwrap(); + + let mut expected = Vec::new(); expected.extend_from_slice(txpower_de.de_header().serialize().as_slice()); txpower_de.write_de_contents(&mut expected); @@ -151,20 +163,22 @@ &encryption_info, &nonce, [0x90, 0x1], + metadata_key, &expected, ); expected.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes()); - - assert_eq!(&expected, decrypted.as_slice()); + assert_eq!(metadata_key, decrypted.metadata_key_plaintext); + assert_eq!(nonce, decrypted.nonce); + assert_eq!(&expected, decrypted.plaintext_contents); } // deserialization to Section works { - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>( key_seed, - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), + key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), + key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), key_pair.public(), ); let signed_identity_resolution_material = @@ -172,8 +186,10 @@ let signed_verification_material = crypto_material.signed_verification_material::<CryptoProviderImpl>(); + let mut arena = deserialization_arena!(); let section = contents - .try_deserialize::<CryptoProviderImpl>( + .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut arena, &signed_identity_resolution_material, &signed_verification_material, ) @@ -187,27 +203,23 @@ raw_salt, SectionContents { section_header: 19 + 2 + 16 + 1 + 1 + 64, - // battery DE - de_data: ArrayView::try_from_slice(&[7]).unwrap(), - data_elements: vec![OffsetDataElement { - offset: 2.into(), - de_type: 0x05_u8.into(), - start_of_contents: 0, - contents_len: 1, - }], + data_element_start_offset: 2, + de_region_excl_identity: + &[txpower_de.de_header().serialize().as_slice(), &[7],].concat(), }, ), section ); + let data_elements = section.collect_data_elements().unwrap(); + assert_eq!( + data_elements, + &[DataElement { offset: 2.into(), de_type: 0x05_u8.into(), contents: &[7] }] + ); assert_eq!( - vec![( - v1_salt::DataElementOffset::from(2_usize), - TxPowerDataElement::DE_TYPE, - vec![7u8] - )], - section - .data_elements() + vec![(v1_salt::DataElementOffset::from(2_u8), TxPowerDataElement::DE_TYPE, vec![7u8])], + data_elements + .into_iter() .map(|de| (de.offset(), de.de_type(), de.contents().to_vec())) .collect::<Vec<_>>() ); @@ -218,7 +230,7 @@ fn deserialize_signature_encrypted_incorrect_aes_key_error() { // bad aes key -> bad metadata key plaintext do_bad_deserialize_params::<CryptoProviderImpl>( - SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, Some([0xFF; 16].into()), None, None, @@ -230,7 +242,7 @@ fn deserialize_signature_encrypted_incorrect_metadata_key_hmac_key_error() { // bad metadata key hmac key -> bad calculated metadata key mac do_bad_deserialize_params::<CryptoProviderImpl>( - SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, None, Some([0xFF; 32].into()), None, @@ -242,7 +254,7 @@ fn deserialize_signature_encrypted_incorrect_expected_metadata_key_hmac_error() { // bad expected metadata key mac do_bad_deserialize_params::<CryptoProviderImpl>( - SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + IdentityResolutionOrDeserializationError::IdentityMatchingError, None, None, Some([0xFF; 32]), @@ -254,7 +266,7 @@ fn deserialize_signature_encrypted_incorrect_pub_key_error() { // a random pub key will lead to signature mismatch do_bad_deserialize_params::<CryptoProviderImpl>( - SignatureEncryptedSectionDeserializationError::SignatureMismatch, + SignatureVerificationError::SignatureMismatch.into(), None, None, None, @@ -266,7 +278,9 @@ fn deserialize_signature_encrypted_incorrect_salt_error() { // bad salt -> bad iv -> bad metadata key plaintext do_bad_deserialize_tampered( - SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError::IdentityMatchingError, + ), None, |_| {}, |adv_mut| adv_mut[5..21].copy_from_slice(&[0xFF; 16]), @@ -276,7 +290,9 @@ #[test] fn deserialize_signature_encrypted_tampered_signature_error() { do_bad_deserialize_tampered( - SignatureEncryptedSectionDeserializationError::SignatureMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMismatch.into(), + ), None, |_| {}, // flip a bit in the middle of the signature @@ -290,7 +306,9 @@ #[test] fn deserialize_signature_encrypted_tampered_ciphertext_error() { do_bad_deserialize_tampered( - SignatureEncryptedSectionDeserializationError::SignatureMismatch, + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMismatch.into(), + ), None, |_| {}, // flip a bit outside of the signature @@ -305,7 +323,9 @@ fn deserialize_signature_encrypted_missing_signature_de_error() { let section_len = 19 + 2 + 16 + 1 + 1; do_bad_deserialize_tampered( - SignatureEncryptedSectionDeserializationError::SignatureMissing, + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMissing.into(), + ), Some(section_len), |_| {}, |adv_mut| { @@ -320,7 +340,7 @@ #[test] fn deserialize_signature_encrypted_des_wont_parse() { do_bad_deserialize_tampered( - SignatureEncryptedSectionDeserializationError::DeParseError, + DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd), Some(19 + 2 + 16 + 1 + 1 + 64 + 1), // add an impossible DE |section| section.try_push(0xFF).unwrap(), @@ -331,28 +351,29 @@ /// Attempt a deserialization that will fail when using the provided parameters for decryption only. /// `None` means use the correct value for that parameter. fn do_bad_deserialize_params<C: CryptoProvider>( - error: SignatureEncryptedSectionDeserializationError, + error: IdentityResolutionOrDeserializationError<SignatureVerificationError>, aes_key: Option<crypto_provider::aes::Aes128Key>, metadata_key_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>, expected_metadata_key_hmac: Option<[u8; 32]>, pub_key: Option<np_ed25519::PublicKey<C>>, ) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let section_salt: v1_salt::V1Salt<C> = [3; 16].into(); let identity_type = EncryptedIdentityDataElementType::Private; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed); let key_pair = np_ed25519::KeyPair::<C>::generate(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new( + .section_builder(SignedEncryptedSectionEncoder::new( identity_type, section_salt, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -370,7 +391,7 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); @@ -389,7 +410,9 @@ .unwrap_or_else(|| key_seed_hkdf.extended_signed_metadata_key_hmac_key()) .as_bytes(), expected_metadata_key_hmac: expected_metadata_key_hmac.unwrap_or_else(|| { - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key) + key_seed_hkdf + .extended_signed_metadata_key_hmac_key() + .calculate_hmac(&metadata_key.0) }), }); @@ -400,7 +423,8 @@ assert_eq!( error, contents - .try_deserialize::<C>( + .try_resolve_identity_and_deserialize::<C>( + &mut deserialization_arena!(), &signed_identity_resolution_material, &signed_verification_material, ) @@ -408,34 +432,40 @@ ); } +#[derive(Debug, PartialEq)] +enum DeserializeError { + IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError<SignatureVerificationError>, + ), + DataElementParseError(DataElementParseError), +} + /// Run a test that mangles the advertisement contents before attempting to deserialize. /// /// Since the advertisement is ciphertext, only changes outside -fn do_bad_deserialize_tampered< - A: Fn(&mut Vec<u8>), - S: Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), ->( - expected_error: SignatureEncryptedSectionDeserializationError, +fn do_bad_deserialize_tampered( + expected_error: DeserializeError, expected_section_len: Option<u8>, - mangle_section: S, - mangle_adv_contents: A, + mangle_section: impl Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), + mangle_adv_contents: impl Fn(&mut Vec<u8>), ) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let section_salt: v1_salt::V1Salt<CryptoProviderImpl> = [3; 16].into(); let identity_type = EncryptedIdentityDataElementType::Private; - let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed); + let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let key_pair = KeyPair::generate(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new( + .section_builder(SignedEncryptedSectionEncoder::new( identity_type, section_salt, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -457,7 +487,7 @@ panic!("incorrect header"); }; - let sections = parse_sections(&adv_header, remaining).unwrap(); + let sections = parse_sections(adv_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections.into_iter().next().unwrap(); @@ -476,23 +506,25 @@ let section_len = 19 + 2 + 16 + 2 + 64; assert_eq!( &SignatureEncryptedSection { - section_header: expected_section_len.unwrap_or(section_len), - adv_header: &adv_header, - encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x1], - offset: 1.into(), - identity_type: EncryptedIdentityDataElementType::Private, + contents: EncryptedSectionContents { + section_header: expected_section_len.unwrap_or(section_len), + adv_header, + encryption_info: EncryptionInfo { bytes: encryption_info_bytes }, + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x1], + offset: 1.into(), + identity_type: EncryptedIdentityDataElementType::Private, + }, + all_ciphertext: &adv_mut[1 + 1 + 19 + 2..], }, - all_ciphertext: &adv_mut[1 + 1 + 19 + 2..], }, contents ); - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>( key_seed, - key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), + key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), + key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), key_pair.public(), ); let identity_resolution_material = @@ -500,13 +532,22 @@ let verification_material = crypto_material.signed_verification_material::<CryptoProviderImpl>(); - assert_eq!( - expected_error, - contents - .try_deserialize::<CryptoProviderImpl>( - &identity_resolution_material, - &verification_material, - ) - .unwrap_err() - ); + match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut deserialization_arena!(), + &identity_resolution_material, + &verification_material, + ) { + Ok(section) => { + assert_eq!( + expected_error, + DeserializeError::DataElementParseError( + section.collect_data_elements().unwrap_err() + ), + ); + } + Err(e) => assert_eq!( + expected_error, + DeserializeError::IdentityResolutionOrDeserializationError(e), + ), + }; }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/mod.rs index 1829ad6..d431325 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
@@ -18,9 +18,8 @@ use alloc::vec::Vec; use core::array::TryFromSliceError; -use core::borrow::Borrow; -use core::slice; +use core::fmt::Debug; use nom::{branch, bytes, combinator, error, multi, number, sequence}; use strum::IntoEnumIterator as _; @@ -28,23 +27,30 @@ use crypto_provider::CryptoProvider; use np_hkdf::v1_salt::{self, V1Salt}; -use crate::extended::NP_V1_ADV_MAX_SECTION_COUNT; +#[cfg(any(feature = "devtools", test))] +use crate::credential::v1::V1DiscoveryCryptoMaterial; +use crate::credential::v1::V1; +use crate::deserialization_arena::ArenaOutOfSpace; +#[cfg(any(feature = "devtools", test))] +use crate::deserialization_arena::DeserializationArena; +#[cfg(test)] +use crate::extended::deserialize::encrypted_section::IdentityResolutionOrDeserializationError; +use crate::extended::{NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT}; use crate::{ - credential::{v1::*, V1Credential}, de_type::{EncryptedIdentityDataElementType, IdentityDataElementType}, extended::{ data_elements::{MIC_ENCRYPTION_SCHEME, SIGNATURE_ENCRYPTION_SCHEME}, de_type::{DeType, ExtendedDataElementType as _}, deserialize::encrypted_section::{ - MicEncryptedSection, MicEncryptedSectionDeserializationError, - SignatureEncryptedSection, SignatureEncryptedSectionDeserializationError, + DeserializationError, EncryptedSectionContents, MicEncryptedSection, + MicVerificationError, SignatureEncryptedSection, SignatureVerificationError, }, - to_array_view, DeLength, ENCRYPTION_INFO_DE_TYPE, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN, + DeLength, ENCRYPTION_INFO_DE_TYPE, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN, }, - PlaintextIdentityMode, V1Header, + HasIdentityMatch, MetadataKey, PlaintextIdentityMode, V1Header, }; -mod encrypted_section; +pub(crate) mod encrypted_section; #[cfg(test)] mod parse_tests; @@ -57,33 +63,39 @@ /// Parse into [IntermediateSection]s, exposing the underlying parsing errors. /// Consumes all of `adv_body`. -pub(crate) fn parse_sections<'a, 'h: 'a>( - adv_header: &'h V1Header, - adv_body: &'a [u8], -) -> Result<Vec<IntermediateSection<'a>>, nom::Err<error::Error<&'a [u8]>>> { - combinator::all_consuming(multi::many_m_n( - 1, - NP_V1_ADV_MAX_SECTION_COUNT, - IntermediateSection::parser_with_header(adv_header), - ))(adv_body) +pub(crate) fn parse_sections( + adv_header: V1Header, + adv_body: &[u8], +) -> Result<Vec<IntermediateSection>, nom::Err<error::Error<&[u8]>>> { + combinator::all_consuming(branch::alt(( + // Public advertisement + multi::many_m_n( + 1, + NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + IntermediateSection::parser_public_section(), + ), + // Encrypted advertisement + multi::many_m_n( + 1, + NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, + IntermediateSection::parser_encrypted_with_header(adv_header), + ), + )))(adv_body) .map(|(_rem, sections)| sections) } /// A partially processed section that hasn't been decrypted (if applicable) yet. #[derive(PartialEq, Eq, Debug)] -// sections are large because they have a stack allocated buffer for the whole section -#[allow(clippy::large_enum_variant)] pub(crate) enum IntermediateSection<'a> { /// A section that was not encrypted, e.g. a public identity or no-identity section. - Plaintext(PlaintextSection), + Plaintext(PlaintextSection<'a>), /// A section whose contents were encrypted, e.g. a private identity section. Ciphertext(CiphertextSection<'a>), } impl<'a> IntermediateSection<'a> { - fn parser_with_header( - adv_header: &'a V1Header, - ) -> impl Fn(&'a [u8]) -> nom::IResult<&[u8], IntermediateSection> { + fn parser_public_section( + ) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], IntermediateSection<'a>> { move |adv_body| { let (remaining, section_contents_len) = combinator::verify(number::complete::u8, |sec_len| { @@ -92,29 +104,42 @@ // Section structure possibilities: // - Public Identity DE, all other DEs - // - No identity DE, all other DEs (including Salt, excluding identity DEs) - // - Encryption information, non-public Identity header, ciphertext - // - MIC, encryption information, non-public Identity header, ciphertext - let parse_public_identity = combinator::map( // 1 starting offset because of public identity before it - sequence::tuple((PublicIdentity::parse, parse_non_identity_des(1))), + sequence::tuple((PublicIdentity::parse, nom::combinator::rest)), // move closure to copy section_len into scope - move |(_identity, des)| { + move |(_identity, rest)| { IntermediateSection::Plaintext(PlaintextSection::new( PlaintextIdentityMode::Public, - SectionContents::new(/* section_header */ section_contents_len, &des), + SectionContents::new( + /* section_header */ section_contents_len, + rest, + 1, + ), )) }, ); + combinator::map_parser( + bytes::complete::take(section_contents_len), + // Guarantee we consume all of the section (not all of the adv body) + // Note: `all_consuming` should never fail, since we used the `rest` parser above. + combinator::all_consuming(parse_public_identity), + )(remaining) + } + } - // 0 starting offset because there's no identity DE - let parse_no_identity_de = combinator::map(parse_non_identity_des(0), move |des| { - IntermediateSection::Plaintext(PlaintextSection::new( - PlaintextIdentityMode::None, - SectionContents::new(/* section_header */ section_contents_len, &des), - )) - }); + fn parser_encrypted_with_header( + adv_header: V1Header, + ) -> impl Fn(&'a [u8]) -> nom::IResult<&[u8], IntermediateSection> { + move |adv_body| { + let (remaining, section_contents_len) = + combinator::verify(number::complete::u8, |sec_len| { + *sec_len as usize <= NP_ADV_MAX_SECTION_LEN && *sec_len as usize > 0 + })(adv_body)?; + + // Section structure possibilities: + // - Encryption information, non-public Identity header, ciphertext + // - Encryption information, non-public Identity header, ciphertext, MIC let parse_encrypted_identity = combinator::map( sequence::tuple(( @@ -139,11 +164,13 @@ let to_skip = identity.header_bytes.len(); IntermediateSection::Ciphertext(CiphertextSection::SignatureEncryptedIdentity( SignatureEncryptedSection { - section_header: section_contents_len, - adv_header, - encryption_info, - identity, - all_ciphertext: &identity_and_ciphertext[to_skip..], + contents: EncryptedSectionContents::new( + adv_header, + section_contents_len, + encryption_info, + identity, + &identity_and_ciphertext[to_skip..], + ), }, )) }, @@ -179,14 +206,16 @@ let to_skip = identity.header_bytes.len(); IntermediateSection::Ciphertext(CiphertextSection::MicEncryptedIdentity( MicEncryptedSection { - section_header: section_contents_len, - adv_header, + contents: EncryptedSectionContents::new( + adv_header, + section_contents_len, + encryption_info, + identity, + &identity_and_ciphertext[to_skip..], + ), mic: mic.try_into().unwrap_or_else(|_| { panic!("{} is a valid length", SectionMic::CONTENTS_LEN) }), - encryption_info, - identity, - all_ciphertext: &identity_and_ciphertext[to_skip..], }, )) }, @@ -198,8 +227,6 @@ combinator::all_consuming(branch::alt(( parse_mic_encrypted_identity, parse_encrypted_identity, - parse_public_identity, - parse_no_identity_de, ))), )(remaining) } @@ -207,61 +234,59 @@ } #[derive(PartialEq, Eq, Debug)] -struct SectionContents { +struct SectionContents<'adv> { section_header: u8, - de_data: ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, - data_elements: Vec<OffsetDataElement>, + de_region_excl_identity: &'adv [u8], + data_element_start_offset: u8, } -impl SectionContents { - fn new(section_header: u8, data_elements: &[RefDataElement]) -> Self { - let (data_elements, de_data) = convert_data_elements(data_elements); - - Self { section_header, de_data, data_elements } +impl<'adv> SectionContents<'adv> { + fn new( + section_header: u8, + de_region_excl_identity: &'adv [u8], + data_element_start_offset: u8, + ) -> Self { + Self { section_header, de_region_excl_identity, data_element_start_offset } } - fn data_elements(&'_ self) -> DataElements<'_> { - DataElements { de_iter: self.data_elements.iter(), de_data: &self.de_data } - } -} - -/// An iterator over data elements in a V1 section -// A concrete type to make it easy to refer to in return types where opaque types aren't available. -pub struct DataElements<'a> { - de_iter: slice::Iter<'a, OffsetDataElement>, - de_data: &'a ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, -} - -impl<'a> Iterator for DataElements<'a> { - type Item = DataElement<'a>; - - fn next(&mut self) -> Option<Self::Item> { - self.de_iter.next().map(|de| DataElement { de_data: self.de_data, de }) + fn iter_data_elements(&self) -> DataElementParsingIterator<'adv> { + DataElementParsingIterator { + input: self.de_region_excl_identity, + offset: self.data_element_start_offset, + } } } /// A section deserialized from a V1 advertisement. -pub trait Section { +pub trait Section<'adv, E: Debug> { /// The iterator type used to iterate over data elements - type Iterator<'a>: Iterator<Item = DataElement<'a>> - where - Self: 'a; + type Iterator: Iterator<Item = Result<DataElement<'adv>, E>>; /// Iterator over the data elements in a section, except for any DEs related to resolving the /// identity or otherwise validating the payload (e.g. MIC, Signature, any identity DEs like /// Private Identity). - fn data_elements(&'_ self) -> Self::Iterator<'_>; + fn iter_data_elements(&self) -> Self::Iterator; + + /// Collects the data elements into a vector, eagerly catching and resolving any errors during + /// parsing. + #[cfg(any(test, feature = "alloc"))] + fn collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, E> + where + Self: Sized, + { + self.iter_data_elements().collect() + } } /// A plaintext section deserialized from a V1 advertisement. #[derive(PartialEq, Eq, Debug)] -pub struct PlaintextSection { +pub struct PlaintextSection<'adv> { identity: PlaintextIdentityMode, - contents: SectionContents, + contents: SectionContents<'adv>, } -impl PlaintextSection { - fn new(identity: PlaintextIdentityMode, contents: SectionContents) -> Self { +impl<'adv> PlaintextSection<'adv> { + fn new(identity: PlaintextIdentityMode, contents: SectionContents<'adv>) -> Self { Self { identity, contents } } @@ -274,11 +299,11 @@ } } -impl Section for PlaintextSection { - type Iterator<'a> = DataElements<'a> where Self: 'a; +impl<'adv> Section<'adv, DataElementParseError> for PlaintextSection<'adv> { + type Iterator = DataElementParsingIterator<'adv>; - fn data_elements(&'_ self) -> Self::Iterator<'_> { - self.contents.data_elements() + fn iter_data_elements(&self) -> Self::Iterator { + self.contents.iter_data_elements() } } @@ -300,23 +325,29 @@ } } -/// The decrypted contents of a ciphertext section. -#[derive(Debug, PartialEq, Eq)] -pub struct DecryptedSection { - identity_type: EncryptedIdentityDataElementType, - verification_mode: VerificationMode, - metadata_key: [u8; METADATA_KEY_LEN], - salt: RawV1Salt, - contents: SectionContents, +impl<C: CryptoProvider> From<V1Salt<C>> for RawV1Salt { + fn from(salt: V1Salt<C>) -> Self { + RawV1Salt(salt.into_array()) + } } -impl DecryptedSection { +/// Fully-parsed and verified decrypted contents from an encrypted section. +#[derive(Debug, PartialEq, Eq)] +pub struct DecryptedSection<'adv> { + identity_type: EncryptedIdentityDataElementType, + verification_mode: VerificationMode, + metadata_key: MetadataKey, + salt: RawV1Salt, + contents: SectionContents<'adv>, +} + +impl<'adv> DecryptedSection<'adv> { fn new( identity_type: EncryptedIdentityDataElementType, verification_mode: VerificationMode, - metadata_key: [u8; 16], + metadata_key: MetadataKey, salt: RawV1Salt, - contents: SectionContents, + contents: SectionContents<'adv>, ) -> Self { Self { identity_type, verification_mode, metadata_key, salt, contents } } @@ -331,22 +362,24 @@ self.verification_mode } - /// The decrypted metadata key from the identity DE. - pub fn metadata_key(&self) -> &[u8; 16] { - &self.metadata_key - } - /// The salt used for decryption of this advertisement. pub fn salt(&self) -> RawV1Salt { self.salt } } -impl Section for DecryptedSection { - type Iterator<'a> = DataElements<'a> where Self: 'a; +impl<'adv> HasIdentityMatch for DecryptedSection<'adv> { + type Version = V1; + fn metadata_key(&self) -> MetadataKey { + self.metadata_key + } +} - fn data_elements(&'_ self) -> Self::Iterator<'_> { - self.contents.data_elements() +impl<'adv> Section<'adv, DataElementParseError> for DecryptedSection<'adv> { + type Iterator = DataElementParsingIterator<'adv>; + + fn iter_data_elements(&self) -> Self::Iterator { + self.contents.iter_data_elements() } } @@ -357,96 +390,208 @@ } impl<'a> CiphertextSection<'a> { - pub(crate) fn try_deserialize<'c, C, P>( + /// Tries to match this section's identity using the given crypto-material, + /// and if successful, tries to decrypt this section. + #[cfg(test)] + pub(crate) fn try_resolve_identity_and_deserialize<C, P>( &'a self, - credential: &'c C, - ) -> Result<DecryptedSection, SectionDeserializeError> + arena: &mut DeserializationArena<'a>, + crypto_material: &C, + ) -> Result<DecryptedSection<'a>, SectionDeserializeError> where - C: V1Credential, + C: V1DiscoveryCryptoMaterial, P: CryptoProvider, { - let crypto_material = credential.crypto_material(); match self { - Self::SignatureEncryptedIdentity(contents) => { + CiphertextSection::SignatureEncryptedIdentity(contents) => { let identity_resolution_material = crypto_material.signed_identity_resolution_material::<P>(); let verification_material = crypto_material.signed_verification_material::<P>(); contents - .try_deserialize::<P>( - identity_resolution_material.borrow(), + .try_resolve_identity_and_deserialize::<P>( + arena, + &identity_resolution_material, &verification_material, ) - .map_err(|e| match e { - SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch - | SignatureEncryptedSectionDeserializationError::SignatureMismatch => { - SectionDeserializeError::IncorrectCredential - } - SignatureEncryptedSectionDeserializationError::SignatureMissing - | SignatureEncryptedSectionDeserializationError::DeParseError => { - SectionDeserializeError::ParseError - } - }) + .map_err(|e| e.into()) } - Self::MicEncryptedIdentity(contents) => { + CiphertextSection::MicEncryptedIdentity(contents) => { let identity_resolution_material = crypto_material.unsigned_identity_resolution_material::<P>(); let verification_material = crypto_material.unsigned_verification_material::<P>(); contents - .try_deserialize::<P>( - identity_resolution_material.borrow(), + .try_resolve_identity_and_deserialize::<P>( + arena, + &identity_resolution_material, &verification_material, ) - .map_err(|e| match e { - MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch - | MicEncryptedSectionDeserializationError::MicMismatch => { - SectionDeserializeError::IncorrectCredential - } - MicEncryptedSectionDeserializationError::DeParseError => { - SectionDeserializeError::ParseError - } - }) + .map_err(|e| e.into()) } } } + + /// Try decrypting into some raw bytes given some raw unsigned crypto-material. + #[cfg(feature = "devtools")] + pub(crate) fn try_resolve_identity_and_decrypt< + C: V1DiscoveryCryptoMaterial, + P: CryptoProvider, + >( + &self, + arena: &mut DeserializationArena<'a>, + crypto_material: &C, + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { + match self { + CiphertextSection::SignatureEncryptedIdentity(x) => { + let identity_resolution_material = + crypto_material.signed_identity_resolution_material::<P>(); + x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material) + } + CiphertextSection::MicEncryptedIdentity(x) => { + let identity_resolution_material = + crypto_material.unsigned_identity_resolution_material::<P>(); + x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material) + } + } + } + + pub(crate) fn contents(&self) -> &EncryptedSectionContents<'a> { + match self { + CiphertextSection::SignatureEncryptedIdentity(x) => &x.contents, + CiphertextSection::MicEncryptedIdentity(x) => &x.contents, + } + } } -/// Errors that can occur when deserializing an advertisement +/// Errors that can occur when deserializing a section +/// of a V1 advertisement. #[derive(Debug, PartialEq, Eq)] pub enum SectionDeserializeError { /// The credential supplied did not match the section's identity data IncorrectCredential, /// Section data is malformed ParseError, + /// The given arena is not large enough to hold the decrypted contents + ArenaOutOfSpace, } -/// Returns a parser function that parses data elements whose type is not one of the known identity -/// DE types, and whose offsets start from the provided `starting_offset`. -/// -/// Consumes all input. -fn parse_non_identity_des( - starting_offset: usize, -) -> impl Fn(&[u8]) -> nom::IResult<&[u8], Vec<RefDataElement>> { - move |input: &[u8]| { - combinator::map_opt( - combinator::all_consuming(multi::many0(combinator::verify( - ProtoDataElement::parse, - |de| IdentityDataElementType::iter().all(|t| t.type_code() != de.header.de_type), - ))), - |proto_des| { - proto_des - .into_iter() - .enumerate() - .map(|(offset, pde)| starting_offset.checked_add(offset).map(|sum| (sum, pde))) - .map(|res| { - res.map(|(offset, pde)| { - pde.into_data_element(v1_salt::DataElementOffset::from(offset)) - }) - }) - .collect::<Option<Vec<_>>>() - }, - )(input) +#[cfg(test)] +impl From<IdentityResolutionOrDeserializationError<MicVerificationError>> + for SectionDeserializeError +{ + fn from(error: IdentityResolutionOrDeserializationError<MicVerificationError>) -> Self { + match error { + IdentityResolutionOrDeserializationError::IdentityMatchingError => { + Self::IncorrectCredential + } + IdentityResolutionOrDeserializationError::DeserializationError(e) => e.into(), + } + } +} + +#[cfg(test)] +impl From<IdentityResolutionOrDeserializationError<SignatureVerificationError>> + for SectionDeserializeError +{ + fn from(error: IdentityResolutionOrDeserializationError<SignatureVerificationError>) -> Self { + match error { + IdentityResolutionOrDeserializationError::IdentityMatchingError => { + Self::IncorrectCredential + } + IdentityResolutionOrDeserializationError::DeserializationError(e) => e.into(), + } + } +} + +impl From<DeserializationError<MicVerificationError>> for SectionDeserializeError { + fn from(mic_deserialization_error: DeserializationError<MicVerificationError>) -> Self { + match mic_deserialization_error { + DeserializationError::VerificationError(MicVerificationError::MicMismatch) => { + Self::IncorrectCredential + } + DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace, + } + } +} + +impl From<DeserializationError<SignatureVerificationError>> for SectionDeserializeError { + fn from( + signature_deserialization_error: DeserializationError<SignatureVerificationError>, + ) -> Self { + match signature_deserialization_error { + DeserializationError::VerificationError( + SignatureVerificationError::SignatureMissing, + ) => Self::ParseError, + DeserializationError::VerificationError( + SignatureVerificationError::SignatureMismatch, + ) => Self::IncorrectCredential, + DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace, + } + } +} + +/// An iterator that parses the given data elements iteratively. In environments where memory is +/// not severely constrained, it is usually safer to collect this into `Result<Vec<DataElement>>` +/// so the validity of the whole advertisement can be checked before proceeding with further +/// processing. +#[derive(Debug)] +pub struct DataElementParsingIterator<'adv> { + input: &'adv [u8], + // The index of the data element this is currently at + offset: u8, +} + +/// The error that may arise while parsing data elements. +#[derive(Debug, PartialEq, Eq)] +pub enum DataElementParseError { + /// Only one identity data element is allowed in an advertisement, but a duplicate is found + /// while parsing. + DuplicateIdentityDataElement, + /// Unexpected data found after the end of the data elements portion. This means either the + /// parser was fed with additional data (it should only be given the bytes within a section, + /// not the whole advertisement), or the length field in the header of the data element is + /// malformed. + UnexpectedDataAfterEnd, + /// There are too many data elements in the advertisement. The maximum number supported by the + /// current parsing logic is 255. + TooManyDataElements, + /// A parse error is returned during nom. + NomError(nom::error::ErrorKind), +} + +impl<'adv> Iterator for DataElementParsingIterator<'adv> { + type Item = Result<DataElement<'adv>, DataElementParseError>; + + fn next(&mut self) -> Option<Self::Item> { + match ProtoDataElement::parse(self.input) { + Ok((rem, pde)) => { + if !IdentityDataElementType::iter().all(|t| t.type_code() != pde.header.de_type) { + return Some(Err(DataElementParseError::DuplicateIdentityDataElement)); + } + self.input = rem; + let current_offset = self.offset; + self.offset = if let Some(offset) = self.offset.checked_add(1) { + offset + } else { + return Some(Err(DataElementParseError::TooManyDataElements)); + }; + Some(Ok(pde.into_data_element(v1_salt::DataElementOffset::from(current_offset)))) + } + Err(nom::Err::Failure(e)) => Some(Err(DataElementParseError::NomError(e.code))), + Err(nom::Err::Incomplete(_)) => { + panic!("Should always complete since we are parsing using the `nom::complete` APIs") + } + Err(nom::Err::Error(_)) => { + // nom `Error` is recoverable, it usually means we should move on the parsing the + // next section. There is nothing after data elements within a section, so we just + // check that there is no remaining data. + if !self.input.is_empty() { + return Some(Err(DataElementParseError::UnexpectedDataAfterEnd)); + } + None + } + } } } @@ -533,8 +678,8 @@ type_bytes .iter() - .fold(Some(0_u32), |accum, b| { - accum.and_then(|n| n.checked_shl(7)).map(|n| n + ((b & 0x7F) as u32)) + .try_fold(0_u32, |accum, b| { + accum.checked_shl(7).map(|n| n + ((b & 0x7F) as u32)) }) .and_then(|type_code| { ArrayView::try_from_slice(header_bytes).map(|header_bytes| DeHeader { @@ -562,56 +707,14 @@ fn parse(input: &[u8]) -> nom::IResult<&[u8], ProtoDataElement> { let (remaining, header) = DeHeader::parse(input)?; let len = header.contents_len; - combinator::map(bytes::complete::take(len.as_usize()), move |slice| { + combinator::map(bytes::complete::take(len.as_u8()), move |slice| { let header_clone = header.clone(); ProtoDataElement { header: header_clone, contents: slice } })(remaining) } - fn into_data_element(self, offset: v1_salt::DataElementOffset) -> RefDataElement<'d> { - RefDataElement { - offset, - header_len: self.header.header_bytes.len().try_into().expect("header is <= 6 bytes"), - de_type: self.header.de_type, - contents: self.contents, - } - } -} - -/// A data element that holds a slice reference for its contents -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct RefDataElement<'d> { - pub(crate) offset: v1_salt::DataElementOffset, - pub(crate) header_len: u8, - pub(crate) de_type: DeType, - pub(crate) contents: &'d [u8], -} - -/// A deserialized data element in a section. -/// -/// The DE has been processed to the point of exposing a DE type and its contents as a `&[u8]`, but -/// no DE-type-specific processing has been performed. -#[derive(Debug)] -pub struct DataElement<'a> { - de_data: &'a ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, - de: &'a OffsetDataElement, -} - -impl<'a> DataElement<'a> { - /// The offset of the DE in its containing Section. - /// - /// Used with the section salt to derive per-DE salt. - pub fn offset(&self) -> v1_salt::DataElementOffset { - self.de.offset - } - /// The type of the DE - pub fn de_type(&self) -> DeType { - self.de.de_type - } - /// The contents of the DE - pub fn contents(&self) -> &[u8] { - &self.de_data.as_slice() - [self.de.start_of_contents..self.de.start_of_contents + self.de.contents_len] + fn into_data_element(self, offset: v1_salt::DataElementOffset) -> DataElement<'d> { + DataElement { offset, de_type: self.header.de_type, contents: self.contents } } } @@ -635,7 +738,7 @@ pub struct EncryptedSectionIdentity { identity_type: EncryptedIdentityDataElementType, validation_mode: VerificationMode, - metadata_key: [u8; METADATA_KEY_LEN], + metadata_key: MetadataKey, } impl EncryptedSectionIdentity { @@ -648,52 +751,37 @@ self.validation_mode } /// The decrypted metadata key from the section's identity DE - pub fn metadata_key(&self) -> &[u8; METADATA_KEY_LEN] { - &self.metadata_key + pub fn metadata_key(&self) -> MetadataKey { + self.metadata_key } } -/// A DE that designates its contents via offset and length to avoid self-referential slices. -#[derive(Debug, PartialEq, Eq)] -struct OffsetDataElement { +/// A deserialized data element in a section. +/// +/// The DE has been processed to the point of exposing a DE type and its contents as a `&[u8]`, but +/// no DE-type-specific processing has been performed. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DataElement<'adv> { offset: v1_salt::DataElementOffset, de_type: DeType, - start_of_contents: usize, - contents_len: usize, + contents: &'adv [u8], } -/// Convert data elements from holding slices to holding offsets and lengths to avoid -/// lifetime woes. -/// This entails some data copying, so if it causes noticeable perf issues we can revisit -/// it, but it is likely to be much cheaper than decryption. -/// -/// # Panics -/// -/// Will panic if handed more data elements than fit in one section. This is only possible if -/// generating data elements from a source other than parsing a section. -fn convert_data_elements( - elements: &[RefDataElement], -) -> (Vec<OffsetDataElement>, ArrayView<u8, NP_ADV_MAX_SECTION_LEN>) { - let mut buf = tinyvec::ArrayVec::new(); - - ( - elements - .iter() - .map(|de| { - let current_len = buf.len(); - // won't overflow because these DEs originally came from a section, and now - // we're packing only their contents without DE headers - buf.extend_from_slice(de.contents); - OffsetDataElement { - offset: de.offset, - de_type: de.de_type, - start_of_contents: current_len, - contents_len: de.contents.len(), - } - }) - .collect(), - to_array_view(buf), - ) +impl<'adv> DataElement<'adv> { + /// The offset of the DE in its containing Section. + /// + /// Used with the section salt to derive per-DE salt. + pub fn offset(&self) -> v1_salt::DataElementOffset { + self.offset + } + /// The type of the DE + pub fn de_type(&self) -> DeType { + self.de_type + } + /// The contents of the DE + pub fn contents(&self) -> &'adv [u8] { + self.contents + } } #[derive(PartialEq, Eq, Debug, Clone)] @@ -787,7 +875,7 @@ combinator::map( combinator::verify(DeHeader::parse, |deh| { deh.de_type == IdentityDataElementType::Public.type_code() - && deh.contents_len.as_usize() == 0 + && deh.contents_len.as_u8() == 0 }), |_| PublicIdentity, )(input) @@ -809,7 +897,7 @@ // 2-byte header, 16-byte metadata key pub(crate) const TOTAL_DE_LEN: usize = 18; - /// Returns a parser function that parses an [EncryptedIdentity] using the provided DE `offset`. + /// Returns a parser function that parses an [`EncryptedIdentityMetadata`] using the provided DE `offset`. fn parser_at_offset( offset: v1_salt::DataElementOffset, ) -> impl Fn(&[u8]) -> nom::IResult<&[u8], EncryptedIdentityMetadata> {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs index f3b8258..506a127 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
@@ -13,85 +13,17 @@ // limitations under the License. extern crate std; - use super::*; use crate::extended::deserialize::encrypted_section::{ MicEncryptedSection, SignatureEncryptedSection, }; +use crate::extended::deserialize::test_stubs::IntermediateSectionExt; +use rand::rngs::StdRng; +use rand::seq::SliceRandom; +use rand::{Rng, SeedableRng}; use std::vec; #[test] -fn parse_adv_ext_no_identity() { - // 2 sections, 2 DEs each - let mut adv_body = vec![]; - // section - adv_body.push(9); - // de 1 byte header, type 5, len 5 - adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); - // de 2 byte header, type 6, len 1 - adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); - - // section - adv_body.push(11); - // de 3 byte header, len 4 - adv_body.extend_from_slice(&[0x84, 0xC3, 0x03, 0x01, 0x02, 0x03, 0x04]); - // de 1 byte header, type 8, len 3 - adv_body.extend_from_slice(&[0x38, 0x01, 0x02, 0x03]); - - assert_eq!( - Ok(vec![ - PlaintextSection::new( - PlaintextIdentityMode::None, - SectionContents::new( - 9, - &[ - // 1 byte header, len 5 - RefDataElement { - offset: 0_usize.into(), - header_len: 1, - de_type: 5_u8.into(), - contents: &[0x01, 0x02, 0x03, 0x04, 0x05] - }, - // 2 byte header, len 1 - RefDataElement { - offset: 1_usize.into(), - header_len: 2, - de_type: 6_u8.into(), - contents: &[0x01] - } - ] - ) - ) - .into(), - PlaintextSection::new( - PlaintextIdentityMode::None, - SectionContents::new( - 11, - &[ - // 3 byte header, len 4 - RefDataElement { - offset: 0_usize.into(), - header_len: 3, - de_type: 0b0000_0000_0000_0000_0010_0001_1000_0011_u32.into(), - contents: &[0x01, 0x02, 0x03, 0x04] - }, - // 1 byte header, len 3 - RefDataElement { - offset: 1_usize.into(), - header_len: 1, - de_type: 8_u8.into(), - contents: &[0x01, 0x02, 0x03] - } - ] - ) - ) - .into() - ]), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) - ); -} - -#[test] fn parse_adv_ext_public_identity() { // 2 sections, 3 DEs each let mut adv_body = vec![]; @@ -104,65 +36,28 @@ // de 2 byte header, type 6, len 1 adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); - // section - adv_body.push(12); - // public identity - adv_body.push(0x03); - // de 3 byte header, len 4 - adv_body.extend_from_slice(&[0x84, 0xC3, 0x03, 0x01, 0x02, 0x03, 0x04]); - // de 1 byte header, type 8, len 3 - adv_body.extend_from_slice(&[0x38, 0x01, 0x02, 0x03]); + let parsed_sections = parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap(); + assert_eq!( + vec![IntermediateSection::from(PlaintextSection::new( + PlaintextIdentityMode::Public, + SectionContents::new(10, &adv_body[2..], 1) + ))], + parsed_sections, + ); + let expected_des = [ + // 1 byte header, len 5 + DataElement { + offset: 1_u8.into(), + de_type: 5_u8.into(), + contents: &[0x01, 0x02, 0x03, 0x04, 0x05], + }, + // 2 byte header, len 1 + DataElement { offset: 2_u8.into(), de_type: 6_u8.into(), contents: &[0x01] }, + ]; assert_eq!( - Ok(vec![ - PlaintextSection::new( - PlaintextIdentityMode::Public, - SectionContents::new( - 10, - &[ - // 1 byte header, len 5 - RefDataElement { - offset: 1_usize.into(), - header_len: 1, - de_type: 5_u8.into(), - contents: &[0x01, 0x02, 0x03, 0x04, 0x05], - }, - // 2 byte header, len 1 - RefDataElement { - offset: 2_usize.into(), - header_len: 2, - de_type: 6_u8.into(), - contents: &[0x01], - }, - ], - ) - ) - .into(), - PlaintextSection::new( - PlaintextIdentityMode::Public, - SectionContents::new( - 12, - &[ - // 3 byte header, len 4 - RefDataElement { - offset: 1_usize.into(), - header_len: 3, - de_type: 0b0000_0000_0000_0000_0010_0001_1000_0011_u32.into(), - contents: &[0x01, 0x02, 0x03, 0x04], - }, - // 1 byte header, len 3 - RefDataElement { - offset: 2_usize.into(), - header_len: 1, - de_type: 8_u8.into(), - contents: &[0x01, 0x02, 0x03], - }, - ], - ) - ) - .into(), - ]), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) + &expected_des[..], + &parsed_sections[0].as_plaintext().unwrap().collect_data_elements().unwrap() ); } @@ -230,48 +125,50 @@ 0x11, 0x11, ], }; - assert_eq!( - Ok(vec![ - SignatureEncryptedSection { + let expected_sections = [ + SignatureEncryptedSection { + contents: EncryptedSectionContents { section_header: 47, - adv_header: &adv_header, + adv_header, encryption_info: encryption_info.clone(), identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x01], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Private, }, // skip section header + encryption info + identity header -> end of section all_ciphertext: &adv_body[1 + 19 + 2..48], - } - .into(), - SignatureEncryptedSection { + }, + }, + SignatureEncryptedSection { + contents: EncryptedSectionContents { section_header: 48, - adv_header: &adv_header, + adv_header, encryption_info: encryption_info.clone(), identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x02], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Trusted, }, all_ciphertext: &adv_body[48 + 1 + 19 + 2..97], - } - .into(), - SignatureEncryptedSection { + }, + }, + SignatureEncryptedSection { + contents: EncryptedSectionContents { section_header: 49, - adv_header: &adv_header, + adv_header, encryption_info, identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x04], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Provisioned, }, all_ciphertext: &adv_body[97 + 1 + 19 + 2..], - } - .into() - ]), - parse_sections(&adv_header, &adv_body) - ); + }, + }, + ]; + let parsed_sections = parse_sections(adv_header, &adv_body).unwrap(); + assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from)); } #[test] @@ -353,145 +250,53 @@ 0x11, 0x11, ], }; - assert_eq!( - Ok(vec![ - MicEncryptedSection { + let expected_sections = [ + MicEncryptedSection { + contents: EncryptedSectionContents { section_header: 63, - adv_header: &adv_header, - mic: SectionMic::from([0x33; 16]), + adv_header, encryption_info: encryption_info.clone(), identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x01], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Private, }, // skip section header + encryption info + identity header -> end of ciphertext all_ciphertext: &adv_body[1 + 19 + 2..64 - 16], - } - .into(), - MicEncryptedSection { + }, + mic: SectionMic::from([0x33; 16]), + }, + MicEncryptedSection { + contents: EncryptedSectionContents { section_header: 64, - adv_header: &adv_header, - mic: SectionMic::from([0x66; 16]), + adv_header, encryption_info: encryption_info.clone(), identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x02], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Trusted, }, all_ciphertext: &adv_body[64 + 1 + 19 + 2..129 - 16], - } - .into(), - MicEncryptedSection { + }, + mic: SectionMic::from([0x66; 16]), + }, + MicEncryptedSection { + contents: EncryptedSectionContents { section_header: 65, - adv_header: &adv_header, - mic: SectionMic::from([0x99; 16]), + adv_header, encryption_info, identity: EncryptedIdentityMetadata { header_bytes: [0x90, 0x04], - offset: 1_usize.into(), + offset: 1_u8.into(), identity_type: EncryptedIdentityDataElementType::Provisioned, }, all_ciphertext: &adv_body[129 + 1 + 19 + 2..195 - 16], - } - .into(), - ]), - parse_sections(&adv_header, &adv_body) - ); -} - -#[test] -fn public_identity_not_first_de_error() { - let mut adv_body = vec![]; - - // section - adv_body.push(3 + 1); - // misc other DE - adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); - // public identity after another DE - adv_body.push(0x03); - - assert_eq!( - Err(nom::Err::Error(error::Error { - input: &adv_body[4..], - // Eof because all_consuming is used to ensure complete section is parsed - code: error::ErrorKind::Eof - })), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) - ); -} - -#[test] -fn public_identity_after_public_identity_error() { - let mut adv_body = vec![]; - - // section - adv_body.push(1 + 3 + 1); - // public identity after another DE - adv_body.push(0x03); - // misc other DE - adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); - // public identity after another DE - adv_body.push(0x03); - - assert_eq!( - Err(nom::Err::Error(error::Error { - // since we use many1, the parser consumes the first byte and returns the remaining in the error - input: &adv_body[1..], - // Eof because all_consuming is used to ensure complete section is parsed - code: error::ErrorKind::Eof - })), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) - ); -} - -#[test] -fn salt_public_identity_error() { - let mut adv_body = vec![]; - // section - adv_body.push(3 + 1 + 3); - // salt - 1 + 2x 0x22 (invalid: must be first DE) - adv_body.extend_from_slice(&[0x20, 0x22, 0x22]); - // public identity - adv_body.push(0x03); - // misc other DE - adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); - - assert_eq!( - Err(nom::Err::Error(error::Error { - input: &adv_body[4..], - // Eof because all_consuming is used to ensure complete section is parsed - code: error::ErrorKind::Eof - })), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) - ); -} - -#[test] -fn salt_mic_public_identity_error() { - let mut adv_body = vec![]; - // section - adv_body.push(3 + 18 + 1 + 3); - // salt - 1 + 2x 0x22 (invalid: must be first DE) - adv_body.extend_from_slice(&[0x20, 0x22, 0x22]); - // mic - 2 + 16x 0x33 - adv_body.extend_from_slice(&[ - 0x90, 0x13, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, - ]); - // public identity - adv_body.push(0x03); - // misc other DE - adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); - - assert_eq!( - Err(nom::Err::Error(error::Error { - input: &adv_body[22..], - // Eof because all_consuming is used to ensure complete section is parsed - code: error::ErrorKind::Eof - })), - parse_sections(&V1Header { header_byte: 0x20 }, &adv_body) - ); + }, + mic: SectionMic::from([0x99; 16]), + }, + ]; + let parsed_sections = parse_sections(adv_header, &adv_body).unwrap(); + assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from)); } #[test] @@ -616,6 +421,191 @@ } #[test] +fn public_identity_not_first_de_error() { + let mut adv_body = vec![]; + // section + adv_body.push(3 + 1); + // misc other DE + adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); + // public identity after another DE + adv_body.push(0x03); + + assert_eq!( + nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Verify }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() + ); +} + +#[test] +fn invalid_public_section() { + let mut rng = StdRng::from_entropy(); + for _ in 0..100 { + let mut adv_body = vec![]; + // Add section length + let remove_section_len = rng.gen_bool(0.5); + // Add public identity + let add_public_identity = rng.gen_bool(0.5); + // Add DEs + let add_des = rng.gen_bool(0.5); + // Shuffle adv + let shuffle = rng.gen_bool(0.5); + + adv_body.push(0); + if add_public_identity { + adv_body[0] += 1; + adv_body.push(0x03); + } + if add_des { + adv_body[0] += 1; + adv_body.extend_from_slice(&[0x81]); + } + if remove_section_len { + adv_body.remove(0); + } + + let ordered_adv = adv_body.clone(); + + if shuffle { + adv_body.shuffle(&mut rng); + } + // A V1 public section is invalid if + // * section length is missing + // * the section is empty + // * the section identity is missing + // * the identity does not follow the section length + // * the section length is 0 + if remove_section_len || !add_public_identity || (ordered_adv != adv_body) { + parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + .expect_err("Expected to fail because of missing section length or identity"); + } + } +} + +// There can only be one identity DE +#[test] +fn public_identity_after_public_identity_error() { + let mut adv_body = vec![]; + // section + adv_body.push(1 + 3 + 1); + // public identity after another DE + adv_body.push(0x03); + // misc other DE + adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); + // public identity after another DE + adv_body.push(0x03); + + let sections = parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap(); + assert_eq!(sections.len(), 1); + assert_eq!( + DataElementParseError::DuplicateIdentityDataElement, + sections[0].as_plaintext().unwrap().collect_data_elements().unwrap_err() + ); +} + +#[test] +fn salt_public_identity_error() { + let mut adv_body = vec![]; + // section + adv_body.push(3 + 1 + 3); + // salt - 1 + 2x 0x22 (invalid: must be first DE) + adv_body.extend_from_slice(&[0x20, 0x22, 0x22]); + // public identity + adv_body.push(0x03); + // misc other DE + adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); + + assert_eq!( + nom::Err::Error(error::Error { + input: &adv_body[1..], + // Eof because all_consuming is used to ensure complete section is parsed + code: error::ErrorKind::Verify + }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() + ); +} + +#[test] +fn salt_mic_public_identity_error() { + let mut adv_body = vec![]; + // section + adv_body.push(3 + 18 + 1 + 3); + // salt - 1 + 2x 0x22 (invalid: must be first DE) + adv_body.extend_from_slice(&[0x20, 0x22, 0x22]); + // mic - 2 + 16x 0x33 + adv_body.extend_from_slice(&[ + 0x90, 0x13, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, + ]); + // public identity + adv_body.push(0x03); + // misc other DE + adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); + + assert_eq!( + nom::Err::Error(error::Error { + input: &adv_body[1..], + // Eof because all_consuming is used to ensure complete section is parsed + code: error::ErrorKind::Verify + }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() + ); +} + +#[test] +fn parse_adv_no_identity() { + let adv_body = vec![0x55, 0x01, 0x02, 0x03, 0x04, 0x05]; + assert_eq!( + nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Eof }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() + ); +} + +#[test] +fn parse_empty_section() { + // empty section - should return an EOF error + let input = []; + assert_eq!( + nom::Err::Error(error::Error { + // attempted to read section contents + input: input.as_slice(), + code: error::ErrorKind::Eof + }), + IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input) + .unwrap_err() + ); +} + +#[test] +fn parse_de_header_non_canonical_multi_byte() { + // length 1, type 1 + // first byte of type doesn't have any bits in it so it contributes nothing + let input = [0b1000_0001, 0b1000_0000, 0b0000_0001]; + assert_eq!( + nom::Err::Error(error::Error { + // attempted to read first type byte + input: &input.as_slice()[1..], + code: error::ErrorKind::Verify + }), + DeHeader::parse(&input).unwrap_err() + ); +} + +#[test] +fn parse_section_length_zero() { + // Section length of 0 - should return a verification error + let input = [0x00]; + assert_eq!( + nom::Err::Error(error::Error { + // attempted to read section contents + input: input.as_slice(), + code: error::ErrorKind::Verify + }), + IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input) + .unwrap_err() + ); +} + +#[test] fn parse_section_length_overrun() { // section of length 0xF0 - legal but way longer than 3 let input = [0xF0, 0x01, 0x02, 0x03]; @@ -625,7 +615,7 @@ input: &input.as_slice()[1..], code: error::ErrorKind::Eof }), - IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input) + IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input) .unwrap_err() ); } @@ -659,47 +649,33 @@ } #[test] -fn parse_de_header_non_canonical_multi_byte() { - // length 1, type 1 - // first byte of type doesn't have any bits in it so it contributes nothing - let input = [0b1000_0001, 0b1000_0000, 0b0000_0001]; - assert_eq!( - nom::Err::Error(error::Error { - // attempted to read first type byte - input: &input.as_slice()[1..], - code: error::ErrorKind::Verify - }), - DeHeader::parse(&input).unwrap_err() - ); -} +fn parse_adv_signature_encrypted_plaintext_mix() { + // 2 sections + let mut adv_body = vec![]; -#[test] -fn parse_section_length_zero() { - // Section length of 0 - should return a verification error - let input = [0x00]; - assert_eq!( - nom::Err::Error(error::Error { - // attempted to read section contents - input: input.as_slice(), - code: error::ErrorKind::Verify - }), - IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input) - .unwrap_err() - ); -} + // section 1 - plaintext - 10 bytes + adv_body.push(10); + // public identity + adv_body.push(0x03); + // de 1 byte header, type 5, len 5 + adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); + // de 2 byte header, type 6, len 1 + adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); -#[test] -fn parse_empty_section() { - // empty section - should return an EOF error - let input = []; + // section 2 - plaintext - 10 bytes + adv_body.push(10); + // public identity + adv_body.push(0x03); + // de 1 byte header, type 5, len 5 + adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); + // de 2 byte header, type 6, len 1 + adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); + + let adv_header = V1Header { header_byte: 0x20 }; + assert_eq!( - nom::Err::Error(error::Error { - // attempted to read section contents - input: input.as_slice(), - code: error::ErrorKind::Eof - }), - IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input) - .unwrap_err() + nom::Err::Error(error::Error { input: &adv_body[11..], code: error::ErrorKind::Eof }), + parse_sections(adv_header, &adv_body).unwrap_err() ); } @@ -717,8 +693,8 @@ } } -impl<'a> From<PlaintextSection> for IntermediateSection<'a> { - fn from(s: PlaintextSection) -> Self { +impl<'a> From<PlaintextSection<'a>> for IntermediateSection<'a> { + fn from(s: PlaintextSection<'a>) -> Self { IntermediateSection::Plaintext(s) } }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs index 7199031..39644ab 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
@@ -15,26 +15,29 @@ extern crate std; use super::*; +use crate::deserialization_arena; +use crate::extended::serialize::AdvertisementType; +use crate::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT; use crate::{ credential::{ - simple::SimpleV1Credential, - source::{CredentialSource, SliceCredentialSource}, - MatchableCredential, V1Credential, + source::{DiscoveryCredentialSource, SliceCredentialSource}, + v1::{SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial, V1}, + DiscoveryCryptoMaterial, EmptyMatchedCredential, MatchableCredential, + MetadataMatchedCredential, SimpleBroadcastCryptoMaterial, }, extended::{ data_elements::GenericDataElement, - deserialize::{ - convert_data_elements, - test_stubs::{HkdfCryptoMaterial, IntermediateSectionExt}, - OffsetDataElement, - }, + deserialize::{test_stubs::IntermediateSectionExt, DataElement}, serialize::{ - self, AdvBuilder, MicEncrypted, SectionBuilder, SignedEncrypted, WriteDataElement, + self, AdvBuilder, MicEncryptedSectionEncoder, PublicSectionEncoder, SectionBuilder, + SignedEncryptedSectionEncoder, WriteDataElement, }, MAX_DE_LEN, }, - parse_adv_header, AdvHeader, PublicIdentity, + parse_adv_header, AdvHeader, WithMatchedCredential, }; +use core::borrow::{Borrow, BorrowMut}; +use core::convert::Into; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; use rand::{seq::SliceRandom as _, Rng as _, SeedableRng as _}; @@ -45,11 +48,10 @@ #[test] fn deserialize_public_identity_section() { - do_deserialize_section_unencrypted::<PublicIdentity>( - PublicIdentity::default(), + do_deserialize_section_unencrypted::<PublicSectionEncoder>( + PublicSectionEncoder::default(), PlaintextIdentityMode::Public, 1, - 1, ); } @@ -68,36 +70,52 @@ // share a metadata key to emphasize that we're _only_ using the identity to // differentiate let metadata_key: [u8; 16] = rng.gen(); + let metadata_key = MetadataKey(metadata_key); let creds = identities .iter() - .enumerate() - .map(|(index, (key_seed, key_pair))| { - SimpleV1Credential::new( - HkdfCryptoMaterial::new(key_seed, &metadata_key, key_pair.public()), - index, + .map(|(key_seed, key_pair)| { + SimpleSignedBroadcastCryptoMaterial::new( + *key_seed, + metadata_key, + key_pair.private_key(), ) }) + .enumerate() + .map(|(index, broadcast_cm)| { + let match_data = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::< + _, + _, + CryptoProviderImpl, + >(&broadcast_cm, &[index as u8]); + + let discovery_credential = + broadcast_cm.derive_v1_discovery_credential::<CryptoProviderImpl>(); + + MatchableCredential { discovery_credential, match_data } + }) .collect::<Vec<_>>(); + let cred_source = SliceCredentialSource::new(&creds); let identity_type = *EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(chosen_key_seed); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(*chosen_key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - &hkdf, + &broadcast_cm, )) .unwrap(); - let (expected_de_data, expected_des, orig_des) = - fill_section_random_des(&mut rng, &mut section_builder, 2); + let mut expected_de_data = vec![]; + let (expected_des, orig_des) = + fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2); section_builder.add_to_advertisement(); @@ -111,31 +129,35 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); - let (section, cred) = try_deserialize_all_creds::<_, _, CryptoProviderImpl>( - sections[0].as_ciphertext().unwrap(), - &cred_source, - ) - .unwrap() - .unwrap(); - assert_eq!(&chosen_index, cred.matched_data()); + let arena = deserialization_arena!(); + let section = sections[0].as_ciphertext().unwrap(); + let matched_section = + try_deserialize_all_creds::<_, CryptoProviderImpl>(arena, section, &cred_source) + .unwrap() + .unwrap(); + + // Verify that the decrypted metadata contains the chosen index + let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap(); + assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata); + + // Verify that the section contents passed through unaltered + let section = matched_section.contents(); assert_eq!(section.identity_type(), identity_type); assert_eq!(section.verification_mode(), VerificationMode::Mic); - assert_eq!(section.metadata_key(), &metadata_key); + assert_eq!(section.metadata_key(), metadata_key); assert_eq!( - section.contents, - SectionContents { - section_header: (19 + 2 + 16 + total_de_len(&orig_des) + 16) as u8, - de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(), - data_elements: expected_des, - } + section.contents.section_header, + (19 + 2 + 16 + total_de_len(&orig_des) + 16) as u8 ); + let data_elements = section.collect_data_elements().unwrap(); + assert_eq!(data_elements, expected_des); assert_eq!( - section - .data_elements() + data_elements + .iter() .map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap()) .collect::<Vec<_>>(), orig_des @@ -156,47 +178,55 @@ // share a metadata key to emphasize that we're _only_ using the identity to // differentiate let metadata_key: [u8; 16] = rng.gen(); + let metadata_key = MetadataKey(metadata_key); let creds = identities .iter() - .enumerate() - .map(|(index, (key_seed, key_pair))| { - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(key_seed); - let unsigned = - hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key); - let signed = - hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key); - SimpleV1Credential::new( - HkdfCryptoMaterial { - hkdf: *key_seed, - expected_unsigned_metadata_key_hmac: unsigned, - expected_signed_metadata_key_hmac: signed, - pub_key: key_pair.public().to_bytes(), - }, - index, + .map(|(key_seed, key_pair)| { + SimpleSignedBroadcastCryptoMaterial::new( + *key_seed, + metadata_key, + key_pair.private_key(), ) }) + .enumerate() + .map(|(index, broadcast_cm)| { + let match_data = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::< + _, + _, + CryptoProviderImpl, + >(&broadcast_cm, &[index as u8]); + + let discovery_credential = + broadcast_cm.derive_v1_discovery_credential::<CryptoProviderImpl>(); + MatchableCredential { discovery_credential, match_data } + }) .collect::<Vec<_>>(); + let cred_source = SliceCredentialSource::new(&creds); let identity_type = *EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(chosen_key_seed); + let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new( + *chosen_key_seed, + metadata_key, + chosen_key_pair.private_key(), + ); + let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - chosen_key_pair, - &hkdf, + &broadcast_cm, )) .unwrap(); - let (expected_de_data, expected_des, orig_des) = - fill_section_random_des(&mut rng, &mut section_builder, 2); + let mut expected_de_data = vec![]; + let (expected_des, orig_des) = + fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2); section_builder.add_to_advertisement(); @@ -210,31 +240,35 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let arena = deserialization_arena!(); + + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); - let (section, cred) = try_deserialize_all_creds::<_, _, CryptoProviderImpl>( - sections[0].as_ciphertext().unwrap(), - &cred_source, - ) - .unwrap() - .unwrap(); - assert_eq!(&chosen_index, cred.matched_data()); + let section = sections[0].as_ciphertext().unwrap(); + let matched_section = + try_deserialize_all_creds::<_, CryptoProviderImpl>(arena, section, &cred_source) + .unwrap() + .unwrap(); + // Verify that the decrypted metadata contains the chosen index + let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap(); + assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata); + + // Verify that the section contents passed through unaltered + let section = matched_section.contents(); assert_eq!(section.identity_type(), identity_type); assert_eq!(section.verification_mode(), VerificationMode::Signature); - assert_eq!(section.metadata_key(), &metadata_key); + assert_eq!(section.metadata_key(), metadata_key); assert_eq!( - section.contents, - SectionContents { - section_header: (19 + 2 + 16 + 64 + total_de_len(&orig_des)) as u8, - de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(), - data_elements: expected_des, - } + section.contents.section_header, + (19 + 2 + 16 + 64 + total_de_len(&orig_des)) as u8 ); + let data_elements = section.collect_data_elements().unwrap(); + assert_eq!(data_elements, expected_des); assert_eq!( - section - .data_elements() + data_elements + .iter() .map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap()) .collect::<Vec<_>>(), orig_des @@ -257,44 +291,60 @@ // share a metadata key to emphasize that we're _only_ using the identity to // differentiate let metadata_key: [u8; 16] = rng.gen(); + let metadata_key = MetadataKey(metadata_key); let credentials = identities .iter() - .enumerate() - .map(|(index, (key_seed, key_pair))| { - SimpleV1Credential::new( - HkdfCryptoMaterial::new(key_seed, &metadata_key, key_pair.public()), - index, + .map(|(key_seed, key_pair)| { + SimpleSignedBroadcastCryptoMaterial::new( + *key_seed, + metadata_key, + key_pair.private_key(), ) + .derive_v1_discovery_credential::<CryptoProviderImpl>() + }) + .map(|discovery_credential| MatchableCredential { + discovery_credential, + match_data: EmptyMatchedCredential, }) .collect::<Vec<_>>(); + let cred_source = SliceCredentialSource::new(&credentials); let identity_type = *EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&chosen_key_seed); + let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new( + chosen_key_seed, + metadata_key, + chosen_key_pair.private_key(), + ); - // awkward split because SectionIdentity isn't object-safe, so we can't just have a - // Box<dyn SectionIdentity> and use that in one code path + // awkward split because SectionEncoder isn't object-safe, so we can't just have a + // Box<dyn SectionEncoder> and use that in one code path if signed { - let identity = SignedEncrypted::new_random_salt( + let identity = SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - &chosen_key_pair, - &hkdf, + &broadcast_cm, ); let mut section_builder = adv_builder.section_builder(identity).unwrap(); - let _ = fill_section_random_des(&mut rng, &mut section_builder, 2); + let mut expected_de_data = vec![]; + let _ = + fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2); section_builder.add_to_advertisement(); } else { - let identity = - MicEncrypted::new_random_salt(&mut crypto_rng, identity_type, &metadata_key, &hkdf); + let identity = MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( + &mut crypto_rng, + identity_type, + &broadcast_cm, + ); let mut section_builder = adv_builder.section_builder(identity).unwrap(); - let _ = fill_section_random_des(&mut rng, &mut section_builder, 2); + let mut expected_de_data = vec![]; + let _ = + fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2); section_builder.add_to_advertisement(); }; @@ -306,12 +356,13 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); - assert!(try_deserialize_all_creds::<_, _, CryptoProviderImpl>( + assert!(try_deserialize_all_creds::<_, CryptoProviderImpl>( + deserialization_arena!(), sections[0].as_ciphertext().unwrap(), - &cred_source + &cred_source, ) .unwrap() .is_none()); @@ -319,138 +370,30 @@ } #[test] -fn convert_data_elements_empty() { - let orig_des = vec![]; - - let (des, data) = convert_data_elements(&orig_des); - - assert_eq!(Vec::<OffsetDataElement>::new(), des); - assert_eq!(&Vec::<u8>::new(), data.as_slice()); -} - -#[test] -fn convert_data_elements_just_fits() { - // this is actually longer than any real section's worth of DEs could be since we aren't putting - // DE headers in the array - let orig_data = vec![0x33; 1000]; - - let orig_des = vec![ - RefDataElement { - offset: 2.into(), - header_len: 2, - de_type: 100_u32.into(), - contents: &orig_data[0..10], - }, - RefDataElement { - offset: 3.into(), - header_len: 2, - de_type: 101_u32.into(), - contents: &orig_data[10..100], - }, - RefDataElement { - offset: 4.into(), - header_len: 2, - de_type: 102_u32.into(), - contents: &orig_data[100..NP_ADV_MAX_SECTION_LEN], - }, - ]; - - let (des, data) = convert_data_elements(&orig_des); - - assert_eq!( - &[ - OffsetDataElement { - offset: 2.into(), - de_type: 100_u32.into(), - start_of_contents: 0, - contents_len: 10 - }, - OffsetDataElement { - offset: 3.into(), - de_type: 101_u32.into(), - start_of_contents: 10, - contents_len: 90 - }, - OffsetDataElement { - offset: 4.into(), - de_type: 102_u32.into(), - start_of_contents: 100, - contents_len: NP_ADV_MAX_SECTION_LEN - 100, - }, - ], - &des[..] - ); - assert_eq!(&[0x33; NP_ADV_MAX_SECTION_LEN], data.as_slice()); -} - -#[test] -#[should_panic] -fn convert_data_elements_doesnt_fit_panic() { - let orig_data = vec![0x33; 1000]; - let orig_des = vec![ - RefDataElement { - offset: 2.into(), - header_len: 2, - de_type: 100_u32.into(), - contents: &orig_data[0..10], - }, - // impossibly large DE - RefDataElement { - offset: 3.into(), - header_len: 2, - de_type: 101_u32.into(), - contents: &orig_data[10..500], - }, - ]; - - let _ = convert_data_elements(&orig_des); -} - -#[test] fn section_des_expose_correct_data() { - let mut orig_data = Vec::new(); - orig_data.resize(130, 0); - for (index, byte) in orig_data.iter_mut().enumerate() { - *byte = index as u8; - } - - let orig_des = vec![ - OffsetDataElement { - offset: 2.into(), - de_type: 100_u32.into(), - start_of_contents: 0, - contents_len: 10, - }, - OffsetDataElement { - offset: 3.into(), - de_type: 101_u32.into(), - start_of_contents: 10, - contents_len: 90, - }, - OffsetDataElement { - offset: 4.into(), - de_type: 102_u32.into(), - start_of_contents: 100, - contents_len: 30, - }, - ]; + // 2 sections, 3 DEs each + let mut de_data = vec![]; + // de 1 byte header, type 5, len 5 + de_data.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]); + // de 2 byte header, type 16, len 1 + de_data.extend_from_slice(&[0x81, 0x10, 0x01]); let section = SectionContents { section_header: 99, - de_data: ArrayView::try_from_slice(&orig_data).unwrap(), - data_elements: orig_des, + de_region_excl_identity: &de_data, + data_element_start_offset: 2, }; // extract out the parts of the DE we care about - let des = section - .data_elements() - .map(|de| (de.offset(), de.de_type(), de.contents().to_vec())) - .collect::<Vec<_>>(); + let des = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); assert_eq!( vec![ - (2.into(), 100_u32.into(), orig_data[0..10].to_vec()), - (3.into(), 101_u32.into(), orig_data[10..100].to_vec()), - (4.into(), 102_u32.into(), orig_data[100..].to_vec()) + DataElement { + offset: 2.into(), + de_type: 5_u32.into(), + contents: &[0x01, 0x02, 0x03, 0x04, 0x05] + }, + DataElement { offset: 3.into(), de_type: 16_u32.into(), contents: &[0x01] }, ], des ); @@ -467,12 +410,12 @@ } else { panic!("incorrect header"); }; - parse_sections(&v1_header, remaining).expect_err("Expected an error"); + parse_sections(v1_header, remaining).expect_err("Expected an error"); } #[test] fn do_deserialize_empty_section() { - let adv_builder = AdvBuilder::new(); + let adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); let adv = adv_builder.into_advertisement(); let (remaining, header) = parse_adv_header(adv.as_slice()).unwrap(); let v1_header = if let AdvHeader::V1(h) = header { @@ -480,12 +423,12 @@ } else { panic!("incorrect header"); }; - parse_sections(&v1_header, remaining).expect_err("Expected an error"); + parse_sections(v1_header, remaining).expect_err("Expected an error"); } #[test] -fn do_deserialize_max_number_of_sections() { - let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_SECTION_COUNT); +fn do_deserialize_max_number_of_public_sections() { + let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT); let adv = adv_builder.into_advertisement(); let (remaining, header) = parse_adv_header(adv.as_slice()).unwrap(); @@ -494,14 +437,13 @@ } else { panic!("incorrect header"); }; - - let sections = parse_sections(&v1_header, remaining).unwrap(); - assert_eq!(NP_V1_ADV_MAX_SECTION_COUNT, sections.len()); + let sections = parse_sections(v1_header, remaining).unwrap(); + assert_eq!(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, sections.len()); } #[test] -fn try_deserialize_over_max_number_of_sections() { - let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_SECTION_COUNT); +fn try_deserialize_over_max_number_of_public_sections() { + let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT); let mut adv = adv_builder.into_advertisement().as_slice().to_vec(); // Push an extra section @@ -520,8 +462,8 @@ } else { panic!("incorrect header"); }; - parse_sections(&v1_header, remaining) - .expect_err("Expected an error because number of sections is over 16"); + parse_sections(v1_header, remaining) + .expect_err("Expected an error because number of sections is over limit"); } pub(crate) fn random_de<R: rand::Rng>(rng: &mut R) -> GenericDataElement { @@ -533,19 +475,19 @@ GenericDataElement::try_from(rng.gen_range(20_u32..1000).into(), data.as_slice()).unwrap() } -fn do_deserialize_section_unencrypted<I: serialize::SectionIdentity>( +fn do_deserialize_section_unencrypted<I: serialize::SectionEncoder>( identity: I, expected_identity: PlaintextIdentityMode, - prefix_len: usize, de_offset: usize, ) { let mut rng = rand::rngs::StdRng::from_entropy(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); let mut section_builder = adv_builder.section_builder(identity).unwrap(); - let (expected_de_data, expected_des, orig_des) = - fill_section_random_des(&mut rng, &mut section_builder, de_offset); + let mut expected_de_data = vec![]; + let (expected_des, orig_des) = + fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, de_offset); section_builder.add_to_advertisement(); @@ -559,41 +501,33 @@ panic!("incorrect header"); }; - let sections = parse_sections(&v1_header, remaining).unwrap(); + let sections = parse_sections(v1_header, remaining).unwrap(); assert_eq!(1, sections.len()); let section = sections[0].as_plaintext().unwrap(); + assert_eq!(section.identity(), expected_identity); + let data_elements = section.collect_data_elements().unwrap(); + assert_eq!(data_elements, expected_des); assert_eq!( - &PlaintextSection { - identity: expected_identity, - contents: SectionContents { - section_header: (prefix_len + total_de_len(&orig_des)) as u8, - de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(), - data_elements: expected_des, - } - }, - section - ); - assert_eq!( - section - .contents - .data_elements() + data_elements + .iter() .map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap()) .collect::<Vec<_>>(), orig_des ); } -fn fill_section_random_des<R: rand::Rng, I: serialize::SectionIdentity>( +fn fill_section_random_des<'adv, R: rand::Rng, I: serialize::SectionEncoder>( mut rng: &mut R, + sink: &'adv mut Vec<u8>, section_builder: &mut SectionBuilder<I>, de_offset: usize, -) -> (Vec<u8>, Vec<OffsetDataElement>, Vec<GenericDataElement>) { - let mut expected_de_data = vec![]; +) -> (Vec<DataElement<'adv>>, Vec<GenericDataElement>) { let mut expected_des = vec![]; let mut orig_des = vec![]; + let mut de_ranges = vec![]; - for index in 0..rng.gen_range(1..10) { + for _ in 0..rng.gen_range(1..10) { let de = random_de(&mut rng); let de_clone = de.clone(); @@ -601,19 +535,21 @@ break; } - let orig_len = expected_de_data.len(); - de.write_de_contents(&mut expected_de_data).unwrap(); - let contents_len = expected_de_data.len() - orig_len; - - expected_des.push(OffsetDataElement { - offset: (index as usize + de_offset).into(), - de_type: de.de_header().de_type, - contents_len, - start_of_contents: orig_len, - }); + let orig_len = sink.len(); + de.write_de_contents(sink).unwrap(); + let contents_len = sink.len() - orig_len; + de_ranges.push(orig_len..orig_len + contents_len); orig_des.push(de); } - (expected_de_data, expected_des, orig_des) + + for (index, (de, range)) in orig_des.iter().zip(de_ranges).enumerate() { + expected_des.push(DataElement { + offset: u8::try_from(index + de_offset).unwrap().into(), + de_type: de.de_header().de_type, + contents: &sink[range], + }); + } + (expected_des, orig_des) } fn total_de_len(des: &[GenericDataElement]) -> usize { @@ -626,29 +562,38 @@ .sum() } -type TryDeserOutput<'c, C> = Option<(DecryptedSection, <C as MatchableCredential>::Matched<'c>)>; +type TryDeserOutput<'adv, M> = Option<WithMatchedCredential<M, DecryptedSection<'adv>>>; /// Returns: /// - `Ok(Some)` if a matching credential was found /// - `Ok(None)` if no matching credential was found, or if `cred_source` provides no credentials /// - `Err` if an error occurred. -fn try_deserialize_all_creds<'c, C, S, P>( - section: &CiphertextSection, - cred_source: &'c S, -) -> Result<TryDeserOutput<'c, C>, BatchSectionDeserializeError> +fn try_deserialize_all_creds<'a, S, P>( + mut arena: impl BorrowMut<DeserializationArena<'a>>, + section: &'a CiphertextSection, + cred_source: &'a S, +) -> Result<TryDeserOutput<'a, S::Matched>, BatchSectionDeserializeError> where - C: V1Credential, - S: CredentialSource<C>, + S: DiscoveryCredentialSource<'a, V1>, P: CryptoProvider, { - for c in cred_source.iter() { - match section.try_deserialize::<C, P>(c) { - Ok(s) => return Ok(Some((s, c.matched()))), + for (crypto_material, match_data) in cred_source.iter() { + match section.try_resolve_identity_and_deserialize::<_, P>( + arena.borrow_mut(), + crypto_material.borrow(), + ) { + Ok(s) => { + let metadata_nonce = crypto_material.metadata_nonce::<P>(); + return Ok(Some(WithMatchedCredential::new(match_data, metadata_nonce, s))); + } Err(e) => match e { SectionDeserializeError::IncorrectCredential => continue, SectionDeserializeError::ParseError => { return Err(BatchSectionDeserializeError::ParseError) } + SectionDeserializeError::ArenaOutOfSpace => { + return Err(BatchSectionDeserializeError::ArenaOutOfSpace) + } }, } } @@ -657,9 +602,9 @@ } fn build_dummy_advertisement_sections(number_of_sections: usize) -> AdvBuilder { - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); for _ in 0..number_of_sections { - let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder.add_to_advertisement(); } adv_builder @@ -669,4 +614,6 @@ enum BatchSectionDeserializeError { /// Advertisement data is malformed ParseError, + /// The given arena is not large enough to hold the decrypted data + ArenaOutOfSpace, }
diff --git a/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs b/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs index 437b997..68b6f5d 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs
@@ -14,99 +14,29 @@ extern crate std; -use crypto_provider::{ed25519, CryptoProvider}; -use np_hkdf::{NpKeySeedHkdf, UnsignedSectionKeys}; use std::prelude::rust_2021::*; use crate::{ - credential::v1::*, extended::deserialize::{CiphertextSection, PlaintextSection}, IntermediateSection, }; -pub(crate) struct HkdfCryptoMaterial { - pub(crate) hkdf: [u8; 32], - pub(crate) expected_unsigned_metadata_key_hmac: [u8; 32], - pub(crate) expected_signed_metadata_key_hmac: [u8; 32], - pub(crate) pub_key: ed25519::RawPublicKey, -} - -impl HkdfCryptoMaterial { - pub(crate) fn new<C: CryptoProvider>( - hkdf_key_seed: &[u8; 32], - metadata_key: &[u8; 16], - pub_key: np_ed25519::PublicKey<C>, - ) -> Self { - let hkdf = NpKeySeedHkdf::<C>::new(hkdf_key_seed); - let unsigned = - hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice()); - let signed = - hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice()); - Self { - hkdf: *hkdf_key_seed, - expected_unsigned_metadata_key_hmac: unsigned, - expected_signed_metadata_key_hmac: signed, - pub_key: pub_key.to_bytes(), - } - } -} - -impl HkdfCryptoMaterial { - fn hkdf<C: CryptoProvider>(&self) -> NpKeySeedHkdf<C> { - NpKeySeedHkdf::<C>::new(&self.hkdf) - } -} - -impl V1CryptoMaterial for HkdfCryptoMaterial { - type SignedIdentityResolverReference<'a> = SignedSectionIdentityResolutionMaterial - where Self: 'a; - type UnsignedIdentityResolverReference<'a> = UnsignedSectionIdentityResolutionMaterial - where Self: 'a; - - fn signed_identity_resolution_material<C: CryptoProvider>( - &self, - ) -> Self::SignedIdentityResolverReference<'_> { - SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac::<C>( - &self.hkdf::<C>(), - self.expected_signed_metadata_key_hmac, - ) - } - fn unsigned_identity_resolution_material<C: CryptoProvider>( - &self, - ) -> Self::UnsignedIdentityResolverReference<'_> { - UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac::<C>( - &self.hkdf::<C>(), - self.expected_unsigned_metadata_key_hmac, - ) - } - fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial { - SignedSectionVerificationMaterial { pub_key: self.pub_key } - } - - fn unsigned_verification_material<C: CryptoProvider>( - &self, - ) -> UnsignedSectionVerificationMaterial { - let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&self.hkdf::<C>()).as_bytes(); - UnsignedSectionVerificationMaterial { mic_hmac_key } - } -} - -pub(crate) trait IntermediateSectionExt { +pub(crate) trait IntermediateSectionExt<'adv> { /// Returns `Some` if `self` is `Plaintext` - fn as_plaintext(&self) -> Option<&PlaintextSection>; + fn as_plaintext(&self) -> Option<&PlaintextSection<'adv>>; /// Returns `Some` if `self` is `Ciphertext` - fn as_ciphertext(&self) -> Option<&CiphertextSection>; + fn as_ciphertext(&self) -> Option<&CiphertextSection<'adv>>; } -impl<'a> IntermediateSectionExt for IntermediateSection<'a> { - fn as_plaintext(&self) -> Option<&PlaintextSection> { +impl<'adv> IntermediateSectionExt<'adv> for IntermediateSection<'adv> { + fn as_plaintext(&self) -> Option<&PlaintextSection<'adv>> { match self { IntermediateSection::Plaintext(s) => Some(s), IntermediateSection::Ciphertext(_) => None, } } - fn as_ciphertext(&self) -> Option<&CiphertextSection> { + fn as_ciphertext(&self) -> Option<&CiphertextSection<'adv>> { match self { IntermediateSection::Plaintext(_) => None, IntermediateSection::Ciphertext(s) => Some(s),
diff --git a/nearby/presence/np_adv/src/extended/mod.rs b/nearby/presence/np_adv/src/extended/mod.rs index c2f485e..68b16c3 100644 --- a/nearby/presence/np_adv/src/extended/mod.rs +++ b/nearby/presence/np_adv/src/extended/mod.rs
@@ -31,7 +31,10 @@ - 2; /// Maximum number of sections in an advertisement -pub const NP_V1_ADV_MAX_SECTION_COUNT: usize = 8; +pub const NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT: usize = 8; + +/// Maximum number of public sections in an advertisement +pub const NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT: usize = 1; /// Maximum size of a NP section, including its header byte pub const NP_ADV_MAX_SECTION_LEN: usize = BLE_ADV_SVC_CONTENT_LEN @@ -53,8 +56,8 @@ /// A convenient constant for zero length. pub const ZERO: DeLength = DeLength { len: 0 }; - fn as_usize(&self) -> usize { - self.len as usize + fn as_u8(&self) -> u8 { + self.len } } @@ -62,7 +65,7 @@ type Error = DeLengthOutOfRange; fn try_from(value: u8) -> Result<Self, Self::Error> { - if value as usize <= MAX_DE_LEN { + if usize::from(value) <= MAX_DE_LEN { Ok(Self { len: value }) } else { Err(DeLengthOutOfRange {})
diff --git a/nearby/presence/np_adv/src/extended/section_signature_payload.rs b/nearby/presence/np_adv/src/extended/section_signature_payload.rs index 2b0ebe5..d8ec93c 100644 --- a/nearby/presence/np_adv/src/extended/section_signature_payload.rs +++ b/nearby/presence/np_adv/src/extended/section_signature_payload.rs
@@ -17,6 +17,7 @@ //! performing signatures and signature verification. use crate::extended::deserialize::EncryptionInfo; +use crate::MetadataKey; use crate::NP_SVC_UUID; use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider}; use sink::{Sink, SinkWriter}; @@ -45,10 +46,10 @@ /// to be included in the signature payload after the derived salt, /// and before the signature itself. Raw(&'a [u8]), - /// References to the plaintext identity DE header and the rest - /// of the section plaintext after that (includes the plaintext - /// identity DE payload). - IdentityHeaderThenRaw([u8; 2], &'a [u8]), + /// Plaintext identity DE header followed by the metadata key, + /// then the rest of the section plaintext (including + /// the plaintext identity DE payload). + IdentityHeaderMetadataKeyAndRemainder([u8; 2], MetadataKey, &'a [u8]), } const ADV_SIGNATURE_CONTEXT: np_ed25519::SignatureContext = { @@ -60,6 +61,30 @@ impl<'a> SectionSignaturePayload<'a> { /// Construct a section signature payload using parts typically found during + /// deserialization of advertisements. + pub(crate) fn from_deserialized_parts( + adv_header_byte: u8, + section_header: u8, + encryption_info: &'a [u8; EncryptionInfo::TOTAL_DE_LEN], + nonce_ref: &'a AesCtrNonce, + identity_header: [u8; 2], + plaintext_metadata_key: MetadataKey, + raw_plaintext_remainder: &'a [u8], + ) -> Self { + Self { + adv_header_byte, + section_header, + encryption_info, + nonce_ref, + after_iv_info: AfterIVInfo::IdentityHeaderMetadataKeyAndRemainder( + identity_header, + plaintext_metadata_key, + raw_plaintext_remainder, + ), + } + } + + /// Construct a section signature payload using parts typically found during /// serialization of advertisements. pub(crate) fn from_serialized_parts( adv_header_byte: u8, @@ -77,28 +102,6 @@ } } - /// Construct a section signature payload using parts typically found during - /// deserialization of advertisements. - pub(crate) fn from_deserialized_parts( - adv_header_byte: u8, - section_header: u8, - encryption_info: &'a [u8; EncryptionInfo::TOTAL_DE_LEN], - nonce_ref: &'a AesCtrNonce, - identity_header: [u8; 2], - raw_after_identity_header_info: &'a [u8], - ) -> Self { - Self { - adv_header_byte, - section_header, - encryption_info, - nonce_ref, - after_iv_info: AfterIVInfo::IdentityHeaderThenRaw( - identity_header, - raw_after_identity_header_info, - ), - } - } - /// Generates a signature for this section signing payload using /// the given Ed25519 key-pair. pub(crate) fn sign<C: CryptoProvider>( @@ -133,9 +136,14 @@ // identity DE and the rest of the DEs except for the suffix match self.after_iv_info { AfterIVInfo::Raw(s) => sink.try_extend_from_slice(s), - AfterIVInfo::IdentityHeaderThenRaw(identity_header, s) => { + AfterIVInfo::IdentityHeaderMetadataKeyAndRemainder( + identity_header, + metadata_key, + remainder, + ) => { sink.try_extend_from_slice(&identity_header)?; - sink.try_extend_from_slice(s) + sink.try_extend_from_slice(&metadata_key.0)?; + sink.try_extend_from_slice(remainder) } } }
diff --git a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs index 47804ea..36e4e5f 100644 --- a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
@@ -20,18 +20,10 @@ #[test] fn adv_encode_no_salt() { - let mut adv_builder = AdvBuilder::new(); - - let mut no_identity_section_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); - no_identity_section_builder - .add_de(|_| DummyDataElement { de_type: 20_u32.into(), data: vec![] }) - .unwrap(); - - no_identity_section_builder.add_to_advertisement(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); let mut public_identity_section_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); + adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); public_identity_section_builder .add_de(|_| DummyDataElement { de_type: 30_u32.into(), data: vec![] }) .unwrap(); @@ -43,9 +35,6 @@ 0x20, // adv header 0x3, // section header 0x3, // public identity - 0x80, 20, // de header - 0x3, // section header - 0x3, // public identity 0x80, 30, // de header ], adv_builder.into_advertisement().as_slice() @@ -53,45 +42,12 @@ } #[test] -fn adv_encode_with_salt() { - let mut adv_builder = AdvBuilder::new(); - - let mut no_identity_section_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); - no_identity_section_builder - .add_de(|_| DummyDataElement { de_type: 20_u32.into(), data: vec![] }) - .unwrap(); - - no_identity_section_builder.add_to_advertisement(); - - let mut public_identity_section_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); - public_identity_section_builder - .add_de(|_| DummyDataElement { de_type: 30_u32.into(), data: vec![] }) - .unwrap(); - - public_identity_section_builder.add_to_advertisement(); - - let mut expected = vec![ - 0x20, // adv header - ]; - expected.extend_from_slice(&[ - 0x3, // section header - 0x3, // public identity - 0x80, 20, // de header - 0x3, // section header - 0x3, // public identity - 0x80, 30, // de header - ]); - assert_eq!(expected, adv_builder.into_advertisement().as_slice()) -} - -#[test] fn adding_any_allowed_section_length_always_works_for_single_section() { // up to section len - 1 to leave room for section header for section_contents_len in 0..NP_ADV_MAX_SECTION_LEN - 1 { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = + adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); fill_section_builder(section_contents_len, &mut section_builder); section_builder.add_to_advertisement(); @@ -108,50 +64,17 @@ } #[test] -fn adding_any_allowed_section_length_always_works_for_two_sections() { - // leave room for both section header bytes and the public identities - for section_1_contents_len in 0..=NP_ADV_MAX_SECTION_LEN - 4 { - // leave room for both section headers + public identities + section 1 contents - for section_2_contents_len in 0..=NP_ADV_MAX_SECTION_LEN - section_1_contents_len - 4 { - let mut adv_builder = AdvBuilder::new(); - - let mut section_1_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); - fill_section_builder(section_1_contents_len, &mut section_1_builder); - section_1_builder.add_to_advertisement(); - - let mut section_2_builder = - adv_builder.section_builder(PublicIdentity::default()).unwrap(); - fill_section_builder(section_2_contents_len, &mut section_2_builder); - section_2_builder.add_to_advertisement(); - - let adv = adv_builder.into_advertisement(); - assert_eq!( - section_1_contents_len + section_2_contents_len + 1 + 4, // adv and section headers - adv.as_slice().len(), - "adv: {:?}\nsection 1 contents len: {}, section 2 contents len: {}", - adv.as_slice(), - section_1_contents_len, - section_2_contents_len, - ); - } - } -} - -#[test] fn building_capacity_0_section_works() { - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); - // leave room for two section headers and the public identities - fill_section_builder(NP_ADV_MAX_SECTION_LEN - 4, &mut section_builder); - section_builder.add_to_advertisement(); + // leave room for section header and the public identity + fill_section_builder(NP_ADV_MAX_SECTION_LEN - 2, &mut section_builder); - let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); - // The section header and the public identity - assert_eq!(2, section_builder.section.capacity); - assert_eq!(2, section_builder.section.len()); + assert_eq!(NP_ADV_MAX_SECTION_LEN, section_builder.section.capacity); + assert_eq!(NP_ADV_MAX_SECTION_LEN, section_builder.section.len()); + section_builder.add_to_advertisement(); assert_eq!(BLE_ADV_SVC_CONTENT_LEN, adv_builder.into_advertisement().as_slice().len()); @@ -161,10 +84,11 @@ #[derive(Default, PartialEq, Eq, Debug)] struct EnormousIdentity {} -impl SectionIdentity for EnormousIdentity { +impl SectionEncoder for EnormousIdentity { const PREFIX_LEN: usize = 200; const SUFFIX_LEN: usize = 0; const INITIAL_DE_OFFSET: DataElementOffset = DataElementOffset::ZERO; + const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Plaintext; fn postprocess( &mut self, @@ -176,7 +100,6 @@ } type DerivedSalt = (); - fn de_salt(&self, _de_offset: DataElementOffset) -> Self::DerivedSalt { panic!("should never be called, just used for its huge prefix") }
diff --git a/nearby/presence/np_adv/src/extended/serialize/mod.rs b/nearby/presence/np_adv/src/extended/serialize/mod.rs index 6cedd1e..f736dea 100644 --- a/nearby/presence/np_adv/src/extended/serialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/serialize/mod.rs
@@ -26,8 +26,8 @@ //! use np_adv::shared_data::TxPower; //! //! // no section identities or DEs need salt in this example -//! let mut adv_builder = AdvBuilder::new(); -//! let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); +//! let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); +//! let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); //! //! section_builder.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap(); //! @@ -54,28 +54,35 @@ //! //! ``` //! use np_adv::{ +//! credential::{SimpleBroadcastCryptoMaterial, v1::V1}, //! de_type::EncryptedIdentityDataElementType, //! extended::{data_elements::*, serialize::*, de_type::DeType }, +//! MetadataKey, //! }; //! use rand::{Rng as _, SeedableRng as _}; //! use crypto_provider::{CryptoProvider, CryptoRng}; //! use crypto_provider_default::CryptoProviderImpl; //! use np_adv::shared_data::TxPower; //! -//! let mut adv_builder = AdvBuilder::new(); +//! let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); //! //! // these would come from the credential//! //! let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); //! let metadata_key: [u8; 16] = rng.gen(); +//! let metadata_key = MetadataKey(metadata_key); //! let key_seed: [u8; 32] = rng.gen(); //! // use your preferred crypto impl //! let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); //! -//! let mut section_builder = adv_builder.section_builder(MicEncrypted::new_random_salt( +//! let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new( +//! key_seed, +//! metadata_key, +//! ); +//! +//! let mut section_builder = adv_builder.section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( //! &mut rng, //! EncryptedIdentityDataElementType::Private, -//! &metadata_key, -//! &key_seed_hkdf, +//! &broadcast_cm, //! )).unwrap(); //! //! section_builder.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap(); @@ -104,8 +111,12 @@ //! .try_into().expect("array sizes match") //! } //! ``` -use crate::extended::NP_V1_ADV_MAX_SECTION_COUNT; +use crate::extended::{NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT}; use crate::{ + credential::{ + v1::{SignedBroadcastCryptoMaterial, V1}, + BroadcastCryptoMaterial, + }, de_type::{EncryptedIdentityDataElementType, IdentityDataElementType}, extended::{ data_elements::EncryptionInfoDataElement, @@ -114,11 +125,10 @@ section_signature_payload::*, to_array_view, DeLength, BLE_ADV_SVC_CONTENT_LEN, NP_ADV_MAX_SECTION_LEN, }, - DeLengthOutOfRange, PublicIdentity, NP_SVC_UUID, + DeLengthOutOfRange, MetadataKey, NP_SVC_UUID, }; use array_view::ArrayView; -use core::fmt; -use core::marker::PhantomData; +use core::fmt::{self, Display}; use crypto_provider::{ aes::{ ctr::{AesCtr, AesCtrNonce, NonceAndCounter}, @@ -145,36 +155,35 @@ pub struct AdvBuilder { /// Contains the adv header byte adv: tinyvec::ArrayVec<[u8; BLE_ADV_SVC_CONTENT_LEN]>, + /// To track the number of sections that are in the advertisement section_count: usize, + /// Advertisement type: Public or Encrypted + advertisement_type: AdvertisementType, } impl AdvBuilder { /// Build an [AdvBuilder]. - pub fn new() -> AdvBuilder { + pub fn new(advertisement_type: AdvertisementType) -> Self { let mut adv = tinyvec::ArrayVec::new(); // version 1, 0bVVVRRRRR adv.push(0b00100000); - - AdvBuilder { adv, section_count: 0 } + Self { adv, section_count: 0, advertisement_type } } /// Create a section builder. /// - /// Returns `Err` if there isn't room in the advertisement for the section at its minimum length - /// with its chosen identity. - /// /// The builder will not accept more DEs than can fit given the space already used in the /// advertisement by previous sections, if any. /// /// Once the builder is populated, add it to the originating advertisement with /// [SectionBuilder.add_to_advertisement]. - pub fn section_builder<I: SectionIdentity>( + pub fn section_builder<SE: SectionEncoder>( &mut self, - identity: I, - ) -> Result<SectionBuilder<I>, AddSectionError> { + section_encoder: SE, + ) -> Result<SectionBuilder<SE>, AddSectionError> { // section header and identity prefix - let prefix_len = 1 + I::PREFIX_LEN; - let minimum_section_len = prefix_len + I::SUFFIX_LEN; + let prefix_len = 1 + SE::PREFIX_LEN; + let minimum_section_len = prefix_len + SE::SUFFIX_LEN; // the max overall len available to the section let available_len = self.adv.capacity() - self.adv.len(); @@ -182,11 +191,15 @@ return Err(AddSectionError::InsufficientAdvSpace); } - if self.section_count >= NP_V1_ADV_MAX_SECTION_COUNT { + if self.section_count >= self.advertisement_type.max_sections() { return Err(AddSectionError::MaxSectionCountExceeded); } - let mut section = tinyvec::ArrayVec::new(); + if self.advertisement_type != SE::ADVERTISEMENT_TYPE { + return Err(AddSectionError::IncompatibleSectionType); + } + + let mut section: tinyvec::ArrayVec<[u8; 249]> = tinyvec::ArrayVec::new(); // placeholder for section header and identity prefix section.resize(prefix_len, 0); @@ -194,11 +207,11 @@ section: CapacityLimitedVec { vec: section, // won't underflow: checked above - capacity: available_len - I::SUFFIX_LEN, + capacity: available_len - SE::SUFFIX_LEN, }, - identity, + section_encoder, adv_builder: self, - next_de_offset: I::INITIAL_DE_OFFSET, + next_de_offset: SE::INITIAL_DE_OFFSET, }) } @@ -221,33 +234,30 @@ } } -impl Default for AdvBuilder { - fn default() -> Self { - Self::new() - } -} - /// Errors that can occur when adding a section to an advertisement #[derive(Debug, PartialEq, Eq)] pub enum AddSectionError { /// The advertisement doesn't have enough space to hold the minimum size of the section InsufficientAdvSpace, - /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_SECTION_COUNT number of sections + /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT number of sections MaxSectionCountExceeded, + /// An incompatible section trying to be added + IncompatibleSectionType, } -/// Derived salt for an individual data element. -pub struct DeSalt<C: CryptoProvider> { - salt: V1Salt<C>, - de_offset: DataElementOffset, -} - -impl<C: CryptoProvider> DeSalt<C> { - /// Derive salt of the requested length. - /// - /// The length must be a valid HKDF-SHA256 length. - pub fn derive<const N: usize>(&self) -> Option<[u8; N]> { - self.salt.derive(Some(self.de_offset)) +impl Display for AddSectionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AddSectionError::InsufficientAdvSpace => { + write!(f, "The advertisement (max {BLE_ADV_SVC_CONTENT_LEN} bytes) doesn't have enough remaining space to hold the section") + } + AddSectionError::MaxSectionCountExceeded => { + write!(f, "The advertisement can only hold a maximum of {NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT} number of sections") + } + AddSectionError::IncompatibleSectionType => { + write!(f, "Public and Encrypted sections cannot be mixed in the same advertisement") + } + } } } @@ -269,22 +279,22 @@ /// Accumulates data elements and encodes them into a section. #[derive(Debug)] -pub struct SectionBuilder<'a, I: SectionIdentity> { +pub struct SectionBuilder<'a, SE: SectionEncoder> { /// Contains the section header, the identity-specified overhead, and any DEs added pub(crate) section: CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>, - identity: I, + section_encoder: SE, /// mut ref to enforce only one active section builder at a time adv_builder: &'a mut AdvBuilder, next_de_offset: DataElementOffset, } -impl<'a, I: SectionIdentity> SectionBuilder<'a, I> { +impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { /// Add this builder to the advertisement that created it. pub fn add_to_advertisement(self) { let adv_builder = self.adv_builder; adv_builder.add_section(Self::build_section( self.section.into_inner(), - self.identity, + self.section_encoder, adv_builder, )) } @@ -292,11 +302,11 @@ /// Add a data element to the section with a closure that returns a `Result`. /// /// The provided `build_de` closure will be invoked with the derived salt for this DE. - pub fn add_de_res<W: WriteDataElement, E, F: FnOnce(I::DerivedSalt) -> Result<W, E>>( + pub fn add_de_res<W: WriteDataElement, E, F: FnOnce(SE::DerivedSalt) -> Result<W, E>>( &mut self, build_de: F, ) -> Result<(), AddDataElementError<E>> { - let writer = build_de(self.identity.de_salt(self.next_de_offset)) + let writer = build_de(self.section_encoder.de_salt(self.next_de_offset)) .map_err(AddDataElementError::BuildDeError)?; let orig_len = self.section.len(); @@ -320,10 +330,10 @@ e })?; - if content_len != de_header.len.as_usize() { + if content_len != usize::from(de_header.len.as_u8()) { panic!( "Buggy WriteDataElement impl: header len {}, actual written len {}", - de_header.len.as_usize(), + de_header.len.as_u8(), content_len ); } @@ -336,7 +346,7 @@ /// Add a data element to the section with a closure that returns the data element directly. /// /// The provided `build_de` closure will be invoked with the derived salt for this DE. - pub fn add_de<W: WriteDataElement, F: FnOnce(I::DerivedSalt) -> W>( + pub fn add_de<W: WriteDataElement, F: FnOnce(SE::DerivedSalt) -> W>( &mut self, build_de: F, ) -> Result<(), AddDataElementError<()>> { @@ -348,11 +358,11 @@ /// Implemented without self to avoid partial-move issues. fn build_section( mut section_contents: tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, - mut identity: I, + mut section_encoder: SE, adv_builder: &AdvBuilder, ) -> EncodedSection { // there is space because the capacity for DEs was restricted to allow it - section_contents.resize(section_contents.len() + I::SUFFIX_LEN, 0); + section_contents.resize(section_contents.len() + SE::SUFFIX_LEN, 0); section_contents[0] = section_contents .len() @@ -361,7 +371,7 @@ .and_then(|len: u8| len.checked_sub(1)) .expect("section length is always <=255 and non-negative"); - identity.postprocess( + section_encoder.postprocess( adv_builder.header_byte(), section_contents[0], &mut section_contents[1..], @@ -380,8 +390,26 @@ InsufficientSectionSpace, } -/// The identity used for an individual section. -pub trait SectionIdentity { +/// The advertisement type, which dictates what sections can exist +#[derive(Debug, PartialEq, Eq)] +pub enum AdvertisementType { + /// Plaintext advertisement with only plaintext sections + Plaintext, + /// Encrypted advertisement with only encrypted sections + Encrypted, +} + +impl AdvertisementType { + fn max_sections(&self) -> usize { + match self { + AdvertisementType::Plaintext => NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, + AdvertisementType::Encrypted => NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, + } + } +} + +/// Everything needed to properly encode a section +pub trait SectionEncoder { /// How much space needs to be reserved for this identity's prefix bytes after the section /// header and before other DEs const PREFIX_LEN: usize; @@ -392,6 +420,9 @@ /// The DE offset to use for any DEs added to the section const INITIAL_DE_OFFSET: DataElementOffset; + /// The advertisement type that can support this section + const ADVERTISEMENT_TYPE: AdvertisementType; + /// Postprocess the contents of the section (the data after the section header byte), which will /// start with [Self::PREFIX_LEN] bytes set aside for the identity's use, and similarly end with /// [Self::SUFFIX_LEN] bytes, with DEs (if any) in the middle. @@ -404,13 +435,16 @@ fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt; } -impl SectionIdentity for PublicIdentity { +/// Public section for plaintext data elements +#[derive(Default, Debug)] +pub struct PublicSectionEncoder {} +impl SectionEncoder for PublicSectionEncoder { /// 1 byte of public identity DE header const PREFIX_LEN: usize = 1; const SUFFIX_LEN: usize = 0; /// Room for the public DE const INITIAL_DE_OFFSET: DataElementOffset = DataElementOffset::ZERO.incremented(); - + const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Plaintext; fn postprocess( &mut self, _adv_header_byte: u8, @@ -423,180 +457,50 @@ .as_slice(), ) } - type DerivedSalt = (); - fn de_salt(&self, _de_offset: DataElementOffset) -> Self::DerivedSalt {} } -/// Encrypts the data elements and protects integrity with a MIC using key material derived from -/// an NP identity. -pub struct MicEncrypted<'a, C: CryptoProvider> { - identity_type: EncryptedIdentityDataElementType, - salt: V1Salt<C>, - identity_payload: &'a [u8; 16], - aes_key: Aes128Key, - mic_hmac_key: np_hkdf::NpHmacSha256Key<C>, -} - -impl<'a, C: CryptoProvider> MicEncrypted<'a, C> { - /// Build a [MicEncrypted] from the provided identity info with a random salt. - pub fn new_random_salt( - rng: &mut C::CryptoRng, - identity_type: EncryptedIdentityDataElementType, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, - ) -> Self { - let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into(); - Self::new(identity_type, salt, identity_payload, keys) - } - - /// Build a [MicEncrypted] from the provided identity info. - fn new( - identity_type: EncryptedIdentityDataElementType, - salt: V1Salt<C>, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, - ) -> Self { - MicEncrypted { - identity_type, - salt, - identity_payload, - aes_key: keys.aes_key(), - mic_hmac_key: keys.hmac_key(), - } - } - - /// Build a [MicEncrypted] from the provided identity info. Exposed outside of this crate for - /// testing purposes only. - #[cfg(any(test, feature = "testing"))] - pub fn new_for_testing( - identity_type: EncryptedIdentityDataElementType, - salt: V1Salt<C>, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, - ) -> Self { - Self::new(identity_type, salt, identity_payload, keys) - } -} - -impl<'a, C: CryptoProvider> SectionIdentity for MicEncrypted<'a, C> { - const PREFIX_LEN: usize = - EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN; - /// Length of mic - const SUFFIX_LEN: usize = SectionMic::CONTENTS_LEN; - /// Room for the mic, encryption info, and identity DEs - const INITIAL_DE_OFFSET: DataElementOffset = - DataElementOffset::ZERO.incremented().incremented(); - - fn postprocess( - &mut self, - adv_header_byte: u8, - section_header: u8, - section_contents: &mut [u8], - ) { - // prefix byte layout: - // 0-18: Encryption Info DE (header + scheme + salt) - // 19-20: Identity DE header - // 21-36: Identity DE contents (metadata key) - - // Encryption Info DE - let encryption_info_bytes = EncryptionInfoDataElement::mic( - self.salt.as_slice().try_into().expect("Salt should be 16 bytes"), - ) - .serialize(); - section_contents[0..19].copy_from_slice(&encryption_info_bytes); - - // Identity DE - let identity_header = identity_de_header(self.identity_type, self.identity_payload); - section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice()); - section_contents[21..37].copy_from_slice(self.identity_payload); - - // DE offset for identity is 1: Encryption Info DE, Identity DE, then other DEs - let nonce: AesCtrNonce = self - .de_salt(v1_salt::DataElementOffset::from(1)) - .derive() - .expect("AES-CTR nonce is a valid HKDF length"); - - let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce)); - - let ciphertext_end = section_contents.len() - SectionMic::CONTENTS_LEN; - - // encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs - cipher.encrypt(&mut section_contents[21..ciphertext_end]); - - // calculate MAC per the spec - let mut section_hmac = self.mic_hmac_key.build_hmac(); - // svc uuid - section_hmac.update(NP_SVC_UUID.as_slice()); - // adv header - section_hmac.update(&[adv_header_byte]); - // section header - section_hmac.update(&[section_header]); - // encryption info - section_hmac.update(&encryption_info_bytes); - // derived salt - section_hmac.update(&nonce); - // identity header + ciphertext - section_hmac.update(§ion_contents[19..ciphertext_end]); - - let mic: [u8; 32] = section_hmac.finalize(); - - // write truncated MIC - section_contents[ciphertext_end..].copy_from_slice(&mic[..SectionMic::CONTENTS_LEN]); - } - - type DerivedSalt = DeSalt<C>; - - fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt { - DeSalt { salt: V1Salt::from(*self.salt.as_array_ref()), de_offset } - } -} - /// Encrypts the data elements and protects integrity with an np_ed25519 signature /// using key material derived from an NP identity. -pub struct SignedEncrypted<'a, C: CryptoProvider> { +pub struct SignedEncryptedSectionEncoder<C: CryptoProvider> { identity_type: EncryptedIdentityDataElementType, salt: V1Salt<C>, - metadata_key: &'a [u8; 16], - key_pair: &'a np_ed25519::KeyPair<C>, + metadata_key: MetadataKey, + key_pair: np_ed25519::KeyPair<C>, aes_key: Aes128Key, - _marker: PhantomData<C>, } -impl<'a, C: CryptoProvider> SignedEncrypted<'a, C> { - /// Build a [SignedEncrypted] from the provided identity material with a random salt. - pub fn new_random_salt( +impl<C: CryptoProvider> SignedEncryptedSectionEncoder<C> { + /// Build a [SignedEncryptedSectionEncoder] from an identity type, + /// some broadcast crypto-material, and with a random salt. + pub fn new_random_salt<B: SignedBroadcastCryptoMaterial>( rng: &mut C::CryptoRng, identity_type: EncryptedIdentityDataElementType, - metadata_key: &'a [u8; 16], - key_pair: &'a np_ed25519::KeyPair<C>, - key_seed_hkdf: &np_hkdf::NpKeySeedHkdf<C>, + crypto_material: &B, ) -> Self { let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into(); - Self::new(identity_type, salt, metadata_key, key_pair, key_seed_hkdf) + Self::new(identity_type, salt, crypto_material) } - /// Build a [SignedEncrypted] from the provided identity material. - pub(crate) fn new( + /// Build a [SignedEncryptedSectionEncoder] from an identity type, + /// a provided salt, and some broadcast crypto-material. + pub(crate) fn new<B: SignedBroadcastCryptoMaterial>( identity_type: EncryptedIdentityDataElementType, salt: V1Salt<C>, - metadata_key: &'a [u8; 16], - key_pair: &'a np_ed25519::KeyPair<C>, - key_seed_hkdf: &np_hkdf::NpKeySeedHkdf<C>, + crypto_material: &B, ) -> Self { - Self { - identity_type, - salt, - metadata_key, - key_pair, - aes_key: key_seed_hkdf.extended_signed_section_aes_key(), - _marker: Default::default(), - } + let metadata_key = crypto_material.metadata_key(); + let key_seed = crypto_material.key_seed(); + let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed); + let private_key = crypto_material.signing_key(); + let key_pair = np_ed25519::KeyPair::<C>::from_private_key(&private_key); + let aes_key = key_seed_hkdf.extended_signed_section_aes_key(); + Self { identity_type, salt, metadata_key, key_pair, aes_key } } } -impl<'a, C: CryptoProvider> SectionIdentity for SignedEncrypted<'a, C> { +impl<C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<C> { const PREFIX_LEN: usize = EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN; /// Ed25519 signature @@ -604,6 +508,7 @@ /// Room for the encryption info and identity DEs const INITIAL_DE_OFFSET: DataElementOffset = DataElementOffset::ZERO.incremented().incremented(); + const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Encrypted; fn postprocess( &mut self, @@ -619,7 +524,7 @@ let identity_header = identity_de_header(self.identity_type, self.metadata_key); section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice()); - section_contents[21..37].copy_from_slice(self.metadata_key); + section_contents[21..37].copy_from_slice(&self.metadata_key.0); let nonce: AesCtrNonce = self .de_salt(v1_salt::DataElementOffset::from(1)) @@ -648,14 +553,14 @@ after_encryption_info, ); - let signature = section_signature_payload.sign(self.key_pair); + let signature = section_signature_payload.sign(&self.key_pair); sig[0..64].copy_from_slice(&signature.to_bytes()); let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce)); // encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs - cipher.encrypt(&mut section_contents[21..]); + cipher.apply_keystream(&mut section_contents[21..]); } type DerivedSalt = DeSalt<C>; @@ -665,6 +570,135 @@ } } +/// Encrypts the data elements and protects integrity with a MIC using key material derived from +/// an NP identity. +pub struct MicEncryptedSectionEncoder<C: CryptoProvider> { + identity_type: EncryptedIdentityDataElementType, + salt: V1Salt<C>, + metadata_key: MetadataKey, + aes_key: Aes128Key, + mic_hmac_key: np_hkdf::NpHmacSha256Key<C>, +} + +impl<C: CryptoProvider> MicEncryptedSectionEncoder<C> { + /// Build a [MicEncryptedSectionEncoder] from the provided identity + /// info with a random salt. + pub fn new_random_salt<B: BroadcastCryptoMaterial<V1>>( + rng: &mut C::CryptoRng, + identity_type: EncryptedIdentityDataElementType, + crypto_material: &B, + ) -> Self { + let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into(); + Self::new(identity_type, salt, crypto_material) + } + + /// Build a [MicEncryptedSectionEncoder] from the provided identity info. + pub(crate) fn new<B: BroadcastCryptoMaterial<V1>>( + identity_type: EncryptedIdentityDataElementType, + salt: V1Salt<C>, + crypto_material: &B, + ) -> Self { + let metadata_key = crypto_material.metadata_key(); + let key_seed = crypto_material.key_seed(); + let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed); + let aes_key = np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf); + let mic_hmac_key = np_hkdf::UnsignedSectionKeys::hmac_key(&key_seed_hkdf); + + Self { identity_type, salt, metadata_key, aes_key, mic_hmac_key } + } + + /// Build a [MicEncrypedSectionEncoder] from the provided identity info. + /// Exposed outside of this crate for testing purposes only, since this + /// does not handle the generation of random salts. + #[cfg(any(test, feature = "testing"))] + pub fn new_for_testing<B: BroadcastCryptoMaterial<V1>>( + identity_type: EncryptedIdentityDataElementType, + salt: V1Salt<C>, + crypto_material: &B, + ) -> Self { + Self::new(identity_type, salt, crypto_material) + } +} + +impl<C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<C> { + const PREFIX_LEN: usize = + EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN; + /// Length of mic + const SUFFIX_LEN: usize = SectionMic::CONTENTS_LEN; + /// Room for the mic, encryption info, and identity DEs + const INITIAL_DE_OFFSET: DataElementOffset = + DataElementOffset::ZERO.incremented().incremented(); + + const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Encrypted; + + fn postprocess( + &mut self, + adv_header_byte: u8, + section_header: u8, + section_contents: &mut [u8], + ) { + // prefix byte layout: + // 0-18: Encryption Info DE (header + scheme + salt) + // 19-20: Identity DE header + // 21-36: Identity DE contents (metadata key) + // Encryption Info DE + let encryption_info_bytes = EncryptionInfoDataElement::mic( + self.salt.as_slice().try_into().expect("Salt should be 16 bytes"), + ) + .serialize(); + section_contents[0..19].copy_from_slice(&encryption_info_bytes); + // Identity DE + let identity_header = identity_de_header(self.identity_type, self.metadata_key); + section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice()); + section_contents[21..37].copy_from_slice(&self.metadata_key.0); + // DE offset for identity is 1: Encryption Info DE, Identity DE, then other DEs + let nonce: AesCtrNonce = self + .de_salt(v1_salt::DataElementOffset::from(1)) + .derive() + .expect("AES-CTR nonce is a valid HKDF length"); + let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce)); + let ciphertext_end = section_contents.len() - SectionMic::CONTENTS_LEN; + // encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs + cipher.apply_keystream(&mut section_contents[21..ciphertext_end]); + // calculate MAC per the spec + let mut section_hmac = self.mic_hmac_key.build_hmac(); + // svc uuid + section_hmac.update(NP_SVC_UUID.as_slice()); + // adv header + section_hmac.update(&[adv_header_byte]); + // section header + section_hmac.update(&[section_header]); + // encryption info + section_hmac.update(&encryption_info_bytes); + // derived salt + section_hmac.update(&nonce); + // identity header + ciphertext + section_hmac.update(§ion_contents[19..ciphertext_end]); + let mic: [u8; 32] = section_hmac.finalize(); + // write truncated MIC + section_contents[ciphertext_end..].copy_from_slice(&mic[..SectionMic::CONTENTS_LEN]); + } + type DerivedSalt = DeSalt<C>; + fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt { + DeSalt { salt: V1Salt::from(*self.salt.as_array_ref()), de_offset } + } +} + +/// Derived salt for an individual data element. +pub struct DeSalt<C: CryptoProvider> { + salt: V1Salt<C>, + de_offset: DataElementOffset, +} + +impl<C: CryptoProvider> DeSalt<C> { + /// Derive salt of the requested length. + /// + /// The length must be a valid HKDF-SHA256 length. + pub fn derive<const N: usize>(&self) -> Option<[u8; N]> { + self.salt.derive(Some(self.de_offset)) + } +} + /// For DE structs that only implement one DE type, rather than multi-type impls. pub trait SingleTypeDataElement { /// The DE type for the DE. @@ -766,11 +800,12 @@ fn identity_de_header( id_type: EncryptedIdentityDataElementType, - metadata_key: &[u8; 16], + metadata_key: MetadataKey, ) -> DeHeader { DeHeader { de_type: id_type.type_code(), len: metadata_key + .0 .len() .try_into() .map_err(|_e| DeLengthOutOfRange)
diff --git a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs index af11076..d418a50 100644 --- a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
@@ -15,11 +15,12 @@ extern crate std; use super::*; -use crate::extended::serialize::AddSectionError::MaxSectionCountExceeded; -use crate::{ - extended::data_elements::{ContextSyncSeqNumDataElement, GenericDataElement}, - shared_data::ContextSyncSeqNum, +use crate::credential::{ + v1::{SimpleSignedBroadcastCryptoMaterial, V1}, + SimpleBroadcastCryptoMaterial, }; +use crate::extended::data_elements::GenericDataElement; +use crate::extended::serialize::AddSectionError::MaxSectionCountExceeded; use crypto_provider::aes::ctr::AES_CTR_NONCE_LEN; use crypto_provider_default::CryptoProviderImpl; use np_hkdf::v1_salt::V1Salt; @@ -32,8 +33,8 @@ #[test] fn public_identity_section_empty() { - let mut adv_builder = AdvBuilder::new(); - let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); assert_eq!(&[1_u8, 0x03], section_builder.into_section().as_slice()); } @@ -62,23 +63,26 @@ .collect::<Vec<_>>(); let metadata_key = rng.gen(); + let metadata_key = MetadataKey(metadata_key); let key_seed = rng.gen(); let identity_type = *EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap(); let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let section_salt = - V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let section_salt = V1Salt::<CryptoProviderImpl>::from( + *section_builder.section_encoder.salt.as_array_ref(), + ); for de in extra_des.iter() { section_builder.add_de(|_| de).unwrap(); @@ -88,9 +92,7 @@ let section_length = 53 + extra_des .iter() - .map(|de| { - de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8 - }) + .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8()) .sum::<u8>(); let encryption_info = [ @@ -123,14 +125,14 @@ &np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf), NonceAndCounter::from_nonce(nonce), ); - let mut plaintext = metadata_key.as_slice().to_vec(); + let mut plaintext = metadata_key.0.as_slice().to_vec(); for de in extra_des { plaintext.extend_from_slice(de.de_header().serialize().as_slice()); de.write_de_contents(&mut plaintext); } - cipher.encrypt(&mut plaintext); + cipher.apply_keystream(&mut plaintext); let ciphertext = plaintext; hmac_input.extend_from_slice(&ciphertext); @@ -162,26 +164,31 @@ for _ in 0..1_000 { let num_des = rng.gen_range(1..=5); - let metadata_key = rng.gen(); + let metadata_key = MetadataKey(rng.gen()); let key_seed = rng.gen(); let identity_type = *EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap(); let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_pair = KeyPair::generate(); + let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new( + key_seed, + metadata_key, + key_pair.private_key(), + ); + let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let section_salt = - V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let section_salt = V1Salt::<CryptoProviderImpl>::from( + *section_builder.section_encoder.salt.as_array_ref(), + ); let extra_des = (0..num_des) .map(|_| { @@ -201,9 +208,7 @@ + 64 + extra_des .iter() - .map(|de| { - de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8 - }) + .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8()) .sum::<u8>(); let encryption_info = [ @@ -224,11 +229,10 @@ let nonce = section_salt.derive::<{ AES_CTR_NONCE_LEN }>(Some(1.into())).unwrap(); - let mut plaintext = metadata_key.as_slice().to_vec(); - + let mut section_body = Vec::new(); for de in extra_des { - plaintext.extend_from_slice(de.de_header().serialize().as_slice()); - de.write_de_contents(&mut plaintext); + section_body.extend_from_slice(de.de_header().serialize().as_slice()); + de.write_de_contents(&mut section_body); } let sig_payload = SectionSignaturePayload::from_deserialized_parts( @@ -237,16 +241,20 @@ &encryption_info, &nonce, identity_de_header, - &plaintext, + metadata_key, + §ion_body, ); + let mut plaintext = metadata_key.0.as_slice().to_vec(); + plaintext.extend_from_slice(section_body.as_slice()); + plaintext.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes()); <CryptoProviderImpl as CryptoProvider>::AesCtr128::new( &key_seed_hkdf.extended_signed_section_aes_key(), NonceAndCounter::from_nonce(nonce), ) - .encrypt(&mut plaintext); + .apply_keystream(&mut plaintext); let ciphertext = plaintext; let mut expected = vec![section_length]; @@ -262,20 +270,23 @@ fn section_builder_too_full_doesnt_advance_de_index() { let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_seed = [22; 32]; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let metadata_key = [33; 16]; + let metadata_key = MetadataKey([33; 16]); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Trusted, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let salt = + V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref()); section_builder .add_de(|de_salt| DummyDataElement { @@ -306,7 +317,7 @@ let mut expected = vec![]; // metadata key - expected.extend_from_slice(&metadata_key); + expected.extend_from_slice(&metadata_key.0); // de header expected.extend_from_slice(&[0x80 + 100, 100]); // section 0 de 2 @@ -321,7 +332,7 @@ NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()), ); - cipher.encrypt(&mut expected); + cipher.apply_keystream(&mut expected); let adv_bytes = adv_builder.into_advertisement(); // ignoring the MIC, etc, since that's tested elsewhere @@ -332,59 +343,47 @@ #[test] fn section_de_fits_exactly() { // leave room for initial filler section's header and the identities - for section_contents_capacity in 1..NP_ADV_MAX_SECTION_LEN - 3 { - let mut adv_builder = AdvBuilder::new(); + // for section_contents_capacity in 1..NP_ADV_MAX_SECTION_LEN - 3 { + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); - // fill up space to produce desired capacity - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); - // leave space for adv header, 2 section headers, the section identities, and desired second section capacity - fill_section_builder( - BLE_ADV_SVC_CONTENT_LEN - 1 - 2 - 2 - section_contents_capacity, - &mut section_builder, - ); - section_builder.add_to_advertisement(); + // fill up space to produce desired capacity + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); + // leave space for adv header, 1 section headers, 1 section identity and fill almost full + fill_section_builder(BLE_ADV_SVC_CONTENT_LEN - 1 - 1 - 1 - 1, &mut section_builder); + assert_eq!(1, section_builder.section.capacity() - section_builder.section.len(), "capacity: "); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + // can't add a 2 byte DE + assert_eq!( + Err(AddDataElementError::InsufficientSectionSpace), + section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[0xFF])), + "capacity: " + ); - // fill it almost full - fill_section_builder(section_contents_capacity - 1, &mut section_builder); - - assert_eq!( - 1, - section_builder.section.capacity() - section_builder.section.len(), - "capacity: {section_contents_capacity}" - ); - - // can't add a 2 byte DE - assert_eq!( - Err(AddDataElementError::InsufficientSectionSpace), - section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[0xFF])), - "capacity: {section_contents_capacity}" - ); - - // can add a 1 byte DE - section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[])).unwrap(); - } + // can add a 1 byte DE + section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[])).unwrap(); } #[test] fn section_builder_build_de_error_doesnt_advance_de_index() { let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_seed = [22; 32]; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let metadata_key = [33; 16]; + let metadata_key = MetadataKey([33; 16]); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Trusted, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let salt = + V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref()); section_builder .add_de(|de_salt| DummyDataElement { @@ -409,7 +408,7 @@ let mut expected = vec![]; // metadata key - expected.extend_from_slice(&metadata_key); + expected.extend_from_slice(&metadata_key.0); // de header expected.extend_from_slice(&[0x80 + 100, 100]); // section 0 de 2 @@ -424,7 +423,7 @@ NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()), ); - cipher.encrypt(&mut expected); + cipher.apply_keystream(&mut expected); let adv_bytes = adv_builder.into_advertisement(); // ignoring the MIC, etc, since that's tested elsewhere @@ -436,20 +435,23 @@ fn add_multiple_de_correct_de_offsets_mic_encrypted_identity() { let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_seed = [22; 32]; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let metadata_key = [33; 16]; + let metadata_key = MetadataKey([33; 16]); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncrypted::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Trusted, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let salt = + V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref()); section_builder .add_de(|de_salt| DummyDataElement { @@ -468,7 +470,7 @@ let mut expected = vec![]; // metadata key - expected.extend_from_slice(&metadata_key); + expected.extend_from_slice(&metadata_key.0); // de header expected.extend_from_slice(&[0x90, 0x40]); // section 0 de 2 @@ -483,7 +485,7 @@ NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()), ); - cipher.encrypt(&mut expected); + cipher.apply_keystream(&mut expected); let adv_bytes = adv_builder.into_advertisement(); // ignoring the MIC, etc, since that's tested elsewhere @@ -495,22 +497,25 @@ fn add_multiple_de_correct_de_offsets_signature_encrypted_identity() { let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_seed = [22; 32]; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let metadata_key = [33; 16]; + let metadata_key = MetadataKey([33; 16]); let key_pair = KeyPair::generate(); + + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); + let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Trusted, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); - let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref()); + let salt = + V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref()); section_builder .add_de(|de_salt| DummyDataElement { @@ -529,7 +534,7 @@ let mut expected = vec![]; // metadata key - expected.extend_from_slice(&metadata_key); + expected.extend_from_slice(&metadata_key.0); // de header expected.extend_from_slice(&[0x90, 0x40]); // section 0 de 2 @@ -543,7 +548,7 @@ &key_seed_hkdf.extended_signed_section_aes_key(), NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()), ) - .encrypt(&mut expected); + .apply_keystream(&mut expected); let adv_bytes = adv_builder.into_advertisement(); // ignoring the signature since that's tested elsewhere @@ -558,19 +563,20 @@ fn signature_encrypted_section_de_lengths_allow_room_for_suffix() { let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let key_seed = [22; 32]; - let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let metadata_key = [33; 16]; + let metadata_key = MetadataKey([33; 16]); let key_pair = KeyPair::generate(); + + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); + let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Trusted, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -606,35 +612,39 @@ } #[test] -fn serialize_max_number_of_sections() { - let mut adv_builder = AdvBuilder::new(); - for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); - section_builder.add_de(|_| context_sync_de(1)).unwrap(); +fn serialize_max_number_of_public_sections() { + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT { + let mut section_builder = + adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); + section_builder + .add_de(|_| DummyDataElement { de_type: 100_u32.into(), data: vec![0; 98] }) + .unwrap(); section_builder.add_to_advertisement(); } assert_eq!( - adv_builder.section_builder(PublicIdentity::default()).unwrap_err(), + adv_builder.section_builder(PublicSectionEncoder::default()).unwrap_err(), MaxSectionCountExceeded ); } fn do_mic_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_des: &[W]) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let adv_header_byte = 0b00100000; let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into(); let identity_type = EncryptedIdentityDataElementType::Private; let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -646,7 +656,7 @@ let section_length = 53 + extra_des .iter() - .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8) + .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8()) .sum::<u8>(); let encryption_info = [ @@ -678,14 +688,14 @@ &np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf), NonceAndCounter::from_nonce(nonce), ); - let mut plaintext = metadata_key.as_slice().to_vec(); + let mut plaintext = metadata_key.0.as_slice().to_vec(); for de in extra_des { plaintext.extend_from_slice(de.de_header().serialize().as_slice()); de.write_de_contents(&mut plaintext); } - cipher.encrypt(&mut plaintext); + cipher.apply_keystream(&mut plaintext); let ciphertext = plaintext; hmac_input.extend_from_slice(&ciphertext); @@ -704,7 +714,7 @@ } fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_des: &[W]) { - let metadata_key = [1; 16]; + let metadata_key = MetadataKey([1; 16]); let key_seed = [2; 32]; let adv_header_byte = 0b00100000; let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into(); @@ -712,15 +722,16 @@ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let key_pair = KeyPair::generate(); - let mut adv_builder = AdvBuilder::new(); + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); + + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -734,7 +745,7 @@ + 64 + extra_des .iter() - .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8) + .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8()) .sum::<u8>(); let encryption_info = [ @@ -751,11 +762,10 @@ DeHeader { len: 16_u8.try_into().unwrap(), de_type: identity_type.type_code() }; let identity_de_header: [u8; 2] = identity_de_header.serialize().as_slice().try_into().unwrap(); - let mut plaintext = metadata_key.as_slice().to_vec(); - + let mut section_body = Vec::new(); for de in extra_des { - plaintext.extend_from_slice(de.de_header().serialize().as_slice()); - de.write_de_contents(&mut plaintext); + section_body.extend_from_slice(de.de_header().serialize().as_slice()); + de.write_de_contents(&mut section_body); } let nonce = section_salt.derive(Some(1.into())).unwrap(); @@ -766,16 +776,19 @@ &encryption_info, &nonce, identity_de_header, - &plaintext, + metadata_key, + §ion_body, ); + let mut plaintext = metadata_key.0.as_slice().to_vec(); + plaintext.extend_from_slice(section_body.as_slice()); plaintext.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes()); <CryptoProviderImpl as CryptoProvider>::AesCtr128::new( &key_seed_hkdf.extended_signed_section_aes_key(), NonceAndCounter::from_nonce(nonce), ) - .encrypt(&mut plaintext); + .apply_keystream(&mut plaintext); let ciphertext = plaintext; let mut expected = vec![section_length]; @@ -788,7 +801,7 @@ } /// Write `section_contents_len` bytes of DE and header into `section_builder` -pub(crate) fn fill_section_builder<I: SectionIdentity>( +pub(crate) fn fill_section_builder<I: SectionEncoder>( section_contents_len: usize, section_builder: &mut SectionBuilder<I>, ) { @@ -844,17 +857,13 @@ } } -fn context_sync_de(num: u8) -> ContextSyncSeqNumDataElement { - ContextSyncSeqNumDataElement::from(ContextSyncSeqNum::try_from(num).unwrap()) -} - pub(crate) trait SectionBuilderExt { fn into_section(self) -> EncodedSection; } -impl<'a, I: SectionIdentity> SectionBuilderExt for SectionBuilder<'a, I> { +impl<'a, I: SectionEncoder> SectionBuilderExt for SectionBuilder<'a, I> { /// Convenience method for tests fn into_section(self) -> EncodedSection { - Self::build_section(self.section.into_inner(), self.identity, self.adv_builder) + Self::build_section(self.section.into_inner(), self.section_encoder, self.adv_builder) } }
diff --git a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs index 9f7aa5c..24e6046 100644 --- a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs +++ b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
@@ -14,12 +14,15 @@ extern crate std; +use crate::extended::serialize::AdvertisementType; use crate::{ + credential::{v1::V1, SimpleBroadcastCryptoMaterial}, de_type::EncryptedIdentityDataElementType, extended::serialize::{ section_tests::{DummyDataElement, SectionBuilderExt}, - AdvBuilder, MicEncrypted, + AdvBuilder, MicEncryptedSectionEncoder, }, + MetadataKey, }; use anyhow::anyhow; use crypto_provider::{aes::ctr::AES_CTR_NONCE_LEN, aes::AesKey}; @@ -49,7 +52,7 @@ for tc in test_cases { { let key_seed = extract_key_array::<32>(&tc, "key_seed"); - let metadata_key = extract_key_array::<16>(&tc, "metadata_key"); + let metadata_key = MetadataKey(extract_key_array::<16>(&tc, "metadata_key")); let adv_header_byte = extract_key_array::<1>(&tc, "adv_header_byte")[0]; let section_salt = v1_salt::V1Salt::<CryptoProviderImpl>::from( extract_key_array::<16>(&tc, "section_salt"), @@ -65,7 +68,7 @@ }) .collect::<Vec<_>>(); - let hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed); + let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); assert_eq!( extract_key_array::<16>(&tc, "aes_key").as_slice(), @@ -80,16 +83,17 @@ section_salt.derive::<{ AES_CTR_NONCE_LEN }>(Some(1.into())).unwrap() ); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + // make an adv builder in the configuration we need - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); assert_eq!(adv_header_byte, adv_builder.header_byte()); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, section_salt, - &metadata_key, - &hkdf, + &broadcast_cm, )) .unwrap(); @@ -127,7 +131,7 @@ }) .collect::<Vec<_>>(); - let metadata_key = rng.gen(); + let metadata_key = MetadataKey(rng.gen()); let key_seed = rng.gen(); let adv_header_byte = 0b00100000; let identity_type = @@ -135,15 +139,16 @@ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let mut adv_builder = AdvBuilder::new(); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); let section_salt = v1_salt::V1Salt::<CryptoProviderImpl>::from(rng.gen::<[u8; 16]>()); let mut section_builder = adv_builder - .section_builder(MicEncrypted::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -156,7 +161,7 @@ array .push(json!({ "key_seed": hex::encode_upper(key_seed), - "metadata_key": hex::encode_upper(metadata_key), + "metadata_key": hex::encode_upper(metadata_key.0), "adv_header_byte": hex::encode_upper([adv_header_byte]), "section_salt": hex::encode_upper(section_salt.as_slice()), "identity_type": identity_type_label(identity_type),
diff --git a/nearby/presence/np_adv/src/filter/mod.rs b/nearby/presence/np_adv/src/filter/mod.rs new file mode 100644 index 0000000..cb020cf --- /dev/null +++ b/nearby/presence/np_adv/src/filter/mod.rs
@@ -0,0 +1,291 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides filtering APIs to determine whether or not a buffer of bytes represents +//! a valid Nearby Presence advertisement and if so which was its corresponding identity +//! it matched with. Used as a first pass option to quickly check if a buffer should +//! further processed. +use crate::credential::MatchedCredential; +use crate::legacy::deserialize::DecryptedAdvContents; +use crate::{ + credential::{book::CredentialBook, v0::V0DiscoveryCryptoMaterial}, + legacy, + legacy::{ + actions, + actions::ActionsDataElement, + deserialize::{ + DecryptError, EncryptedAdvContents, IntermediateAdvContents, PlainDataElement, + }, + PacketFlavor, + }, + parse_adv_header, AdvHeader, V1Header, +}; +use array_view::ArrayView; +use core::fmt::Debug; +use crypto_provider::CryptoProvider; + +#[cfg(test)] +mod tests; + +/// The options to filter for in an advertisement payload. +pub enum FilterOptions { + /// Filter criteria for matching against only v0 advertisements + V0FilterOptions(V0Filter), + /// Filter criteria for matching against only v1 advertisements + V1FilterOptions(V1Filter), + /// Criteria to filter for in both v0 and v1 advertisements + Either(V0Filter, V1Filter), +} + +/// Error returned when no advertisement matching the criteria was found. +#[derive(PartialEq, Debug)] +pub struct NoMatch; + +/// Error returned if the provided slice is of invalid length +#[derive(PartialEq, Debug)] +pub struct InvalidLength; + +/// The criteria of what to filter for in a V0 advertisement. +pub struct V0Filter { + identity: IdentityFilterType, + data_elements: V0DataElementsFilter, +} + +/// The type of identity to filter for. +pub enum IdentityFilterType { + /// Filter only on public identity advertisements + Public, + /// Filter only on private identity advertisements + Private, + /// Don't filter on any specific identity type + Any, +} + +/// Returned by the filter API indicating the status and in the case of private identity the key seed +/// which matched the decrypted advertisement +#[derive(Debug, PartialEq)] +pub enum FilterResult<M: MatchedCredential> { + /// A public identity advertisement matching the filter criteria was detected. + Public, + /// A Private identity advertisement matching the filter criteria was detected and decrypted + /// with the contained `KeySeed`. + Private(M), +} + +/// The criteria of what data elements to filter for in a V0 advertisement. +pub struct V0DataElementsFilter { + contains_tx_power: Option<bool>, + actions_filter: Option<V0ActionsFilter>, +} + +/// The total number of unique boolean action types +const NUM_ACTIONS: usize = 7; + +/// Specify which specific actions bits to filter on, will filter on if any of the specified +/// actions are matched +//TODO: do we need a more specific filter criteria, eg: only return if `(A AND B) || C` are found? +// Potentially this could be pulled out more generally into a V0Filter trait which has many different +// implementations. +pub struct V0ActionsFilter { + actions: array_view::ArrayView<Option<actions::ActionType>, NUM_ACTIONS>, +} + +impl FilterOptions { + /// WARNING: This does not perform signature verification on the advertisement, even if it detects + /// a valid advertisement it does not verify it. Verification must occur before further processing + /// of the adv. + /// + /// Returns `Ok` if a advertisement was detected which matched the filter criteria, and `Err` otherwise. + pub fn match_advertisement<'a, B, P>( + &self, + cred_book: &'a B, + adv: &[u8], + ) -> Result<FilterResult<B::Matched>, NoMatch> + where + B: CredentialBook<'a>, + P: CryptoProvider, + { + parse_adv_header(adv) + .map(|(remaining, header)| match header { + AdvHeader::V0 => { + let filter = match self { + FilterOptions::V0FilterOptions(filter) => filter, + FilterOptions::Either(filter, _) => filter, + _ => return Err(NoMatch), + }; + filter.match_v0_adv::<B, P>(cred_book, remaining) + } + AdvHeader::V1(header) => { + let filter = match self { + FilterOptions::V1FilterOptions(filter) => filter, + FilterOptions::Either(_, filter) => filter, + _ => return Err(NoMatch), + }; + filter.match_v1_adv::<B, P>(cred_book, remaining, header) + } + }) + .map_err(|_| NoMatch)? + } +} + +impl V0Filter { + /// Filter for the provided criteria returning success if the advertisement bytes successfully + /// match the filter criteria + fn match_v0_adv<'a, B, P>( + &self, + cred_book: &'a B, + remaining: &[u8], + ) -> Result<FilterResult<B::Matched>, NoMatch> + where + B: CredentialBook<'a>, + P: CryptoProvider, + { + let contents = + legacy::deserialize::deserialize_adv_contents::<P>(remaining).map_err(|_| NoMatch)?; + match contents { + IntermediateAdvContents::Plaintext(p) => match self.identity { + IdentityFilterType::Public | IdentityFilterType::Any => self + .data_elements + .match_v0_legible_adv(p.data_elements()) + .map(|()| FilterResult::Public), + _ => Err(NoMatch), + }, + IntermediateAdvContents::Ciphertext(c) => match self.identity { + IdentityFilterType::Private | IdentityFilterType::Any => { + let (legible_adv, m) = try_decrypt_and_match::<B, P>(cred_book.v0_iter(), &c)?; + self.data_elements + .match_v0_legible_adv(legible_adv.data_elements()) + .map(|()| FilterResult::Private(m)) + } + _ => Err(NoMatch), + }, + } + } +} + +fn try_decrypt_and_match<'a, B, P>( + v0_creds: B::V0Iterator, + adv: &EncryptedAdvContents, +) -> Result<(DecryptedAdvContents, B::Matched), NoMatch> +where + B: CredentialBook<'a>, + P: CryptoProvider, +{ + for (crypto_material, m) in v0_creds { + let ldt = crypto_material.ldt_adv_cipher::<P>(); + match adv.try_decrypt(&ldt) { + Ok(c) => return Ok((c, m)), + Err(e) => match e { + DecryptError::DecryptOrVerifyError => continue, + DecryptError::DeserializeError(_) => { + return Err(NoMatch); + } + }, + } + } + Err(NoMatch) +} + +impl V0DataElementsFilter { + /// A legible adv is either plaintext to begin with, or decrypted contents from an encrypted adv + fn match_v0_legible_adv<'a, F: PacketFlavor + 'a>( + &self, + mut data_elements: impl ExactSizeIterator<Item = &'a PlainDataElement<F>>, + ) -> Result<(), NoMatch> { + match &self.contains_tx_power { + None => Ok(()), + Some(c) => { + if c == &data_elements.any(|de| matches!(de, PlainDataElement::TxPower(_))) { + Ok(()) + } else { + Err(NoMatch) + } + } + }?; + + match &self.actions_filter { + None => Ok(()), + Some(filter) => { + // find if an actions DE exists, if so match on the provided action filter + let actions = data_elements.find_map(|de| match de { + PlainDataElement::Actions(actions) => Some(actions), + _ => None, + }); + if let Some(actions) = actions { + filter.match_v0_actions(actions) + } else { + return Err(NoMatch); + } + } + }?; + + Ok(()) + } +} + +impl V0ActionsFilter { + /// Creates a new filter from a slice of Actions to look for. Will succeed if any of the provided + /// actions are found. Maximum length of `actions` is 7, as there are 7 unique actions possible to + /// filter on for V0. + pub fn new_from_slice(actions: &[actions::ActionType]) -> Result<Self, InvalidLength> { + if actions.len() > NUM_ACTIONS { + return Err(InvalidLength); + } + let mut filter_actions = [None; NUM_ACTIONS]; + for (i, action) in actions.iter().map(|action| Some(*action)).enumerate() { + // i will always be in bounds because the length is checked above + filter_actions[i] = action; + } + Ok(ArrayView::try_from_array(filter_actions, actions.len()) + .map(|actions| Self { actions }) + .expect("Length checked above, so this can't happen")) + } + + fn match_v0_actions<F: PacketFlavor>( + &self, + actions: &ActionsDataElement<F>, + ) -> Result<(), NoMatch> { + for action in self.actions.as_slice().iter() { + if actions + .action + .has_action(&action.expect("This will always contain Some")) + .unwrap_or(false) + { + return Ok(()); + } + } + Err(NoMatch) + } +} + +/// The criteria of what to filter for in a V1 advertisement. +pub struct V1Filter; + +impl V1Filter { + /// Filter for the provided criteria returning success if the advertisement bytes successfully + /// match the filter criteria + #[allow(clippy::extra_unused_type_parameters)] + fn match_v1_adv<'a, B, P>( + &self, + _cred_book: &'a B, + _remaining: &[u8], + _header: V1Header, + ) -> Result<FilterResult<B::Matched>, NoMatch> + where + B: CredentialBook<'a>, + P: CryptoProvider, + { + todo!() + } +}
diff --git a/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs new file mode 100644 index 0000000..946c969 --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
@@ -0,0 +1,136 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::super::*; +use crate::legacy::actions::{ActionBits, InstantTethering, NearbyShare}; +use crate::legacy::{Ciphertext, Plaintext}; + +#[test] +fn new_v0_actions_invalid_length() { + let actions = [actions::ActionType::ActiveUnlock; 8]; + let result = V0ActionsFilter::new_from_slice(&actions); + assert!(result.is_err()); + assert_eq!(result.err().unwrap(), InvalidLength) +} + +#[test] +fn new_v0_actions() { + let actions = [actions::ActionType::ActiveUnlock; 7]; + let result = V0ActionsFilter::new_from_slice(&actions); + assert!(result.is_ok()); +} + +#[test] +fn new_v0_actions_empty_slice() { + let result = V0ActionsFilter::new_from_slice(&[]); + assert!(result.is_ok()); +} + +#[test] +fn test_actions_filter_single_action_not_present() { + // default is all 0 bits + let action_bits = ActionBits::<Plaintext>::default(); + + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1]) + .expect("1 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch)) +} + +#[test] +fn test_actions_filter_all_actions_not_present() { + // default is all 0 bits + let action_bits = ActionBits::<Plaintext>::default(); + + let filter = V0ActionsFilter::new_from_slice(&[ + actions::ActionType::ActiveUnlock, + actions::ActionType::NearbyShare, + actions::ActionType::InstantTethering, + actions::ActionType::PhoneHub, + actions::ActionType::Finder, + actions::ActionType::FastPairSass, + actions::ActionType::PresenceManager, + ]) + .expect("7 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch)) +} + +#[test] +fn test_actions_filter_single_action_present() { + // default is all 0 bits + let mut action_bits = ActionBits::<Plaintext>::default(); + action_bits.set_action(NearbyShare::from(true)); + + let filter = V0ActionsFilter::new_from_slice(&[ + actions::ActionType::ActiveUnlock, + actions::ActionType::NearbyShare, + actions::ActionType::InstantTethering, + actions::ActionType::PhoneHub, + actions::ActionType::Finder, + actions::ActionType::FastPairSass, + actions::ActionType::PresenceManager, + ]) + .expect("7 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(())) +} + +#[test] +fn test_actions_filter_desired_action_not_present() { + // default is all 0 bits + let mut action_bits = ActionBits::<Plaintext>::default(); + action_bits.set_action(NearbyShare::from(true)); + + let filter = V0ActionsFilter::new_from_slice(&[ + actions::ActionType::ActiveUnlock, + actions::ActionType::InstantTethering, + actions::ActionType::PhoneHub, + actions::ActionType::Finder, + actions::ActionType::FastPairSass, + actions::ActionType::PresenceManager, + ]) + .expect("6 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch)) +} + +#[test] +fn test_multiple_actions_set() { + // default is all 0 bits + let mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(NearbyShare::from(true)); + action_bits.set_action(InstantTethering::from(true)); + + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::InstantTethering]) + .expect("1 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(())) +} + +#[test] +fn test_multiple_actions_set_both_present() { + // default is all 0 bits + let mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(NearbyShare::from(true)); + action_bits.set_action(InstantTethering::from(true)); + + let filter = V0ActionsFilter::new_from_slice(&[ + actions::ActionType::InstantTethering, + actions::ActionType::NearbyShare, + ]) + .expect("7 is a valid length"); + + assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(())) +}
diff --git a/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs new file mode 100644 index 0000000..cb7c067 --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
@@ -0,0 +1,106 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::super::*; +use crate::legacy::actions::{ActionBits, ActiveUnlock}; +use crate::legacy::data_elements::TxPowerDataElement; +use crate::legacy::{Ciphertext, Plaintext}; +use crate::shared_data::TxPower; + +#[test] +fn match_contains_tx_power() { + let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None }; + + let tx_power = TxPower::try_from(5).expect("within range"); + let tx_power_de = TxPowerDataElement::from(tx_power); + let result = + filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter()); + assert_eq!(result, Ok(())) +} + +#[test] +fn match_not_contains_tx_power() { + let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None }; + let result = filter.match_v0_legible_adv::<Plaintext>([].iter()); + assert_eq!(result, Err(NoMatch)) +} + +#[test] +fn match_not_contains_actions() { + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1]) + .expect("1 is a valid length"); + let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) }; + let tx_power = TxPower::try_from(5).expect("within range"); + let tx_power_de = TxPowerDataElement::from(tx_power); + let result = + filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter()); + assert_eq!(result, Err(NoMatch)) +} + +#[test] +fn match_contains_actions() { + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1]) + .expect("1 is a valid length"); + let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) }; + let tx_power = TxPower::try_from(5).expect("within range"); + let tx_power_de = TxPowerDataElement::from(tx_power); + + let mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(ActiveUnlock::from(true)); + + let result = filter.match_v0_legible_adv( + [ + PlainDataElement::<Ciphertext>::TxPower(tx_power_de), + PlainDataElement::Actions(action_bits.into()), + ] + .iter(), + ); + assert_eq!(result, Ok(())) +} + +#[test] +fn match_contains_both() { + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1]) + .expect("1 is a valid length"); + let filter = + V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) }; + let tx_power = TxPower::try_from(5).expect("within range"); + let tx_power_de = TxPowerDataElement::from(tx_power); + + let mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(ActiveUnlock::from(true)); + + let result = filter.match_v0_legible_adv( + [ + PlainDataElement::<Ciphertext>::TxPower(tx_power_de), + PlainDataElement::Actions(action_bits.into()), + ] + .iter(), + ); + assert_eq!(result, Ok(())) +} + +#[test] +fn match_contains_either() { + let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1]) + .expect("1 is a valid length"); + let filter = + V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) }; + let tx_power = TxPower::try_from(5).expect("within range"); + let tx_power_de = TxPowerDataElement::from(tx_power); + + let result = + filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter()); + assert_eq!(result, Err(NoMatch)) +}
diff --git a/nearby/presence/np_adv/src/filter/tests/mod.rs b/nearby/presence/np_adv/src/filter/tests/mod.rs new file mode 100644 index 0000000..29d3e7c --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/mod.rs
@@ -0,0 +1,107 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::credential::book::CredentialBookBuilder; +use crate::credential::KeySeedMatchedCredential; +use crate::filter::IdentityFilterType::Any; +use crate::filter::{ + FilterOptions, FilterResult, NoMatch, V0DataElementsFilter, V0Filter, V1Filter, +}; +use crypto_provider_default::CryptoProviderImpl; + +mod actions_filter_tests; +mod data_elements_filter_tests; +mod v0_filter_tests; + +#[test] +fn top_level_match_v0_adv() { + let v0_filter = V0Filter { + identity: Any, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let empty_cred_book = + CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let filter = FilterOptions::V0FilterOptions(v0_filter); + let result = filter.match_advertisement::<_, CryptoProviderImpl>( + &empty_cred_book, + &[ + 0x0, // adv header + 0x03, // public DE + 0x16, 0x00, // actions + ], + ); + + assert_eq!(Ok(FilterResult::Public), result); +} + +#[test] +fn top_level_match_v0_adv_either() { + let v0_filter = V0Filter { + identity: Any, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let empty_cred_book = + CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let filter = FilterOptions::Either(v0_filter, V1Filter {}); + let result = filter.match_advertisement::<_, CryptoProviderImpl>( + &empty_cred_book, + &[ + 0x0, // adv header + 0x03, // public DE + 0x16, 0x00, // actions + ], + ); + + assert_eq!(Ok(FilterResult::Public), result); +} + +#[test] +fn top_level_no_match_v0_adv() { + let v0_filter = V0Filter { + identity: Any, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let empty_cred_book = + CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let filter = FilterOptions::V0FilterOptions(v0_filter); + let result = filter.match_advertisement::<_, CryptoProviderImpl>( + &empty_cred_book, + &[ + 0x20, // V1 Advertisement header + 0x03, // Section Header + 0x03, // Public Identity DE header + 0x15, 0x03, // Length 1 Tx Power DE with value 3 + ], + ); + + assert_eq!(Err(NoMatch), result); +}
diff --git a/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs new file mode 100644 index 0000000..971a39e --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs
@@ -0,0 +1,221 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::super::*; +use crate::credential::book::CredentialBookBuilder; +use crate::credential::v0::{V0DiscoveryCredential, V0}; +use crate::credential::{ + KeySeedMatchedCredential, MatchableCredential, ReferencedMatchedCredential, +}; +use crate::filter::IdentityFilterType::{Any, Private, Public}; +use crypto_provider_default::CryptoProviderImpl; +use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN; + +const METADATA_KEY: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN]; +const KEY_SEED: [u8; 32] = [0x11_u8; 32]; +const PRIVATE_IDENTITY_V0_ADV_CONTENTS: [u8; 19] = [ + 0x21, // private DE + 0x22, 0x22, // salt + // ciphertext + 0x85, 0xBF, 0xA8, 0x83, 0x58, 0x7C, 0x50, 0xCF, 0x98, 0x38, 0xA7, 0x8A, 0xC0, 0x1C, 0x96, 0xF9, +]; + +const PUBLIC_IDENTITY_V0_ADV_CONTENTS: [u8; 3] = [ + 0x03, // public DE + 0x16, 0x00, // actions +]; + +#[test] +fn test_contains_public_identity() { + let filter = V0Filter { + identity: Public, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Ok(FilterResult::Public)); +} + +#[test] +fn test_not_contains_private() { + let filter = V0Filter { + identity: Private, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Err(NoMatch)); +} + +#[test] +fn test_contains_any_public() { + let filter = V0Filter { + identity: Any, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Ok(FilterResult::Public)); +} + +#[test] +fn test_not_contains_public_identity() { + let filter = V0Filter { + identity: Public, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Err(NoMatch)); +} + +#[test] +fn test_contains_private_identity() { + let filter = V0Filter { + identity: Private, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&KEY_SEED); + let metadata_key_hmac: [u8; 32] = + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY); + let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, metadata_key_hmac); + let match_data: KeySeedMatchedCredential = KEY_SEED.into(); + let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] = + [MatchableCredential { discovery_credential, match_data: match_data.clone() }]; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&v0_creds, &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Ok(FilterResult::Private(ReferencedMatchedCredential::from(&match_data)))); +} + +#[test] +fn test_contains_any_private_identity() { + let filter = V0Filter { + identity: Any, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&KEY_SEED); + let metadata_key_hmac: [u8; 32] = + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY); + let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, metadata_key_hmac); + let match_data: KeySeedMatchedCredential = KEY_SEED.into(); + let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] = + [MatchableCredential { discovery_credential, match_data: match_data.clone() }]; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&v0_creds, &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Ok(FilterResult::Private(ReferencedMatchedCredential::from(&match_data)))); +} + +#[test] +fn test_contains_private_identity_no_matching_credential() { + let filter = V0Filter { + identity: Private, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let mut key_seed = [0u8; 32]; + key_seed.copy_from_slice(&KEY_SEED); + // change one byte + key_seed[0] = 0x00; + + let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); + let metadata_key_hmac: [u8; 32] = + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY); + let discovery_credential = V0DiscoveryCredential::new(key_seed, metadata_key_hmac); + let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] = + [MatchableCredential { discovery_credential, match_data: KEY_SEED.into() }]; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&v0_creds, &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Err(NoMatch)); +} + +#[test] +fn test_contains_private_identity_invalid_hmac_match() { + let filter = V0Filter { + identity: Private, + data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None }, + }; + + let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, [0u8; 32]); + let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] = + [MatchableCredential { discovery_credential, match_data: KEY_SEED.into() }]; + + let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&v0_creds, &[]); + + let result = + filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS); + + assert_eq!(result, Err(NoMatch)); +}
diff --git a/nearby/presence/np_adv/src/legacy/actions/macros.rs b/nearby/presence/np_adv/src/legacy/actions/macros.rs index ecafeb3..8208a57 100644 --- a/nearby/presence/np_adv/src/legacy/actions/macros.rs +++ b/nearby/presence/np_adv/src/legacy/actions/macros.rs
@@ -44,7 +44,7 @@ }; } -/// Create a struct w/ From<bool> and ActionElement impls. +/// Create a struct w/ `From<bool>` and [`ActionElement`](super::ActionElement) impls. /// Use `plaintext_only`, `ciphertext_only`, or `plaintext_and_ciphertext` to create appropriate /// impls. macro_rules! boolean_element { @@ -108,7 +108,7 @@ }; } -/// Create a [ToActionElement<Encrypted>] impl with the given index and length 1. +/// Create a [`ToActionElement<Encrypted>`](super::ToActionElement) impl with the given index and length 1. macro_rules! boolean_element_to_encrypted_element { ( $type_name:ident) => { impl $crate::legacy::actions::ToActionElement<$crate::legacy::Ciphertext> for $type_name { @@ -119,7 +119,7 @@ }; } -/// Create a [ToActionElement<Plaintext>] impl with the given index and length 1. +/// Create a [`ToActionElement<Plaintext>`](super::ToActionElement) impl with the given index and length 1. macro_rules! boolean_element_to_plaintext_element { ( $type_name:ident) => { impl $crate::legacy::actions::ToActionElement<$crate::legacy::Plaintext> for $type_name {
diff --git a/nearby/presence/np_adv/src/legacy/actions/mod.rs b/nearby/presence/np_adv/src/legacy/actions/mod.rs index 353848e..90a0186 100644 --- a/nearby/presence/np_adv/src/legacy/actions/mod.rs +++ b/nearby/presence/np_adv/src/legacy/actions/mod.rs
@@ -42,27 +42,8 @@ /// of 3 bytes occupied by the DE payload. #[derive(Debug, PartialEq, Eq)] pub struct ActionsDataElement<F: PacketFlavor> { - action: ActionBits<F>, -} - -impl<F: PacketFlavor> ActionsDataElement<F> { - /// Returns the actions bits as a u32. The upper limit of an actions field is 3 bytes, - /// so the last bytes of this u32 will always be 0 - pub fn as_u32(self) -> u32 { - self.action.bits - } - - /// Return whether a boolean action type is set in this data element, or `None` if the given - /// action type does not represent a boolean. - pub fn has_action(&self, action_type: &ActionType) -> Option<bool> { - (action_type.bits_len() == 1).then_some(self.action.bits_for_type(action_type) != 0) - } - - /// Return the context sync sequence number. - pub fn context_sync_seq_num(&self) -> ContextSyncSeqNum { - ContextSyncSeqNum::try_from(self.action.bits_for_type(&ActionType::ContextSyncSeqNum) as u8) - .expect("Masking with ActionType::ContextSyncSeqNum should always be in range") - } + /// The action bits + pub action: ActionBits<F>, } pub(crate) const ACTIONS_MAX_LEN: usize = 3; @@ -172,6 +153,26 @@ flavor: marker::PhantomData<F>, } +impl<F: PacketFlavor> ActionBits<F> { + /// Returns the actions bits as a u32. The upper limit of an actions field is 3 bytes, + /// so the last bytes of this u32 will always be 0 + pub fn as_u32(self) -> u32 { + self.bits + } + + /// Return whether a boolean action type is set in this data element, or `None` if the given + /// action type does not represent a boolean. + pub fn has_action(&self, action_type: &ActionType) -> Option<bool> { + (action_type.bits_len() == 1).then_some(self.bits_for_type(action_type) != 0) + } + + /// Return the context sync sequence number. + pub fn context_sync_seq_num(&self) -> ContextSyncSeqNum { + ContextSyncSeqNum::try_from(self.bits_for_type(&ActionType::ContextSyncSeqNum) as u8) + .expect("Masking with ActionType::ContextSyncSeqNum should always be in range") + } +} + impl<F: PacketFlavor> Default for ActionBits<F> { fn default() -> Self { ActionBits {
diff --git a/nearby/presence/np_adv/src/legacy/actions/tests.rs b/nearby/presence/np_adv/src/legacy/actions/tests.rs index 2e3fda0..86b7a08 100644 --- a/nearby/presence/np_adv/src/legacy/actions/tests.rs +++ b/nearby/presence/np_adv/src/legacy/actions/tests.rs
@@ -294,14 +294,14 @@ let mut action_bits = ActionBits::<Plaintext>::default(); action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap()); let action_de = ActionsDataElement::from(action_bits); - assert_eq!(15, action_de.context_sync_seq_num().as_u8()); + assert_eq!(15, action_de.action.context_sync_seq_num().as_u8()); } #[test] fn context_sync_seq_num_default_zero() { let action_bits = ActionBits::<Plaintext>::default(); let action_de = ActionsDataElement::from(action_bits); - assert_eq!(0, action_de.context_sync_seq_num().as_u8()); + assert_eq!(0, action_de.action.context_sync_seq_num().as_u8()); } #[test] @@ -310,9 +310,9 @@ action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap()); action_bits.set_action(NearbyShare::from(true)); let action_de = ActionsDataElement::from(action_bits); - assert_eq!(action_de.has_action(&ActionType::NearbyShare), Some(true)); - assert_eq!(action_de.has_action(&ActionType::ActiveUnlock), Some(false)); - assert_eq!(action_de.has_action(&ActionType::PhoneHub), Some(false)); + assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true)); + assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(false)); + assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false)); } #[test] @@ -322,10 +322,10 @@ action_bits.set_action(NearbyShare::from(true)); action_bits.set_action(ActiveUnlock::from(true)); let action_de = ActionsDataElement::from(action_bits); - assert_eq!(action_de.has_action(&ActionType::NearbyShare), Some(true)); - assert_eq!(action_de.has_action(&ActionType::ActiveUnlock), Some(true)); - assert_eq!(action_de.has_action(&ActionType::PhoneHub), Some(false)); - assert_eq!(action_de.has_action(&ActionType::ContextSyncSeqNum), None); + assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true)); + assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(true)); + assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false)); + assert_eq!(action_de.action.has_action(&ActionType::ContextSyncSeqNum), None); } // hypothetical action using the last bit
diff --git a/nearby/presence/np_adv/src/legacy/data_elements.rs b/nearby/presence/np_adv/src/legacy/data_elements.rs index e9c2308..505fdd9 100644 --- a/nearby/presence/np_adv/src/legacy/data_elements.rs +++ b/nearby/presence/np_adv/src/legacy/data_elements.rs
@@ -58,7 +58,8 @@ /// Data element holding a [TxPower]. #[derive(Debug, PartialEq, Eq)] pub struct TxPowerDataElement { - tx_power: TxPower, + /// The tx power value + pub tx_power: TxPower, } impl TxPowerDataElement {
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs index 3119f48..48555be 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
@@ -19,14 +19,15 @@ extern crate alloc; use crate::{ + credential::v0::V0, de_type::EncryptedIdentityDataElementType, legacy::{ actions, data_elements::{DataElement, *}, de_type::{DataElementType, DeEncodedLength, DeTypeCode, PlainDataElementType}, - Ciphertext, PacketFlavor, Plaintext, NP_MAX_DE_CONTENT_LEN, + Ciphertext, PacketFlavor, Plaintext, ShortMetadataKey, NP_MAX_DE_CONTENT_LEN, }, - PlaintextIdentityMode, + HasIdentityMatch, PlaintextIdentityMode, }; use alloc::vec::Vec; use crypto_provider::CryptoProvider; @@ -42,10 +43,15 @@ input: &[u8], ) -> Result<IntermediateAdvContents, AdvDeserializeError> { parse_raw_adv_contents::<C>(input).and_then(|raw_adv| match raw_adv { - RawAdvertisement::Plaintext(parc) => parc - .try_deserialize() - .map(IntermediateAdvContents::Plaintext) - .map_err(AdvDeserializeError::DataElementDeserializeError), + RawAdvertisement::Plaintext(parc) => { + if parc.data_elements.is_empty() { + return Err(AdvDeserializeError::NoPublicDataElements); + } + + parc.try_deserialize() + .map(IntermediateAdvContents::Plaintext) + .map_err(AdvDeserializeError::DataElementDeserializeError) + } RawAdvertisement::Ciphertext(eac) => Ok(IntermediateAdvContents::Ciphertext(eac)), }) } @@ -100,12 +106,7 @@ }), } } else { - plain_data_elements(&data_elements).map(|pde| { - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::None, - data_elements: pde, - }) - }) + Err(AdvDeserializeError::MissingIdentity) } } @@ -120,6 +121,10 @@ TooManyTopLevelDataElements, /// Must not have an identity DE inside an identity DE InvalidDataElementHierarchy, + /// Missing identity DE + MissingIdentity, + /// Non-identity DE contents must not be empty + NoPublicDataElements, } /// Parse an advertisement's contents into raw DEs. @@ -284,7 +289,6 @@ .map_err(|_e| DecryptError::DecryptOrVerifyError)?; // plaintext starts with 14 bytes of metadata key, then DEs. - let (remaining, metadata_key) = combinator::map_res( bytes::complete::take(NP_LEGACY_METADATA_KEY_LEN), |slice: &[u8]| slice.try_into(), @@ -308,7 +312,7 @@ .map(|data_elements| { DecryptedAdvContents::new( self.identity_type, - metadata_key, + ShortMetadataKey(metadata_key), self.salt, data_elements, ) @@ -351,10 +355,12 @@ pub fn identity(&self) -> PlaintextIdentityMode { self.identity_type } - /// Returns the deserialized data elements - pub fn data_elements(&self) -> impl Iterator<Item = &PlainDataElement<Plaintext>> { + + /// Returns an iterator over the v0 data elements + pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Plaintext>> { self.data_elements.iter() } + /// Destructures this V0 plaintext advertisement /// into just the contained data elements pub fn to_data_elements(self) -> Vec<PlainDataElement<Plaintext>> { @@ -366,7 +372,7 @@ #[derive(Debug, PartialEq, Eq)] pub struct DecryptedAdvContents { identity_type: EncryptedIdentityDataElementType, - metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN], + metadata_key: ShortMetadataKey, salt: LegacySalt, data_elements: Vec<PlainDataElement<Ciphertext>>, } @@ -375,7 +381,7 @@ /// Returns a new DecryptedAdvContents with the provided contents. fn new( identity_type: EncryptedIdentityDataElementType, - metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN], + metadata_key: ShortMetadataKey, salt: LegacySalt, data_elements: Vec<PlainDataElement<Ciphertext>>, ) -> Self { @@ -387,15 +393,10 @@ self.identity_type } - /// The decrypted metadata key from the identity DE. - pub fn metadata_key(&self) -> &[u8; 14] { - &self.metadata_key - } - /// Iterator over the data elements in an advertisement, except for any DEs related to resolving /// the identity or otherwise validating the payload (e.g. any identity DEs like Private /// Identity). - pub fn data_elements(&self) -> impl Iterator<Item = &PlainDataElement<Ciphertext>> { + pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Ciphertext>> { self.data_elements.iter() } @@ -405,6 +406,13 @@ } } +impl HasIdentityMatch for DecryptedAdvContents { + type Version = V0; + fn metadata_key(&self) -> ShortMetadataKey { + self.metadata_key + } +} + /// The contents of an advertisement after plaintext DEs, if any, have been deserialized, but /// before any decryption is done. #[derive(Debug, PartialEq, Eq)]
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs index b577cd9..4512bf7 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
@@ -18,6 +18,7 @@ use crate::legacy::actions::{ActionBits, ActionsDataElement}; use crate::shared_data::TxPower; use crate::{ + credential::{v0::V0, SimpleBroadcastCryptoMaterial}, de_type::IdentityDataElementType, legacy::{ actions, @@ -29,7 +30,7 @@ }, PacketFlavorEnum, BLE_ADV_SVC_CONTENT_LEN, }, - parse_adv_header, shared_data, AdvHeader, NoIdentity, PublicIdentity, + parse_adv_header, shared_data, AdvHeader, PublicIdentity, }; use array_view::ArrayView; use crypto_provider_default::CryptoProviderImpl; @@ -42,30 +43,8 @@ #[test] fn parse_empty_raw_adv() { - let data_elements = parse_raw_adv_contents::<CryptoProviderImpl>(&[]).unwrap(); - assert_eq!( - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::None, - data_elements: Vec::new() - }), - data_elements - ); -} - -#[test] -fn parse_raw_adv_1_de_short_no_identity() { - // battery uses the header length as is - let adv = parse_raw_adv_contents::<CryptoProviderImpl>(&[0x36, 0x01, 0x02, 0x03]).unwrap(); - assert_eq!( - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::None, - data_elements: vec![RawPlainDataElement { - de_type: PlainDataElementType::Actions, - contents: &[0x01, 0x02, 0x03] - }], - }), - adv - ); + let adv_data = parse_raw_adv_contents::<CryptoProviderImpl>(&[]); + assert_eq!(AdvDeserializeError::MissingIdentity, adv_data.unwrap_err()); } #[test] @@ -161,7 +140,7 @@ 0x15, 0x03, // tx power de ]; assert_eq!( - AdvDeserializeError::InvalidDataElementHierarchy, + AdvDeserializeError::MissingIdentity, parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap_err() ); } @@ -373,13 +352,19 @@ } #[test] -fn plaintext_random_adv_contents_round_trip_public() { - plaintext_random_adv_contents_round_trip(PublicIdentity::default, PlaintextIdentityMode::Public) +fn deserialize_adv_public_identity_empty_des() { + let input = &[ + 0x03, // public identity + ]; + assert_eq!( + AdvDeserializeError::NoPublicDataElements, + deserialize_adv_contents::<CryptoProviderImpl>(input).unwrap_err() + ); } #[test] -fn plaintext_random_adv_contents_round_trip_no_identity() { - plaintext_random_adv_contents_round_trip(NoIdentity::default, PlaintextIdentityMode::None) +fn plaintext_random_adv_contents_round_trip_public() { + plaintext_random_adv_contents_round_trip(PublicIdentity::default, PlaintextIdentityMode::Public) } #[test] @@ -399,15 +384,17 @@ let salt: ldt_np_adv::LegacySalt = rng.gen::<[u8; 2]>().into(); let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = rng.gen(); let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let ldt_key = hkdf.legacy_ldt_key(); let metadata_key_hmac: [u8; 32] = hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key); let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac); + + let metadata_key = ShortMetadataKey(metadata_key); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); + let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( identity_type, salt, - metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key), + &broadcast_cm, )); loop { @@ -462,15 +449,18 @@ let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN]; let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); - let ldt_key = hkdf.legacy_ldt_key(); let metadata_key_hmac: [u8; 32] = hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key); let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac); + + let metadata_key = ShortMetadataKey(metadata_key); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); + let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( EncryptedIdentityDataElementType::Private, salt, - metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key), + &broadcast_cm, )); let tx = shared_data::TxPower::try_from(3).unwrap(); @@ -524,7 +514,7 @@ #[test] fn decrypt_and_deserialize_plaintext_adv_canned() { - let mut builder = AdvBuilder::new(PublicIdentity::default()); + let mut builder = AdvBuilder::new(PublicIdentity); let actions = ActionBits::default(); builder.add_data_element(ActionsDataElement::from(actions)).unwrap(); @@ -694,18 +684,19 @@ correct_key_seed: [u8; 32], ) -> (ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>) { let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&correct_key_seed); - let ldt_key = hkdf.legacy_ldt_key(); let metadata_key_hmac: [u8; 32] = hkdf.legacy_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice()); let correct_cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac); + let broadcast_cm = + SimpleBroadcastCryptoMaterial::<V0>::new(correct_key_seed, ShortMetadataKey(*metadata_key)); + let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( EncryptedIdentityDataElementType::Private, salt, - *metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key), + &broadcast_cm, )); builder.add_data_element(TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap(); (builder.into_advertisement().unwrap(), correct_cipher)
diff --git a/nearby/presence/np_adv/src/legacy/mod.rs b/nearby/presence/np_adv/src/legacy/mod.rs index 0eb035d..59f2308 100644 --- a/nearby/presence/np_adv/src/legacy/mod.rs +++ b/nearby/presence/np_adv/src/legacy/mod.rs
@@ -14,7 +14,10 @@ //! V0 advertisement support. +use crate::MetadataKey; use core::fmt; +use crypto_provider::CryptoProvider; +use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN; pub mod actions; pub mod data_elements; @@ -31,6 +34,26 @@ /// Maximum possible DE content: packet size minus 2 for adv header & DE header const NP_MAX_DE_CONTENT_LEN: usize = BLE_ADV_SVC_CONTENT_LEN - 2; +/// "Short" 14-byte metadata key type employed for V0, which needs to be +/// expanded to a regular-size 16-byte metadata key to decrypt metadata. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct ShortMetadataKey(pub [u8; NP_LEGACY_METADATA_KEY_LEN]); + +impl AsRef<[u8]> for ShortMetadataKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl ShortMetadataKey { + /// Expand this short 14-byte metadata key to a 16-byte metadata key + /// which may be used to decrypt metadata. + pub fn expand<C: CryptoProvider>(&self) -> MetadataKey { + let expanded_bytes = np_hkdf::legacy_metadata_expanded_key::<C>(&self.0); + MetadataKey(expanded_bytes) + } +} + /// Marker type to allow disambiguating between plaintext and encrypted packets at compile time. /// /// See also [PacketFlavorEnum] for when runtime flavor checks are more suitable.
diff --git a/nearby/presence/np_adv/src/legacy/serialize/mod.rs b/nearby/presence/np_adv/src/legacy/serialize/mod.rs index 38e46e1..2920b67 100644 --- a/nearby/presence/np_adv/src/legacy/serialize/mod.rs +++ b/nearby/presence/np_adv/src/legacy/serialize/mod.rs
@@ -40,24 +40,29 @@ //! Serializing an encrypted advertisement: //! //! ``` -//! use np_adv::{shared_data::*, de_type::*, legacy::{de_type::*, data_elements::*, serialize::*}}; +//! use np_adv::{shared_data::*, de_type::*, legacy::{de_type::*, data_elements::*, serialize::*, *}}; +//! use np_adv::credential::{v0::V0, SimpleBroadcastCryptoMaterial}; //! use crypto_provider::CryptoProvider; //! use crypto_provider_default::CryptoProviderImpl; //! use ldt_np_adv::{salt_padder, LegacySalt, LdtEncrypterXtsAes128}; //! //! // Generate these from proper CSPRNGs -- using fixed data here -//! let metadata_key = [0x33; 14]; +//! let metadata_key = ShortMetadataKey([0x33; 14]); //! let salt = LegacySalt::from([0x01, 0x02]); //! let key_seed = [0x44; 32]; //! let ldt_enc = LdtEncrypterXtsAes128::<CryptoProviderImpl>::new( //! &np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed).legacy_ldt_key() //! ); //! +//! let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new( +//! key_seed, +//! metadata_key, +//! ); +//! //! let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( //! EncryptedIdentityDataElementType::Private, //! salt, -//! metadata_key, -//! ldt_enc, +//! &broadcast_cm, //! )); //! //! builder @@ -66,18 +71,19 @@ //! //! let packet = builder.into_advertisement().unwrap(); //! ``` +use crate::credential::{v0::V0, BroadcastCryptoMaterial}; use crate::{ de_type::{EncryptedIdentityDataElementType, IdentityDataElementType}, legacy::{ de_type::{DataElementType, DeActualLength, DeEncodedLength, PlainDataElementType}, - Ciphertext, PacketFlavor, Plaintext, BLE_ADV_SVC_CONTENT_LEN, NP_MAX_DE_CONTENT_LEN, + Ciphertext, PacketFlavor, Plaintext, ShortMetadataKey, BLE_ADV_SVC_CONTENT_LEN, + NP_MAX_DE_CONTENT_LEN, }, - DeLengthOutOfRange, NoIdentity, PublicIdentity, + DeLengthOutOfRange, PublicIdentity, }; use array_view::ArrayView; use core::{convert, fmt, marker}; use crypto_provider::CryptoProvider; -use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN; #[cfg(test)] mod tests; @@ -121,26 +127,13 @@ } } -impl Identity for NoIdentity { - type Flavor = Plaintext; - type Error = convert::Infallible; - // No DE header - const OVERHEAD_LEN: usize = 0; - - fn postprocess(&self, _buf: &mut [u8]) -> Result<(), Self::Error> { - Ok(()) - } -} - /// Identity used for encrypted packets (private, trusted, provisioned) that encrypts other DEs /// (as well as the metadata key). pub struct LdtIdentity<C: CryptoProvider> { de_type: EncryptedIdentityDataElementType, salt: ldt_np_adv::LegacySalt, - metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN], + metadata_key: ShortMetadataKey, ldt_enc: ldt_np_adv::LdtEncrypterXtsAes128<C>, - // keep C parameter alive for when A disappears into it - _crypto_provider: marker::PhantomData<C>, } // Exclude sensitive members @@ -151,14 +144,20 @@ } impl<C: CryptoProvider> LdtIdentity<C> { - /// Build an `LdtIdentity` for the provided identity, salt, metadata key, and ldt. - pub fn new( + /// Build an `LdtIdentity` for the provided identity type, salt, and + /// broadcast crypto-materials. + pub fn new<B: BroadcastCryptoMaterial<V0>>( de_type: EncryptedIdentityDataElementType, salt: ldt_np_adv::LegacySalt, - metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN], - ldt_enc: ldt_np_adv::LdtEncrypterXtsAes128<C>, - ) -> LdtIdentity<C> { - LdtIdentity { de_type, salt, metadata_key, ldt_enc, _crypto_provider: marker::PhantomData } + crypto_material: &B, + ) -> Self { + let metadata_key = crypto_material.metadata_key(); + let key_seed = crypto_material.key_seed(); + let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed); + let ldt_key = key_seed_hkdf.legacy_ldt_key(); + let ldt_enc = ldt_np_adv::LdtEncrypterXtsAes128::<C>::new(&ldt_key); + + Self { de_type, salt, metadata_key, ldt_enc } } } @@ -180,7 +179,7 @@ buf[0] = encode_de_header_actual_len(id_de_type_as_generic_de_type(de_type), actual_len) .map_err(|_e| LdtPostprocessError::InvalidLength)?; buf[1..3].copy_from_slice(self.salt.bytes().as_slice()); - buf[3..17].copy_from_slice(&self.metadata_key); + buf[3..17].copy_from_slice(&self.metadata_key.0); // encrypt everything after DE header and salt self.ldt_enc.encrypt(&mut buf[3..], &ldt_np_adv::salt_padder::<16, C>(self.salt)).map_err(
diff --git a/nearby/presence/np_adv/src/legacy/serialize/tests.rs b/nearby/presence/np_adv/src/legacy/serialize/tests.rs index 192b8f4..b4adc9f 100644 --- a/nearby/presence/np_adv/src/legacy/serialize/tests.rs +++ b/nearby/presence/np_adv/src/legacy/serialize/tests.rs
@@ -17,6 +17,7 @@ use crate::legacy::actions::FastPairSass; use crate::legacy::actions::NearbyShare; use crate::{ + credential::{v0::V0, SimpleBroadcastCryptoMaterial}, de_type::EncryptedIdentityDataElementType, legacy::{actions::*, data_elements::*, serialize::*}, shared_data::TxPower, @@ -27,7 +28,7 @@ #[test] fn public_identity_packet_serialization() { - let mut builder = AdvBuilder::new(PublicIdentity::default()); + let mut builder = AdvBuilder::new(PublicIdentity); let tx_power = TxPower::try_from(3).unwrap(); let mut action = ActionBits::default(); @@ -48,29 +49,8 @@ } #[test] -fn no_identity_packet_serialization() { - let mut builder = AdvBuilder::new(NoIdentity::default()); - - let tx_power = TxPower::try_from(3).unwrap(); - let mut action = ActionBits::default(); - action.set_action(NearbyShare::from(true)); - builder.add_data_element(TxPowerDataElement::from(tx_power)).unwrap(); - builder.add_data_element(ActionsDataElement::from(action)).unwrap(); - - let packet = builder.into_advertisement().unwrap(); - assert_eq!( - &[ - 0x00, // Adv Header - 0x15, 0x03, // Tx Power DE with value 3 - 0x26, 0x00, 0x40, // Actions DE w/ bit 9 - ], - packet.as_slice() - ); -} - -#[test] fn packet_limits_capacity() { - let mut builder = AdvBuilder::new(PublicIdentity::default()); + let mut builder = AdvBuilder::new(PublicIdentity); // 2 + 1 left out of 24 payload bytes builder.len = 21; let mut bits = ActionBits::default(); @@ -90,22 +70,24 @@ #[test] fn ldt_packet_serialization() { // don't care about the HMAC since we're not decrypting - let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&[0; 32]); + let key_seed = [0; 32]; + let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let ldt = LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&hkdf.legacy_ldt_key()); - let metadata_key = [0x33; 14]; + let metadata_key = ShortMetadataKey([0x33; 14]); let salt = LegacySalt::from([0x01, 0x02]); let mut ciphertext = vec![]; - ciphertext.extend_from_slice(&metadata_key); + ciphertext.extend_from_slice(&metadata_key.0); // tx power & action DEs ciphertext.extend_from_slice(&[0x15, 0x03, 0x26, 0x00, 0x10]); ldt.encrypt(&mut ciphertext, &salt_padder::<16, CryptoProviderImpl>(salt)).unwrap(); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); + let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( EncryptedIdentityDataElementType::Private, salt, - metadata_key, - ldt, + &broadcast_cm, )); let tx_power = TxPower::try_from(3).unwrap(); @@ -126,16 +108,16 @@ #[test] fn ldt_packet_cant_encrypt_without_des() { - let metadata_key = [0x33; 14]; + let metadata_key = ShortMetadataKey([0x33; 14]); let salt = LegacySalt::from([0x01, 0x02]); + let key_seed = [0xFE; 32]; + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); let builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new( EncryptedIdentityDataElementType::Private, salt, - metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new( - &np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&[0xFE; 32]).legacy_ldt_key(), - ), + &broadcast_cm, )); // not enough ciphertext @@ -144,7 +126,7 @@ #[test] fn nearby_share_action() { - let mut builder = AdvBuilder::new(PublicIdentity::default()); + let mut builder = AdvBuilder::new(PublicIdentity); let mut action = ActionBits::default(); action.set_action(NearbyShare::from(true));
diff --git a/nearby/presence/np_adv/src/lib.rs b/nearby/presence/np_adv/src/lib.rs index a6a155e..45207ff 100644 --- a/nearby/presence/np_adv/src/lib.rs +++ b/nearby/presence/np_adv/src/lib.rs
@@ -25,38 +25,48 @@ extern crate core; use crate::{ credential::{ - source::{BothCredentialSource, CredentialSource}, - v0::V0CryptoMaterial, - MatchedCredFromCred, MatchedCredential, V0Credential, V1Credential, + book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v1::V1DiscoveryCryptoMaterial, + DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion, }, + deserialization_arena::ArenaOutOfSpace, extended::deserialize::{ - parse_sections, CiphertextSection, DataElements, DecryptedSection, IntermediateSection, - PlaintextSection, Section, SectionDeserializeError, + encrypted_section::*, parse_sections, CiphertextSection, DataElementParseError, + DataElementParsingIterator, DecryptedSection, IntermediateSection, PlaintextSection, + Section, SectionDeserializeError, }, legacy::deserialize::{ DecryptError, DecryptedAdvContents, IntermediateAdvContents, PlaintextAdvContents, }, }; + use alloc::vec::Vec; #[cfg(feature = "devtools")] use array_view::ArrayView; -use core::{fmt::Debug, marker}; +use core::borrow::BorrowMut; +use core::fmt::Debug; use crypto_provider::CryptoProvider; +use deserialization_arena::DeserializationArena; + #[cfg(feature = "devtools")] use extended::NP_ADV_MAX_SECTION_LEN; use legacy::{data_elements::DataElementDeserializeError, deserialize::AdvDeserializeError}; use nom::{combinator, number}; +pub use strum; + pub mod credential; pub mod de_type; #[cfg(test)] mod deser_v0_tests; #[cfg(test)] mod deser_v1_tests; +pub mod deserialization_arena; pub mod extended; +pub mod filter; #[cfg(test)] mod header_parse_tests; pub mod legacy; pub mod shared_data; + /// Canonical form of NP's service UUID. /// /// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be @@ -65,73 +75,40 @@ /// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of /// the service data for the NP UUID). -pub fn deserialize_advertisement<'s, C0, C1, M, S, P>( - adv: &[u8], - cred_source: &'s S, -) -> Result<DeserializedAdvertisement<'s, M>, AdvDeserializationError> +pub fn deserialize_advertisement<'adv, 'cred, B, P>( + mut arena: impl BorrowMut<DeserializationArena<'adv>>, + adv: &'adv [u8], + cred_book: &'cred B, +) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError> where - C0: V0Credential<Matched<'s> = M> + 's, - C1: V1Credential<Matched<'s> = M> + 's, - M: MatchedCredential<'s>, - S: BothCredentialSource<C0, C1>, + B: CredentialBook<'cred>, + P: CryptoProvider, +{ + // Turn `impl BorrowMut` into the concrete `&mut` reference so that the compiler does not need + // to duplicate the function code for different concrete types. + deserialize_advertisement_impl::<B, P>(arena.borrow_mut(), adv, cred_book) +} + +fn deserialize_advertisement_impl<'adv, 'cred, B, P>( + arena: &mut DeserializationArena<'adv>, + adv: &'adv [u8], + cred_book: &'cred B, +) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError> +where + B: CredentialBook<'cred>, P: CryptoProvider, { let (remaining, header) = parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?; match header { - AdvHeader::V1(header) => { - deser_decrypt_v1::<C1, S::V1Source, P>(cred_source.v1(), remaining, header) - .map(DeserializedAdvertisement::V1) + AdvHeader::V0 => { + deser_decrypt_v0::<B, P>(cred_book, remaining).map(DeserializedAdvertisement::V0) } - AdvHeader::V0 => deser_decrypt_v0::<C0, S::V0Source, P>(cred_source.v0(), remaining) - .map(DeserializedAdvertisement::V0), + AdvHeader::V1(header) => deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header) + .map(DeserializedAdvertisement::V1), } } -/// Parse, deserialize, decrypt, and validate a complete V0 NP advertisement (the entire contents -/// of the service data for the NP UUID). If the advertisement version header does not match V0, -/// this method will return an [`AdvDeserializationError::HeaderParseError`] -pub fn deserialize_v0_advertisement<'s, C, S, P>( - adv: &[u8], - cred_source: &'s S, -) -> Result<V0AdvertisementContents<'s, C>, AdvDeserializationError> -where - C: V0Credential, - S: CredentialSource<C>, - P: CryptoProvider, -{ - let (remaining, header) = - parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?; - - match header { - AdvHeader::V0 => deser_decrypt_v0::<C, S, P>(cred_source, remaining), - AdvHeader::V1(_) => Err(AdvDeserializationError::HeaderParseError), - } -} - -/// Parse, deserialize, decrypt, and validate a complete V1 NP advertisement (the entire contents -/// of the service data for the NP UUID). If the advertisement version header does not match V1, -/// this method will return an [`AdvDeserializationError::HeaderParseError`] -pub fn deserialize_v1_advertisement<'s, C, S, P>( - adv: &[u8], - cred_source: &'s S, -) -> Result<V1AdvertisementContents<'s, C>, AdvDeserializationError> -where - C: V1Credential, - S: CredentialSource<C>, - P: CryptoProvider, -{ - let (remaining, header) = - parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?; - - match header { - AdvHeader::V0 => Err(AdvDeserializationError::HeaderParseError), - AdvHeader::V1(header) => deser_decrypt_v1::<C, S, P>(cred_source, remaining, header), - } -} - -type V1AdvertisementContents<'s, C> = V1AdvContents<'s, MatchedCredFromCred<'s, C>>; - /// The encryption scheme used for a V1 advertisement. #[derive(Debug, Clone, PartialEq, Eq)] pub enum V1EncryptionScheme { @@ -151,157 +128,282 @@ ParseError, /// No suitable credential found to decrypt the given section. NoMatchingCredentials, + /// Given Arena is not large enough to hold the decrypted data. + ArenaOutOfSpace, +} + +#[cfg(feature = "devtools")] +impl From<ArenaOutOfSpace> for AdvDecryptionError { + fn from(_: ArenaOutOfSpace) -> Self { + Self::ArenaOutOfSpace + } } /// Decrypt, but do not further deserialize the v1 bytes, intended for developer tooling uses only. -/// Production uses should use [deserialize_v1_advertisement] instead, which deserializes to a +/// Production uses should use [deserialize_advertisement] instead, which deserializes to a /// structured format and provides extra type safety. #[cfg(feature = "devtools")] -pub fn deser_decrypt_v1_section_bytes_for_dev_tools<S, V1, P>( - cred_source: &S, +pub fn deser_decrypt_v1_section_bytes_for_dev_tools<'adv, 'cred, B, P>( + arena: &mut DeserializationArena<'adv>, + cred_book: &'cred B, header_byte: u8, - section_bytes: &[u8], + section_bytes: &'adv [u8], ) -> Result<(ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, V1EncryptionScheme), AdvDecryptionError> where - S: CredentialSource<V1>, - V1: V1Credential, + B: CredentialBook<'cred>, P: CryptoProvider, { let header = V1Header { header_byte }; let int_sections = - parse_sections(&header, section_bytes).map_err(|_| AdvDecryptionError::ParseError)?; + parse_sections(header, section_bytes).map_err(|_| AdvDecryptionError::ParseError)?; let cipher_section = match &int_sections[0] { IntermediateSection::Plaintext(_) => Err(AdvDecryptionError::InputNotEncrypted)?, IntermediateSection::Ciphertext(section) => section, }; - use crate::credential::v1::V1CryptoMaterial; - use core::borrow::Borrow; - - for cred in cred_source.iter() { - let crypto_material = cred.crypto_material(); - - match cipher_section { - CiphertextSection::SignatureEncryptedIdentity(encrypted_section) => { - let identity_resolution_material = - crypto_material.signed_identity_resolution_material::<P>(); - match encrypted_section.try_decrypt::<P>(identity_resolution_material.borrow()) { - Ok(plaintext) => return Ok((plaintext, V1EncryptionScheme::Signature)), - Err(_) => continue, - } - } - CiphertextSection::MicEncryptedIdentity(encrypted_section) => { - let identity_resolution_material = - crypto_material.unsigned_identity_resolution_material::<P>(); - let verification_material = crypto_material.unsigned_verification_material::<P>(); - match encrypted_section - .try_decrypt::<P>(identity_resolution_material.borrow(), &verification_material) - { - Ok(plaintext) => return Ok((plaintext, V1EncryptionScheme::Mic)), - Err(_) => continue, - } - } + for (crypto_material, _) in cred_book.v1_iter() { + if let Some(plaintext) = cipher_section + .try_resolve_identity_and_decrypt::<_, P>(arena.borrow_mut(), &crypto_material) + { + return plaintext + .map(|pt| { + let encryption_scheme = match cipher_section { + CiphertextSection::SignatureEncryptedIdentity(_) => { + V1EncryptionScheme::Signature + } + CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic, + }; + (pt, encryption_scheme) + }) + .map_err(|e| e.into()); } } Err(AdvDecryptionError::NoMatchingCredentials) } -/// Deserialize and decrypt the contents of a v1 adv after the version header -fn deser_decrypt_v1<'s, C, S, P>( - cred_source: &'s S, - remaining: &[u8], - header: V1Header, -) -> Result<V1AdvertisementContents<'s, C>, AdvDeserializationError> -where - C: V1Credential, - S: CredentialSource<C>, - P: CryptoProvider, -{ - let int_sections = - parse_sections(&header, remaining).map_err(|_| AdvDeserializationError::ParseError { - details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError, - })?; - let mut sections = Vec::new(); - let mut to_decrypt = Vec::new(); - // keep track of ordering for later sorting - for (idx, s) in int_sections.into_iter().enumerate() { - match s { - IntermediateSection::Plaintext(p) => { - sections.push((idx, V1DeserializedSection::Plaintext(p))) +/// A ciphertext section which has not yet been +/// resolved to an identity, but for which some +/// `SectionIdentityResolutionContents` have been +/// pre-computed for speedy identity-resolution. +struct ResolvableCiphertextSection<'a> { + identity_resolution_contents: SectionIdentityResolutionContents, + ciphertext_section: CiphertextSection<'a>, +} + +/// A collection of possibly-deserialized sections which are separated according +/// to whether/not they're intermediate encrypted sections (of either type) +/// or fully-deserialized, with a running count of the number of malformed sections. +/// Each potentially-valid section is tagged with a 0-based index derived from the original +/// section ordering as they appeared within the original advertisement to ensure +/// that the fully-deserialized advertisement may be correctly reconstructed. +struct SectionsInProcessing<'adv, M: MatchedCredential> { + deserialized_sections: Vec<(usize, V1DeserializedSection<'adv, M>)>, + encrypted_sections: Vec<(usize, ResolvableCiphertextSection<'adv>)>, + malformed_sections_count: usize, +} + +impl<'adv, M: MatchedCredential> SectionsInProcessing<'adv, M> { + /// Attempts to parse a V1 advertisement's contents after the version header + /// into a collection of not-yet-fully-deserialized sections which may + /// require credentials to be decrypted. + fn from_advertisement_contents<C: CryptoProvider>( + header: V1Header, + remaining: &'adv [u8], + ) -> Result<Self, AdvDeserializationError> { + let int_sections = + parse_sections(header, remaining).map_err(|_| AdvDeserializationError::ParseError { + details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError, + })?; + let mut deserialized_sections = Vec::new(); + let mut encrypted_sections = Vec::new(); + // keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`. + for (idx, s) in int_sections.into_iter().enumerate() { + match s { + IntermediateSection::Plaintext(p) => { + deserialized_sections.push((idx, V1DeserializedSection::Plaintext(p))) + } + IntermediateSection::Ciphertext(ciphertext_section) => { + let identity_resolution_contents = + ciphertext_section.contents().compute_identity_resolution_contents::<C>(); + let resolvable_ciphertext_section = ResolvableCiphertextSection { + identity_resolution_contents, + ciphertext_section, + }; + encrypted_sections.push((idx, resolvable_ciphertext_section)); + } } - IntermediateSection::Ciphertext(c) => to_decrypt.push((idx, c)), } + Ok(Self { deserialized_sections, encrypted_sections, malformed_sections_count: 0 }) } - let mut invalid_sections = 0; - // Hot loop - // We assume that iterating credentials is more expensive than iterating sections - for cred in cred_source.iter() { + + /// Returns true iff we have resolved all sections to identities. + fn resolved_all_identities(&self) -> bool { + self.encrypted_sections.is_empty() + } + + /// Runs through all of the encrypted sections in processing, and attempts + /// to use the given credential to decrypt them. Suitable for situations + /// where iterating over credentials is relatively slow compared to + /// the cost of iterating over sections-in-memory. + fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>( + &mut self, + arena: &mut DeserializationArena<'adv>, + crypto_material: C, + match_data: M, + ) -> Result<(), ArenaOutOfSpace> { let mut i = 0; - while i < to_decrypt.len() { - let (section_idx, c): &(usize, CiphertextSection) = &to_decrypt[i]; - match c.try_deserialize::<C, P>(cred) { - Ok(s) => { - sections.push(( - *section_idx, - V1DeserializedSection::Decrypted(WithMatchedCredential::new( - cred.matched(), - s, - )), - )); - // we don't care about maintaining order, so use O(1) remove - to_decrypt.swap_remove(i); + while i < self.encrypted_sections.len() { + let (section_idx, section): &(usize, ResolvableCiphertextSection) = + &self.encrypted_sections[i]; + // Fast-path: Check for an identity match, ignore if there's no identity match. + let identity_resolution_contents = §ion.identity_resolution_contents; + let identity_resolution_material = match §ion.ciphertext_section { + CiphertextSection::MicEncryptedIdentity(_) => crypto_material + .unsigned_identity_resolution_material::<P>() + .into_raw_resolution_material(), + CiphertextSection::SignatureEncryptedIdentity(_) => crypto_material + .signed_identity_resolution_material::<P>() + .into_raw_resolution_material(), + }; + match identity_resolution_contents.try_match::<P>(&identity_resolution_material) { + None => { + // Try again with another section + i += 1; + continue; + } + Some(identity_match) => { + // The identity matched, so now we need to more closely scrutinize + // the provided ciphertext. Try to decrypt and parse the section. + let metadata_nonce = crypto_material.metadata_nonce::<P>(); + let deserialization_result = match §ion.ciphertext_section { + CiphertextSection::SignatureEncryptedIdentity(c) => c + .try_deserialize( + arena.borrow_mut(), + identity_match, + &crypto_material.signed_verification_material::<P>(), + ) + .map_err(SectionDeserializeError::from), + CiphertextSection::MicEncryptedIdentity(c) => c + .try_deserialize( + arena.borrow_mut(), + identity_match, + &crypto_material.unsigned_verification_material::<P>(), + ) + .map_err(SectionDeserializeError::from), + }; + match deserialization_result { + Ok(s) => { + self.deserialized_sections.push(( + *section_idx, + V1DeserializedSection::Decrypted(WithMatchedCredential::new( + match_data.clone(), + metadata_nonce, + s, + )), + )); + } + Err(e) => match e { + SectionDeserializeError::IncorrectCredential => { + // keep it around to try with another credential + i += 1; + continue; + } + SectionDeserializeError::ParseError => { + // the credential worked, but the section itself was bogus + self.malformed_sections_count += 1; + } + SectionDeserializeError::ArenaOutOfSpace => { + return Err(ArenaOutOfSpace) + } + }, + } + // By default, if we have an identity match, assume that decrypting the section worked, + // or that the section was somehow invalid. + // We don't care about maintaining order, so use O(1) remove + self.encrypted_sections.swap_remove(i); // don't advance i -- it now points to a new element } - Err(e) => match e { - SectionDeserializeError::IncorrectCredential => { - // keep it around to try with another credential - i += 1; - } - SectionDeserializeError::ParseError => { - // the credential worked, but the section itself was bogus, so drop - // it - invalid_sections += 1; - to_decrypt.swap_remove(i); - } - }, } } - if to_decrypt.is_empty() { - // no need to consider the remaining credentials + Ok(()) + } + + /// Packages the current state of the deserialization process into a + /// `V1AdvertisementContents` representing a fully-deserialized V1 advertisement. + /// + /// This method should only be called after all sections were either successfully + /// decrypted or have had all relevant credentials checked against + /// them without obtaining a successful identity-match and/or subsequent + /// cryptographic verification of the section contents. + fn finished_with_decryption_attempts(mut self) -> V1AdvertisementContents<'adv, M> { + // Invalid sections = malformed sections + number of encrypted sections + // which we could not manage to decrypt with any of our credentials + let invalid_sections_count = self.malformed_sections_count + self.encrypted_sections.len(); + + // Put the deserialized sections back into the original ordering for + // the returned `V1AdvertisementContents` + // (Note: idx is unique, so unstable sort is ok) + self.deserialized_sections.sort_unstable_by_key(|(idx, _section)| *idx); + let ordered_sections = self.deserialized_sections.into_iter().map(|(_idx, s)| s).collect(); + V1AdvertisementContents::new(ordered_sections, invalid_sections_count) + } +} + +/// Deserialize and decrypt the contents of a v1 adv after the version header +fn deser_decrypt_v1<'adv, 'cred, B, P>( + arena: &mut DeserializationArena<'adv>, + cred_book: &'cred B, + remaining: &'adv [u8], + header: V1Header, +) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError> +where + B: CredentialBook<'cred>, + P: CryptoProvider, +{ + let mut sections_in_processing = + SectionsInProcessing::<'_, B::Matched>::from_advertisement_contents::<P>( + header, remaining, + )?; + + // Hot loop + // We assume that iterating credentials is more expensive than iterating sections + for (crypto_material, match_data) in cred_book.v1_iter() { + sections_in_processing.try_decrypt_with_credential::<_, P>( + arena.borrow_mut(), + crypto_material, + match_data, + )?; + if sections_in_processing.resolved_all_identities() { + // No need to consider the other credentials break; } } - invalid_sections += to_decrypt.len(); - // decryption may produce sections out of order - sections.sort_by_key(|(idx, _section)| *idx); - Ok(V1AdvContents::new(sections.into_iter().map(|(_idx, s)| s).collect(), invalid_sections)) + Ok(sections_in_processing.finished_with_decryption_attempts()) } -type V0AdvertisementContents<'s, C> = V0AdvContents<'s, MatchedCredFromCred<'s, C>>; - /// Deserialize and decrypt the contents of a v0 adv after the version header -fn deser_decrypt_v0<'s, C, S, P>( - cred_source: &'s S, +fn deser_decrypt_v0<'a, B, P>( + cred_book: &'a B, remaining: &[u8], -) -> Result<V0AdvertisementContents<'s, C>, AdvDeserializationError> +) -> Result<V0AdvertisementContents<B::Matched>, AdvDeserializationError> where - C: V0Credential, - S: CredentialSource<C>, + B: CredentialBook<'a>, P: CryptoProvider, { let contents = legacy::deserialize::deserialize_adv_contents::<P>(remaining)?; - return match contents { - IntermediateAdvContents::Plaintext(p) => Ok(V0AdvContents::Plaintext(p)), + match contents { + IntermediateAdvContents::Plaintext(p) => Ok(V0AdvertisementContents::Plaintext(p)), IntermediateAdvContents::Ciphertext(c) => { - for cred in cred_source.iter() { - let cm = cred.crypto_material(); - let ldt = cm.ldt_adv_cipher::<P>(); + for (crypto_material, matched) in cred_book.v0_iter() { + let ldt = crypto_material.ldt_adv_cipher::<P>(); match c.try_decrypt(&ldt) { Ok(c) => { - return Ok(V0AdvContents::Decrypted(WithMatchedCredential::new( - cred.matched(), + let metadata_nonce = crypto_material.metadata_nonce::<P>(); + return Ok(V0AdvertisementContents::Decrypted(WithMatchedCredential::new( + matched, + metadata_nonce, c, - ))) + ))); } Err(e) => match e { DecryptError::DecryptOrVerifyError => continue, @@ -311,10 +413,11 @@ }, } } - Ok(V0AdvContents::NoMatchingCredentials) + Ok(V0AdvertisementContents::NoMatchingCredentials) } - }; + } } + /// Parse a NP advertisement header. /// /// This can be used on all versions of advertisements since it's the header that determines the @@ -338,88 +441,157 @@ _ => unreachable!(), } } + #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum AdvHeader { V0, V1(V1Header), } + /// An NP advertisement with its header parsed. #[derive(Debug, PartialEq, Eq)] -pub enum DeserializedAdvertisement<'m, M: MatchedCredential<'m>> { +pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> { /// V0 header has all reserved bits, so there is no data to represent other than the version /// itself. - V0(V0AdvContents<'m, M>), + V0(V0AdvertisementContents<M>), /// V1 advertisement - V1(V1AdvContents<'m, M>), + V1(V1AdvertisementContents<'adv, M>), } + +impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> { + /// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents` + /// variant. If the underlying advertisement is not V0, this will instead return `None`. + pub fn into_v0(self) -> Option<V0AdvertisementContents<M>> { + match self { + Self::V0(x) => Some(x), + _ => None, + } + } + /// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents` + /// variant. If the underlying advertisement is not V1, this will instead return `None`. + pub fn into_v1(self) -> Option<V1AdvertisementContents<'adv, M>> { + match self { + Self::V1(x) => Some(x), + _ => None, + } + } +} + /// The contents of a deserialized and decrypted V1 advertisement. #[derive(Debug, PartialEq, Eq)] -pub struct V1AdvContents<'m, M: MatchedCredential<'m>> { - sections: Vec<V1DeserializedSection<'m, M>>, +pub struct V1AdvertisementContents<'adv, M: MatchedCredential> { + sections: Vec<V1DeserializedSection<'adv, M>>, invalid_sections: usize, } -impl<'m, M: MatchedCredential<'m>> V1AdvContents<'m, M> { - fn new(sections: Vec<V1DeserializedSection<'m, M>>, invalid_sections: usize) -> Self { + +impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> { + fn new(sections: Vec<V1DeserializedSection<'adv, M>>, invalid_sections: usize) -> Self { Self { sections, invalid_sections } } + /// Destructures this V1 advertisement into just the sections /// which could be successfully deserialized and decrypted - pub fn into_valid_sections(self) -> Vec<V1DeserializedSection<'m, M>> { + pub fn into_sections(self) -> Vec<V1DeserializedSection<'adv, M>> { self.sections } + /// The sections that could be successfully deserialized and decrypted - pub fn sections(&self) -> impl Iterator<Item = &V1DeserializedSection<M>> { + pub fn sections(&self) -> impl ExactSizeIterator<Item = &V1DeserializedSection<M>> { self.sections.iter() } + /// The number of sections that could not be parsed or decrypted. pub fn invalid_sections_count(&self) -> usize { self.invalid_sections } } + /// Advertisement content that was either already plaintext or has been decrypted. #[derive(Debug, PartialEq, Eq)] -pub enum V0AdvContents<'m, M: MatchedCredential<'m>> { +pub enum V0AdvertisementContents<M: MatchedCredential> { /// Contents of an originally plaintext advertisement Plaintext(PlaintextAdvContents), /// Contents that was ciphertext in the original advertisement, and has been decrypted /// with the credential in the [MatchedCredential] - Decrypted(WithMatchedCredential<'m, M, DecryptedAdvContents>), + Decrypted(WithMatchedCredential<M, DecryptedAdvContents>), /// The advertisement was encrypted, but no credentials matched NoMatchingCredentials, } + /// Advertisement content that was either already plaintext or has been decrypted. #[derive(Debug, PartialEq, Eq)] -pub enum V1DeserializedSection<'m, M: MatchedCredential<'m>> { +pub enum V1DeserializedSection<'adv, M: MatchedCredential> { /// Section that was plaintext in the original advertisement - Plaintext(PlaintextSection), + Plaintext(PlaintextSection<'adv>), /// Section that was ciphertext in the original advertisement, and has been decrypted /// with the credential in the [MatchedCredential] - Decrypted(WithMatchedCredential<'m, M, DecryptedSection>), + Decrypted(WithMatchedCredential<M, DecryptedSection<'adv>>), } -impl<'m, M> Section for V1DeserializedSection<'m, M> + +impl<'adv, M> Section<'adv, DataElementParseError> for V1DeserializedSection<'adv, M> where - M: MatchedCredential<'m>, + M: MatchedCredential, { - type Iterator<'d> = DataElements<'d> where Self: 'd; - fn data_elements(&'_ self) -> Self::Iterator<'_> { + type Iterator = DataElementParsingIterator<'adv>; + + fn iter_data_elements(&self) -> Self::Iterator { match self { - V1DeserializedSection::Plaintext(p) => p.data_elements(), - V1DeserializedSection::Decrypted(d) => d.contents.data_elements(), + V1DeserializedSection::Plaintext(p) => p.iter_data_elements(), + V1DeserializedSection::Decrypted(d) => d.contents.iter_data_elements(), } } } -/// Decrypted advertisement content with the [MatchedCredential] from the credential that decrypted -/// it. -#[derive(Debug, PartialEq, Eq)] -pub struct WithMatchedCredential<'m, M: MatchedCredential<'m>, T> { - matched: M, - contents: T, - // the compiler sees 'm as unused - marker: marker::PhantomData<&'m ()>, + +/// 16-byte metadata keys, as employed for metadata decryption. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub struct MetadataKey(pub [u8; 16]); + +impl AsRef<[u8]> for MetadataKey { + fn as_ref(&self) -> &[u8] { + &self.0 + } } -impl<'m, M: MatchedCredential<'m>, T> WithMatchedCredential<'m, M, T> { - fn new(matched: M, contents: T) -> Self { - Self { matched, contents, marker: marker::PhantomData } + +/// Common trait to deserialized, decrypted V0 advs and V1 sections which +/// exposes relevant data about matched identities. +pub trait HasIdentityMatch { + /// The protocol version for which this advertisement + /// content has an identity-match. + type Version: ProtocolVersion; + + /// Gets the decrypted plaintext version-specific + /// metadata key for the associated identity. + fn metadata_key(&self) -> <Self::Version as ProtocolVersion>::MetadataKey; +} + +#[cfg(any(test, feature = "alloc"))] +/// Type for errors from [`WithMatchedCredential#decrypt_metadata`] +#[derive(Debug)] +pub enum MatchedMetadataDecryptionError<M: MatchedCredential> { + /// Retrieving the encrypted metadata failed for one reason + /// or another, so we didn't get a chance to try decryption. + RetrievalFailed(<M as MatchedCredential>::EncryptedMetadataFetchError), + /// The encrypted metadata could be retrieved, but it did + /// not successfully decrypt against the matched identity. + /// This could be an indication of data corruption or + /// of malformed crypto on the sender-side. + DecryptionFailed, +} + +/// Decrypted advertisement content with the [MatchedCredential] from the credential that decrypted +/// it, along with any other information which is relevant to the identity-match. +#[derive(Debug, PartialEq, Eq)] +pub struct WithMatchedCredential<M: MatchedCredential, T: HasIdentityMatch> { + matched: M, + /// The 12-byte metadata nonce as derived from the key-seed HKDF + /// to be used for decrypting the encrypted metadata in the attached + /// matched-credential. + metadata_nonce: [u8; 12], + contents: T, +} +impl<M: MatchedCredential, T: HasIdentityMatch> WithMatchedCredential<M, T> { + fn new(matched: M, metadata_nonce: [u8; 12], contents: T) -> Self { + Self { matched, metadata_nonce, contents } } /// Credential data for the credential that decrypted the content. pub fn matched_credential(&self) -> &M { @@ -429,12 +601,41 @@ pub fn contents(&self) -> &T { &self.contents } + + #[cfg(any(test, feature = "alloc"))] + fn decrypt_metadata_from_fetch<C: CryptoProvider>( + &self, + encrypted_metadata: &[u8], + ) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> { + let metadata_key = self.contents.metadata_key(); + <<T as HasIdentityMatch>::Version as ProtocolVersion>::decrypt_metadata::<C>( + self.metadata_nonce, + metadata_key, + encrypted_metadata, + ) + .map_err(|_| MatchedMetadataDecryptionError::DecryptionFailed) + } + + #[cfg(any(test, feature = "alloc"))] + /// Attempts to decrypt the encrypted metadata + /// associated with the matched credential + /// based on the details of the identity-match. + pub fn decrypt_metadata<C: CryptoProvider>( + &self, + ) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> { + self.matched + .fetch_encrypted_metadata() + .map_err(|e| MatchedMetadataDecryptionError::RetrievalFailed(e)) + .and_then(|x| Self::decrypt_metadata_from_fetch::<C>(self, x.as_ref())) + } } + /// Data in a V1 advertisement header. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) struct V1Header { header_byte: u8, } + const PROTOCOL_VERSION_LEGACY: u8 = 0; const PROTOCOL_VERSION_EXTENDED: u8 = 1; @@ -449,6 +650,14 @@ /// [AdvDeserializationErrorDetailsHazmat] before using this field. details_hazmat: AdvDeserializationErrorDetailsHazmat, }, + /// The given arena is not large enough to hold the decrypted data. + ArenaOutOfSpace, +} + +impl From<ArenaOutOfSpace> for AdvDeserializationError { + fn from(_: ArenaOutOfSpace) -> Self { + Self::ArenaOutOfSpace + } } impl Debug for AdvDeserializationError { @@ -456,6 +665,7 @@ match self { AdvDeserializationError::HeaderParseError => write!(f, "HeaderParseError"), AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"), + AdvDeserializationError::ArenaOutOfSpace => write!(f, "ArenaOutOfSpace"), } } } @@ -474,6 +684,10 @@ TooManyTopLevelDataElements, /// Must not have an identity DE inside an identity DE InvalidDataElementHierarchy, + /// Must have an identity DE + MissingIdentity, + /// Non-identity DE contents must not be empty + NoPublicDataElements, } impl From<AdvDeserializeError> for AdvDeserializationError { @@ -503,6 +717,12 @@ AdvDeserializationErrorDetailsHazmat::InvalidDataElementHierarchy, } } + AdvDeserializeError::MissingIdentity => AdvDeserializationError::ParseError { + details_hazmat: AdvDeserializationErrorDetailsHazmat::MissingIdentity, + }, + AdvDeserializeError::NoPublicDataElements => AdvDeserializationError::ParseError { + details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements, + }, } } } @@ -515,8 +735,6 @@ /// The identity mode for a deserialized plaintext section or advertisement. #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum PlaintextIdentityMode { - /// No identity DE was present in the section - None, /// A "Public Identity" DE was present in the section Public, } @@ -525,10 +743,4 @@ /// /// Used when serializing V0 advertisements or V1 sections. #[derive(Default, Debug)] -pub struct PublicIdentity {} - -/// The lack of any identity information whatsoever, which is distinct from [PublicIdentity]. -/// -/// Used when serializing V0 advertisements or V1 sections. -#[derive(Default, Debug)] -pub struct NoIdentity {} +pub struct PublicIdentity;
diff --git a/nearby/presence/np_adv/tests/examples_v0.rs b/nearby/presence/np_adv/tests/examples_v0.rs index e30e12f..27ea7c5 100644 --- a/nearby/presence/np_adv/tests/examples_v0.rs +++ b/nearby/presence/np_adv/tests/examples_v0.rs
@@ -1,5 +1,4 @@ // Copyright 2023 Google LLC -// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -17,32 +16,40 @@ use np_adv::legacy::data_elements::TxPowerDataElement; use np_adv::{ credential::{ - simple::SimpleV0Credential, source::SliceCredentialSource, - v0::MinimumFootprintV0CryptoMaterial, + book::CredentialBookBuilder, + v0::{V0DiscoveryCredential, V0}, + EmptyMatchedCredential, MatchableCredential, MetadataMatchedCredential, + SimpleBroadcastCryptoMaterial, }, de_type::*, - deserialize_v0_advertisement, - legacy::deserialize::*, + legacy::{deserialize::*, ShortMetadataKey}, shared_data::*, *, }; +use serde::{Deserialize, Serialize}; #[test] fn v0_deser_plaintext() { - let creds = - SliceCredentialSource::<SimpleV0Credential<MinimumFootprintV0CryptoMaterial, ()>>::new(&[]); - let adv = deserialize_v0_advertisement::<_, _, CryptoProviderImpl>( + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + let adv = deserialize_advertisement::<_, CryptoProviderImpl>( + deserialization_arena!(), &[ 0x00, // adv header 0x03, // public identity 0x15, 0x03, // Length 1 Tx Power DE with value 3 ], - &creds, + &cred_book, ) - .unwrap(); + .expect("Should be a valid advertisement") + .into_v0() + .expect("Should be V0"); match adv { - V0AdvContents::Plaintext(p) => { + V0AdvertisementContents::Plaintext(p) => { assert_eq!(PlaintextIdentityMode::Public, p.identity()); assert_eq!( vec![&PlainDataElement::TxPower(TxPowerDataElement::from( @@ -55,14 +62,49 @@ } } +/// Sample contents for some encrypted identity metadata +/// which includes just a name and an e-mail address. +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct IdentityMetadata { + name: String, + email: String, +} + +impl IdentityMetadata { + /// Serialize this identity metadata to a JSON byte-string. + fn to_bytes(&self) -> Vec<u8> { + serde_json::to_vec(&self).expect("serialization should always succeed") + } + /// Attempt to deserialize identity metadata from a JSON byte-string. + fn try_from_bytes(serialized: &[u8]) -> Option<Self> { + serde_json::from_slice(serialized).ok() + } +} + #[test] fn v0_deser_ciphertext() { + // These are kept fixed in this example for reproducibility. + // In practice, these should instead be derived from a cryptographically-secure + // random number generator. let key_seed = [0x11_u8; 32]; let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN]; + let metadata_key = ShortMetadataKey(metadata_key); + + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key); let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let metadata_key_hmac: [u8; 32] = - hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key); + hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key.0); + + // Serialize and encrypt some identity metadata (sender-side) + let sender_metadata = + IdentityMetadata { name: "Alice".to_string(), email: "alice@gmail.com".to_string() }; + let sender_metadata_bytes = sender_metadata.to_bytes(); + let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::< + _, + _, + CryptoProviderImpl, + >(&broadcast_cm, &sender_metadata_bytes); // output of building a packet using AdvBuilder let adv = &[ @@ -74,25 +116,42 @@ 0xF9, ]; - let credentials: [SimpleV0Credential<_, [u8; 32]>; 1] = [SimpleV0Credential::new( - MinimumFootprintV0CryptoMaterial::new(key_seed, metadata_key_hmac), - key_seed, - )]; - let cred_source = SliceCredentialSource::new(credentials.as_slice()); + let discovery_credential = V0DiscoveryCredential::new(key_seed, metadata_key_hmac); - let matched = match deserialize_v0_advertisement::<_, _, CryptoProviderImpl>(adv, &cred_source) - .unwrap() + let credentials: [MatchableCredential<V0, MetadataMatchedCredential<_>>; 1] = + [MatchableCredential { discovery_credential, match_data: encrypted_sender_metadata }]; + + let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>( + &credentials, + &[], + ); + + let matched = match deserialize_advertisement::<_, CryptoProviderImpl>( + deserialization_arena!(), + adv, + &cred_book, + ) + .expect("Should be a valid advertisement") + .into_v0() + .expect("Should be V0") { - V0AdvContents::Decrypted(c) => c, + V0AdvertisementContents::Decrypted(c) => c, _ => panic!("this examples is ciphertext"), }; - assert_eq!(&key_seed, matched.matched_credential().matched_data()); + let decrypted_metadata_bytes = matched + .decrypt_metadata::<CryptoProviderImpl>() + .expect("Sender metadata should be decryptable"); + let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes) + .expect("Sender metadata should be deserializable"); + + assert_eq!(sender_metadata, decrypted_metadata); + let decrypted = matched.contents(); assert_eq!(EncryptedIdentityDataElementType::Private, decrypted.identity_type()); - assert_eq!(&metadata_key, decrypted.metadata_key()); + assert_eq!(metadata_key, decrypted.metadata_key()); assert_eq!( vec![&PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),],
diff --git a/nearby/presence/np_adv/tests/examples_v1.rs b/nearby/presence/np_adv/tests/examples_v1.rs index fbda763..bf633e5 100644 --- a/nearby/presence/np_adv/tests/examples_v1.rs +++ b/nearby/presence/np_adv/tests/examples_v1.rs
@@ -15,38 +15,47 @@ use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; use np_adv::extended::data_elements::TxPowerDataElement; -use np_adv::extended::serialize::SingleTypeDataElement; +use np_adv::extended::serialize::{AdvertisementType, PublicSectionEncoder, SingleTypeDataElement}; +use np_adv::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT; use np_adv::shared_data::TxPower; use np_adv::{ credential::{ - simple::SimpleV1Credential, source::SliceCredentialSource, - v1::MinimumFootprintV1CryptoMaterial, + book::CredentialBookBuilder, + v1::{SimpleSignedBroadcastCryptoMaterial, V1DiscoveryCredential, V1}, + EmptyMatchedCredential, MatchableCredential, MetadataMatchedCredential, }, de_type::*, - deserialize_v1_advertisement, extended::{ deserialize::{Section, VerificationMode}, - serialize::{AdvBuilder, SignedEncrypted}, - NP_V1_ADV_MAX_SECTION_COUNT, + serialize::{AdvBuilder, SignedEncryptedSectionEncoder}, }, PlaintextIdentityMode, *, }; use np_hkdf::v1_salt; +use serde::{Deserialize, Serialize}; #[test] fn v1_deser_plaintext() { - let mut adv_builder = AdvBuilder::new(); - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(6).unwrap())) .unwrap(); section_builder.add_to_advertisement(); let adv = adv_builder.into_advertisement(); - let creds = - SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + + let arena = deserialization_arena!(); let contents = - deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds).unwrap(); + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book) + .expect("Should be a valid advertisemement") + .into_v1() + .expect("Should be V1"); assert_eq!(0, contents.invalid_sections_count()); @@ -59,7 +68,7 @@ _ => panic!("this is a plaintext adv"), }; assert_eq!(PlaintextIdentityMode::Public, section.identity()); - let data_elements = section.data_elements().collect::<Vec<_>>(); + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); assert_eq!(1, data_elements.len()); let de = &data_elements[0]; @@ -68,24 +77,61 @@ assert_eq!(&[6], de.contents()); } +/// Sample contents for some encrypted identity metadata +/// which consists of a UUID together with a display name +/// and a general location. +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct IdentityMetadata { + uuid: String, + display_name: String, + location: String, +} + +impl IdentityMetadata { + /// Serialize this identity metadata to a MsgPack byte-string. + fn to_bytes(&self) -> Vec<u8> { + rmp_serde::to_vec(&self).expect("serialization should always succeed") + } + /// Attempt to deserialize identity metadata from a MsgPack byte-string. + fn try_from_bytes(serialized: &[u8]) -> Option<Self> { + rmp_serde::from_slice(serialized).ok() + } +} + #[test] fn v1_deser_ciphertext() { // identity material let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new(); let metadata_key: [u8; 16] = rng.gen(); + let metadata_key = MetadataKey(metadata_key); let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate(); let key_seed = rng.gen(); let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); + + // Serialize and encrypt some identity metadata (sender-side) + let sender_metadata = IdentityMetadata { + uuid: "378845e1-2616-420d-86f5-674177a7504d".to_string(), + display_name: "Alice".to_string(), + location: "Wonderland".to_string(), + }; + let sender_metadata_bytes = sender_metadata.to_bytes(); + let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::< + _, + _, + CryptoProviderImpl, + >(&broadcast_cm, &sender_metadata_bytes); + // prepare advertisement - let mut adv_builder = AdvBuilder::new(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted); + let mut section_builder = adv_builder - .section_builder(SignedEncrypted::new_random_salt( + .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut rng, EncryptedIdentityDataElementType::Private, - &metadata_key, - &key_pair, - &hkdf, + &broadcast_cm, )) .unwrap(); section_builder @@ -94,37 +140,50 @@ section_builder.add_to_advertisement(); let adv = adv_builder.into_advertisement(); - let cred_array: [SimpleV1Credential<_, [u8; 32]>; 1] = [SimpleV1Credential::new( - MinimumFootprintV1CryptoMaterial::new( - key_seed, - [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here. - hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key), - key_pair.public(), - ), + let discovery_credential = V1DiscoveryCredential::new( key_seed, - )]; - let creds = SliceCredentialSource::new(&cred_array); + [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here. + hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0), + key_pair.public(), + ); + + let credentials: [MatchableCredential<V1, MetadataMatchedCredential<_>>; 1] = + [MatchableCredential { discovery_credential, match_data: encrypted_sender_metadata }]; + let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>( + &[], + &credentials, + ); + let arena = deserialization_arena!(); let contents = - deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds).unwrap(); + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book) + .expect("Should be a valid advertisement") + .into_v1() + .expect("Should be V1"); assert_eq!(0, contents.invalid_sections_count()); let sections = contents.sections().collect::<Vec<_>>(); assert_eq!(1, sections.len()); - let matched_credential = match §ions[0] { + let matched: &WithMatchedCredential<_, _> = match §ions[0] { V1DeserializedSection::Decrypted(d) => d, _ => panic!("this is a ciphertext adv"), }; - assert_eq!(&key_seed, matched_credential.matched_credential().matched_data()); - let section = matched_credential.contents(); + let decrypted_metadata_bytes = matched + .decrypt_metadata::<CryptoProviderImpl>() + .expect("Sender metadata should be decryptable"); + let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes) + .expect("Sender metadata should be deserializable"); + assert_eq!(sender_metadata, decrypted_metadata); + + let section = matched.contents(); assert_eq!(EncryptedIdentityDataElementType::Private, section.identity_type()); assert_eq!(VerificationMode::Signature, section.verification_mode()); - assert_eq!(&metadata_key, section.metadata_key()); + assert_eq!(metadata_key, section.metadata_key()); - let data_elements = section.data_elements().collect::<Vec<_>>(); + let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap(); assert_eq!(1, data_elements.len()); let de = &data_elements[0]; @@ -135,12 +194,16 @@ #[test] fn v1_deser_no_section() { - let adv_builder = AdvBuilder::new(); + let adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); let adv = adv_builder.into_advertisement(); - let creds = - SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + let arena = deserialization_arena!(); let v1_deserialize_error = - deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds) + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book) .expect_err(" Expected an error"); assert_eq!( v1_deserialize_error, @@ -152,9 +215,10 @@ #[test] fn v1_deser_plaintext_over_max_sections() { - let mut adv_builder = AdvBuilder::new(); - for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT { - let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap(); + let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext); + for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT { + let mut section_builder = + adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); section_builder .add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(7).unwrap())) .unwrap(); @@ -169,10 +233,14 @@ ] .as_slice(), ); - let creds = - SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]); + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); + let arena = deserialization_arena!(); assert_eq!( - deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds) + deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book) .unwrap_err(), AdvDeserializationError::ParseError { details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
diff --git a/nearby/presence/np_c_ffi/Cargo.lock b/nearby/presence/np_c_ffi/Cargo.lock index ca23800..f55cf29 100644 --- a/nearby/presence/np_c_ffi/Cargo.lock +++ b/nearby/presence/np_c_ffi/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -40,23 +54,6 @@ ] [[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] name = "array_ref" version = "0.1.0" @@ -70,7 +67,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] @@ -88,12 +85,24 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -112,10 +121,21 @@ ] [[package]] +name = "bssl-crypto" +version = "0.1.0" +dependencies = [ + "bssl-sys", +] + +[[package]] +name = "bssl-sys" +version = "0.1.0" + +[[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cbc" @@ -146,12 +166,6 @@ ] [[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -174,7 +188,7 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex", "indexmap", "strsim", @@ -193,24 +207,24 @@ [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", "rand_core", @@ -232,6 +246,17 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "crypto_provider_boringssl" +version = "0.1.0" +dependencies = [ + "bssl-crypto", + "crypto_provider", +] [[package]] name = "crypto_provider_default" @@ -239,6 +264,7 @@ dependencies = [ "cfg-if", "crypto_provider", + "crypto_provider_boringssl", "crypto_provider_rustcrypto", ] @@ -248,6 +274,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -277,9 +304,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ "cfg-if", "cpufeatures", @@ -289,24 +316,25 @@ "platforms", "rustc_version", "subtle", + "zeroize", ] [[package]] name = "curve25519-dalek-derive" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", @@ -325,30 +353,33 @@ [[package]] name = "ed25519" -version = "2.2.1" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", + "serde", "sha2", + "zeroize", ] [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" dependencies = [ "base16ct", "crypto-bigint", @@ -365,33 +396,19 @@ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ff" @@ -405,9 +422,9 @@ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7" [[package]] name = "generic-array" @@ -432,6 +449,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -447,10 +474,7 @@ version = "0.1.0" dependencies = [ "crypto_provider", - "hashbrown 0.14.0", - "lock_api", - "portable-atomic", - "spin 0.9.8", + "lock_adapter", ] [[package]] @@ -460,16 +484,6 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -485,12 +499,6 @@ ] [[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] name = "hkdf" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -515,7 +523,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -529,30 +537,10 @@ ] [[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.2", - "libc", - "windows-sys", -] - -[[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" @@ -560,7 +548,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -592,37 +580,31 @@ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "lock_adapter" +version = "0.1.0" [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "minimal-lexical" @@ -652,7 +634,6 @@ "nom", "np_ed25519", "np_hkdf", - "rand", "sink", "strum", "strum_macros", @@ -665,9 +646,8 @@ version = "0.1.0" dependencies = [ "cbindgen", - "crypto_provider_default", + "lock_adapter", "np_ffi_core", - "spin 0.9.8", ] [[package]] @@ -688,8 +668,9 @@ "crypto_provider", "crypto_provider_default", "handle_map", + "lazy_static", + "lock_adapter", "np_adv", - "spin 0.9.8", ] [[package]] @@ -702,12 +683,6 @@ ] [[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] name = "opaque-debug" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -715,9 +690,9 @@ [[package]] name = "os_str_bytes" -version = "6.5.1" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "p256" @@ -730,10 +705,20 @@ ] [[package]] -name = "platforms" -version = "3.0.2" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] name = "polyval" @@ -748,12 +733,6 @@ ] [[package]] -name = "portable-atomic" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" - -[[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -770,18 +749,18 @@ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -792,6 +771,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -816,11 +797,11 @@ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -834,13 +815,12 @@ [[package]] name = "rustix" -version = "0.37.23" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", @@ -848,27 +828,21 @@ [[package]] name = "rustversion" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sec1" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", @@ -879,35 +853,35 @@ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.166" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.166" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.38", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -916,9 +890,9 @@ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -945,12 +919,13 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "spin" -version = "0.9.8" +name = "spki" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ - "lock_api", + "base64ct", + "der", ] [[package]] @@ -961,21 +936,21 @@ [[package]] name = "strum" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -997,9 +972,9 @@ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -1008,11 +983,10 @@ [[package]] name = "tempfile" -version = "3.6.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ - "autocfg", "cfg-if", "fastrand", "redox_syscall", @@ -1022,9 +996,9 @@ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -1052,15 +1026,15 @@ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "universal-hash" @@ -1102,9 +1076,9 @@ [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1126,9 +1100,9 @@ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1141,51 +1115,51 @@ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/presence/np_c_ffi/Cargo.toml b/nearby/presence/np_c_ffi/Cargo.toml index 1dd9f32..86bbd53 100644 --- a/nearby/presence/np_c_ffi/Cargo.toml +++ b/nearby/presence/np_c_ffi/Cargo.toml
@@ -5,14 +5,27 @@ publish = false [dependencies] -# TODO: We need to make this configurable for this crate and for np_ffi below it. -crypto_provider_default = {path = "../../crypto/crypto_provider_default", features = ["rustcrypto"]} -np_ffi_core = {path = "../np_ffi_core"} -spin = "0.9.8" +np_ffi_core = { path = "../np_ffi_core" } +lock_adapter = {path = "../../util/lock_adapter"} [build-dependencies] cbindgen = "0.24.5" +[features] +default = ["rustcrypto"] +rustcrypto = ["np_ffi_core/rustcrypto"] +boringssl = ["np_ffi_core/boringssl"] + [lib] # boringssl and bssl-sys are built as a static lib, so we need to as well -crate-type = ["staticlib"] \ No newline at end of file +crate-type = ["staticlib"] + +# build profile optimized for size +[profile.release-min-size] +inherits = "release" +panic = "abort" +codegen-units = 1 +lto = true +# z optimizes for size +opt-level = "z" +strip = true \ No newline at end of file
diff --git a/nearby/presence/np_c_ffi/deny.toml b/nearby/presence/np_c_ffi/deny.toml new file mode 100644 index 0000000..ca51e11 --- /dev/null +++ b/nearby/presence/np_c_ffi/deny.toml
@@ -0,0 +1,213 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "BSD-3-Clause", + "Unicode-DFS-2016", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[[licenses.clarify]] +name = "ring" +version = "*" +expression = "MIT AND ISC AND OpenSSL" +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "allow" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] \ No newline at end of file
diff --git a/nearby/presence/np_c_ffi/src/deserialize/v0.rs b/nearby/presence/np_c_ffi/src/deserialize/v0.rs index 877cee5..24c4c2a 100644 --- a/nearby/presence/np_c_ffi/src/deserialize/v0.rs +++ b/nearby/presence/np_c_ffi/src/deserialize/v0.rs
@@ -49,7 +49,7 @@ pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_payload( adv: LegibleDeserializedV0Advertisement, ) -> V0Payload { - adv.into_payload() + adv.payload() } /// Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`. @@ -57,7 +57,7 @@ pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_identity( adv: LegibleDeserializedV0Advertisement, ) -> DeserializedV0Identity { - adv.into_identity() + adv.identity() } /// Deallocates any internal data of a `LegibleDeserializedV0Advertisement`
diff --git a/nearby/presence/np_c_ffi/src/lib.rs b/nearby/presence/np_c_ffi/src/lib.rs index bd475ab..0dd6d33 100644 --- a/nearby/presence/np_c_ffi/src/lib.rs +++ b/nearby/presence/np_c_ffi/src/lib.rs
@@ -14,17 +14,15 @@ //! NP Rust C FFI -#![cfg_attr(not(test), no_std)] -#![allow(dead_code)] -extern crate alloc; -extern crate core; - pub mod credentials; pub mod deserialize; +use lock_adapter::std::RwLock; +use lock_adapter::RwLock as _; + /// Structure for categorized reasons for why a NP C FFI call may /// be panicking. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] #[repr(u8)] pub enum PanicReason { /// Some enum cast to a variant failed. Utilized @@ -45,7 +43,6 @@ /// Structure which maintains information about the panic-handler /// for panicking calls in the NP C FFI. -#[derive(Debug)] struct PanicHandler { /// Optional function-pointer to client-specified panic behavior. handler: Option<unsafe extern "C" fn(PanicReason) -> ()>, @@ -81,27 +78,18 @@ } Self::system_handler(panic_reason) } - #[cfg(feature = "std")] + fn system_handler(panic_reason: PanicReason) -> ! { - eprintln!("NP FFI Panicked: {:?}", panic_reason); + std::eprintln!("NP FFI Panicked: {:?}", panic_reason); let backtrace = std::backtrace::Backtrace::capture(); - eprintln!("Stack trace: {}", backtrace); - std::process::abort!(); - } - #[cfg(not(feature = "std"))] - #[allow(clippy::empty_loop)] - fn system_handler(_: PanicReason) -> ! { - // Looping is the only platform-independent thing - // that we can really do in this scenario. - // (Even clippy's explanation for the empty-loop - // lint mentions platform-specific intrinsics - // as being the only true way to avoid this - // in a no_std environment.) - loop {} + std::eprintln!("Stack trace: {}", backtrace); + std::process::abort() } } -static PANIC_HANDLER: spin::RwLock<PanicHandler> = spin::RwLock::new(PanicHandler::new()); +//TODO: use a std library RwLock if we have that available, spin is only needed for no_std and +// won't be as performant +static PANIC_HANDLER: RwLock<PanicHandler> = RwLock::new(PanicHandler::new()); pub(crate) fn panic(reason: PanicReason) -> ! { PANIC_HANDLER.read().panic(reason)
diff --git a/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt b/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt index 17f2f1f..0a1c088 100644 --- a/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt +++ b/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt
@@ -16,6 +16,7 @@ # libfuzzer needs clang SET(CMAKE_C_COMPILER "clang") +SET(CMAKE_CXX_COMPILER "clang++") add_compile_definitions("CORPUS_DIR=${CMAKE_CURRENT_SOURCE_DIR}/corpus") file(COPY corpus DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
diff --git a/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt b/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt index da33258..3987edd 100644 --- a/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt +++ b/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt
@@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -add_executable(np_cpp_sample main.cpp) +add_executable(np_cpp_sample main.cc) target_link_libraries(np_cpp_sample nearby_protocol)
diff --git a/nearby/presence/np_cpp_ffi/sample/main.cpp b/nearby/presence/np_cpp_ffi/sample/main.cc similarity index 98% rename from nearby/presence/np_cpp_ffi/sample/main.cpp rename to nearby/presence/np_cpp_ffi/sample/main.cc index 114ed64..a18b996 100644 --- a/nearby/presence/np_cpp_ffi/sample/main.cpp +++ b/nearby/presence/np_cpp_ffi/sample/main.cc
@@ -132,7 +132,7 @@ std::cout << "AssertFailed \n"; break; } - case np_ffi::internal::PanicReason::InvalidActionBits: { + case nearby_protocol::PanicReason::InvalidActionBits: { std::cout << "InvalidActionBits \n"; break; }
diff --git a/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc b/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc index 27e98a6..67ca828 100644 --- a/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc +++ b/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc
@@ -14,6 +14,9 @@ #include "nearby_protocol.h" +#include <cstdlib> +#include <cstddef> // IWYU pragma: keep + std::string PanicReasonToString(nearby_protocol::PanicReason reason) { switch (reason) { case nearby_protocol::PanicReason::EnumCastFailed: { @@ -22,7 +25,7 @@ case nearby_protocol::PanicReason::AssertFailed: { return "AssertFailed"; } - case np_ffi::internal::PanicReason::InvalidActionBits: { + case nearby_protocol::PanicReason::InvalidActionBits: { return "InvalidActionBits"; } }
diff --git a/nearby/presence/np_cpp_ffi/shared/shared_test_util.h b/nearby/presence/np_cpp_ffi/shared/shared_test_util.h index 52c5363..22d1bb3 100644 --- a/nearby/presence/np_cpp_ffi/shared/shared_test_util.h +++ b/nearby/presence/np_cpp_ffi/shared/shared_test_util.h
@@ -18,7 +18,7 @@ #include "nearby_protocol.h" inline nearby_protocol::RawAdvertisementPayload - V0AdvEmpty(nearby_protocol::ByteBuffer<255>({1, {0x00}})); + V0AdvEmpty(nearby_protocol::ByteBuffer<255>({2, {0x00, 0x03}})); inline nearby_protocol::RawAdvertisementPayload V0AdvSimple(nearby_protocol::ByteBuffer<255>({ @@ -37,20 +37,6 @@ 0x03, // Public Identity DE header 0x15, 0x03 // Length 1 Tx Power DE with value 3 }})); - -inline nearby_protocol::RawAdvertisementPayload - V1AdvMultipleSections(nearby_protocol::ByteBuffer<255>( - {10, - { - 0x20, // V1 Advertisement header - 0x04, // Section Header - 0x03, // Public Identity DE header - 0x26, 0x00, 0x46, // Length 2 Actions DE - 0x03, // Section Header - 0x03, // Public Identity DE header - 0x15, 0x03 // Length 1 Tx Power DE with value 3 - }})); - void test_panic_handler(nearby_protocol::PanicReason reason); std::string generate_hex_string(size_t length);
diff --git a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc index 5eb9ce2..53218ee 100644 --- a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
@@ -20,7 +20,7 @@ TEST(NpFfiCredentialBookTests, TestMoveConstructor) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -28,7 +28,7 @@ // still result in success nearby_protocol::CredentialBook next_book(std::move(book)); auto deserialize_result_moved = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, next_book); ASSERT_EQ(deserialize_result_moved.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -36,21 +36,21 @@ // The old object should now lead to use after moved assert failure ASSERT_DEATH([[maybe_unused]] auto failure = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book), // NOLINT(bugprone-use-after-move) + V0AdvSimple, book), // NOLINT(bugprone-use-after-move) ""); // moving again should still lead to a use after moved assert failure nearby_protocol::CredentialBook another_moved_book(std::move(book)); ASSERT_DEATH([[maybe_unused]] auto failure = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, another_moved_book), + V0AdvSimple, another_moved_book), ""); } TEST(NpFfiCredentialBookTests, TestMoveAssignment) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -60,7 +60,7 @@ // new credential book should still be successful auto deserialize_result_other = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, other_book); ASSERT_EQ(deserialize_result_other.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -68,13 +68,13 @@ // The old object should now lead to use after moved assert failure ASSERT_DEATH([[maybe_unused]] auto failure = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book), // NOLINT(bugprone-use-after-move) + V0AdvSimple, book), // NOLINT(bugprone-use-after-move) ""); // moving again should still lead to a use after moved assert failure auto another_moved_book = std::move(book); ASSERT_DEATH([[maybe_unused]] auto failure = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, another_moved_book), + V0AdvSimple, another_moved_book), ""); } \ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc index e3a44d5..54872c6 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
@@ -21,7 +21,7 @@ TEST(NpFfiDeserializeResultTests, TestResultMoveConstructor) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -89,13 +89,13 @@ TEST(NpFfiDeserializeResultTests, TestResultMoveAssignment) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); // create a second result auto another_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -165,7 +165,7 @@ ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, maybe_credential_book.value()); + V0AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), nearby_protocol::DeserializeAdvertisementResultKind::V0); @@ -179,7 +179,7 @@ auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -215,7 +215,7 @@ auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -253,7 +253,7 @@ auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0);
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc index 9781720..cd95c48 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
@@ -207,29 +207,13 @@ V0AdvEmpty, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), - nearby_protocol::DeserializeAdvertisementResultKind::V0); - auto v0_adv = deserialize_result.IntoV0(); - - ASSERT_EQ(v0_adv.GetKind(), - nearby_protocol::DeserializedV0AdvertisementKind::Legible); - auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), - nearby_protocol::DeserializedV0IdentityKind::Plaintext); - - auto num_des = legible_adv.GetNumberOfDataElements(); - ASSERT_EQ(num_des, 0); - auto payload = legible_adv.IntoPayload(); - - auto result = payload.TryGetDataElement(0); - ASSERT_FALSE(result.ok()); - ASSERT_TRUE(absl::IsOutOfRange(result.status())); + nearby_protocol::DeserializeAdvertisementResultKind::Error); } TEST(NpFfiDeserializeV0Tests, TestV0AdvMoveConstructor) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto adv = result.IntoV0(); @@ -256,14 +240,14 @@ TEST(NpFfiDeserializeResultTests, TestV0AdvMoveAssignment) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto adv = result.IntoV0(); // create a second result auto another_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto adv2 = another_result.IntoV0(); @@ -293,14 +277,14 @@ { auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); // Going over max amount should result in error auto deserialize_result2 = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result2.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::Error); @@ -313,7 +297,7 @@ // will create room for one more to be created. auto deserialize_result3 = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result3.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); } @@ -341,14 +325,14 @@ TEST(NpFfiDeserializeV0Tests, TestLegibleAdvMoveConstructor) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto legible = result.IntoV0().IntoLegible(); // Now move the adv into a new value, and make sure its still valid nearby_protocol::LegibleDeserializedV0Advertisement moved(std::move(legible)); - ASSERT_EQ(moved.GetNumberOfDataElements(), 0); + ASSERT_EQ(moved.GetNumberOfDataElements(), 1); ASSERT_EQ(moved.GetIdentity().GetKind(), np_ffi::internal::DeserializedV0IdentityKind::Plaintext); @@ -375,14 +359,14 @@ TEST(NpFfiDeserializeResultTests, TestLegibleAdvMoveAssignment) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto legible = result.IntoV0().IntoLegible(); // create a second result auto another_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto legible2 = another_result.IntoV0().IntoLegible();
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc index 48b881b..115ad14 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
@@ -55,50 +55,6 @@ ASSERT_EQ(vec, expected); } -TEST(NpFfiDeserializeV1Tests, V1MultipleSections) { - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(maybe_credential_book.ok()); - - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvMultipleSections, maybe_credential_book.value()); - ASSERT_EQ(deserialize_result.GetKind(), - nearby_protocol::DeserializeAdvertisementResultKind::V1); - - auto v1_adv = deserialize_result.IntoV1(); - ASSERT_EQ(v1_adv.GetNumLegibleSections(), 2); - ASSERT_EQ(v1_adv.GetNumUndecryptableSections(), 0); - - auto invalid = v1_adv.TryGetSection(2); - ASSERT_FALSE(invalid.ok()); - ASSERT_TRUE(absl::IsOutOfRange(invalid.status())); - - auto section = v1_adv.TryGetSection(0); - ASSERT_TRUE(section.ok()); - ASSERT_EQ(section.value().GetIdentityKind(), - nearby_protocol::DeserializedV1IdentityKind::Plaintext); - ASSERT_EQ(section.value().NumberOfDataElements(), 1); - - auto invalid_de = section.value().TryGetDataElement(1); - ASSERT_FALSE(invalid_de.ok()); - ASSERT_TRUE(absl::IsOutOfRange(invalid_de.status())); - - auto de = section.value().TryGetDataElement(0); - ASSERT_TRUE(de.ok()); - ASSERT_EQ(de.value().GetDataElementTypeCode(), 6); - - auto payload = de.value().GetPayload(); - auto vec = payload.ToVector(); - std::vector<uint8_t> expected{0x00, 0x46}; - ASSERT_EQ(vec, expected); - - auto section2 = v1_adv.TryGetSection(1); - ASSERT_TRUE(section2.ok()); - ASSERT_EQ(section2.value().GetIdentityKind(), - nearby_protocol::DeserializedV1IdentityKind::Plaintext); - ASSERT_EQ(section2.value().NumberOfDataElements(), 1); -} - TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveConstructor) { auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -216,39 +172,50 @@ ASSERT_TRUE(TryDeserializeNewV1Adv(maybe_credential_book.value())); } -void NestedGetSectionsPart2(nearby_protocol::DeserializedV1Advertisement &adv, - nearby_protocol::CredentialBook &book) { - { auto section = adv.TryGetSection(1); } - assert(!TryDeserializeNewV1Adv(book)); -} - -nearby_protocol::DeserializedV1Section -NestedGetSections(nearby_protocol::CredentialBook &book) { - auto v1_adv = nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvMultipleSections, book) - .IntoV1(); - auto section = v1_adv.TryGetSection(0); - NestedGetSectionsPart2(v1_adv, book); - return section.value(); -} - -// Make sure handle stays in scope until all of the sections and adv have gone -// out of scope -TEST(NpFfiDeserializeV1Tests, TestSectionOwnershipMultipleSections) { - // Capping the max number so we can verify that de-allocations are happening - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1); +/* + * Multiple sections are not supported in plaintext advertisements + * TODO Update the below test to use encrypted sections +TEST(NpFfiDeserializeV1Tests, V1MultipleSections) { auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); ASSERT_TRUE(maybe_credential_book.ok()); - { - auto section = NestedGetSections(maybe_credential_book.value()); - ASSERT_EQ(section.GetIdentityKind(), - nearby_protocol::DeserializedV1IdentityKind::Plaintext); - ASSERT_EQ(section.NumberOfDataElements(), 1); - ASSERT_FALSE(TryDeserializeNewV1Adv(maybe_credential_book.value())); - } + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V1AdvMultipleSections, maybe_credential_book.value()); + ASSERT_EQ(deserialize_result.GetKind(), + nearby_protocol::DeserializeAdvertisementResultKind::V1); - // now that the section has gone out of scope, deserializing a new adv should - // succeed - ASSERT_TRUE(TryDeserializeNewV1Adv(maybe_credential_book.value())); -} \ No newline at end of file + auto v1_adv = deserialize_result.IntoV1(); + ASSERT_EQ(v1_adv.GetNumLegibleSections(), 2); + ASSERT_EQ(v1_adv.GetNumUndecryptableSections(), 0); + + auto invalid = v1_adv.TryGetSection(2); + ASSERT_FALSE(invalid.ok()); + ASSERT_TRUE(absl::IsOutOfRange(invalid.status())); + + auto section = v1_adv.TryGetSection(0); + ASSERT_TRUE(section.ok()); + ASSERT_EQ(section.value().GetIdentityKind(), + nearby_protocol::DeserializedV1IdentityKind::Plaintext); + ASSERT_EQ(section.value().NumberOfDataElements(), 1); + + auto invalid_de = section.value().TryGetDataElement(1); + ASSERT_FALSE(invalid_de.ok()); + ASSERT_TRUE(absl::IsOutOfRange(invalid_de.status())); + + auto de = section.value().TryGetDataElement(0); + ASSERT_TRUE(de.ok()); + ASSERT_EQ(de.value().GetDataElementTypeCode(), 6); + + auto payload = de.value().GetPayload(); + auto vec = payload.ToVector(); + std::vector<uint8_t> expected{0x00, 0x46}; + ASSERT_EQ(vec, expected); + + auto section2 = v1_adv.TryGetSection(1); + ASSERT_TRUE(section2.ok()); + ASSERT_EQ(section2.value().GetIdentityKind(), + nearby_protocol::DeserializedV1IdentityKind::Plaintext); + ASSERT_EQ(section2.value().NumberOfDataElements(), 1); +} +*/ \ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc b/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc index e120aee..6936f23 100644 --- a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
@@ -24,7 +24,7 @@ nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); auto book = nearby_protocol::CredentialBook::TryCreate().value(); auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -54,7 +54,7 @@ auto book2 = nearby_protocol::CredentialBook::TryCreate().value(); auto book3 = nearby_protocol::CredentialBook::TryCreate().value(); auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); // Should still work ASSERT_EQ(deserialize_result.GetKind(), @@ -64,12 +64,12 @@ // still work. nearby_protocol::GlobalConfig::SetNumShards(1); auto deserialize_result_2 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book2); ASSERT_EQ(deserialize_result_2.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto deserialize_result_3 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book3); ASSERT_EQ(deserialize_result_3.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -81,11 +81,11 @@ // should still be able to parse 2 payloads with only one shard auto deserialize_result1 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(deserialize_result1.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto deserialize_result2 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); ASSERT_EQ(deserialize_result2.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); } @@ -119,14 +119,14 @@ { auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); // Going over max amount should result in error auto deserialize_result2 = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result2.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::Error); } @@ -135,7 +135,7 @@ // will create room for one more to be created. auto deserialize_result3 = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, book_result.value()); ASSERT_EQ(deserialize_result3.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); }
diff --git a/nearby/presence/np_ed25519/Cargo.toml b/nearby/presence/np_ed25519/Cargo.toml index b4bca66..601fc82 100644 --- a/nearby/presence/np_ed25519/Cargo.toml +++ b/nearby/presence/np_ed25519/Cargo.toml
@@ -6,7 +6,7 @@ [dependencies] array_view.workspace = true -crypto_provider.workspace = true +crypto_provider = {workspace = true, features = ["raw_private_key_permit"]} sink.workspace = true tinyvec.workspace = true
diff --git a/nearby/presence/np_ed25519/src/lib.rs b/nearby/presence/np_ed25519/src/lib.rs index 60c8302..81a1a10 100644 --- a/nearby/presence/np_ed25519/src/lib.rs +++ b/nearby/presence/np_ed25519/src/lib.rs
@@ -26,8 +26,8 @@ use array_view::ArrayView; use crypto_provider::ed25519::{ - Ed25519Provider, KeyPair as _, PublicKey as _, RawPrivateKey, RawPublicKey, RawSignature, - Signature as _, SignatureError, + Ed25519Provider, KeyPair as _, PrivateKey, PublicKey as _, RawPrivateKey, RawPrivateKeyPermit, + RawPublicKey, RawSignature, Signature as _, SignatureError, }; use crypto_provider::CryptoProvider; use sink::{Sink, SinkWriter}; @@ -55,15 +55,30 @@ pub struct KeyPair<C: CryptoProvider>(CpKeyPair<C>); impl<C: CryptoProvider> KeyPair<C> { - /// Returns the `KeyPair`'s private key bytes. This method should only ever be called by code - /// which securely stores private credentials. - pub fn private_key(&self) -> RawPrivateKey { + /// Returns the `KeyPair`'s private key bytes. + /// This method is only usable in situations where + /// the caller has permission to handle the raw bytes + /// of a private key. + pub fn raw_private_key(&self, permit: &RawPrivateKeyPermit) -> RawPrivateKey { + self.0.raw_private_key(permit) + } + + /// Builds this key-pair from an array of its private key bytes in the format + /// yielded by `private_key`. + /// This method is only usable in situations where + /// the caller has permission to handle the raw bytes + /// of a private key. + pub fn from_raw_private_key(private_key: &RawPrivateKey, permit: &RawPrivateKeyPermit) -> Self { + Self(CpKeyPair::<C>::from_raw_private_key(private_key, permit)) + } + + /// Returns the private key of this key-pair. + pub fn private_key(&self) -> PrivateKey { self.0.private_key() } - /// Builds this key-pair from an array of its private key bytes in the yielded by `private_key`. - /// This method should only ever be called by code which securely stores private credentials. - pub fn from_private_key(private_key: &RawPrivateKey) -> Self { + /// Builds this key-pair from a private key. + pub fn from_private_key(private_key: &PrivateKey) -> Self { Self(CpKeyPair::<C>::from_private_key(private_key)) } @@ -127,6 +142,11 @@ } impl<C: CryptoProvider> PublicKey<C> { + /// Constructs a public key for NP adv signature verification + /// from a public key under the given crypto-provider + pub fn new(public_key: CpPublicKey<C>) -> Self { + Self { public_key } + } /// Succeeds if the signature was a valid signature created via the corresponding /// keypair to this public key using the given [`SignatureContext`] on the given /// message payload. The message payload is represented
diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml index 5be03a6..cdcc7d3 100644 --- a/nearby/presence/np_ffi_core/Cargo.toml +++ b/nearby/presence/np_ffi_core/Cargo.toml
@@ -9,9 +9,12 @@ np_adv.workspace = true handle_map.workspace = true crypto_provider.workspace = true -#TODO: Allow this to be configurable! -crypto_provider_default = {workspace = true, features = ["rustcrypto"]} -spin.workspace = true +crypto_provider_default = {workspace = true, default-features = false} +lock_adapter.workspace = true +lazy_static.workspace = true [features] -std = [] +default = ["rustcrypto"] +rustcrypto = ["crypto_provider_default/rustcrypto", "crypto_provider_default/std"] +opensslbssl = ["crypto_provider_default/opensslbssl"] +boringssl = ["crypto_provider_default/boringssl"]
diff --git a/nearby/presence/np_ffi_core/src/common.rs b/nearby/presence/np_ffi_core/src/common.rs index f00c358..1683b68 100644 --- a/nearby/presence/np_ffi_core/src/common.rs +++ b/nearby/presence/np_ffi_core/src/common.rs
@@ -14,9 +14,11 @@ //! Common externally-accessible FFI constructs which are needed //! in order to define the interfaces in this crate's various modules. -use alloc::string::String; use array_view::ArrayView; use handle_map::HandleNotPresentError; +use lock_adapter::std::RwLock; +use lock_adapter::RwLock as _; +use std::string::String; const DEFAULT_MAX_HANDLES: u32 = u32::MAX - 1; @@ -133,7 +135,7 @@ } } -static COMMON_CONFIG: spin::RwLock<CommonConfig> = spin::RwLock::new(CommonConfig::new()); +static COMMON_CONFIG: RwLock<CommonConfig> = RwLock::new(CommonConfig::new()); pub(crate) fn global_num_shards() -> u8 { COMMON_CONFIG.read().num_shards()
diff --git a/nearby/presence/np_ffi_core/src/credentials.rs b/nearby/presence/np_ffi_core/src/credentials.rs index 412ebc1..5392417 100644 --- a/nearby/presence/np_ffi_core/src/credentials.rs +++ b/nearby/presence/np_ffi_core/src/credentials.rs
@@ -19,7 +19,7 @@ /// Internal, Rust-side implementation of a credential-book. /// See [`CredentialBook`] for the FFI-side handles. -// TODO(b/283249542): Give this a real definition! +// TODO: Give this a real definition! pub struct CredentialBookInternals; fn get_credential_book_handle_map_dimensions() -> HandleMapDimensions { @@ -29,8 +29,13 @@ } } -declare_handle_map! {credential_book, CredentialBook, super::CredentialBookInternals, super::get_credential_book_handle_map_dimensions()} -use credential_book::CredentialBook; +declare_handle_map! { + mod credential_book { + #[dimensions = super::get_credential_book_handle_map_dimensions()] + type CredentialBook: HandleLike<Object = super::CredentialBookInternals>; + } +} +pub use credential_book::CredentialBook; /// Discriminant for `CreateCredentialBookResult` #[repr(u8)]
diff --git a/nearby/presence/np_ffi_core/src/deserialize/mod.rs b/nearby/presence/np_ffi_core/src/deserialize/mod.rs index a651bcf..9c20fde 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/mod.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
@@ -20,6 +20,7 @@ use crate::utils::FfiEnum; use crypto_provider_default::CryptoProviderImpl; use handle_map::{HandleLike, HandleMapFullError, HandleNotPresentError}; +use np_adv::deserialization_arena; pub mod v0; pub mod v1; @@ -106,17 +107,6 @@ } } -type DefaultV0Credential = np_adv::credential::simple::SimpleV0Credential< - np_adv::credential::v0::MinimumFootprintV0CryptoMaterial, - (), ->; -type DefaultV1Credential = np_adv::credential::simple::SimpleV1Credential< - np_adv::credential::v1::MinimumFootprintV1CryptoMaterial, - (), ->; -type DefaultBothCredentialSource = - np_adv::credential::source::OwnedBothCredentialSource<DefaultV0Credential, DefaultV1Credential>; - fn deserialize_advertisement_from_slice_internal( adv_payload: &[u8], credential_book: CredentialBook, @@ -124,14 +114,14 @@ // Deadlock Safety: Credential-book locks always live longer than deserialized advs. let _credential_book_read_guard = credential_book.get()?; - //TODO(b/283249542): Use an actual credential source - let cred_source: DefaultBothCredentialSource = DefaultBothCredentialSource::new_empty(); + //TODO: Use an actual credential source + let cred_book = np_adv::credential::book::CredentialBookBuilder::< + np_adv::credential::EmptyMatchedCredential, + >::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &[]); + let arena = deserialization_arena!(); let deserialized_advertisement = - np_adv::deserialize_advertisement::<_, _, _, _, CryptoProviderImpl>( - adv_payload, - &cred_source, - )?; + np_adv::deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv_payload, &cred_book)?; match deserialized_advertisement { np_adv::DeserializedAdvertisement::V0(adv_contents) => { let adv_handle = DeserializedV0Advertisement::allocate_with_contents(adv_contents)?;
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs index 0f4b24b..7b9ee75 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
@@ -16,10 +16,10 @@ use crate::common::*; use crate::credentials::credential_book::CredentialBook; use crate::utils::{FfiEnum, LocksLongerThan}; -use alloc::vec::Vec; use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; use np_adv::legacy::actions::ActionsDataElement; use np_adv::legacy::{data_elements as np_adv_de, Ciphertext, PacketFlavorEnum, Plaintext}; +use std::vec::Vec; /// Discriminant for possible results of V0 advertisement deserialization #[derive(Clone, Copy)] @@ -66,20 +66,22 @@ } } - pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>( - contents: np_adv::V0AdvContents<'m, M>, + pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>( + contents: np_adv::V0AdvertisementContents<M>, ) -> Result<Self, HandleMapFullError> { match contents { - np_adv::V0AdvContents::Plaintext(plaintext_contents) => { + np_adv::V0AdvertisementContents::Plaintext(plaintext_contents) => { let adv = LegibleDeserializedV0Advertisement::allocate_with_plaintext_contents( plaintext_contents, )?; Ok(Self::Legible(adv)) } - np_adv::V0AdvContents::Decrypted(_) => { + np_adv::V0AdvertisementContents::Decrypted(_) => { unimplemented!(); } - np_adv::V0AdvContents::NoMatchingCredentials => Ok(Self::NoMatchingCredentials), + np_adv::V0AdvertisementContents::NoMatchingCredentials => { + Ok(Self::NoMatchingCredentials) + } } } @@ -109,11 +111,11 @@ self.num_des } /// Destructures this legible advertisement into just the payload - pub fn into_payload(self) -> V0Payload { + pub fn payload(&self) -> V0Payload { self.payload } /// Destructures this legible advertisement into just the identity information - pub fn into_identity(self) -> DeserializedV0Identity { + pub fn identity(&self) -> DeserializedV0Identity { self.identity } /// Deallocates the underlying handle of the payload @@ -136,6 +138,7 @@ /// by a deserialized V0 advertisement #[repr(C)] #[allow(missing_docs)] +#[derive(Copy, Clone, Eq, PartialEq)] pub enum DeserializedV0Identity { Plaintext, // TODO: This gets a payload once we support creds @@ -176,7 +179,12 @@ } } -declare_handle_map! {v0_payload, V0Payload, super::V0PayloadInternals, super::get_v0_payload_handle_map_dimensions() } +declare_handle_map! { + mod v0_payload { + #[dimensions = super::get_v0_payload_handle_map_dimensions()] + type V0Payload: HandleLike<Object = super::V0PayloadInternals>; + } +} use v0_payload::V0Payload; impl LocksLongerThan<V0Payload> for CredentialBook {} @@ -327,10 +335,10 @@ fn from(value: ActionsDataElement<F>) -> Self { match F::ENUM_VARIANT { PacketFlavorEnum::Plaintext => { - Self::Plaintext(V0ActionBits { bitfield: value.as_u32() }) + Self::Plaintext(V0ActionBits { bitfield: value.action.as_u32() }) } PacketFlavorEnum::Ciphertext => { - Self::Encrypted(V0ActionBits { bitfield: value.as_u32() }) + Self::Encrypted(V0ActionBits { bitfield: value.action.as_u32() }) } } } @@ -402,6 +410,7 @@ let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); Ok(actions_de + .action .has_action(&action_type.into()) .expect("BooleanActionType only has one bit")) } @@ -413,6 +422,7 @@ let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); Ok(actions_de + .action .has_action(&action_type.into()) .expect("BooleanActionType only has one bit")) } @@ -430,7 +440,7 @@ .map_err(|_| InvalidActionBits)?; let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); - Ok(actions_de.context_sync_seq_num().as_u8()) + Ok(actions_de.action.context_sync_seq_num().as_u8()) } V0Actions::Encrypted(action_bits) => { let bits = np_adv::legacy::actions::ActionBits::<Ciphertext>::try_from( @@ -438,7 +448,7 @@ ) .map_err(|_| InvalidActionBits)?; let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits); - Ok(actions_de.context_sync_seq_num().as_u8()) + Ok(actions_de.action.context_sync_seq_num().as_u8()) } } }
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v1.rs b/nearby/presence/np_ffi_core/src/deserialize/v1.rs index 9b4a3f4..be44fb7 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
@@ -16,9 +16,10 @@ use crate::common::*; use crate::credentials::credential_book::CredentialBook; use crate::utils::*; -use alloc::vec::Vec; use array_view::ArrayView; use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use np_adv::extended::deserialize::DataElementParseError; +use std::vec::Vec; /// Representation of a deserialized V1 advertisement #[repr(C)] @@ -56,12 +57,12 @@ self.legible_sections.deallocate().map(|_| ()).into() } - pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>( - contents: np_adv::V1AdvContents<'m, M>, + pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>( + contents: np_adv::V1AdvertisementContents<M>, ) -> Result<Self, HandleMapFullError> { // 16-section limit enforced by np_adv let num_undecryptable_sections = contents.invalid_sections_count() as u8; - let legible_sections = contents.into_valid_sections(); + let legible_sections = contents.into_sections(); let num_legible_sections = legible_sections.len() as u8; let legible_sections = LegibleV1Sections::allocate_with_contents(legible_sections)?; Ok(Self { num_undecryptable_sections, num_legible_sections, legible_sections }) @@ -105,11 +106,14 @@ } } -impl<'m, M: np_adv::credential::MatchedCredential<'m>> - From<Vec<np_adv::V1DeserializedSection<'m, M>>> for LegibleV1SectionsInternals +impl<'adv, M: np_adv::credential::MatchedCredential> + From<Vec<np_adv::V1DeserializedSection<'adv, M>>> for LegibleV1SectionsInternals { - fn from(contents: Vec<np_adv::V1DeserializedSection<'m, M>>) -> Self { - let sections = contents.into_iter().map(DeserializedV1SectionInternals::from).collect(); + fn from(contents: Vec<np_adv::V1DeserializedSection<'adv, M>>) -> Self { + let sections = contents + .into_iter() + .filter_map(|s| DeserializedV1SectionInternals::try_from(s).ok()) + .collect(); Self { sections } } } @@ -121,14 +125,19 @@ } } -declare_handle_map! {legible_v1_sections, LegibleV1Sections, super::LegibleV1SectionsInternals, super::get_legible_v1_sections_handle_map_dimensions() } +declare_handle_map! { + mod legible_v1_sections { + #[dimensions = super::get_legible_v1_sections_handle_map_dimensions()] + type LegibleV1Sections: HandleLike<Object = super::LegibleV1SectionsInternals>; + } +} use legible_v1_sections::LegibleV1Sections; impl LocksLongerThan<LegibleV1Sections> for CredentialBook {} impl LegibleV1Sections { - pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>( - contents: Vec<np_adv::V1DeserializedSection<'m, M>>, + pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>( + contents: Vec<np_adv::V1DeserializedSection<M>>, ) -> Result<Self, HandleMapFullError> { Self::allocate(move || LegibleV1SectionsInternals::from(contents)) } @@ -197,17 +206,22 @@ } } -impl<'m, M: np_adv::credential::MatchedCredential<'m>> From<np_adv::V1DeserializedSection<'m, M>> +impl<'adv, M: np_adv::credential::MatchedCredential> TryFrom<np_adv::V1DeserializedSection<'adv, M>> for DeserializedV1SectionInternals { - fn from(section: np_adv::V1DeserializedSection<'m, M>) -> Self { + type Error = DataElementParseError; + + fn try_from(section: np_adv::V1DeserializedSection<M>) -> Result<Self, Self::Error> { use np_adv::extended::deserialize::Section; use np_adv::V1DeserializedSection; match section { V1DeserializedSection::Plaintext(section) => { - let des = section.data_elements().map(V1DataElement::from).collect(); + let des = section + .iter_data_elements() + .map(|r| r.map(|de| V1DataElement::from(&de))) + .collect::<Result<Vec<_>, _>>()?; let identity = DeserializedV1Identity::Plaintext; - Self { des, identity } + Ok(Self { des, identity }) } V1DeserializedSection::Decrypted(_) => { unimplemented!(); @@ -341,8 +355,8 @@ } } -impl<'a> From<np_adv::extended::deserialize::DataElement<'a>> for V1DataElement { - fn from(de: np_adv::extended::deserialize::DataElement<'a>) -> Self { +impl<'a> From<&'a np_adv::extended::deserialize::DataElement<'a>> for V1DataElement { + fn from(de: &'a np_adv::extended::deserialize::DataElement<'a>) -> Self { let de_type = V1DEType::from(de.de_type()); let contents_as_slice = de.contents(); //Guaranteed not to panic due DE size limit.
diff --git a/nearby/presence/np_ffi_core/src/lib.rs b/nearby/presence/np_ffi_core/src/lib.rs index dd9e49e..bba33e2 100644 --- a/nearby/presence/np_ffi_core/src/lib.rs +++ b/nearby/presence/np_ffi_core/src/lib.rs
@@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Core functionality common to all NP Rust FFI layers -#![cfg_attr(not(feature = "std"), no_std)] #![forbid(unsafe_code)] #![deny( missing_docs, @@ -22,8 +21,8 @@ clippy::expect_used )] -extern crate alloc; -extern crate core; +#[macro_use] +extern crate lazy_static; #[macro_use] pub mod utils;
diff --git a/nearby/presence/np_hkdf/src/v1_salt.rs b/nearby/presence/np_hkdf/src/v1_salt.rs index b139e84..ae327db 100644 --- a/nearby/presence/np_hkdf/src/v1_salt.rs +++ b/nearby/presence/np_hkdf/src/v1_salt.rs
@@ -60,6 +60,11 @@ self.data.as_slice() } + /// Returns the salt bytes as an array + pub fn into_array(self) -> [u8; 16] { + self.data + } + /// Returns the salt bytes as a reference to an array pub fn as_array_ref(&self) -> &[u8; 16] { &self.data @@ -91,7 +96,7 @@ #[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)] pub struct DataElementOffset { /// 0-based offset of the DE in the advertisement - offset: usize, + offset: u8, } impl DataElementOffset { @@ -99,7 +104,7 @@ pub const ZERO: DataElementOffset = Self { offset: 0 }; /// Returns the offset as a usize - pub fn as_usize(&self) -> usize { + pub fn as_u8(&self) -> u8 { self.offset } @@ -111,8 +116,8 @@ } } -impl From<usize> for DataElementOffset { - fn from(num: usize) -> Self { +impl From<u8> for DataElementOffset { + fn from(num: u8) -> Self { Self { offset: num } } }
diff --git a/nearby/presence/xts_aes/Cargo.toml b/nearby/presence/xts_aes/Cargo.toml index d0b88b6..ba7aadb 100644 --- a/nearby/presence/xts_aes/Cargo.toml +++ b/nearby/presence/xts_aes/Cargo.toml
@@ -14,9 +14,10 @@ ldt_tbc.workspace = true [dev-dependencies] -crypto_provider_default.workspace = true +crypto_provider_default = {workspace = true, features = ["rustcrypto"]} rand_ext.workspace = true test_helper.workspace = true +wycheproof.workspace = true anyhow.workspace = true base64.workspace = true
diff --git a/nearby/presence/xts_aes/fuzz/Cargo.lock b/nearby/presence/xts_aes/fuzz/Cargo.lock index f0b7f56..1844bd3 100644 --- a/nearby/presence/xts_aes/fuzz/Cargo.lock +++ b/nearby/presence/xts_aes/fuzz/Cargo.lock
@@ -25,6 +25,20 @@ ] [[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] name = "aes-gcm-siv" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -157,6 +171,9 @@ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_rustcrypto" @@ -164,6 +181,7 @@ dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -193,9 +211,9 @@ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.3" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", "cpufeatures", @@ -261,9 +279,9 @@ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", @@ -329,6 +347,16 @@ ] [[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -564,6 +592,12 @@ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -599,9 +633,9 @@ [[package]] name = "x25519-dalek" -version = "2.0.0-rc.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ "curve25519-dalek", "rand_core",
diff --git a/nearby/presence/xts_aes/src/lib.rs b/nearby/presence/xts_aes/src/lib.rs index ff68d76..ac2cf15 100644 --- a/nearby/presence/xts_aes/src/lib.rs +++ b/nearby/presence/xts_aes/src/lib.rs
@@ -34,7 +34,7 @@ use core::fmt; use core::marker::PhantomData; -use crypto_provider::aes::{Aes, AesCipher, AesDecryptCipher, AesEncryptCipher}; +use crypto_provider::aes::{Aes, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher}; use crypto_provider::{ aes::{AesKey, BLOCK_SIZE}, CryptoProvider, @@ -58,7 +58,7 @@ /// little endian bytes (e.g. with `u128::to_le_bytes()`). /// /// Inside each data unit, there are one or more 16-byte blocks. The length of a data unit SHOULD -/// not to exceed 2^20 blocks (16GiB) in order to maintain security properties; see +/// not to exceed 2^20 blocks (16MB) in order to maintain security properties; see /// [appendix D](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf). /// The last block (if there is more than one block) may have fewer than 16 bytes, in which case /// ciphertext stealing will be applied. @@ -104,7 +104,6 @@ } #[allow(clippy::expect_used)] -#[allow(clippy::indexing_slicing)] impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> { /// Encrypt a data unit in place, using sequential block numbers for each block. /// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than @@ -118,20 +117,24 @@ standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| { // Until array_chunks is stabilized, using a macro to get array refs out of slice chunks // Won't panic because we are only processing complete blocks + #[allow(clippy::indexing_slicing)] tweaked_xts.encrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE)); tweaked_xts.advance_to_next_block_num(); }); if partial_last_block.is_empty() { + #[allow(clippy::indexing_slicing)] tweaked_xts.encrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE)); // nothing to do for the last block since it's empty } else { + // This implements ciphertext stealing for the last two blocks which allows processing + // of a message length not divisible by block size without any expansion or padding. // b is in bytes not bits; we do not consider partial bytes let b = partial_last_block.len(); // Per spec: CC = encrypt P_{m-1} (the last complete block) let cc = { - let mut last_complete_block_plaintext: crypto_provider::aes::AesBlock = + let mut last_complete_block_plaintext: AesBlock = last_complete_block.try_into().expect("complete block"); tweaked_xts.encrypt_block(&mut last_complete_block_plaintext); tweaked_xts.advance_to_next_block_num(); @@ -139,7 +142,7 @@ }; // Copy b bytes of P_m before we overwrite them with C_m - let mut partial_last_block_plaintext: crypto_provider::aes::AesBlock = [0; BLOCK_SIZE]; + let mut partial_last_block_plaintext: AesBlock = [0; BLOCK_SIZE]; partial_last_block_plaintext .get_mut(0..b) .expect("this should never be hit, since a partial block is always smaller than a complete block") @@ -148,7 +151,7 @@ // C_m = first b bytes of CC partial_last_block.copy_from_slice( cc.get(0..b) - .expect("partial block len should always be less than a complete block"), + .expect("b is the len of partial_last_block so it will always be in bounds"), ); // PP = P_m || last (16 - b) bytes of CC @@ -173,7 +176,7 @@ Ok(()) } - /// Returns an [XtsTweaked] configured with the specified tweak and a block number of 0. + /// Returns an [`XtsEncrypterTweaked`] configured with the specified tweak and a block number of 0. fn tweaked(&self, tweak: Tweak) -> XtsEncrypterTweaked<A> { let mut bytes = tweak.bytes; self.tweak_encryption_cipher.encrypt(&mut bytes); @@ -186,7 +189,6 @@ } #[allow(clippy::expect_used)] -#[allow(clippy::indexing_slicing)] impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> { /// Decrypt a data unit in place, using sequential block numbers for each block. /// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than @@ -198,11 +200,13 @@ let mut tweaked_xts = self.tweaked(tweak); standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| { + #[allow(clippy::indexing_slicing)] tweaked_xts.decrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE)); tweaked_xts.advance_to_next_block_num(); }); if partial_last_block.is_empty() { + #[allow(clippy::indexing_slicing)] tweaked_xts.decrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE)); } else { let b = partial_last_block.len(); @@ -215,7 +219,7 @@ tweaked_xts.advance_to_next_block_num(); // Block num is now at m // We need C_m later, so make a copy to avoid overwriting - let mut last_complete_block_ciphertext: crypto_provider::aes::AesBlock = + let mut last_complete_block_ciphertext: AesBlock = last_complete_block.try_into().expect("complete block"); tweaked_xts.decrypt_block(&mut last_complete_block_ciphertext); tweaked_xts.set_tweak(tweak_state_m_1); @@ -223,28 +227,47 @@ }; // Copy b bytes of C_m before we overwrite them with P_m - let mut partial_last_block_ciphertext: crypto_provider::aes::AesBlock = [0; BLOCK_SIZE]; - partial_last_block_ciphertext[0..b].copy_from_slice(partial_last_block); + let mut partial_last_block_ciphertext: AesBlock = [0; BLOCK_SIZE]; + partial_last_block_ciphertext + .get_mut(0..b) + .expect("this should never be hit, since a partial block is always smaller than a complete block") + .copy_from_slice(partial_last_block); // P_m = first b bytes of PP - partial_last_block.copy_from_slice(&pp[0..b]); + partial_last_block.copy_from_slice( + pp.get(0..b) + .expect("b is the len of partial_last_block so it will always be in bounds"), + ); // CC = C_m | CP (last 16-b bytes of PP) let cc = { - let cp = &pp[b..]; - last_complete_block[0..b].copy_from_slice(&partial_last_block_ciphertext[0..b]); - last_complete_block[b..].copy_from_slice(cp); + let cp = pp.get(b..).expect( + "b is in bounds since a partial block is always smaller than a complete block", + ); + last_complete_block + .get_mut(0..b) + .expect("partial block length within bounds of complete block") + .copy_from_slice( + partial_last_block_ciphertext + .get(0..b) + .expect("partial block length within bounds of complete block"), + ); + last_complete_block + .get_mut(b..) + .expect("partial block length within bounds of complete block") + .copy_from_slice(cp); last_complete_block }; // decrypting at block num = m -1 + #[allow(clippy::indexing_slicing)] tweaked_xts.decrypt_block(array_mut_ref!(cc, 0, BLOCK_SIZE)); } Ok(()) } - /// Returns an [XtsTweaked] configured with the specified tweak and a block number of 0. + /// Returns an [`XtsDecrypterTweaked`] configured with the specified tweak and a block number of 0. fn tweaked(&self, tweak: Tweak) -> XtsDecrypterTweaked<A> { let mut bytes = tweak.bytes; self.tweak_encryption_cipher.encrypt(&mut bytes); @@ -289,10 +312,10 @@ } #[allow(clippy::expect_used)] - fn encrypt(&self, tweak: Self::Tweak, block: &mut [u8; 16]) { - // we're encrypting precisely one block, so the block number won't advance, and ciphertext - // stealing will not be applied. - self.encrypt_data_unit(tweak, block).expect("One block is a valid size"); + fn encrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) { + // We're encrypting precisely one block, so the block number won't advance, and there is no + // need to apply ciphertext stealing + self.tweaked(tweak).encrypt_block(block) } } @@ -311,8 +334,9 @@ } #[allow(clippy::expect_used)] - fn decrypt(&self, tweak: Self::Tweak, block: &mut [u8; 16]) { - self.decrypt_data_unit(tweak, block).expect("One block is a valid size"); + fn decrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) { + // We don't need the ciphertext stealing here since the input size is always exactly one block + self.tweaked(tweak).decrypt_block(block) } } @@ -341,6 +365,8 @@ fn key_2(&self) -> &Self::BlockCipherKey; } +const AES_128_KEY_SIZE: usize = 16; + /// An XTS-AES-128 key. pub struct XtsAes128Key { key_1: <Self as XtsKey>::BlockCipherKey, @@ -364,7 +390,7 @@ type Error = XtsKeyTryFromSliceError; fn try_from(slice: &[u8]) -> Result<Self, Self::Error> { - try_split_concat_key::<16>(slice) + try_split_concat_key::<AES_128_KEY_SIZE>(slice) .map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() }) .ok_or_else(XtsKeyTryFromSliceError::new) } @@ -373,8 +399,10 @@ #[allow(clippy::expect_used)] impl From<&[u8; 32]> for XtsAes128Key { fn from(array: &[u8; 32]) -> Self { - let arr1: [u8; 16] = array[..16].try_into().expect("array is correctly sized"); - let arr2: [u8; 16] = array[16..].try_into().expect("array is correctly sized"); + let arr1: [u8; AES_128_KEY_SIZE] = + array[..AES_128_KEY_SIZE].try_into().expect("array is correctly sized"); + let arr2: [u8; AES_128_KEY_SIZE] = + array[AES_128_KEY_SIZE..].try_into().expect("array is correctly sized"); Self { key_1: crypto_provider::aes::Aes128Key::from(arr1), key_2: crypto_provider::aes::Aes128Key::from(arr2), @@ -402,6 +430,8 @@ } } +const AES_256_KEY_SIZE: usize = 32; + /// An XTS-AES-256 key. pub struct XtsAes256Key { key_1: <Self as XtsKey>::BlockCipherKey, @@ -425,7 +455,7 @@ type Error = XtsKeyTryFromSliceError; fn try_from(slice: &[u8]) -> Result<Self, Self::Error> { - try_split_concat_key::<32>(slice) + try_split_concat_key::<AES_256_KEY_SIZE>(slice) .map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() }) .ok_or_else(XtsKeyTryFromSliceError::new) } @@ -434,8 +464,10 @@ #[allow(clippy::expect_used)] impl From<&[u8; 64]> for XtsAes256Key { fn from(array: &[u8; 64]) -> Self { - let arr1: [u8; 32] = array[..32].try_into().expect("array is correctly sized"); - let arr2: [u8; 32] = array[32..].try_into().expect("array is correctly sized"); + let arr1: [u8; AES_256_KEY_SIZE] = + array[..AES_256_KEY_SIZE].try_into().expect("array is correctly sized"); + let arr2: [u8; AES_256_KEY_SIZE] = + array[AES_256_KEY_SIZE..].try_into().expect("array is correctly sized"); Self { key_1: crypto_provider::aes::Aes256Key::from(arr1), key_2: crypto_provider::aes::Aes256Key::from(arr2), @@ -478,18 +510,18 @@ /// The tweak for an XTS-AES cipher. #[derive(Clone)] pub struct Tweak { - bytes: crypto_provider::aes::AesBlock, + bytes: AesBlock, } impl Tweak { /// Little-endian content of the tweak. - pub fn le_bytes(&self) -> crypto_provider::aes::AesBlock { + pub fn le_bytes(&self) -> AesBlock { self.bytes } } -impl From<crypto_provider::aes::AesBlock> for Tweak { - fn from(bytes: crypto_provider::aes::AesBlock) -> Self { +impl From<AesBlock> for Tweak { + fn from(bytes: AesBlock) -> Self { Self { bytes } } } @@ -506,15 +538,31 @@ /// The block number inside the data unit. Should not exceed 2^20. block_num: u32, /// Original tweak multiplied by the primitive polynomial `block_num` times as per section 5.2 - tweak: crypto_provider::aes::AesBlock, + tweak: AesBlock, } impl TweakState { /// Create a TweakState from the provided state with block_num = 0. - fn new(tweak: [u8; 16]) -> TweakState { + fn new(tweak: [u8; BLOCK_SIZE]) -> TweakState { TweakState { block_num: 0, tweak } } + /// Advance the tweak state in the data unit to the next block without encrypting + /// or decrypting the intermediate blocks. + #[allow(clippy::indexing_slicing)] + fn advance_to_next_block(&mut self) { + // Conceptual left shift across the bytes. + // Most significant byte: if shift would carry, XOR in the coefficients of primitive + // polynomial in F_2^128 (x^128 = x^7 + x^2 + x + 1) => 0b1000_0111 in binary. + let mut target = [0_u8; BLOCK_SIZE]; + target[0] = (self.tweak[0] << 1) ^ ((self.tweak[BLOCK_SIZE - 1] >> 7) * 0b1000_0111); + for (j, byte) in target.iter_mut().enumerate().skip(1) { + *byte = (self.tweak[j] << 1) ^ (self.tweak[j - 1] >> 7); + } + self.tweak = target; + self.block_num += 1; + } + /// Advance the tweak state in the data unit to the `block_num`'th block without encrypting /// or decrypting the intermediate blocks. /// @@ -522,39 +570,17 @@ /// /// # Panics /// - If `block_num` is less than the current block num + #[cfg(test)] fn advance_to_block(&mut self, block_num: u32) { // It's a programmer error; nothing to recover from assert!(self.block_num <= block_num); - let mut target = [0_u8; BLOCK_SIZE]; - // Multiply by the primitive polynomial as many times as needed, as per section 5.2 // of IEEE spec - #[allow(clippy::expect_used)] + #[allow(clippy::indexing_slicing)] for _ in 0..(block_num - self.block_num) { - // Conceptual left shift across the bytes. - // Most significant byte: if shift would carry, XOR in the coefficients of primitive - // polynomial in F_2^128 (x^128 = x^7 + x^2 + x + 1 = 0) = 135 decimal. - // % 128 is compiled as & !128 (i.e. fast). - target[0] = (2 - * (self.tweak.first().expect("aes block must have non zero length") % 128)) - ^ (135 - * select_hi_bit( - *self.tweak.get(15).expect("15 is a valid index in an aes block"), - )); - // Remaining bytes - for (j, byte) in target.iter_mut().enumerate().skip(1) { - *byte = (2 - * (self.tweak.get(j).expect("j is always in range of block size") % 128)) - ^ select_hi_bit( - *self.tweak.get(j - 1).expect("j > 0 always because of the .skip(1)"), - ); - } - self.tweak = target; - // no need to zero target as it will be overwritten completely next iteration + self.advance_to_next_block() } - - self.block_num = block_num; } } @@ -570,11 +596,11 @@ impl<'a, A: Aes> XtsEncrypterTweaked<'a, A> { fn advance_to_next_block_num(&mut self) { - self.tweak_state.advance_to_block(self.tweak_state.block_num + 1) + self.tweak_state.advance_to_next_block() } /// Encrypt a block in place using the configured tweak and current block number. - fn encrypt_block(&self, block: &mut crypto_provider::aes::AesBlock) { + fn encrypt_block(&self, block: &mut AesBlock) { array_xor(block, &self.tweak_state.tweak); self.enc_cipher.encrypt(block); array_xor(block, &self.tweak_state.tweak); @@ -593,7 +619,7 @@ impl<'a, A: Aes> XtsDecrypterTweaked<'a, A> { fn advance_to_next_block_num(&mut self) { - self.tweak_state.advance_to_block(self.tweak_state.block_num + 1) + self.tweak_state.advance_to_next_block() } /// Get the current tweak state -- useful if needed to reset to an earlier block num. @@ -601,11 +627,11 @@ self.tweak_state.clone() } - /// Set the tweak to a state captured via [current_tweak]. + /// Set the tweak to a state captured via [`current_tweak`](XtsDecrypterTweaked::current_tweak). fn set_tweak(&mut self, tweak_state: TweakState) { self.tweak_state = tweak_state; } - fn decrypt_block(&self, block: &mut crypto_provider::aes::AesBlock) { + fn decrypt_block(&self, block: &mut AesBlock) { // CC = C ^ T array_xor(block, &self.tweak_state.tweak); // PP = decrypt CC @@ -617,7 +643,7 @@ /// Calculate `base = base ^ rhs` for each byte. #[allow(clippy::expect_used)] -fn array_xor(base: &mut crypto_provider::aes::AesBlock, rhs: &crypto_provider::aes::AesBlock) { +fn array_xor(base: &mut AesBlock, rhs: &AesBlock) { // hopefully this gets done smartly by the compiler (intel pxor, arm veorq, or equivalent). // This seems to happen in practice at opt level 3: https://gcc.godbolt.org/z/qvjE8joMv for i in 0..BLOCK_SIZE { @@ -626,12 +652,6 @@ } } -/// 1 if hi bit set, 0 if not. -fn select_hi_bit(byte: u8) -> u8 { - // compiled as shr 7: https://gcc.godbolt.org/z/1rzvfshnx - byte / 128 -} - fn try_split_concat_key<const N: usize>(slice: &[u8]) -> Option<([u8; N], [u8; N])> { slice.get(0..N).and_then(|slice| slice.try_into().ok()).and_then(|k1: [u8; N]| { slice.get(N..).and_then(|slice| slice.try_into().ok()).map(|k2: [u8; N]| (k1, k2))
diff --git a/nearby/presence/xts_aes/tests/tests.rs b/nearby/presence/xts_aes/tests/tests.rs new file mode 100644 index 0000000..0452f06 --- /dev/null +++ b/nearby/presence/xts_aes/tests/tests.rs
@@ -0,0 +1,117 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crypto_provider::aes::{Aes, BLOCK_SIZE}; +use crypto_provider::CryptoProvider; +use crypto_provider_default::CryptoProviderImpl; +use ldt_tbc::{TweakableBlockCipherDecrypter, TweakableBlockCipherEncrypter}; +use xts_aes::{XtsAes128Key, XtsAes256Key, XtsDecrypter, XtsEncrypter, XtsError, XtsKey}; + +const MAX_XTS_SIZE: usize = (1 << 20) * BLOCK_SIZE; + +#[test] +fn too_small_payload() { + let key = [0_u8; 32]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>( + &XtsAes128Key::from(&key), + ); + let tweak = [0u8; 16]; + let mut payload = [0u8; BLOCK_SIZE - 1]; + assert_eq!(enc.encrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort)); + assert_eq!(dec.decrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort)); + + let key = [0u8; 64]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>( + &XtsAes256Key::from(&key), + ); + assert_eq!(enc.encrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort)); + assert_eq!(dec.decrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort)); +} + +#[test] +fn block_size_payload() { + let key = [0_u8; 32]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>( + &XtsAes128Key::from(&key), + ); + let tweak = [0u8; 16]; + let mut payload = [0u8; BLOCK_SIZE]; + assert!(enc.encrypt_data_unit(tweak.into(), &mut payload).is_ok()); + assert!(dec.decrypt_data_unit(tweak.into(), &mut payload).is_ok()); + + let key = [0u8; 64]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>( + &XtsAes256Key::from(&key), + ); + assert!(enc.encrypt_data_unit(tweak.into(), &mut payload).is_ok()); + assert!(dec.decrypt_data_unit(tweak.into(), &mut payload).is_ok()); +} + +#[test] +fn max_xts_sized_payload() { + let key = [0_u8; 32]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>( + &XtsAes128Key::from(&key), + ); + let tweak = [0u8; 16]; + let mut payload = vec![0u8; MAX_XTS_SIZE]; + assert!(enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok()); + assert!(dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok()); + + let key = [0u8; 64]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>( + &XtsAes256Key::from(&key), + ); + assert!(enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok()); + assert!(dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok()); +} + +#[test] +fn too_large_payload() { + let key = [0_u8; 32]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>( + &XtsAes128Key::from(&key), + ); + let tweak = [0u8; 16]; + let mut payload = vec![0u8; MAX_XTS_SIZE + 1]; + assert_eq!( + enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()), + Err(XtsError::DataTooLong) + ); + assert_eq!( + dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()), + Err(XtsError::DataTooLong) + ); + + let key = [0u8; 64]; + let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>( + &XtsAes256Key::from(&key), + ); + assert_eq!( + enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()), + Err(XtsError::DataTooLong) + ); + assert_eq!( + dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()), + Err(XtsError::DataTooLong) + ); +} + +fn build_ciphers<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + ldt_tbc::TweakableBlockCipherKey>( + key: &K, +) -> (XtsEncrypter<A, K>, XtsDecrypter<A, K>) { + let enc = XtsEncrypter::<A, _>::new(key); + let dec = XtsDecrypter::<A, _>::new(key); + (enc, dec) +}
diff --git a/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs new file mode 100644 index 0000000..724e3bd --- /dev/null +++ b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
@@ -0,0 +1,77 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate core; + +use crypto_provider::CryptoProvider; +use crypto_provider_default::CryptoProviderImpl; +use ldt_tbc::TweakableBlockCipherDecrypter; +use ldt_tbc::TweakableBlockCipherEncrypter; +use ldt_tbc::TweakableBlockCipherKey; +use wycheproof::cipher::TestGroup; +use xts_aes::{self, XtsAes128Key, XtsAes256Key, XtsDecrypter, XtsEncrypter, XtsKey}; + +#[test] +fn run_wycheproof_vectors() { + run_test_vectors(); +} + +fn run_test_vectors() { + let test_set = wycheproof::cipher::TestSet::load(wycheproof::cipher::TestName::AesXts) + .expect("Should be able to load test set"); + + for test_group in test_set.test_groups { + match test_group.key_size { + 256 => run_tests::<XtsAes128Key, <CryptoProviderImpl as CryptoProvider>::Aes128>( + test_group, + ), + 512 => run_tests::<XtsAes256Key, <CryptoProviderImpl as CryptoProvider>::Aes256>( + test_group, + ), + _ => {} + } + } +} + +fn run_tests<K, A>(test_group: TestGroup) +where + K: XtsKey + TweakableBlockCipherKey, + A: crypto_provider::aes::Aes<Key = K::BlockCipherKey>, +{ + let mut buf = Vec::new(); + for test in test_group.tests { + buf.clear(); + let key = test.key.as_slice(); + let iv = test.nonce.as_slice(); + let msg = test.pt.as_slice(); + let ct = test.ct.as_slice(); + + let mut block = [0u8; 16]; + block[..iv.len()].copy_from_slice(iv); + let tweak = xts_aes::Tweak::from(block); + + // test encryption + let xts_enc = XtsEncrypter::<A, _>::new(&K::try_from(key).unwrap()); + buf.extend_from_slice(msg); + xts_enc.encrypt_data_unit(tweak.clone(), &mut buf).unwrap(); + assert_eq!(ct, buf, "Test case id: {}", test.tc_id); + + // test decryption + buf.clear(); + let xts_dec = XtsDecrypter::<A, _>::new(&K::try_from(key).unwrap()); + buf.extend_from_slice(ct); + xts_dec.decrypt_data_unit(tweak, &mut buf).unwrap(); + assert_eq!(msg, buf, "Test case id: {}", test.tc_id); + } +}
diff --git a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch b/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch deleted file mode 100644 index be6ca09..0000000 --- a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch +++ /dev/null
@@ -1,2712 +0,0 @@ -From 66e1daa9b4a345617bf9a090d56fa09ccd9b1d35 Mon Sep 17 00:00:00 2001 -From: Maurice Lam <yukl@google.com> -Date: Wed, 26 Apr 2023 02:21:36 +0000 -Subject: [PATCH] Apply Android patches - ---- - openssl/src/bio.rs | 6 +- - openssl/src/bn.rs | 2 +- - openssl/src/cipher.rs | 4 + - openssl/src/dh.rs | 2 +- - openssl/src/ec.rs | 20 + - openssl/src/encrypt.rs | 4 +- - openssl/src/hkdf.rs | 89 ++ - openssl/src/hmac.rs | 217 ++++ - openssl/src/lib.rs | 12 + - openssl/src/pkey.rs | 20 +- - openssl/src/sign.rs | 10 +- - openssl/src/symm.rs | 7 +- - openssl/src/x509/mod.rs | 52 +- - openssl/src/x509/mod.rs.orig | 1879 ++++++++++++++++++++++++++++++++++ - 14 files changed, 2292 insertions(+), 32 deletions(-) - create mode 100644 openssl/src/hkdf.rs - create mode 100644 openssl/src/hmac.rs - create mode 100644 openssl/src/x509/mod.rs.orig - -diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs -index 6a72552a..03242188 100644 ---- a/openssl/src/bio.rs -+++ b/openssl/src/bio.rs -@@ -4,7 +4,7 @@ use std::marker::PhantomData; - use std::ptr; - use std::slice; - --use crate::cvt_p; -+use crate::{cvt_p, SignedLenType}; - use crate::error::ErrorStack; - - pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>); -@@ -25,7 +25,7 @@ impl<'a> MemBioSlice<'a> { - let bio = unsafe { - cvt_p(BIO_new_mem_buf( - buf.as_ptr() as *const _, -- buf.len() as c_int, -+ buf.len() as SignedLenType, - ))? - }; - -@@ -78,7 +78,7 @@ cfg_if! { - use ffi::BIO_new_mem_buf; - } else { - #[allow(bad_style)] -- unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO { -+ unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO { - ffi::BIO_new_mem_buf(buf as *mut _, len) - } - } -diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs -index 0328730a..fe820a2c 100644 ---- a/openssl/src/bn.rs -+++ b/openssl/src/bn.rs -@@ -814,7 +814,7 @@ impl BigNumRef { - /// assert_eq!(&bn_vec, &[0, 0, 0x45, 0x43]); - /// ``` - #[corresponds(BN_bn2binpad)] -- #[cfg(ossl110)] -+ #[cfg(any(boringssl, ossl110))] - pub fn to_vec_padded(&self, pad_to: i32) -> Result<Vec<u8>, ErrorStack> { - let mut v = Vec::with_capacity(pad_to as usize); - unsafe { -diff --git a/openssl/src/cipher.rs b/openssl/src/cipher.rs -index aeedf459..570794ca 100644 ---- a/openssl/src/cipher.rs -+++ b/openssl/src/cipher.rs -@@ -208,6 +208,7 @@ impl Cipher { - unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb1() as *mut _) } - } - -+ #[cfg(not(boringssl))] - pub fn aes_192_cfb128() -> &'static CipherRef { - unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb128() as *mut _) } - } -@@ -253,6 +254,7 @@ impl Cipher { - unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb1() as *mut _) } - } - -+ #[cfg(not(boringssl))] - pub fn aes_256_cfb128() -> &'static CipherRef { - unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb128() as *mut _) } - } -@@ -282,11 +284,13 @@ impl Cipher { - } - - #[cfg(not(osslconf = "OPENSSL_NO_BF"))] -+ #[cfg(not(boringssl))] - pub fn bf_cbc() -> &'static CipherRef { - unsafe { CipherRef::from_ptr(ffi::EVP_bf_cbc() as *mut _) } - } - - #[cfg(not(osslconf = "OPENSSL_NO_BF"))] -+ #[cfg(not(boringssl))] - pub fn bf_ecb() -> &'static CipherRef { - unsafe { CipherRef::from_ptr(ffi::EVP_bf_ecb() as *mut _) } - } -diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs -index 12170b99..e781543e 100644 ---- a/openssl/src/dh.rs -+++ b/openssl/src/dh.rs -@@ -239,7 +239,7 @@ where - } - - cfg_if! { -- if #[cfg(any(ossl110, libressl270))] { -+ if #[cfg(any(ossl110, libressl270, boringssl))] { - use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key}; - } else { - #[allow(bad_style)] -diff --git a/openssl/src/ec.rs b/openssl/src/ec.rs -index 248ced3e..c56f5da7 100644 ---- a/openssl/src/ec.rs -+++ b/openssl/src/ec.rs -@@ -954,6 +954,26 @@ impl EcKey<Private> { - EcKey<Private>, - ffi::d2i_ECPrivateKey - } -+ -+ /// Decodes a DER-encoded elliptic curve private key structure for the specified curve. -+ #[corresponds(EC_KEY_parse_private_key)] -+ #[cfg(boringssl)] -+ pub fn private_key_from_der_for_group( -+ der: &[u8], -+ group: &EcGroupRef, -+ ) -> Result<EcKey<Private>, ErrorStack> { -+ unsafe { -+ let mut cbs = ffi::CBS { -+ data: der.as_ptr(), -+ len: der.len(), -+ }; -+ cvt_p(ffi::EC_KEY_parse_private_key( -+ &mut cbs as *mut ffi::CBS, -+ group.as_ptr(), -+ )) -+ .map(|p| EcKey::from_ptr(p)) -+ } -+ } - } - - impl<T> Clone for EcKey<T> { -diff --git a/openssl/src/encrypt.rs b/openssl/src/encrypt.rs -index d3db0fd4..19a9a459 100644 ---- a/openssl/src/encrypt.rs -+++ b/openssl/src/encrypt.rs -@@ -148,7 +148,7 @@ impl<'a> Encrypter<'a> { - /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`]. - /// - /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html -- #[cfg(any(ossl102, libressl310))] -+ #[cfg(any(ossl102, libressl310, boringssl))] - pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md( -@@ -352,7 +352,7 @@ impl<'a> Decrypter<'a> { - /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`]. - /// - /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html -- #[cfg(any(ossl102, libressl310))] -+ #[cfg(any(ossl102, libressl310, boringssl))] - pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> { - unsafe { - cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md( -diff --git a/openssl/src/hkdf.rs b/openssl/src/hkdf.rs -new file mode 100644 -index 00000000..cc7e5b3a ---- /dev/null -+++ b/openssl/src/hkdf.rs -@@ -0,0 +1,89 @@ -+use crate::cvt; -+use crate::error::ErrorStack; -+use crate::md::MdRef; -+use foreign_types::ForeignTypeRef; -+use openssl_macros::corresponds; -+ -+/// Computes HKDF (as specified by RFC 5869). -+/// -+/// HKDF is an Extract-and-Expand algorithm. It does not do any key stretching, -+/// and as such, is not suited to be used alone to generate a key from a -+/// password. -+#[corresponds(HKDF)] -+#[inline] -+pub fn hkdf( -+ out_key: &mut [u8], -+ md: &MdRef, -+ secret: &[u8], -+ salt: &[u8], -+ info: &[u8], -+) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::HKDF( -+ out_key.as_mut_ptr(), -+ out_key.len(), -+ md.as_ptr(), -+ secret.as_ptr(), -+ secret.len(), -+ salt.as_ptr(), -+ salt.len(), -+ info.as_ptr(), -+ info.len(), -+ ))?; -+ } -+ -+ Ok(()) -+} -+ -+/// Computes a HKDF PRK (as specified by RFC 5869). -+/// -+/// WARNING: This function orders the inputs differently from RFC 5869 -+/// specification. Double-check which parameter is the secret/IKM and which is -+/// the salt when using. -+#[corresponds(HKDF_extract)] -+#[inline] -+pub fn hkdf_extract<'a>( -+ out_key: &'a mut [u8], -+ md: &MdRef, -+ secret: &[u8], -+ salt: &[u8], -+) -> Result<&'a [u8], ErrorStack> { -+ let mut out_len = out_key.len(); -+ unsafe { -+ cvt(ffi::HKDF_extract( -+ out_key.as_mut_ptr(), -+ &mut out_len, -+ md.as_ptr(), -+ secret.as_ptr(), -+ secret.len(), -+ salt.as_ptr(), -+ salt.len(), -+ ))?; -+ } -+ -+ Ok(&out_key[..out_len]) -+} -+ -+/// Computes a HKDF OKM (as specified by RFC 5869). -+#[corresponds(HKDF_expand)] -+#[inline] -+pub fn hkdf_expand( -+ out_key: &mut [u8], -+ md: &MdRef, -+ prk: &[u8], -+ info: &[u8], -+) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::HKDF_expand( -+ out_key.as_mut_ptr(), -+ out_key.len(), -+ md.as_ptr(), -+ prk.as_ptr(), -+ prk.len(), -+ info.as_ptr(), -+ info.len(), -+ ))?; -+ } -+ -+ Ok(()) -+} -diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs -new file mode 100644 -index 00000000..465781e2 ---- /dev/null -+++ b/openssl/src/hmac.rs -@@ -0,0 +1,217 @@ -+use crate::error::ErrorStack; -+use crate::md::MdRef; -+use crate::{cvt, cvt_p}; -+use ffi::HMAC_CTX; -+use foreign_types::ForeignTypeRef; -+use libc::{c_uint, c_void}; -+use openssl_macros::corresponds; -+use std::convert::TryFrom; -+use std::ptr; -+ -+/// Computes the HMAC as a one-shot operation. -+/// -+/// Calculates the HMAC of data, using the given |key| -+/// and hash function |md|, and returns the result re-using the space from -+/// buffer |out|. On entry, |out| must contain at least |EVP_MD_size| bytes -+/// of space. The actual length of the result is used to resize the returned -+/// slice. An output size of |EVP_MAX_MD_SIZE| will always be large enough. -+/// It returns a resized |out| or ErrorStack on error. -+#[corresponds(HMAC)] -+#[inline] -+pub fn hmac<'a>( -+ md: &MdRef, -+ key: &[u8], -+ data: &[u8], -+ out: &'a mut [u8], -+) -> Result<&'a [u8], ErrorStack> { -+ assert!(out.len() >= md.size()); -+ let mut out_len = c_uint::try_from(out.len()).unwrap(); -+ unsafe { -+ cvt_p(ffi::HMAC( -+ md.as_ptr(), -+ key.as_ptr() as *const c_void, -+ key.len(), -+ data.as_ptr(), -+ data.len(), -+ out.as_mut_ptr(), -+ &mut out_len, -+ ))?; -+ } -+ Ok(&out[..out_len as usize]) -+} -+ -+/// A context object used to perform HMAC operations. -+/// -+/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message -+/// authentication, which is based on a hash function. -+/// -+/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead. -+#[cfg(boringssl)] -+pub struct HmacCtx { -+ ctx: *mut HMAC_CTX, -+ output_size: usize, -+} -+ -+#[cfg(boringssl)] -+impl HmacCtx { -+ /// Creates a new [HmacCtx] to use the hash function `md` and key `key`. -+ #[corresponds(HMAC_Init_ex)] -+ pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> { -+ unsafe { -+ // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into -+ // ErrorStack in the returned result by `cvt_p`. -+ let ctx = cvt_p(ffi::HMAC_CTX_new())?; -+ // Safety: -+ // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, -+ // which is the line above. -+ // - HMAC_Init_ex may return an error if key is null but the md is different from -+ // before. This is avoided here since key is guaranteed to be non-null. -+ cvt(ffi::HMAC_Init_ex( -+ ctx, -+ key.as_ptr() as *const c_void, -+ key.len(), -+ md.as_ptr(), -+ ptr::null_mut(), -+ ))?; -+ Ok(Self { -+ ctx, -+ output_size: md.size(), -+ }) -+ } -+ } -+ -+ /// `update` can be called repeatedly with chunks of the message `data` to be authenticated. -+ #[corresponds(HMAC_Update)] -+ pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { -+ unsafe { -+ // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the -+ // returned result by `cvt`. -+ cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ()) -+ } -+ } -+ -+ /// Finishes the HMAC process, and places the message authentication code in `output`. -+ /// The number of bytes written to `output` is returned. -+ /// -+ /// # Panics -+ /// -+ /// Panics if the `output` is smaller than the required size. The output size is indicated by -+ /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will -+ /// always be large enough. -+ #[corresponds(HMAC_Final)] -+ pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> { -+ assert!(output.len() >= self.output_size); -+ unsafe { -+ // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer -+ // than the length of `output`. -+ let mut size: c_uint = 0; -+ cvt(ffi::HMAC_Final( -+ self.ctx, -+ output.as_mut_ptr(), -+ &mut size as *mut c_uint, -+ )) -+ .map(|_| size as usize) -+ } -+ } -+} -+ -+impl Drop for HmacCtx { -+ #[corresponds(HMAC_CTX_free)] -+ fn drop(&mut self) { -+ unsafe { -+ ffi::HMAC_CTX_free(self.ctx); -+ } -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::*; -+ use crate::md::Md; -+ -+ const SHA_256_DIGEST_SIZE: usize = 32; -+ -+ #[test] -+ fn hmac_sha256_test() { -+ let expected_hmac = [ -+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, -+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, -+ 0x2e, 0x32, 0xcf, 0xf7, -+ ]; -+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let data = b"Hi There"; -+ let hmac_result = -+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); -+ assert_eq!(&hmac_result, &expected_hmac); -+ } -+ -+ #[test] -+ #[should_panic] -+ fn hmac_sha256_output_too_short() { -+ let mut out = vec![0_u8; 1]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let data = b"Hi There"; -+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); -+ } -+ -+ #[test] -+ fn hmac_sha256_test_big_buffer() { -+ let expected_hmac = [ -+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, -+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, -+ 0x2e, 0x32, 0xcf, 0xf7, -+ ]; -+ let mut out: [u8; 100] = [0; 100]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let data = b"Hi There"; -+ let hmac_result = -+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); -+ assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE); -+ assert_eq!(&hmac_result, &expected_hmac); -+ } -+ -+ #[test] -+ fn hmac_sha256_update_test() { -+ let expected_hmac = [ -+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, -+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, -+ 0x2e, 0x32, 0xcf, 0xf7, -+ ]; -+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let data = b"Hi There"; -+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); -+ hmac_ctx.update(data).unwrap(); -+ let size = hmac_ctx.finalize(&mut out).unwrap(); -+ assert_eq!(&out, &expected_hmac); -+ assert_eq!(size, SHA_256_DIGEST_SIZE); -+ } -+ -+ #[test] -+ fn hmac_sha256_update_chunks_test() { -+ let expected_hmac = [ -+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, -+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, -+ 0x2e, 0x32, 0xcf, 0xf7, -+ ]; -+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); -+ hmac_ctx.update(b"Hi").unwrap(); -+ hmac_ctx.update(b" There").unwrap(); -+ let size = hmac_ctx.finalize(&mut out).unwrap(); -+ assert_eq!(&out, &expected_hmac); -+ assert_eq!(size, SHA_256_DIGEST_SIZE); -+ } -+ -+ #[test] -+ #[should_panic] -+ fn hmac_sha256_update_output_too_short() { -+ let mut out = vec![0_u8; 1]; -+ let key: [u8; 20] = [0x0b; 20]; -+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); -+ hmac_ctx.update(b"Hi There").unwrap(); -+ hmac_ctx.finalize(&mut out).unwrap(); -+ } -+} -diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs -index 035c90c6..02a51dbb 100644 ---- a/openssl/src/lib.rs -+++ b/openssl/src/lib.rs -@@ -120,6 +120,9 @@ - #![doc(html_root_url = "https://docs.rs/openssl/0.10")] - #![warn(rust_2018_idioms)] - -+#[cfg(all(soong, boringssl))] -+extern crate bssl_ffi as ffi; -+ - #[doc(inline)] - pub use ffi::init; - -@@ -155,6 +158,10 @@ pub mod ex_data; - #[cfg(not(any(libressl, ossl300)))] - pub mod fips; - pub mod hash; -+#[cfg(boringssl)] -+pub mod hkdf; -+#[cfg(boringssl)] -+pub mod hmac; - #[cfg(ossl300)] - pub mod lib_ctx; - pub mod md; -@@ -189,6 +196,11 @@ type LenType = libc::size_t; - #[cfg(not(boringssl))] - type LenType = libc::c_int; - -+#[cfg(boringssl)] -+type SignedLenType = libc::ssize_t; -+#[cfg(not(boringssl))] -+type SignedLenType = libc::c_int; -+ - #[inline] - fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> { - if r.is_null() { -diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs -index 780bd637..92daa882 100644 ---- a/openssl/src/pkey.rs -+++ b/openssl/src/pkey.rs -@@ -47,7 +47,7 @@ use crate::dh::Dh; - use crate::dsa::Dsa; - use crate::ec::EcKey; - use crate::error::ErrorStack; --#[cfg(ossl110)] -+#[cfg(any(boringssl, ossl110))] - use crate::pkey_ctx::PkeyCtx; - use crate::rsa::Rsa; - use crate::symm::Cipher; -@@ -89,11 +89,11 @@ impl Id { - #[cfg(ossl110)] - pub const HKDF: Id = Id(ffi::EVP_PKEY_HKDF); - -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub const ED25519: Id = Id(ffi::EVP_PKEY_ED25519); - #[cfg(ossl111)] - pub const ED448: Id = Id(ffi::EVP_PKEY_ED448); -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub const X25519: Id = Id(ffi::EVP_PKEY_X25519); - #[cfg(ossl111)] - pub const X448: Id = Id(ffi::EVP_PKEY_X448); -@@ -252,7 +252,7 @@ where - /// This function only works for algorithms that support raw public keys. - /// Currently this is: [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`]. - #[corresponds(EVP_PKEY_get_raw_public_key)] -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn raw_public_key(&self) -> Result<Vec<u8>, ErrorStack> { - unsafe { - let mut len = 0; -@@ -303,7 +303,7 @@ where - /// This function only works for algorithms that support raw private keys. - /// Currently this is: [`Id::HMAC`], [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`]. - #[corresponds(EVP_PKEY_get_raw_private_key)] -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn raw_private_key(&self) -> Result<Vec<u8>, ErrorStack> { - unsafe { - let mut len = 0; -@@ -503,7 +503,7 @@ impl PKey<Private> { - ctx.keygen() - } - -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - fn generate_eddsa(id: Id) -> Result<PKey<Private>, ErrorStack> { - let mut ctx = PkeyCtx::new_id(id)?; - ctx.keygen_init()?; -@@ -533,7 +533,7 @@ impl PKey<Private> { - /// assert_eq!(secret.len(), 32); - /// # Ok(()) } - /// ``` -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn generate_x25519() -> Result<PKey<Private>, ErrorStack> { - PKey::generate_eddsa(Id::X25519) - } -@@ -587,7 +587,7 @@ impl PKey<Private> { - /// assert_eq!(signature.len(), 64); - /// # Ok(()) } - /// ``` -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn generate_ed25519() -> Result<PKey<Private>, ErrorStack> { - PKey::generate_eddsa(Id::ED25519) - } -@@ -737,7 +737,7 @@ impl PKey<Private> { - /// - /// Algorithm types that support raw private keys are HMAC, X25519, ED25519, X448 or ED448 - #[corresponds(EVP_PKEY_new_raw_private_key)] -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn private_key_from_raw_bytes( - bytes: &[u8], - key_type: Id, -@@ -778,7 +778,7 @@ impl PKey<Public> { - /// - /// Algorithm types that support raw public keys are X25519, ED25519, X448 or ED448 - #[corresponds(EVP_PKEY_new_raw_public_key)] -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn public_key_from_raw_bytes( - bytes: &[u8], - key_type: Id, -diff --git a/openssl/src/sign.rs b/openssl/src/sign.rs -index 9cfda481..e5b17a87 100644 ---- a/openssl/src/sign.rs -+++ b/openssl/src/sign.rs -@@ -290,7 +290,7 @@ impl<'a> Signer<'a> { - self.len_intern() - } - -- #[cfg(not(ossl111))] -+ #[cfg(not(any(boringssl, ossl111)))] - fn len_intern(&self) -> Result<usize, ErrorStack> { - unsafe { - let mut len = 0; -@@ -303,7 +303,7 @@ impl<'a> Signer<'a> { - } - } - -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - fn len_intern(&self) -> Result<usize, ErrorStack> { - unsafe { - let mut len = 0; -@@ -360,7 +360,7 @@ impl<'a> Signer<'a> { - /// OpenSSL documentation at [`EVP_DigestSign`]. - /// - /// [`EVP_DigestSign`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestSign.html -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn sign_oneshot( - &mut self, - sig_buf: &mut [u8], -@@ -382,7 +382,7 @@ impl<'a> Signer<'a> { - /// Returns the signature. - /// - /// This is a simple convenience wrapper over `len` and `sign_oneshot`. -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn sign_oneshot_to_vec(&mut self, data_buf: &[u8]) -> Result<Vec<u8>, ErrorStack> { - let mut sig_buf = vec![0; self.len()?]; - let len = self.sign_oneshot(&mut sig_buf, data_buf)?; -@@ -596,7 +596,7 @@ impl<'a> Verifier<'a> { - /// OpenSSL documentation at [`EVP_DigestVerify`]. - /// - /// [`EVP_DigestVerify`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestVerify.html -- #[cfg(ossl111)] -+ #[cfg(any(boringssl, ossl111))] - pub fn verify_oneshot(&mut self, signature: &[u8], buf: &[u8]) -> Result<bool, ErrorStack> { - unsafe { - let r = ffi::EVP_DigestVerify( -diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs -index 911a7ab2..75b0365c 100644 ---- a/openssl/src/symm.rs -+++ b/openssl/src/symm.rs -@@ -119,6 +119,7 @@ impl Cipher { - unsafe { Cipher(ffi::EVP_aes_128_cfb1()) } - } - -+ #[cfg(not(boringssl))] - pub fn aes_128_cfb128() -> Cipher { - unsafe { Cipher(ffi::EVP_aes_128_cfb128()) } - } -@@ -164,6 +165,7 @@ impl Cipher { - unsafe { Cipher(ffi::EVP_aes_192_cfb1()) } - } - -+ #[cfg(not(boringssl))] - pub fn aes_192_cfb128() -> Cipher { - unsafe { Cipher(ffi::EVP_aes_192_cfb128()) } - } -@@ -214,6 +216,7 @@ impl Cipher { - unsafe { Cipher(ffi::EVP_aes_256_cfb1()) } - } - -+ #[cfg(not(boringssl))] - pub fn aes_256_cfb128() -> Cipher { - unsafe { Cipher(ffi::EVP_aes_256_cfb128()) } - } -@@ -242,12 +245,12 @@ impl Cipher { - unsafe { Cipher(ffi::EVP_aes_256_ocb()) } - } - -- #[cfg(not(osslconf = "OPENSSL_NO_BF"))] -+ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))] - pub fn bf_cbc() -> Cipher { - unsafe { Cipher(ffi::EVP_bf_cbc()) } - } - -- #[cfg(not(osslconf = "OPENSSL_NO_BF"))] -+ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))] - pub fn bf_ecb() -> Cipher { - unsafe { Cipher(ffi::EVP_bf_ecb()) } - } -diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs -index 940c8c9c..f9477e4a 100644 ---- a/openssl/src/x509/mod.rs -+++ b/openssl/src/x509/mod.rs -@@ -356,6 +356,19 @@ impl X509Builder { - unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) } - } - -+ /// Signs the certificate with a private key but without a digest. -+ /// -+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null -+ /// message digest. -+ #[cfg(boringssl)] -+ #[corresponds(X509_sign)] -+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) } -+ } -+ - /// Consumes the builder, returning the certificate. - pub fn build(self) -> X509 { - self.0 -@@ -898,13 +911,13 @@ impl X509NameBuilder { - pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> { - unsafe { - let field = CString::new(field).unwrap(); -- assert!(value.len() <= c_int::max_value() as usize); -+ assert!(value.len() <= isize::max_value() as usize); - cvt(ffi::X509_NAME_add_entry_by_txt( - self.0.as_ptr(), - field.as_ptr() as *mut _, - ffi::MBSTRING_UTF8, - value.as_ptr(), -- value.len() as c_int, -+ value.len() as isize, - -1, - 0, - )) -@@ -925,13 +938,13 @@ impl X509NameBuilder { - ) -> Result<(), ErrorStack> { - unsafe { - let field = CString::new(field).unwrap(); -- assert!(value.len() <= c_int::max_value() as usize); -+ assert!(value.len() <= isize::max_value() as usize); - cvt(ffi::X509_NAME_add_entry_by_txt( - self.0.as_ptr(), - field.as_ptr() as *mut _, - ty.as_raw(), - value.as_ptr(), -- value.len() as c_int, -+ value.len() as isize, - -1, - 0, - )) -@@ -946,13 +959,13 @@ impl X509NameBuilder { - /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html - pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> { - unsafe { -- assert!(value.len() <= c_int::max_value() as usize); -+ assert!(value.len() <= isize::max_value() as usize); - cvt(ffi::X509_NAME_add_entry_by_NID( - self.0.as_ptr(), - field.as_raw(), - ffi::MBSTRING_UTF8, - value.as_ptr() as *mut _, -- value.len() as c_int, -+ value.len() as isize, - -1, - 0, - )) -@@ -972,13 +985,13 @@ impl X509NameBuilder { - ty: Asn1Type, - ) -> Result<(), ErrorStack> { - unsafe { -- assert!(value.len() <= c_int::max_value() as usize); -+ assert!(value.len() <= isize::max_value() as usize); - cvt(ffi::X509_NAME_add_entry_by_NID( - self.0.as_ptr(), - field.as_raw(), - ty.as_raw(), - value.as_ptr() as *mut _, -- value.len() as c_int, -+ value.len() as isize, - -1, - 0, - )) -@@ -1286,6 +1299,29 @@ impl X509ReqBuilder { - } - } - -+ /// Sign the request using a private key without a digest. -+ /// -+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null -+ /// message digest. -+ /// -+ /// This corresponds to [`X509_REQ_sign`]. -+ /// -+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html -+ #[cfg(boringssl)] -+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { -+ cvt(ffi::X509_REQ_sign( -+ self.0.as_ptr(), -+ key.as_ptr(), -+ ptr::null(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ - /// Returns the `X509Req`. - pub fn build(self) -> X509Req { - self.0 -diff --git a/openssl/src/x509/mod.rs.orig b/openssl/src/x509/mod.rs.orig -new file mode 100644 -index 00000000..34b86c10 ---- /dev/null -+++ b/openssl/src/x509/mod.rs.orig -@@ -0,0 +1,1879 @@ -+//! The standard defining the format of public key certificates. -+//! -+//! An `X509` certificate binds an identity to a public key, and is either -+//! signed by a certificate authority (CA) or self-signed. An entity that gets -+//! a hold of a certificate can both verify your identity (via a CA) and encrypt -+//! data with the included public key. `X509` certificates are used in many -+//! Internet protocols, including SSL/TLS, which is the basis for HTTPS, -+//! the secure protocol for browsing the web. -+ -+use cfg_if::cfg_if; -+use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; -+use libc::{c_int, c_long, c_uint}; -+use std::cmp::{self, Ordering}; -+use std::convert::TryFrom; -+use std::error::Error; -+use std::ffi::{CStr, CString}; -+use std::fmt; -+use std::marker::PhantomData; -+use std::mem; -+use std::net::IpAddr; -+use std::path::Path; -+use std::ptr; -+use std::slice; -+use std::str; -+ -+use crate::asn1::{ -+ Asn1BitStringRef, Asn1IntegerRef, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef, Asn1Type, -+}; -+use crate::bio::MemBioSlice; -+use crate::conf::ConfRef; -+use crate::error::ErrorStack; -+use crate::ex_data::Index; -+use crate::hash::{DigestBytes, MessageDigest}; -+use crate::nid::Nid; -+use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public}; -+use crate::ssl::SslRef; -+use crate::stack::{Stack, StackRef, Stackable}; -+use crate::string::OpensslString; -+use crate::util::{ForeignTypeExt, ForeignTypeRefExt}; -+use crate::{cvt, cvt_n, cvt_p}; -+use openssl_macros::corresponds; -+ -+#[cfg(any(ossl102, libressl261))] -+pub mod verify; -+ -+pub mod extension; -+pub mod store; -+ -+#[cfg(test)] -+mod tests; -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_STORE_CTX; -+ fn drop = ffi::X509_STORE_CTX_free; -+ -+ /// An `X509` certificate store context. -+ pub struct X509StoreContext; -+ -+ /// A reference to an [`X509StoreContext`]. -+ pub struct X509StoreContextRef; -+} -+ -+impl X509StoreContext { -+ /// Returns the index which can be used to obtain a reference to the `Ssl` associated with a -+ /// context. -+ #[corresponds(SSL_get_ex_data_X509_STORE_CTX_idx)] -+ pub fn ssl_idx() -> Result<Index<X509StoreContext, SslRef>, ErrorStack> { -+ unsafe { cvt_n(ffi::SSL_get_ex_data_X509_STORE_CTX_idx()).map(|idx| Index::from_raw(idx)) } -+ } -+ -+ /// Creates a new `X509StoreContext` instance. -+ #[corresponds(X509_STORE_CTX_new)] -+ pub fn new() -> Result<X509StoreContext, ErrorStack> { -+ unsafe { -+ ffi::init(); -+ cvt_p(ffi::X509_STORE_CTX_new()).map(X509StoreContext) -+ } -+ } -+} -+ -+impl X509StoreContextRef { -+ /// Returns application data pertaining to an `X509` store context. -+ #[corresponds(X509_STORE_CTX_get_ex_data)] -+ pub fn ex_data<T>(&self, index: Index<X509StoreContext, T>) -> Option<&T> { -+ unsafe { -+ let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw()); -+ if data.is_null() { -+ None -+ } else { -+ Some(&*(data as *const T)) -+ } -+ } -+ } -+ -+ /// Returns the error code of the context. -+ #[corresponds(X509_STORE_CTX_get_error)] -+ pub fn error(&self) -> X509VerifyResult { -+ unsafe { X509VerifyResult::from_raw(ffi::X509_STORE_CTX_get_error(self.as_ptr())) } -+ } -+ -+ /// Initializes this context with the given certificate, certificates chain and certificate -+ /// store. After initializing the context, the `with_context` closure is called with the prepared -+ /// context. As long as the closure is running, the context stays initialized and can be used -+ /// to e.g. verify a certificate. The context will be cleaned up, after the closure finished. -+ /// -+ /// * `trust` - The certificate store with the trusted certificates. -+ /// * `cert` - The certificate that should be verified. -+ /// * `cert_chain` - The certificates chain. -+ /// * `with_context` - The closure that is called with the initialized context. -+ /// -+ /// This corresponds to [`X509_STORE_CTX_init`] before calling `with_context` and to -+ /// [`X509_STORE_CTX_cleanup`] after calling `with_context`. -+ /// -+ /// [`X509_STORE_CTX_init`]: https://www.openssl.org/docs/manmaster/crypto/X509_STORE_CTX_init.html -+ /// [`X509_STORE_CTX_cleanup`]: https://www.openssl.org/docs/manmaster/crypto/X509_STORE_CTX_cleanup.html -+ pub fn init<F, T>( -+ &mut self, -+ trust: &store::X509StoreRef, -+ cert: &X509Ref, -+ cert_chain: &StackRef<X509>, -+ with_context: F, -+ ) -> Result<T, ErrorStack> -+ where -+ F: FnOnce(&mut X509StoreContextRef) -> Result<T, ErrorStack>, -+ { -+ struct Cleanup<'a>(&'a mut X509StoreContextRef); -+ -+ impl<'a> Drop for Cleanup<'a> { -+ fn drop(&mut self) { -+ unsafe { -+ ffi::X509_STORE_CTX_cleanup(self.0.as_ptr()); -+ } -+ } -+ } -+ -+ unsafe { -+ cvt(ffi::X509_STORE_CTX_init( -+ self.as_ptr(), -+ trust.as_ptr(), -+ cert.as_ptr(), -+ cert_chain.as_ptr(), -+ ))?; -+ -+ let cleanup = Cleanup(self); -+ with_context(cleanup.0) -+ } -+ } -+ -+ /// Verifies the stored certificate. -+ /// -+ /// Returns `true` if verification succeeds. The `error` method will return the specific -+ /// validation error if the certificate was not valid. -+ /// -+ /// This will only work inside of a call to `init`. -+ #[corresponds(X509_verify_cert)] -+ pub fn verify_cert(&mut self) -> Result<bool, ErrorStack> { -+ unsafe { cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0) } -+ } -+ -+ /// Set the error code of the context. -+ #[corresponds(X509_STORE_CTX_set_error)] -+ pub fn set_error(&mut self, result: X509VerifyResult) { -+ unsafe { -+ ffi::X509_STORE_CTX_set_error(self.as_ptr(), result.as_raw()); -+ } -+ } -+ -+ /// Returns a reference to the certificate which caused the error or None if -+ /// no certificate is relevant to the error. -+ #[corresponds(X509_STORE_CTX_get_current_cert)] -+ pub fn current_cert(&self) -> Option<&X509Ref> { -+ unsafe { -+ let ptr = ffi::X509_STORE_CTX_get_current_cert(self.as_ptr()); -+ X509Ref::from_const_ptr_opt(ptr) -+ } -+ } -+ -+ /// Returns a non-negative integer representing the depth in the certificate -+ /// chain where the error occurred. If it is zero it occurred in the end -+ /// entity certificate, one if it is the certificate which signed the end -+ /// entity certificate and so on. -+ #[corresponds(X509_STORE_CTX_get_error_depth)] -+ pub fn error_depth(&self) -> u32 { -+ unsafe { ffi::X509_STORE_CTX_get_error_depth(self.as_ptr()) as u32 } -+ } -+ -+ /// Returns a reference to a complete valid `X509` certificate chain. -+ #[corresponds(X509_STORE_CTX_get0_chain)] -+ pub fn chain(&self) -> Option<&StackRef<X509>> { -+ unsafe { -+ let chain = X509_STORE_CTX_get0_chain(self.as_ptr()); -+ -+ if chain.is_null() { -+ None -+ } else { -+ Some(StackRef::from_ptr(chain)) -+ } -+ } -+ } -+} -+ -+/// A builder used to construct an `X509`. -+pub struct X509Builder(X509); -+ -+impl X509Builder { -+ /// Creates a new builder. -+ #[corresponds(X509_new)] -+ pub fn new() -> Result<X509Builder, ErrorStack> { -+ unsafe { -+ ffi::init(); -+ cvt_p(ffi::X509_new()).map(|p| X509Builder(X509(p))) -+ } -+ } -+ -+ /// Sets the notAfter constraint on the certificate. -+ #[corresponds(X509_set1_notAfter)] -+ pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> { -+ unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) } -+ } -+ -+ /// Sets the notBefore constraint on the certificate. -+ #[corresponds(X509_set1_notBefore)] -+ pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> { -+ unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) } -+ } -+ -+ /// Sets the version of the certificate. -+ /// -+ /// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of -+ /// the X.509 standard should pass `2` to this method. -+ #[corresponds(X509_set_version)] -+ #[allow(clippy::useless_conversion)] -+ pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { -+ unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version as c_long)).map(|_| ()) } -+ } -+ -+ /// Sets the serial number of the certificate. -+ #[corresponds(X509_set_serialNumber)] -+ pub fn set_serial_number(&mut self, serial_number: &Asn1IntegerRef) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_set_serialNumber( -+ self.0.as_ptr(), -+ serial_number.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Sets the issuer name of the certificate. -+ #[corresponds(X509_set_issuer_name)] -+ pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_set_issuer_name( -+ self.0.as_ptr(), -+ issuer_name.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Sets the subject name of the certificate. -+ /// -+ /// When building certificates, the `C`, `ST`, and `O` options are common when using the openssl command line tools. -+ /// The `CN` field is used for the common name, such as a DNS name. -+ /// -+ /// ``` -+ /// use openssl::x509::{X509, X509NameBuilder}; -+ /// -+ /// let mut x509_name = openssl::x509::X509NameBuilder::new().unwrap(); -+ /// x509_name.append_entry_by_text("C", "US").unwrap(); -+ /// x509_name.append_entry_by_text("ST", "CA").unwrap(); -+ /// x509_name.append_entry_by_text("O", "Some organization").unwrap(); -+ /// x509_name.append_entry_by_text("CN", "www.example.com").unwrap(); -+ /// let x509_name = x509_name.build(); -+ /// -+ /// let mut x509 = openssl::x509::X509::builder().unwrap(); -+ /// x509.set_subject_name(&x509_name).unwrap(); -+ /// ``` -+ #[corresponds(X509_set_subject_name)] -+ pub fn set_subject_name(&mut self, subject_name: &X509NameRef) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_set_subject_name( -+ self.0.as_ptr(), -+ subject_name.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Sets the public key associated with the certificate. -+ #[corresponds(X509_set_pubkey)] -+ pub fn set_pubkey<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPublic, -+ { -+ unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } -+ } -+ -+ /// Returns a context object which is needed to create certain X509 extension values. -+ /// -+ /// Set `issuer` to `None` if the certificate will be self-signed. -+ #[corresponds(X509V3_set_ctx)] -+ pub fn x509v3_context<'a>( -+ &'a self, -+ issuer: Option<&'a X509Ref>, -+ conf: Option<&'a ConfRef>, -+ ) -> X509v3Context<'a> { -+ unsafe { -+ let mut ctx = mem::zeroed(); -+ -+ let issuer = match issuer { -+ Some(issuer) => issuer.as_ptr(), -+ None => self.0.as_ptr(), -+ }; -+ let subject = self.0.as_ptr(); -+ ffi::X509V3_set_ctx( -+ &mut ctx, -+ issuer, -+ subject, -+ ptr::null_mut(), -+ ptr::null_mut(), -+ 0, -+ ); -+ -+ // nodb case taken care of since we zeroed ctx above -+ if let Some(conf) = conf { -+ ffi::X509V3_set_nconf(&mut ctx, conf.as_ptr()); -+ } -+ -+ X509v3Context(ctx, PhantomData) -+ } -+ } -+ -+ /// Adds an X509 extension value to the certificate. -+ /// -+ /// This works just as `append_extension` except it takes ownership of the `X509Extension`. -+ pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> { -+ self.append_extension2(&extension) -+ } -+ -+ /// Adds an X509 extension value to the certificate. -+ #[corresponds(X509_add_ext)] -+ pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?; -+ Ok(()) -+ } -+ } -+ -+ /// Signs the certificate with a private key. -+ #[corresponds(X509_sign)] -+ pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) } -+ } -+ -+ /// Signs the certificate with a private key but without a digest. -+ /// -+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null -+ /// message digest. -+ #[cfg(boringssl)] -+ #[corresponds(X509_sign)] -+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) } -+ } -+ -+ /// Consumes the builder, returning the certificate. -+ pub fn build(self) -> X509 { -+ self.0 -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509; -+ fn drop = ffi::X509_free; -+ -+ /// An `X509` public key certificate. -+ pub struct X509; -+ /// Reference to `X509`. -+ pub struct X509Ref; -+} -+ -+#[cfg(boringssl)] -+type X509LenTy = c_uint; -+#[cfg(not(boringssl))] -+type X509LenTy = c_int; -+ -+impl X509Ref { -+ /// Returns this certificate's subject name. -+ #[corresponds(X509_get_subject_name)] -+ pub fn subject_name(&self) -> &X509NameRef { -+ unsafe { -+ let name = ffi::X509_get_subject_name(self.as_ptr()); -+ X509NameRef::from_const_ptr_opt(name).expect("subject name must not be null") -+ } -+ } -+ -+ /// Returns the hash of the certificates subject -+ #[corresponds(X509_subject_name_hash)] -+ pub fn subject_name_hash(&self) -> u32 { -+ unsafe { ffi::X509_subject_name_hash(self.as_ptr()) as u32 } -+ } -+ -+ /// Returns this certificate's issuer name. -+ #[corresponds(X509_get_issuer_name)] -+ pub fn issuer_name(&self) -> &X509NameRef { -+ unsafe { -+ let name = ffi::X509_get_issuer_name(self.as_ptr()); -+ X509NameRef::from_const_ptr_opt(name).expect("issuer name must not be null") -+ } -+ } -+ -+ /// Returns the hash of the certificates issuer -+ #[corresponds(X509_issuer_name_hash)] -+ pub fn issuer_name_hash(&self) -> u32 { -+ unsafe { ffi::X509_issuer_name_hash(self.as_ptr()) as u32 } -+ } -+ -+ /// Returns this certificate's subject alternative name entries, if they exist. -+ #[corresponds(X509_get_ext_d2i)] -+ pub fn subject_alt_names(&self) -> Option<Stack<GeneralName>> { -+ unsafe { -+ let stack = ffi::X509_get_ext_d2i( -+ self.as_ptr(), -+ ffi::NID_subject_alt_name, -+ ptr::null_mut(), -+ ptr::null_mut(), -+ ); -+ Stack::from_ptr_opt(stack as *mut _) -+ } -+ } -+ -+ /// Returns this certificate's issuer alternative name entries, if they exist. -+ #[corresponds(X509_get_ext_d2i)] -+ pub fn issuer_alt_names(&self) -> Option<Stack<GeneralName>> { -+ unsafe { -+ let stack = ffi::X509_get_ext_d2i( -+ self.as_ptr(), -+ ffi::NID_issuer_alt_name, -+ ptr::null_mut(), -+ ptr::null_mut(), -+ ); -+ Stack::from_ptr_opt(stack as *mut _) -+ } -+ } -+ -+ /// Returns this certificate's [`authority information access`] entries, if they exist. -+ /// -+ /// [`authority information access`]: https://tools.ietf.org/html/rfc5280#section-4.2.2.1 -+ #[corresponds(X509_get_ext_d2i)] -+ pub fn authority_info(&self) -> Option<Stack<AccessDescription>> { -+ unsafe { -+ let stack = ffi::X509_get_ext_d2i( -+ self.as_ptr(), -+ ffi::NID_info_access, -+ ptr::null_mut(), -+ ptr::null_mut(), -+ ); -+ Stack::from_ptr_opt(stack as *mut _) -+ } -+ } -+ -+ #[corresponds(X509_get_pubkey)] -+ pub fn public_key(&self) -> Result<PKey<Public>, ErrorStack> { -+ unsafe { -+ let pkey = cvt_p(ffi::X509_get_pubkey(self.as_ptr()))?; -+ Ok(PKey::from_ptr(pkey)) -+ } -+ } -+ -+ /// Returns a digest of the DER representation of the certificate. -+ #[corresponds(X509_digest)] -+ pub fn digest(&self, hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> { -+ unsafe { -+ let mut digest = DigestBytes { -+ buf: [0; ffi::EVP_MAX_MD_SIZE as usize], -+ len: ffi::EVP_MAX_MD_SIZE as usize, -+ }; -+ let mut len = ffi::EVP_MAX_MD_SIZE as c_uint; -+ cvt(ffi::X509_digest( -+ self.as_ptr(), -+ hash_type.as_ptr(), -+ digest.buf.as_mut_ptr() as *mut _, -+ &mut len, -+ ))?; -+ digest.len = len as usize; -+ -+ Ok(digest) -+ } -+ } -+ -+ #[deprecated(since = "0.10.9", note = "renamed to digest")] -+ pub fn fingerprint(&self, hash_type: MessageDigest) -> Result<Vec<u8>, ErrorStack> { -+ self.digest(hash_type).map(|b| b.to_vec()) -+ } -+ -+ /// Returns the certificate's Not After validity period. -+ #[corresponds(X509_getm_notAfter)] -+ pub fn not_after(&self) -> &Asn1TimeRef { -+ unsafe { -+ let date = X509_getm_notAfter(self.as_ptr()); -+ Asn1TimeRef::from_const_ptr_opt(date).expect("not_after must not be null") -+ } -+ } -+ -+ /// Returns the certificate's Not Before validity period. -+ #[corresponds(X509_getm_notBefore)] -+ pub fn not_before(&self) -> &Asn1TimeRef { -+ unsafe { -+ let date = X509_getm_notBefore(self.as_ptr()); -+ Asn1TimeRef::from_const_ptr_opt(date).expect("not_before must not be null") -+ } -+ } -+ -+ /// Returns the certificate's signature -+ #[corresponds(X509_get0_signature)] -+ pub fn signature(&self) -> &Asn1BitStringRef { -+ unsafe { -+ let mut signature = ptr::null(); -+ X509_get0_signature(&mut signature, ptr::null_mut(), self.as_ptr()); -+ Asn1BitStringRef::from_const_ptr_opt(signature).expect("signature must not be null") -+ } -+ } -+ -+ /// Returns the certificate's signature algorithm. -+ #[corresponds(X509_get0_signature)] -+ pub fn signature_algorithm(&self) -> &X509AlgorithmRef { -+ unsafe { -+ let mut algor = ptr::null(); -+ X509_get0_signature(ptr::null_mut(), &mut algor, self.as_ptr()); -+ X509AlgorithmRef::from_const_ptr_opt(algor) -+ .expect("signature algorithm must not be null") -+ } -+ } -+ -+ /// Returns the list of OCSP responder URLs specified in the certificate's Authority Information -+ /// Access field. -+ #[corresponds(X509_get1_ocsp)] -+ pub fn ocsp_responders(&self) -> Result<Stack<OpensslString>, ErrorStack> { -+ unsafe { cvt_p(ffi::X509_get1_ocsp(self.as_ptr())).map(|p| Stack::from_ptr(p)) } -+ } -+ -+ /// Checks that this certificate issued `subject`. -+ #[corresponds(X509_check_issued)] -+ pub fn issued(&self, subject: &X509Ref) -> X509VerifyResult { -+ unsafe { -+ let r = ffi::X509_check_issued(self.as_ptr(), subject.as_ptr()); -+ X509VerifyResult::from_raw(r) -+ } -+ } -+ -+ /// Returns certificate version. If this certificate has no explicit version set, it defaults to -+ /// version 1. -+ /// -+ /// Note that `0` return value stands for version 1, `1` for version 2 and so on. -+ #[corresponds(X509_get_version)] -+ #[cfg(ossl110)] -+ pub fn version(&self) -> i32 { -+ unsafe { ffi::X509_get_version(self.as_ptr()) as i32 } -+ } -+ -+ /// Check if the certificate is signed using the given public key. -+ /// -+ /// Only the signature is checked: no other checks (such as certificate chain validity) -+ /// are performed. -+ /// -+ /// Returns `true` if verification succeeds. -+ #[corresponds(X509_verify)] -+ pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack> -+ where -+ T: HasPublic, -+ { -+ unsafe { cvt_n(ffi::X509_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) } -+ } -+ -+ /// Returns this certificate's serial number. -+ #[corresponds(X509_get_serialNumber)] -+ pub fn serial_number(&self) -> &Asn1IntegerRef { -+ unsafe { -+ let r = ffi::X509_get_serialNumber(self.as_ptr()); -+ Asn1IntegerRef::from_const_ptr_opt(r).expect("serial number must not be null") -+ } -+ } -+ -+ to_pem! { -+ /// Serializes the certificate into a PEM-encoded X509 structure. -+ /// -+ /// The output will have a header of `-----BEGIN CERTIFICATE-----`. -+ #[corresponds(PEM_write_bio_X509)] -+ to_pem, -+ ffi::PEM_write_bio_X509 -+ } -+ -+ to_der! { -+ /// Serializes the certificate into a DER-encoded X509 structure. -+ #[corresponds(i2d_X509)] -+ to_der, -+ ffi::i2d_X509 -+ } -+ -+ to_pem! { -+ /// Converts the certificate to human readable text. -+ #[corresponds(X509_print)] -+ to_text, -+ ffi::X509_print -+ } -+} -+ -+impl ToOwned for X509Ref { -+ type Owned = X509; -+ -+ fn to_owned(&self) -> X509 { -+ unsafe { -+ X509_up_ref(self.as_ptr()); -+ X509::from_ptr(self.as_ptr()) -+ } -+ } -+} -+ -+impl Ord for X509Ref { -+ fn cmp(&self, other: &Self) -> cmp::Ordering { -+ // X509_cmp returns a number <0 for less than, 0 for equal and >0 for greater than. -+ // It can't fail if both pointers are valid, which we know is true. -+ let cmp = unsafe { ffi::X509_cmp(self.as_ptr(), other.as_ptr()) }; -+ cmp.cmp(&0) -+ } -+} -+ -+impl PartialOrd for X509Ref { -+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { -+ Some(self.cmp(other)) -+ } -+} -+ -+impl PartialOrd<X509> for X509Ref { -+ fn partial_cmp(&self, other: &X509) -> Option<cmp::Ordering> { -+ <X509Ref as PartialOrd<X509Ref>>::partial_cmp(self, other) -+ } -+} -+ -+impl PartialEq for X509Ref { -+ fn eq(&self, other: &Self) -> bool { -+ self.cmp(other) == cmp::Ordering::Equal -+ } -+} -+ -+impl PartialEq<X509> for X509Ref { -+ fn eq(&self, other: &X509) -> bool { -+ <X509Ref as PartialEq<X509Ref>>::eq(self, other) -+ } -+} -+ -+impl Eq for X509Ref {} -+ -+impl X509 { -+ /// Returns a new builder. -+ pub fn builder() -> Result<X509Builder, ErrorStack> { -+ X509Builder::new() -+ } -+ -+ from_pem! { -+ /// Deserializes a PEM-encoded X509 structure. -+ /// -+ /// The input should have a header of `-----BEGIN CERTIFICATE-----`. -+ #[corresponds(PEM_read_bio_X509)] -+ from_pem, -+ X509, -+ ffi::PEM_read_bio_X509 -+ } -+ -+ from_der! { -+ /// Deserializes a DER-encoded X509 structure. -+ #[corresponds(d2i_X509)] -+ from_der, -+ X509, -+ ffi::d2i_X509 -+ } -+ -+ /// Deserializes a list of PEM-formatted certificates. -+ #[corresponds(PEM_read_bio_X509)] -+ pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509>, ErrorStack> { -+ unsafe { -+ ffi::init(); -+ let bio = MemBioSlice::new(pem)?; -+ -+ let mut certs = vec![]; -+ loop { -+ let r = -+ ffi::PEM_read_bio_X509(bio.as_ptr(), ptr::null_mut(), None, ptr::null_mut()); -+ if r.is_null() { -+ let err = ffi::ERR_peek_last_error(); -+ if ffi::ERR_GET_LIB(err) as X509LenTy == ffi::ERR_LIB_PEM -+ && ffi::ERR_GET_REASON(err) == ffi::PEM_R_NO_START_LINE -+ { -+ ffi::ERR_clear_error(); -+ break; -+ } -+ -+ return Err(ErrorStack::get()); -+ } else { -+ certs.push(X509(r)); -+ } -+ } -+ -+ Ok(certs) -+ } -+ } -+} -+ -+impl Clone for X509 { -+ fn clone(&self) -> X509 { -+ X509Ref::to_owned(self) -+ } -+} -+ -+impl fmt::Debug for X509 { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ let serial = match &self.serial_number().to_bn() { -+ Ok(bn) => match bn.to_hex_str() { -+ Ok(hex) => hex.to_string(), -+ Err(_) => "".to_string(), -+ }, -+ Err(_) => "".to_string(), -+ }; -+ let mut debug_struct = formatter.debug_struct("X509"); -+ debug_struct.field("serial_number", &serial); -+ debug_struct.field("signature_algorithm", &self.signature_algorithm().object()); -+ debug_struct.field("issuer", &self.issuer_name()); -+ debug_struct.field("subject", &self.subject_name()); -+ if let Some(subject_alt_names) = &self.subject_alt_names() { -+ debug_struct.field("subject_alt_names", subject_alt_names); -+ } -+ debug_struct.field("not_before", &self.not_before()); -+ debug_struct.field("not_after", &self.not_after()); -+ -+ if let Ok(public_key) = &self.public_key() { -+ debug_struct.field("public_key", public_key); -+ }; -+ // TODO: Print extensions once they are supported on the X509 struct. -+ -+ debug_struct.finish() -+ } -+} -+ -+impl AsRef<X509Ref> for X509Ref { -+ fn as_ref(&self) -> &X509Ref { -+ self -+ } -+} -+ -+impl Stackable for X509 { -+ type StackType = ffi::stack_st_X509; -+} -+ -+impl Ord for X509 { -+ fn cmp(&self, other: &Self) -> cmp::Ordering { -+ X509Ref::cmp(self, other) -+ } -+} -+ -+impl PartialOrd for X509 { -+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { -+ X509Ref::partial_cmp(self, other) -+ } -+} -+ -+impl PartialOrd<X509Ref> for X509 { -+ fn partial_cmp(&self, other: &X509Ref) -> Option<cmp::Ordering> { -+ X509Ref::partial_cmp(self, other) -+ } -+} -+ -+impl PartialEq for X509 { -+ fn eq(&self, other: &Self) -> bool { -+ X509Ref::eq(self, other) -+ } -+} -+ -+impl PartialEq<X509Ref> for X509 { -+ fn eq(&self, other: &X509Ref) -> bool { -+ X509Ref::eq(self, other) -+ } -+} -+ -+impl Eq for X509 {} -+ -+/// A context object required to construct certain `X509` extension values. -+pub struct X509v3Context<'a>(ffi::X509V3_CTX, PhantomData<(&'a X509Ref, &'a ConfRef)>); -+ -+impl<'a> X509v3Context<'a> { -+ pub fn as_ptr(&self) -> *mut ffi::X509V3_CTX { -+ &self.0 as *const _ as *mut _ -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_EXTENSION; -+ fn drop = ffi::X509_EXTENSION_free; -+ -+ /// Permit additional fields to be added to an `X509` v3 certificate. -+ pub struct X509Extension; -+ /// Reference to `X509Extension`. -+ pub struct X509ExtensionRef; -+} -+ -+impl Stackable for X509Extension { -+ type StackType = ffi::stack_st_X509_EXTENSION; -+} -+ -+impl X509Extension { -+ /// Constructs an X509 extension value. See `man x509v3_config` for information on supported -+ /// names and their value formats. -+ /// -+ /// Some extension types, such as `subjectAlternativeName`, require an `X509v3Context` to be -+ /// provided. -+ /// -+ /// See the extension module for builder types which will construct certain common extensions. -+ pub fn new( -+ conf: Option<&ConfRef>, -+ context: Option<&X509v3Context<'_>>, -+ name: &str, -+ value: &str, -+ ) -> Result<X509Extension, ErrorStack> { -+ let name = CString::new(name).unwrap(); -+ let value = CString::new(value).unwrap(); -+ unsafe { -+ ffi::init(); -+ let conf = conf.map_or(ptr::null_mut(), ConfRef::as_ptr); -+ let context = context.map_or(ptr::null_mut(), X509v3Context::as_ptr); -+ let name = name.as_ptr() as *mut _; -+ let value = value.as_ptr() as *mut _; -+ -+ cvt_p(ffi::X509V3_EXT_nconf(conf, context, name, value)).map(X509Extension) -+ } -+ } -+ -+ /// Constructs an X509 extension value. See `man x509v3_config` for information on supported -+ /// extensions and their value formats. -+ /// -+ /// Some extension types, such as `nid::SUBJECT_ALTERNATIVE_NAME`, require an `X509v3Context` to -+ /// be provided. -+ /// -+ /// See the extension module for builder types which will construct certain common extensions. -+ pub fn new_nid( -+ conf: Option<&ConfRef>, -+ context: Option<&X509v3Context<'_>>, -+ name: Nid, -+ value: &str, -+ ) -> Result<X509Extension, ErrorStack> { -+ let value = CString::new(value).unwrap(); -+ unsafe { -+ ffi::init(); -+ let conf = conf.map_or(ptr::null_mut(), ConfRef::as_ptr); -+ let context = context.map_or(ptr::null_mut(), X509v3Context::as_ptr); -+ let name = name.as_raw(); -+ let value = value.as_ptr() as *mut _; -+ -+ cvt_p(ffi::X509V3_EXT_nconf_nid(conf, context, name, value)).map(X509Extension) -+ } -+ } -+ -+ /// Adds an alias for an extension -+ /// -+ /// # Safety -+ /// -+ /// This method modifies global state without locking and therefore is not thread safe -+ #[corresponds(X509V3_EXT_add_alias)] -+ pub unsafe fn add_alias(to: Nid, from: Nid) -> Result<(), ErrorStack> { -+ ffi::init(); -+ cvt(ffi::X509V3_EXT_add_alias(to.as_raw(), from.as_raw())).map(|_| ()) -+ } -+} -+ -+/// A builder used to construct an `X509Name`. -+pub struct X509NameBuilder(X509Name); -+ -+impl X509NameBuilder { -+ /// Creates a new builder. -+ pub fn new() -> Result<X509NameBuilder, ErrorStack> { -+ unsafe { -+ ffi::init(); -+ cvt_p(ffi::X509_NAME_new()).map(|p| X509NameBuilder(X509Name(p))) -+ } -+ } -+ -+ /// Add a name entry -+ #[corresponds(X509_NAME_add_entry)] -+ #[cfg(any(ossl101, libressl350))] -+ pub fn append_entry(&mut self, ne: &X509NameEntryRef) -> std::result::Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_NAME_add_entry( -+ self.0.as_ptr(), -+ ne.as_ptr(), -+ -1, -+ 0, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Add a field entry by str. -+ /// -+ /// This corresponds to [`X509_NAME_add_entry_by_txt`]. -+ /// -+ /// [`X509_NAME_add_entry_by_txt`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_txt.html -+ pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> { -+ unsafe { -+ let field = CString::new(field).unwrap(); -+ assert!(value.len() <= c_int::max_value() as usize); -+ cvt(ffi::X509_NAME_add_entry_by_txt( -+ self.0.as_ptr(), -+ field.as_ptr() as *mut _, -+ ffi::MBSTRING_UTF8, -+ value.as_ptr(), -+ value.len() as c_int, -+ -1, -+ 0, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Add a field entry by str with a specific type. -+ /// -+ /// This corresponds to [`X509_NAME_add_entry_by_txt`]. -+ /// -+ /// [`X509_NAME_add_entry_by_txt`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_txt.html -+ pub fn append_entry_by_text_with_type( -+ &mut self, -+ field: &str, -+ value: &str, -+ ty: Asn1Type, -+ ) -> Result<(), ErrorStack> { -+ unsafe { -+ let field = CString::new(field).unwrap(); -+ assert!(value.len() <= c_int::max_value() as usize); -+ cvt(ffi::X509_NAME_add_entry_by_txt( -+ self.0.as_ptr(), -+ field.as_ptr() as *mut _, -+ ty.as_raw(), -+ value.as_ptr(), -+ value.len() as c_int, -+ -1, -+ 0, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Add a field entry by NID. -+ /// -+ /// This corresponds to [`X509_NAME_add_entry_by_NID`]. -+ /// -+ /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html -+ pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> { -+ unsafe { -+ assert!(value.len() <= c_int::max_value() as usize); -+ cvt(ffi::X509_NAME_add_entry_by_NID( -+ self.0.as_ptr(), -+ field.as_raw(), -+ ffi::MBSTRING_UTF8, -+ value.as_ptr() as *mut _, -+ value.len() as c_int, -+ -1, -+ 0, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Add a field entry by NID with a specific type. -+ /// -+ /// This corresponds to [`X509_NAME_add_entry_by_NID`]. -+ /// -+ /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html -+ pub fn append_entry_by_nid_with_type( -+ &mut self, -+ field: Nid, -+ value: &str, -+ ty: Asn1Type, -+ ) -> Result<(), ErrorStack> { -+ unsafe { -+ assert!(value.len() <= c_int::max_value() as usize); -+ cvt(ffi::X509_NAME_add_entry_by_NID( -+ self.0.as_ptr(), -+ field.as_raw(), -+ ty.as_raw(), -+ value.as_ptr() as *mut _, -+ value.len() as c_int, -+ -1, -+ 0, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Return an `X509Name`. -+ pub fn build(self) -> X509Name { -+ self.0 -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_NAME; -+ fn drop = ffi::X509_NAME_free; -+ -+ /// The names of an `X509` certificate. -+ pub struct X509Name; -+ /// Reference to `X509Name`. -+ pub struct X509NameRef; -+} -+ -+impl X509Name { -+ /// Returns a new builder. -+ pub fn builder() -> Result<X509NameBuilder, ErrorStack> { -+ X509NameBuilder::new() -+ } -+ -+ /// Loads subject names from a file containing PEM-formatted certificates. -+ /// -+ /// This is commonly used in conjunction with `SslContextBuilder::set_client_ca_list`. -+ pub fn load_client_ca_file<P: AsRef<Path>>(file: P) -> Result<Stack<X509Name>, ErrorStack> { -+ let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap(); -+ unsafe { cvt_p(ffi::SSL_load_client_CA_file(file.as_ptr())).map(|p| Stack::from_ptr(p)) } -+ } -+ -+ from_der! { -+ /// Deserializes a DER-encoded X509 name structure. -+ /// -+ /// This corresponds to [`d2i_X509_NAME`]. -+ /// -+ /// [`d2i_X509_NAME`]: https://www.openssl.org/docs/manmaster/man3/d2i_X509_NAME.html -+ from_der, -+ X509Name, -+ ffi::d2i_X509_NAME -+ } -+} -+ -+impl Stackable for X509Name { -+ type StackType = ffi::stack_st_X509_NAME; -+} -+ -+impl X509NameRef { -+ /// Returns the name entries by the nid. -+ pub fn entries_by_nid(&self, nid: Nid) -> X509NameEntries<'_> { -+ X509NameEntries { -+ name: self, -+ nid: Some(nid), -+ loc: -1, -+ } -+ } -+ -+ /// Returns an iterator over all `X509NameEntry` values -+ pub fn entries(&self) -> X509NameEntries<'_> { -+ X509NameEntries { -+ name: self, -+ nid: None, -+ loc: -1, -+ } -+ } -+ -+ /// Compare two names, like [`Ord`] but it may fail. -+ /// -+ /// With OpenSSL versions from 3.0.0 this may return an error if the underlying `X509_NAME_cmp` -+ /// call fails. -+ /// For OpenSSL versions before 3.0.0 it will never return an error, but due to a bug it may -+ /// spuriously return `Ordering::Less` if the `X509_NAME_cmp` call fails. -+ #[corresponds(X509_NAME_cmp)] -+ pub fn try_cmp(&self, other: &X509NameRef) -> Result<Ordering, ErrorStack> { -+ let cmp = unsafe { ffi::X509_NAME_cmp(self.as_ptr(), other.as_ptr()) }; -+ if cfg!(ossl300) && cmp == -2 { -+ return Err(ErrorStack::get()); -+ } -+ Ok(cmp.cmp(&0)) -+ } -+ -+ /// Copies the name to a new `X509Name`. -+ #[corresponds(X509_NAME_dup)] -+ #[cfg(any(boringssl, ossl110, libressl270))] -+ pub fn to_owned(&self) -> Result<X509Name, ErrorStack> { -+ unsafe { cvt_p(ffi::X509_NAME_dup(self.as_ptr())).map(|n| X509Name::from_ptr(n)) } -+ } -+ -+ to_der! { -+ /// Serializes the certificate into a DER-encoded X509 name structure. -+ /// -+ /// This corresponds to [`i2d_X509_NAME`]. -+ /// -+ /// [`i2d_X509_NAME`]: https://www.openssl.org/docs/manmaster/crypto/i2d_X509_NAME.html -+ to_der, -+ ffi::i2d_X509_NAME -+ } -+} -+ -+impl fmt::Debug for X509NameRef { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter.debug_list().entries(self.entries()).finish() -+ } -+} -+ -+/// A type to destructure and examine an `X509Name`. -+pub struct X509NameEntries<'a> { -+ name: &'a X509NameRef, -+ nid: Option<Nid>, -+ loc: c_int, -+} -+ -+impl<'a> Iterator for X509NameEntries<'a> { -+ type Item = &'a X509NameEntryRef; -+ -+ fn next(&mut self) -> Option<&'a X509NameEntryRef> { -+ unsafe { -+ match self.nid { -+ Some(nid) => { -+ // There is a `Nid` specified to search for -+ self.loc = -+ ffi::X509_NAME_get_index_by_NID(self.name.as_ptr(), nid.as_raw(), self.loc); -+ if self.loc == -1 { -+ return None; -+ } -+ } -+ None => { -+ // Iterate over all `Nid`s -+ self.loc += 1; -+ if self.loc >= ffi::X509_NAME_entry_count(self.name.as_ptr()) { -+ return None; -+ } -+ } -+ } -+ -+ let entry = ffi::X509_NAME_get_entry(self.name.as_ptr(), self.loc); -+ -+ Some(X509NameEntryRef::from_const_ptr_opt(entry).expect("entry must not be null")) -+ } -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_NAME_ENTRY; -+ fn drop = ffi::X509_NAME_ENTRY_free; -+ -+ /// A name entry associated with a `X509Name`. -+ pub struct X509NameEntry; -+ /// Reference to `X509NameEntry`. -+ pub struct X509NameEntryRef; -+} -+ -+impl X509NameEntryRef { -+ /// Returns the field value of an `X509NameEntry`. -+ /// -+ /// This corresponds to [`X509_NAME_ENTRY_get_data`]. -+ /// -+ /// [`X509_NAME_ENTRY_get_data`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_ENTRY_get_data.html -+ pub fn data(&self) -> &Asn1StringRef { -+ unsafe { -+ let data = ffi::X509_NAME_ENTRY_get_data(self.as_ptr()); -+ Asn1StringRef::from_ptr(data) -+ } -+ } -+ -+ /// Returns the `Asn1Object` value of an `X509NameEntry`. -+ /// This is useful for finding out about the actual `Nid` when iterating over all `X509NameEntries`. -+ /// -+ /// This corresponds to [`X509_NAME_ENTRY_get_object`]. -+ /// -+ /// [`X509_NAME_ENTRY_get_object`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_ENTRY_get_object.html -+ pub fn object(&self) -> &Asn1ObjectRef { -+ unsafe { -+ let object = ffi::X509_NAME_ENTRY_get_object(self.as_ptr()); -+ Asn1ObjectRef::from_ptr(object) -+ } -+ } -+} -+ -+impl fmt::Debug for X509NameEntryRef { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ formatter.write_fmt(format_args!("{:?} = {:?}", self.object(), self.data())) -+ } -+} -+ -+/// A builder used to construct an `X509Req`. -+pub struct X509ReqBuilder(X509Req); -+ -+impl X509ReqBuilder { -+ /// Returns a builder for a certificate request. -+ /// -+ /// This corresponds to [`X509_REQ_new`]. -+ /// -+ ///[`X509_REQ_new`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_new.html -+ pub fn new() -> Result<X509ReqBuilder, ErrorStack> { -+ unsafe { -+ ffi::init(); -+ cvt_p(ffi::X509_REQ_new()).map(|p| X509ReqBuilder(X509Req(p))) -+ } -+ } -+ -+ /// Set the numerical value of the version field. -+ /// -+ /// This corresponds to [`X509_REQ_set_version`]. -+ /// -+ ///[`X509_REQ_set_version`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_version.html -+ #[allow(clippy::useless_conversion)] -+ pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_REQ_set_version( -+ self.0.as_ptr(), -+ version as c_long, -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Set the issuer name. -+ /// -+ /// This corresponds to [`X509_REQ_set_subject_name`]. -+ /// -+ /// [`X509_REQ_set_subject_name`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_subject_name.html -+ pub fn set_subject_name(&mut self, subject_name: &X509NameRef) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_REQ_set_subject_name( -+ self.0.as_ptr(), -+ subject_name.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Set the public key. -+ /// -+ /// This corresponds to [`X509_REQ_set_pubkey`]. -+ /// -+ /// [`X509_REQ_set_pubkey`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_pubkey.html -+ pub fn set_pubkey<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPublic, -+ { -+ unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) } -+ } -+ -+ /// Return an `X509v3Context`. This context object can be used to construct -+ /// certain `X509` extensions. -+ pub fn x509v3_context<'a>(&'a self, conf: Option<&'a ConfRef>) -> X509v3Context<'a> { -+ unsafe { -+ let mut ctx = mem::zeroed(); -+ -+ ffi::X509V3_set_ctx( -+ &mut ctx, -+ ptr::null_mut(), -+ ptr::null_mut(), -+ self.0.as_ptr(), -+ ptr::null_mut(), -+ 0, -+ ); -+ -+ // nodb case taken care of since we zeroed ctx above -+ if let Some(conf) = conf { -+ ffi::X509V3_set_nconf(&mut ctx, conf.as_ptr()); -+ } -+ -+ X509v3Context(ctx, PhantomData) -+ } -+ } -+ -+ /// Permits any number of extension fields to be added to the certificate. -+ pub fn add_extensions( -+ &mut self, -+ extensions: &StackRef<X509Extension>, -+ ) -> Result<(), ErrorStack> { -+ unsafe { -+ cvt(ffi::X509_REQ_add_extensions( -+ self.0.as_ptr(), -+ extensions.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Sign the request using a private key. -+ /// -+ /// This corresponds to [`X509_REQ_sign`]. -+ /// -+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_sign.html -+ pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { -+ cvt(ffi::X509_REQ_sign( -+ self.0.as_ptr(), -+ key.as_ptr(), -+ hash.as_ptr(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Sign the request using a private key without a digest. -+ /// -+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null -+ /// message digest. -+ /// -+ /// This corresponds to [`X509_REQ_sign`]. -+ /// -+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html -+ #[cfg(boringssl)] -+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> -+ where -+ T: HasPrivate, -+ { -+ unsafe { -+ cvt(ffi::X509_REQ_sign( -+ self.0.as_ptr(), -+ key.as_ptr(), -+ ptr::null(), -+ )) -+ .map(|_| ()) -+ } -+ } -+ -+ /// Returns the `X509Req`. -+ pub fn build(self) -> X509Req { -+ self.0 -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_REQ; -+ fn drop = ffi::X509_REQ_free; -+ -+ /// An `X509` certificate request. -+ pub struct X509Req; -+ /// Reference to `X509Req`. -+ pub struct X509ReqRef; -+} -+ -+impl X509Req { -+ /// A builder for `X509Req`. -+ pub fn builder() -> Result<X509ReqBuilder, ErrorStack> { -+ X509ReqBuilder::new() -+ } -+ -+ from_pem! { -+ /// Deserializes a PEM-encoded PKCS#10 certificate request structure. -+ /// -+ /// The input should have a header of `-----BEGIN CERTIFICATE REQUEST-----`. -+ /// -+ /// This corresponds to [`PEM_read_bio_X509_REQ`]. -+ /// -+ /// [`PEM_read_bio_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/PEM_read_bio_X509_REQ.html -+ from_pem, -+ X509Req, -+ ffi::PEM_read_bio_X509_REQ -+ } -+ -+ from_der! { -+ /// Deserializes a DER-encoded PKCS#10 certificate request structure. -+ /// -+ /// This corresponds to [`d2i_X509_REQ`]. -+ /// -+ /// [`d2i_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/d2i_X509_REQ.html -+ from_der, -+ X509Req, -+ ffi::d2i_X509_REQ -+ } -+} -+ -+impl X509ReqRef { -+ to_pem! { -+ /// Serializes the certificate request to a PEM-encoded PKCS#10 structure. -+ /// -+ /// The output will have a header of `-----BEGIN CERTIFICATE REQUEST-----`. -+ /// -+ /// This corresponds to [`PEM_write_bio_X509_REQ`]. -+ /// -+ /// [`PEM_write_bio_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/PEM_write_bio_X509_REQ.html -+ to_pem, -+ ffi::PEM_write_bio_X509_REQ -+ } -+ -+ to_der! { -+ /// Serializes the certificate request to a DER-encoded PKCS#10 structure. -+ /// -+ /// This corresponds to [`i2d_X509_REQ`]. -+ /// -+ /// [`i2d_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/i2d_X509_REQ.html -+ to_der, -+ ffi::i2d_X509_REQ -+ } -+ -+ to_pem! { -+ /// Converts the request to human readable text. -+ #[corresponds(X509_Req_print)] -+ to_text, -+ ffi::X509_REQ_print -+ } -+ -+ /// Returns the numerical value of the version field of the certificate request. -+ /// -+ /// This corresponds to [`X509_REQ_get_version`] -+ /// -+ /// [`X509_REQ_get_version`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_version.html -+ pub fn version(&self) -> i32 { -+ unsafe { X509_REQ_get_version(self.as_ptr()) as i32 } -+ } -+ -+ /// Returns the subject name of the certificate request. -+ /// -+ /// This corresponds to [`X509_REQ_get_subject_name`] -+ /// -+ /// [`X509_REQ_get_subject_name`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_subject_name.html -+ pub fn subject_name(&self) -> &X509NameRef { -+ unsafe { -+ let name = X509_REQ_get_subject_name(self.as_ptr()); -+ X509NameRef::from_const_ptr_opt(name).expect("subject name must not be null") -+ } -+ } -+ -+ /// Returns the public key of the certificate request. -+ /// -+ /// This corresponds to [`X509_REQ_get_pubkey"] -+ /// -+ /// [`X509_REQ_get_pubkey`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_pubkey.html -+ pub fn public_key(&self) -> Result<PKey<Public>, ErrorStack> { -+ unsafe { -+ let key = cvt_p(ffi::X509_REQ_get_pubkey(self.as_ptr()))?; -+ Ok(PKey::from_ptr(key)) -+ } -+ } -+ -+ /// Check if the certificate request is signed using the given public key. -+ /// -+ /// Returns `true` if verification succeeds. -+ /// -+ /// This corresponds to [`X509_REQ_verify"]. -+ /// -+ /// [`X509_REQ_verify`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_verify.html -+ pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack> -+ where -+ T: HasPublic, -+ { -+ unsafe { cvt_n(ffi::X509_REQ_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) } -+ } -+ -+ /// Returns the extensions of the certificate request. -+ /// -+ /// This corresponds to [`X509_REQ_get_extensions"] -+ pub fn extensions(&self) -> Result<Stack<X509Extension>, ErrorStack> { -+ unsafe { -+ let extensions = cvt_p(ffi::X509_REQ_get_extensions(self.as_ptr()))?; -+ Ok(Stack::from_ptr(extensions)) -+ } -+ } -+} -+ -+/// The result of peer certificate verification. -+#[derive(Copy, Clone, PartialEq, Eq)] -+pub struct X509VerifyResult(c_int); -+ -+impl fmt::Debug for X509VerifyResult { -+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -+ fmt.debug_struct("X509VerifyResult") -+ .field("code", &self.0) -+ .field("error", &self.error_string()) -+ .finish() -+ } -+} -+ -+impl fmt::Display for X509VerifyResult { -+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { -+ fmt.write_str(self.error_string()) -+ } -+} -+ -+impl Error for X509VerifyResult {} -+ -+impl X509VerifyResult { -+ /// Creates an `X509VerifyResult` from a raw error number. -+ /// -+ /// # Safety -+ /// -+ /// Some methods on `X509VerifyResult` are not thread safe if the error -+ /// number is invalid. -+ pub unsafe fn from_raw(err: c_int) -> X509VerifyResult { -+ X509VerifyResult(err) -+ } -+ -+ /// Return the integer representation of an `X509VerifyResult`. -+ #[allow(clippy::trivially_copy_pass_by_ref)] -+ pub fn as_raw(&self) -> c_int { -+ self.0 -+ } -+ -+ /// Return a human readable error string from the verification error. -+ /// -+ /// This corresponds to [`X509_verify_cert_error_string`]. -+ /// -+ /// [`X509_verify_cert_error_string`]: https://www.openssl.org/docs/manmaster/crypto/X509_verify_cert_error_string.html -+ #[allow(clippy::trivially_copy_pass_by_ref)] -+ pub fn error_string(&self) -> &'static str { -+ ffi::init(); -+ -+ unsafe { -+ let s = ffi::X509_verify_cert_error_string(self.0 as c_long); -+ str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap() -+ } -+ } -+ -+ /// Successful peer certificate verification. -+ pub const OK: X509VerifyResult = X509VerifyResult(ffi::X509_V_OK); -+ /// Application verification failure. -+ pub const APPLICATION_VERIFICATION: X509VerifyResult = -+ X509VerifyResult(ffi::X509_V_ERR_APPLICATION_VERIFICATION); -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::GENERAL_NAME; -+ fn drop = ffi::GENERAL_NAME_free; -+ -+ /// An `X509` certificate alternative names. -+ pub struct GeneralName; -+ /// Reference to `GeneralName`. -+ pub struct GeneralNameRef; -+} -+ -+impl GeneralNameRef { -+ fn ia5_string(&self, ffi_type: c_int) -> Option<&str> { -+ unsafe { -+ if (*self.as_ptr()).type_ != ffi_type { -+ return None; -+ } -+ -+ #[cfg(boringssl)] -+ let d = (*self.as_ptr()).d.ptr; -+ #[cfg(not(boringssl))] -+ let d = (*self.as_ptr()).d; -+ -+ let ptr = ASN1_STRING_get0_data(d as *mut _); -+ let len = ffi::ASN1_STRING_length(d as *mut _); -+ -+ let slice = slice::from_raw_parts(ptr as *const u8, len as usize); -+ // IA5Strings are stated to be ASCII (specifically IA5). Hopefully -+ // OpenSSL checks that when loading a certificate but if not we'll -+ // use this instead of from_utf8_unchecked just in case. -+ str::from_utf8(slice).ok() -+ } -+ } -+ -+ /// Returns the contents of this `GeneralName` if it is an `rfc822Name`. -+ pub fn email(&self) -> Option<&str> { -+ self.ia5_string(ffi::GEN_EMAIL) -+ } -+ -+ /// Returns the contents of this `GeneralName` if it is a `dNSName`. -+ pub fn dnsname(&self) -> Option<&str> { -+ self.ia5_string(ffi::GEN_DNS) -+ } -+ -+ /// Returns the contents of this `GeneralName` if it is an `uniformResourceIdentifier`. -+ pub fn uri(&self) -> Option<&str> { -+ self.ia5_string(ffi::GEN_URI) -+ } -+ -+ /// Returns the contents of this `GeneralName` if it is an `iPAddress`. -+ pub fn ipaddress(&self) -> Option<&[u8]> { -+ unsafe { -+ if (*self.as_ptr()).type_ != ffi::GEN_IPADD { -+ return None; -+ } -+ #[cfg(boringssl)] -+ let d: *const ffi::ASN1_STRING = std::mem::transmute((*self.as_ptr()).d); -+ #[cfg(not(boringssl))] -+ let d = (*self.as_ptr()).d; -+ -+ let ptr = ASN1_STRING_get0_data(d as *mut _); -+ let len = ffi::ASN1_STRING_length(d as *mut _); -+ -+ Some(slice::from_raw_parts(ptr as *const u8, len as usize)) -+ } -+ } -+} -+ -+impl fmt::Debug for GeneralNameRef { -+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { -+ if let Some(email) = self.email() { -+ formatter.write_str(email) -+ } else if let Some(dnsname) = self.dnsname() { -+ formatter.write_str(dnsname) -+ } else if let Some(uri) = self.uri() { -+ formatter.write_str(uri) -+ } else if let Some(ipaddress) = self.ipaddress() { -+ let address = <[u8; 16]>::try_from(ipaddress) -+ .map(IpAddr::from) -+ .or_else(|_| <[u8; 4]>::try_from(ipaddress).map(IpAddr::from)); -+ match address { -+ Ok(a) => fmt::Debug::fmt(&a, formatter), -+ Err(_) => fmt::Debug::fmt(ipaddress, formatter), -+ } -+ } else { -+ formatter.write_str("(empty)") -+ } -+ } -+} -+ -+impl Stackable for GeneralName { -+ type StackType = ffi::stack_st_GENERAL_NAME; -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::ACCESS_DESCRIPTION; -+ fn drop = ffi::ACCESS_DESCRIPTION_free; -+ -+ /// `AccessDescription` of certificate authority information. -+ pub struct AccessDescription; -+ /// Reference to `AccessDescription`. -+ pub struct AccessDescriptionRef; -+} -+ -+impl AccessDescriptionRef { -+ /// Returns the access method OID. -+ pub fn method(&self) -> &Asn1ObjectRef { -+ unsafe { Asn1ObjectRef::from_ptr((*self.as_ptr()).method) } -+ } -+ -+ // Returns the access location. -+ pub fn location(&self) -> &GeneralNameRef { -+ unsafe { GeneralNameRef::from_ptr((*self.as_ptr()).location) } -+ } -+} -+ -+impl Stackable for AccessDescription { -+ type StackType = ffi::stack_st_ACCESS_DESCRIPTION; -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_ALGOR; -+ fn drop = ffi::X509_ALGOR_free; -+ -+ /// An `X509` certificate signature algorithm. -+ pub struct X509Algorithm; -+ /// Reference to `X509Algorithm`. -+ pub struct X509AlgorithmRef; -+} -+ -+impl X509AlgorithmRef { -+ /// Returns the ASN.1 OID of this algorithm. -+ pub fn object(&self) -> &Asn1ObjectRef { -+ unsafe { -+ let mut oid = ptr::null(); -+ X509_ALGOR_get0(&mut oid, ptr::null_mut(), ptr::null_mut(), self.as_ptr()); -+ Asn1ObjectRef::from_const_ptr_opt(oid).expect("algorithm oid must not be null") -+ } -+ } -+} -+ -+foreign_type_and_impl_send_sync! { -+ type CType = ffi::X509_OBJECT; -+ fn drop = X509_OBJECT_free; -+ -+ /// An `X509` or an X509 certificate revocation list. -+ pub struct X509Object; -+ /// Reference to `X509Object` -+ pub struct X509ObjectRef; -+} -+ -+impl X509ObjectRef { -+ pub fn x509(&self) -> Option<&X509Ref> { -+ unsafe { -+ let ptr = X509_OBJECT_get0_X509(self.as_ptr()); -+ X509Ref::from_const_ptr_opt(ptr) -+ } -+ } -+} -+ -+impl Stackable for X509Object { -+ type StackType = ffi::stack_st_X509_OBJECT; -+} -+ -+cfg_if! { -+ if #[cfg(any(boringssl, ossl110, libressl273))] { -+ use ffi::{X509_getm_notAfter, X509_getm_notBefore, X509_up_ref, X509_get0_signature}; -+ } else { -+ #[allow(bad_style)] -+ unsafe fn X509_getm_notAfter(x: *mut ffi::X509) -> *mut ffi::ASN1_TIME { -+ (*(*(*x).cert_info).validity).notAfter -+ } -+ -+ #[allow(bad_style)] -+ unsafe fn X509_getm_notBefore(x: *mut ffi::X509) -> *mut ffi::ASN1_TIME { -+ (*(*(*x).cert_info).validity).notBefore -+ } -+ -+ #[allow(bad_style)] -+ unsafe fn X509_up_ref(x: *mut ffi::X509) { -+ ffi::CRYPTO_add_lock( -+ &mut (*x).references, -+ 1, -+ ffi::CRYPTO_LOCK_X509, -+ "mod.rs\0".as_ptr() as *const _, -+ line!() as c_int, -+ ); -+ } -+ -+ #[allow(bad_style)] -+ unsafe fn X509_get0_signature( -+ psig: *mut *const ffi::ASN1_BIT_STRING, -+ palg: *mut *const ffi::X509_ALGOR, -+ x: *const ffi::X509, -+ ) { -+ if !psig.is_null() { -+ *psig = (*x).signature; -+ } -+ if !palg.is_null() { -+ *palg = (*x).sig_alg; -+ } -+ } -+ } -+} -+ -+cfg_if! { -+ if #[cfg(any(boringssl, ossl110, libressl350))] { -+ use ffi::{ -+ X509_ALGOR_get0, ASN1_STRING_get0_data, X509_STORE_CTX_get0_chain, X509_set1_notAfter, -+ X509_set1_notBefore, X509_REQ_get_version, X509_REQ_get_subject_name, -+ }; -+ } else { -+ use ffi::{ -+ ASN1_STRING_data as ASN1_STRING_get0_data, -+ X509_STORE_CTX_get_chain as X509_STORE_CTX_get0_chain, -+ X509_set_notAfter as X509_set1_notAfter, -+ X509_set_notBefore as X509_set1_notBefore, -+ }; -+ -+ #[allow(bad_style)] -+ unsafe fn X509_REQ_get_version(x: *mut ffi::X509_REQ) -> ::libc::c_long { -+ ffi::ASN1_INTEGER_get((*(*x).req_info).version) -+ } -+ -+ #[allow(bad_style)] -+ unsafe fn X509_REQ_get_subject_name(x: *mut ffi::X509_REQ) -> *mut ::ffi::X509_NAME { -+ (*(*x).req_info).subject -+ } -+ -+ #[allow(bad_style)] -+ unsafe fn X509_ALGOR_get0( -+ paobj: *mut *const ffi::ASN1_OBJECT, -+ pptype: *mut c_int, -+ pval: *mut *mut ::libc::c_void, -+ alg: *const ffi::X509_ALGOR, -+ ) { -+ if !paobj.is_null() { -+ *paobj = (*alg).algorithm; -+ } -+ assert!(pptype.is_null()); -+ assert!(pval.is_null()); -+ } -+ } -+} -+ -+cfg_if! { -+ if #[cfg(any(ossl110, boringssl, libressl270))] { -+ use ffi::X509_OBJECT_get0_X509; -+ } else { -+ #[allow(bad_style)] -+ unsafe fn X509_OBJECT_get0_X509(x: *mut ffi::X509_OBJECT) -> *mut ffi::X509 { -+ if (*x).type_ == ffi::X509_LU_X509 { -+ (*x).data.x509 -+ } else { -+ ptr::null_mut() -+ } -+ } -+ } -+} -+ -+cfg_if! { -+ if #[cfg(any(ossl110, libressl350))] { -+ use ffi::X509_OBJECT_free; -+ } else if #[cfg(boringssl)] { -+ use ffi::X509_OBJECT_free_contents as X509_OBJECT_free; -+ } else { -+ #[allow(bad_style)] -+ unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) { -+ ffi::X509_OBJECT_free_contents(x); -+ ffi::CRYPTO_free(x as *mut libc::c_void); -+ } -+ } -+} -+ -+#[derive(Copy, Clone, PartialEq, Eq)] -+pub struct X509PurposeId(c_int); -+ -+impl X509PurposeId { -+ pub const SSL_CLIENT: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SSL_CLIENT); -+ pub const SSL_SERVER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SSL_SERVER); -+ pub const NS_SSL_SERVER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_NS_SSL_SERVER); -+ pub const SMIME_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SMIME_SIGN); -+ pub const SMIME_ENCRYPT: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SMIME_ENCRYPT); -+ pub const CRL_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_CRL_SIGN); -+ pub const ANY: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_ANY); -+ pub const OCSP_HELPER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_OCSP_HELPER); -+ pub const TIMESTAMP_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_TIMESTAMP_SIGN); -+ -+ /// Constructs an `X509PurposeId` from a raw OpenSSL value. -+ pub fn from_raw(id: c_int) -> Self { -+ X509PurposeId(id) -+ } -+ -+ /// Returns the raw OpenSSL value represented by this type. -+ pub fn as_raw(&self) -> c_int { -+ self.0 -+ } -+} -+ -+/// A reference to an [`X509_PURPOSE`]. -+pub struct X509PurposeRef(Opaque); -+ -+/// Implements a wrapper type for the static `X509_PURPOSE` table in OpenSSL. -+impl ForeignTypeRef for X509PurposeRef { -+ type CType = ffi::X509_PURPOSE; -+} -+ -+impl X509PurposeRef { -+ /// Get the internal table index of an X509_PURPOSE for a given short name. Valid short -+ /// names include -+ /// - "sslclient", -+ /// - "sslserver", -+ /// - "nssslserver", -+ /// - "smimesign", -+ /// - "smimeencrypt", -+ /// - "crlsign", -+ /// - "any", -+ /// - "ocsphelper", -+ /// - "timestampsign" -+ /// The index can be used with `X509PurposeRef::from_idx()` to get the purpose. -+ #[allow(clippy::unnecessary_cast)] -+ pub fn get_by_sname(sname: &str) -> Result<c_int, ErrorStack> { -+ unsafe { -+ let sname = CString::new(sname).unwrap(); -+ cfg_if! { -+ if #[cfg(any(ossl110, libressl280))] { -+ let purpose = cvt_n(ffi::X509_PURPOSE_get_by_sname(sname.as_ptr() as *const _))?; -+ } else { -+ let purpose = cvt_n(ffi::X509_PURPOSE_get_by_sname(sname.as_ptr() as *mut _))?; -+ } -+ } -+ Ok(purpose) -+ } -+ } -+ /// Get an `X509PurposeRef` for a given index value. The index can be obtained from e.g. -+ /// `X509PurposeRef::get_by_sname()`. -+ #[corresponds(X509_PURPOSE_get0)] -+ pub fn from_idx(idx: c_int) -> Result<&'static X509PurposeRef, ErrorStack> { -+ unsafe { -+ let ptr = cvt_p(ffi::X509_PURPOSE_get0(idx))?; -+ Ok(X509PurposeRef::from_ptr(ptr)) -+ } -+ } -+ -+ /// Get the purpose value from an X509Purpose structure. This value is one of -+ /// - `X509_PURPOSE_SSL_CLIENT` -+ /// - `X509_PURPOSE_SSL_SERVER` -+ /// - `X509_PURPOSE_NS_SSL_SERVER` -+ /// - `X509_PURPOSE_SMIME_SIGN` -+ /// - `X509_PURPOSE_SMIME_ENCRYPT` -+ /// - `X509_PURPOSE_CRL_SIGN` -+ /// - `X509_PURPOSE_ANY` -+ /// - `X509_PURPOSE_OCSP_HELPER` -+ /// - `X509_PURPOSE_TIMESTAMP_SIGN` -+ pub fn purpose(&self) -> X509PurposeId { -+ unsafe { -+ let x509_purpose: *mut ffi::X509_PURPOSE = self.as_ptr(); -+ X509PurposeId::from_raw((*x509_purpose).purpose) -+ } -+ } -+} --- -2.40.1.495.gc816e09b53d-goog -
diff --git a/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch b/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch new file mode 100644 index 0000000..0ee52b1 --- /dev/null +++ b/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch
@@ -0,0 +1,1017 @@ +From 5c9103f02cb56f0f04444b16dd67eeaa05429d47 Mon Sep 17 00:00:00 2001 +From: Nabil Wadih <nwadih@google.com> +Date: Thu, 24 Aug 2023 15:51:26 -0700 +Subject: [PATCH] Apply android patches + +--- + openssl/.cargo/config.toml | 2 + + openssl/src/asn1.rs | 2 +- + openssl/src/bio.rs | 6 +- + openssl/src/bn.rs | 2 +- + openssl/src/cipher.rs | 4 + + openssl/src/dh.rs | 2 +- + openssl/src/dsa.rs | 5 +- + openssl/src/ec.rs | 20 ++++ + openssl/src/ecdsa.rs | 2 +- + openssl/src/encrypt.rs | 4 +- + openssl/src/hash.rs | 2 +- + openssl/src/hkdf.rs | 89 +++++++++++++++ + openssl/src/hmac.rs | 217 +++++++++++++++++++++++++++++++++++++ + openssl/src/lib.rs | 12 ++ + openssl/src/md_ctx.rs | 2 +- + openssl/src/pkey.rs | 22 ++-- + openssl/src/pkey_ctx.rs | 21 +++- + openssl/src/rsa.rs | 2 +- + openssl/src/sign.rs | 10 +- + openssl/src/symm.rs | 7 +- + openssl/src/x509/mod.rs | 52 +++++++-- + 21 files changed, 439 insertions(+), 46 deletions(-) + create mode 100644 openssl/.cargo/config.toml + create mode 100644 openssl/src/hkdf.rs + create mode 100644 openssl/src/hmac.rs + +diff --git a/openssl/.cargo/config.toml b/openssl/.cargo/config.toml +new file mode 100644 +index 00000000..e2b197d8 +--- /dev/null ++++ b/openssl/.cargo/config.toml +@@ -0,0 +1,2 @@ ++[patch.crates-io] ++bssl-ffi = { package = "bssl-sys", version = "0.1.0", path = "../../../boringssl/build/rust", optional=true } +diff --git a/openssl/src/asn1.rs b/openssl/src/asn1.rs +index b02f9ac4..939a1732 100644 +--- a/openssl/src/asn1.rs ++++ b/openssl/src/asn1.rs +@@ -651,7 +651,7 @@ impl fmt::Debug for Asn1ObjectRef { + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl273))] { ++ if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::ASN1_STRING_get0_data; + } else { + #[allow(bad_style)] +diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs +index 6a72552a..03242188 100644 +--- a/openssl/src/bio.rs ++++ b/openssl/src/bio.rs +@@ -4,7 +4,7 @@ use std::marker::PhantomData; + use std::ptr; + use std::slice; + +-use crate::cvt_p; ++use crate::{cvt_p, SignedLenType}; + use crate::error::ErrorStack; + + pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>); +@@ -25,7 +25,7 @@ impl<'a> MemBioSlice<'a> { + let bio = unsafe { + cvt_p(BIO_new_mem_buf( + buf.as_ptr() as *const _, +- buf.len() as c_int, ++ buf.len() as SignedLenType, + ))? + }; + +@@ -78,7 +78,7 @@ cfg_if! { + use ffi::BIO_new_mem_buf; + } else { + #[allow(bad_style)] +- unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO { ++ unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO { + ffi::BIO_new_mem_buf(buf as *mut _, len) + } + } +diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs +index 1cd00dd4..dbd7ae94 100644 +--- a/openssl/src/bn.rs ++++ b/openssl/src/bn.rs +@@ -814,7 +814,7 @@ impl BigNumRef { + /// assert_eq!(&bn_vec, &[0, 0, 0x45, 0x43]); + /// ``` + #[corresponds(BN_bn2binpad)] +- #[cfg(ossl110)] ++ #[cfg(any(boringssl, ossl110))] + pub fn to_vec_padded(&self, pad_to: i32) -> Result<Vec<u8>, ErrorStack> { + let mut v = Vec::with_capacity(pad_to as usize); + unsafe { +diff --git a/openssl/src/cipher.rs b/openssl/src/cipher.rs +index ab5f49d2..84a82654 100644 +--- a/openssl/src/cipher.rs ++++ b/openssl/src/cipher.rs +@@ -208,6 +208,7 @@ impl Cipher { + unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb1() as *mut _) } + } + ++ #[cfg(not(boringssl))] + pub fn aes_192_cfb128() -> &'static CipherRef { + unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb128() as *mut _) } + } +@@ -253,6 +254,7 @@ impl Cipher { + unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb1() as *mut _) } + } + ++ #[cfg(not(boringssl))] + pub fn aes_256_cfb128() -> &'static CipherRef { + unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb128() as *mut _) } + } +@@ -282,11 +284,13 @@ impl Cipher { + } + + #[cfg(not(osslconf = "OPENSSL_NO_BF"))] ++ #[cfg(not(boringssl))] + pub fn bf_cbc() -> &'static CipherRef { + unsafe { CipherRef::from_ptr(ffi::EVP_bf_cbc() as *mut _) } + } + + #[cfg(not(osslconf = "OPENSSL_NO_BF"))] ++ #[cfg(not(boringssl))] + pub fn bf_ecb() -> &'static CipherRef { + unsafe { CipherRef::from_ptr(ffi::EVP_bf_ecb() as *mut _) } + } +diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs +index 12170b99..e781543e 100644 +--- a/openssl/src/dh.rs ++++ b/openssl/src/dh.rs +@@ -239,7 +239,7 @@ where + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl270))] { ++ if #[cfg(any(ossl110, libressl270, boringssl))] { + use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key}; + } else { + #[allow(bad_style)] +diff --git a/openssl/src/dsa.rs b/openssl/src/dsa.rs +index 5f59ba8a..0aceeb55 100644 +--- a/openssl/src/dsa.rs ++++ b/openssl/src/dsa.rs +@@ -7,6 +7,7 @@ + + use cfg_if::cfg_if; + use foreign_types::{ForeignType, ForeignTypeRef}; ++#[cfg(not(boringssl))] + use libc::c_int; + use std::fmt; + use std::mem; +@@ -283,7 +284,7 @@ impl<T> fmt::Debug for Dsa<T> { + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl273))] { ++ if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::{DSA_get0_key, DSA_get0_pqg, DSA_set0_key, DSA_set0_pqg}; + } else { + #[allow(bad_style)] +@@ -462,7 +463,7 @@ impl DsaSigRef { + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl273))] { ++ if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::{DSA_SIG_set0, DSA_SIG_get0}; + } else { + #[allow(bad_style)] +diff --git a/openssl/src/ec.rs b/openssl/src/ec.rs +index 24b38322..20785428 100644 +--- a/openssl/src/ec.rs ++++ b/openssl/src/ec.rs +@@ -954,6 +954,26 @@ impl EcKey<Private> { + EcKey<Private>, + ffi::d2i_ECPrivateKey + } ++ ++ /// Decodes a DER-encoded elliptic curve private key structure for the specified curve. ++ #[corresponds(EC_KEY_parse_private_key)] ++ #[cfg(boringssl)] ++ pub fn private_key_from_der_for_group( ++ der: &[u8], ++ group: &EcGroupRef, ++ ) -> Result<EcKey<Private>, ErrorStack> { ++ unsafe { ++ let mut cbs = ffi::CBS { ++ data: der.as_ptr(), ++ len: der.len(), ++ }; ++ cvt_p(ffi::EC_KEY_parse_private_key( ++ &mut cbs as *mut ffi::CBS, ++ group.as_ptr(), ++ )) ++ .map(|p| EcKey::from_ptr(p)) ++ } ++ } + } + + impl<T> Clone for EcKey<T> { +diff --git a/openssl/src/ecdsa.rs b/openssl/src/ecdsa.rs +index 0a960e7b..f3b27b39 100644 +--- a/openssl/src/ecdsa.rs ++++ b/openssl/src/ecdsa.rs +@@ -110,7 +110,7 @@ impl EcdsaSigRef { + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl273))] { ++ if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::{ECDSA_SIG_set0, ECDSA_SIG_get0}; + } else { + #[allow(bad_style)] +diff --git a/openssl/src/encrypt.rs b/openssl/src/encrypt.rs +index 3cb10fcc..34a9eb8b 100644 +--- a/openssl/src/encrypt.rs ++++ b/openssl/src/encrypt.rs +@@ -148,7 +148,7 @@ impl<'a> Encrypter<'a> { + /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`]. + /// + /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html +- #[cfg(any(ossl102, libressl310))] ++ #[cfg(any(ossl102, libressl310, boringssl))] + pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md( +@@ -352,7 +352,7 @@ impl<'a> Decrypter<'a> { + /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`]. + /// + /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html +- #[cfg(any(ossl102, libressl310))] ++ #[cfg(any(ossl102, libressl310, boringssl))] + pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md( +diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs +index 8e27505a..7f6fa89e 100644 +--- a/openssl/src/hash.rs ++++ b/openssl/src/hash.rs +@@ -43,7 +43,7 @@ use crate::nid::Nid; + use crate::{cvt, cvt_p}; + + cfg_if! { +- if #[cfg(ossl110)] { ++ if #[cfg(any(ossl110, boringssl))] { + use ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new}; + } else { + use ffi::{EVP_MD_CTX_create as EVP_MD_CTX_new, EVP_MD_CTX_destroy as EVP_MD_CTX_free}; +diff --git a/openssl/src/hkdf.rs b/openssl/src/hkdf.rs +new file mode 100644 +index 00000000..cc7e5b3a +--- /dev/null ++++ b/openssl/src/hkdf.rs +@@ -0,0 +1,89 @@ ++use crate::cvt; ++use crate::error::ErrorStack; ++use crate::md::MdRef; ++use foreign_types::ForeignTypeRef; ++use openssl_macros::corresponds; ++ ++/// Computes HKDF (as specified by RFC 5869). ++/// ++/// HKDF is an Extract-and-Expand algorithm. It does not do any key stretching, ++/// and as such, is not suited to be used alone to generate a key from a ++/// password. ++#[corresponds(HKDF)] ++#[inline] ++pub fn hkdf( ++ out_key: &mut [u8], ++ md: &MdRef, ++ secret: &[u8], ++ salt: &[u8], ++ info: &[u8], ++) -> Result<(), ErrorStack> { ++ unsafe { ++ cvt(ffi::HKDF( ++ out_key.as_mut_ptr(), ++ out_key.len(), ++ md.as_ptr(), ++ secret.as_ptr(), ++ secret.len(), ++ salt.as_ptr(), ++ salt.len(), ++ info.as_ptr(), ++ info.len(), ++ ))?; ++ } ++ ++ Ok(()) ++} ++ ++/// Computes a HKDF PRK (as specified by RFC 5869). ++/// ++/// WARNING: This function orders the inputs differently from RFC 5869 ++/// specification. Double-check which parameter is the secret/IKM and which is ++/// the salt when using. ++#[corresponds(HKDF_extract)] ++#[inline] ++pub fn hkdf_extract<'a>( ++ out_key: &'a mut [u8], ++ md: &MdRef, ++ secret: &[u8], ++ salt: &[u8], ++) -> Result<&'a [u8], ErrorStack> { ++ let mut out_len = out_key.len(); ++ unsafe { ++ cvt(ffi::HKDF_extract( ++ out_key.as_mut_ptr(), ++ &mut out_len, ++ md.as_ptr(), ++ secret.as_ptr(), ++ secret.len(), ++ salt.as_ptr(), ++ salt.len(), ++ ))?; ++ } ++ ++ Ok(&out_key[..out_len]) ++} ++ ++/// Computes a HKDF OKM (as specified by RFC 5869). ++#[corresponds(HKDF_expand)] ++#[inline] ++pub fn hkdf_expand( ++ out_key: &mut [u8], ++ md: &MdRef, ++ prk: &[u8], ++ info: &[u8], ++) -> Result<(), ErrorStack> { ++ unsafe { ++ cvt(ffi::HKDF_expand( ++ out_key.as_mut_ptr(), ++ out_key.len(), ++ md.as_ptr(), ++ prk.as_ptr(), ++ prk.len(), ++ info.as_ptr(), ++ info.len(), ++ ))?; ++ } ++ ++ Ok(()) ++} +diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs +new file mode 100644 +index 00000000..465781e2 +--- /dev/null ++++ b/openssl/src/hmac.rs +@@ -0,0 +1,217 @@ ++use crate::error::ErrorStack; ++use crate::md::MdRef; ++use crate::{cvt, cvt_p}; ++use ffi::HMAC_CTX; ++use foreign_types::ForeignTypeRef; ++use libc::{c_uint, c_void}; ++use openssl_macros::corresponds; ++use std::convert::TryFrom; ++use std::ptr; ++ ++/// Computes the HMAC as a one-shot operation. ++/// ++/// Calculates the HMAC of data, using the given |key| ++/// and hash function |md|, and returns the result re-using the space from ++/// buffer |out|. On entry, |out| must contain at least |EVP_MD_size| bytes ++/// of space. The actual length of the result is used to resize the returned ++/// slice. An output size of |EVP_MAX_MD_SIZE| will always be large enough. ++/// It returns a resized |out| or ErrorStack on error. ++#[corresponds(HMAC)] ++#[inline] ++pub fn hmac<'a>( ++ md: &MdRef, ++ key: &[u8], ++ data: &[u8], ++ out: &'a mut [u8], ++) -> Result<&'a [u8], ErrorStack> { ++ assert!(out.len() >= md.size()); ++ let mut out_len = c_uint::try_from(out.len()).unwrap(); ++ unsafe { ++ cvt_p(ffi::HMAC( ++ md.as_ptr(), ++ key.as_ptr() as *const c_void, ++ key.len(), ++ data.as_ptr(), ++ data.len(), ++ out.as_mut_ptr(), ++ &mut out_len, ++ ))?; ++ } ++ Ok(&out[..out_len as usize]) ++} ++ ++/// A context object used to perform HMAC operations. ++/// ++/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message ++/// authentication, which is based on a hash function. ++/// ++/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead. ++#[cfg(boringssl)] ++pub struct HmacCtx { ++ ctx: *mut HMAC_CTX, ++ output_size: usize, ++} ++ ++#[cfg(boringssl)] ++impl HmacCtx { ++ /// Creates a new [HmacCtx] to use the hash function `md` and key `key`. ++ #[corresponds(HMAC_Init_ex)] ++ pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> { ++ unsafe { ++ // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into ++ // ErrorStack in the returned result by `cvt_p`. ++ let ctx = cvt_p(ffi::HMAC_CTX_new())?; ++ // Safety: ++ // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new, ++ // which is the line above. ++ // - HMAC_Init_ex may return an error if key is null but the md is different from ++ // before. This is avoided here since key is guaranteed to be non-null. ++ cvt(ffi::HMAC_Init_ex( ++ ctx, ++ key.as_ptr() as *const c_void, ++ key.len(), ++ md.as_ptr(), ++ ptr::null_mut(), ++ ))?; ++ Ok(Self { ++ ctx, ++ output_size: md.size(), ++ }) ++ } ++ } ++ ++ /// `update` can be called repeatedly with chunks of the message `data` to be authenticated. ++ #[corresponds(HMAC_Update)] ++ pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { ++ unsafe { ++ // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the ++ // returned result by `cvt`. ++ cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ()) ++ } ++ } ++ ++ /// Finishes the HMAC process, and places the message authentication code in `output`. ++ /// The number of bytes written to `output` is returned. ++ /// ++ /// # Panics ++ /// ++ /// Panics if the `output` is smaller than the required size. The output size is indicated by ++ /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will ++ /// always be large enough. ++ #[corresponds(HMAC_Final)] ++ pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> { ++ assert!(output.len() >= self.output_size); ++ unsafe { ++ // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer ++ // than the length of `output`. ++ let mut size: c_uint = 0; ++ cvt(ffi::HMAC_Final( ++ self.ctx, ++ output.as_mut_ptr(), ++ &mut size as *mut c_uint, ++ )) ++ .map(|_| size as usize) ++ } ++ } ++} ++ ++impl Drop for HmacCtx { ++ #[corresponds(HMAC_CTX_free)] ++ fn drop(&mut self) { ++ unsafe { ++ ffi::HMAC_CTX_free(self.ctx); ++ } ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use crate::md::Md; ++ ++ const SHA_256_DIGEST_SIZE: usize = 32; ++ ++ #[test] ++ fn hmac_sha256_test() { ++ let expected_hmac = [ ++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, ++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, ++ 0x2e, 0x32, 0xcf, 0xf7, ++ ]; ++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let data = b"Hi There"; ++ let hmac_result = ++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); ++ assert_eq!(&hmac_result, &expected_hmac); ++ } ++ ++ #[test] ++ #[should_panic] ++ fn hmac_sha256_output_too_short() { ++ let mut out = vec![0_u8; 1]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let data = b"Hi There"; ++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); ++ } ++ ++ #[test] ++ fn hmac_sha256_test_big_buffer() { ++ let expected_hmac = [ ++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, ++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, ++ 0x2e, 0x32, 0xcf, 0xf7, ++ ]; ++ let mut out: [u8; 100] = [0; 100]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let data = b"Hi There"; ++ let hmac_result = ++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac"); ++ assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE); ++ assert_eq!(&hmac_result, &expected_hmac); ++ } ++ ++ #[test] ++ fn hmac_sha256_update_test() { ++ let expected_hmac = [ ++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, ++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, ++ 0x2e, 0x32, 0xcf, 0xf7, ++ ]; ++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let data = b"Hi There"; ++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); ++ hmac_ctx.update(data).unwrap(); ++ let size = hmac_ctx.finalize(&mut out).unwrap(); ++ assert_eq!(&out, &expected_hmac); ++ assert_eq!(size, SHA_256_DIGEST_SIZE); ++ } ++ ++ #[test] ++ fn hmac_sha256_update_chunks_test() { ++ let expected_hmac = [ ++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb, ++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, ++ 0x2e, 0x32, 0xcf, 0xf7, ++ ]; ++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); ++ hmac_ctx.update(b"Hi").unwrap(); ++ hmac_ctx.update(b" There").unwrap(); ++ let size = hmac_ctx.finalize(&mut out).unwrap(); ++ assert_eq!(&out, &expected_hmac); ++ assert_eq!(size, SHA_256_DIGEST_SIZE); ++ } ++ ++ #[test] ++ #[should_panic] ++ fn hmac_sha256_update_output_too_short() { ++ let mut out = vec![0_u8; 1]; ++ let key: [u8; 20] = [0x0b; 20]; ++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap(); ++ hmac_ctx.update(b"Hi There").unwrap(); ++ hmac_ctx.finalize(&mut out).unwrap(); ++ } ++} +diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs +index 891651ec..e8d07d8a 100644 +--- a/openssl/src/lib.rs ++++ b/openssl/src/lib.rs +@@ -120,6 +120,9 @@ + #![doc(html_root_url = "https://docs.rs/openssl/0.10")] + #![warn(rust_2018_idioms)] + ++#[cfg(all(soong, boringssl))] ++extern crate bssl_ffi as ffi; ++ + #[doc(inline)] + pub use ffi::init; + +@@ -155,6 +158,10 @@ pub mod ex_data; + #[cfg(not(any(libressl, ossl300)))] + pub mod fips; + pub mod hash; ++#[cfg(boringssl)] ++pub mod hkdf; ++#[cfg(boringssl)] ++pub mod hmac; + #[cfg(ossl300)] + pub mod lib_ctx; + pub mod md; +@@ -189,6 +196,11 @@ type LenType = libc::size_t; + #[cfg(not(boringssl))] + type LenType = libc::c_int; + ++#[cfg(boringssl)] ++type SignedLenType = libc::ssize_t; ++#[cfg(not(boringssl))] ++type SignedLenType = libc::c_int; ++ + #[inline] + fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> { + if r.is_null() { +diff --git a/openssl/src/md_ctx.rs b/openssl/src/md_ctx.rs +index c4d3f06b..156f3c2f 100644 +--- a/openssl/src/md_ctx.rs ++++ b/openssl/src/md_ctx.rs +@@ -93,7 +93,7 @@ use std::convert::TryFrom; + use std::ptr; + + cfg_if! { +- if #[cfg(ossl110)] { ++ if #[cfg(any(ossl110, boringssl))] { + use ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new}; + } else { + use ffi::{EVP_MD_CTX_create as EVP_MD_CTX_new, EVP_MD_CTX_destroy as EVP_MD_CTX_free}; +diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs +index 2039e7e9..21ba7118 100644 +--- a/openssl/src/pkey.rs ++++ b/openssl/src/pkey.rs +@@ -47,7 +47,7 @@ use crate::dh::Dh; + use crate::dsa::Dsa; + use crate::ec::EcKey; + use crate::error::ErrorStack; +-#[cfg(ossl110)] ++#[cfg(any(boringssl, ossl110))] + use crate::pkey_ctx::PkeyCtx; + use crate::rsa::Rsa; + use crate::symm::Cipher; +@@ -86,14 +86,14 @@ impl Id { + pub const DH: Id = Id(ffi::EVP_PKEY_DH); + pub const EC: Id = Id(ffi::EVP_PKEY_EC); + +- #[cfg(ossl110)] ++ #[cfg(any(boringssl, ossl110))] + pub const HKDF: Id = Id(ffi::EVP_PKEY_HKDF); + +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub const ED25519: Id = Id(ffi::EVP_PKEY_ED25519); + #[cfg(ossl111)] + pub const ED448: Id = Id(ffi::EVP_PKEY_ED448); +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub const X25519: Id = Id(ffi::EVP_PKEY_X25519); + #[cfg(ossl111)] + pub const X448: Id = Id(ffi::EVP_PKEY_X448); +@@ -252,7 +252,7 @@ where + /// This function only works for algorithms that support raw public keys. + /// Currently this is: [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`]. + #[corresponds(EVP_PKEY_get_raw_public_key)] +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn raw_public_key(&self) -> Result<Vec<u8>, ErrorStack> { + unsafe { + let mut len = 0; +@@ -303,7 +303,7 @@ where + /// This function only works for algorithms that support raw private keys. + /// Currently this is: [`Id::HMAC`], [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`]. + #[corresponds(EVP_PKEY_get_raw_private_key)] +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn raw_private_key(&self) -> Result<Vec<u8>, ErrorStack> { + unsafe { + let mut len = 0; +@@ -484,7 +484,7 @@ impl PKey<Private> { + ctx.keygen() + } + +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + fn generate_eddsa(id: Id) -> Result<PKey<Private>, ErrorStack> { + let mut ctx = PkeyCtx::new_id(id)?; + ctx.keygen_init()?; +@@ -514,7 +514,7 @@ impl PKey<Private> { + /// assert_eq!(secret.len(), 32); + /// # Ok(()) } + /// ``` +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn generate_x25519() -> Result<PKey<Private>, ErrorStack> { + PKey::generate_eddsa(Id::X25519) + } +@@ -568,7 +568,7 @@ impl PKey<Private> { + /// assert_eq!(signature.len(), 64); + /// # Ok(()) } + /// ``` +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn generate_ed25519() -> Result<PKey<Private>, ErrorStack> { + PKey::generate_eddsa(Id::ED25519) + } +@@ -718,7 +718,7 @@ impl PKey<Private> { + /// + /// Algorithm types that support raw private keys are HMAC, X25519, ED25519, X448 or ED448 + #[corresponds(EVP_PKEY_new_raw_private_key)] +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn private_key_from_raw_bytes( + bytes: &[u8], + key_type: Id, +@@ -759,7 +759,7 @@ impl PKey<Public> { + /// + /// Algorithm types that support raw public keys are X25519, ED25519, X448 or ED448 + #[corresponds(EVP_PKEY_new_raw_public_key)] +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn public_key_from_raw_bytes( + bytes: &[u8], + key_type: Id, +diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs +index f79372fb..3d4203fa 100644 +--- a/openssl/src/pkey_ctx.rs ++++ b/openssl/src/pkey_ctx.rs +@@ -470,7 +470,7 @@ impl<T> PkeyCtxRef<T> { + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(EVP_PKEY_CTX_set_hkdf_md)] +- #[cfg(ossl110)] ++ #[cfg(any(ossl110, boringssl))] + #[inline] + pub fn set_hkdf_md(&mut self, digest: &MdRef) -> Result<(), ErrorStack> { + unsafe { +@@ -503,10 +503,13 @@ impl<T> PkeyCtxRef<T> { + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(EVP_PKEY_CTX_set1_hkdf_key)] +- #[cfg(ossl110)] ++ #[cfg(any(ossl110, boringssl))] + #[inline] + pub fn set_hkdf_key(&mut self, key: &[u8]) -> Result<(), ErrorStack> { ++ #[cfg(not(boringssl))] + let len = c_int::try_from(key.len()).unwrap(); ++ #[cfg(boringssl)] ++ let len = key.len(); + + unsafe { + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_key( +@@ -523,10 +526,13 @@ impl<T> PkeyCtxRef<T> { + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(EVP_PKEY_CTX_set1_hkdf_salt)] +- #[cfg(ossl110)] ++ #[cfg(any(ossl110, boringssl))] + #[inline] + pub fn set_hkdf_salt(&mut self, salt: &[u8]) -> Result<(), ErrorStack> { ++ #[cfg(not(boringssl))] + let len = c_int::try_from(salt.len()).unwrap(); ++ #[cfg(boringssl)] ++ let len = salt.len(); + + unsafe { + cvt(ffi::EVP_PKEY_CTX_set1_hkdf_salt( +@@ -543,10 +549,13 @@ impl<T> PkeyCtxRef<T> { + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(EVP_PKEY_CTX_add1_hkdf_info)] +- #[cfg(ossl110)] ++ #[cfg(any(ossl110, boringssl))] + #[inline] + pub fn add_hkdf_info(&mut self, info: &[u8]) -> Result<(), ErrorStack> { ++ #[cfg(not(boringssl))] + let len = c_int::try_from(info.len()).unwrap(); ++ #[cfg(boringssl)] ++ let len = info.len(); + + unsafe { + cvt(ffi::EVP_PKEY_CTX_add1_hkdf_info( +@@ -604,7 +613,7 @@ mod test { + #[cfg(not(boringssl))] + use crate::cipher::Cipher; + use crate::ec::{EcGroup, EcKey}; +- #[cfg(any(ossl102, libressl310))] ++ #[cfg(any(ossl102, libressl310, boringssl))] + use crate::md::Md; + use crate::nid::Nid; + use crate::pkey::PKey; +@@ -689,7 +698,7 @@ mod test { + } + + #[test] +- #[cfg(ossl110)] ++ #[cfg(any(ossl110, boringssl))] + fn hkdf() { + let mut ctx = PkeyCtx::new_id(Id::HKDF).unwrap(); + ctx.derive_init().unwrap(); +diff --git a/openssl/src/rsa.rs b/openssl/src/rsa.rs +index 68cf64b0..f155b12d 100644 +--- a/openssl/src/rsa.rs ++++ b/openssl/src/rsa.rs +@@ -581,7 +581,7 @@ impl<T> fmt::Debug for Rsa<T> { + } + + cfg_if! { +- if #[cfg(any(ossl110, libressl273))] { ++ if #[cfg(any(ossl110, libressl273, boringssl))] { + use ffi::{ + RSA_get0_key, RSA_get0_factors, RSA_get0_crt_params, RSA_set0_key, RSA_set0_factors, + RSA_set0_crt_params, +diff --git a/openssl/src/sign.rs b/openssl/src/sign.rs +index b675825e..e5e80608 100644 +--- a/openssl/src/sign.rs ++++ b/openssl/src/sign.rs +@@ -290,7 +290,7 @@ impl<'a> Signer<'a> { + self.len_intern() + } + +- #[cfg(not(ossl111))] ++ #[cfg(not(any(boringssl, ossl111)))] + fn len_intern(&self) -> Result<usize, ErrorStack> { + unsafe { + let mut len = 0; +@@ -303,7 +303,7 @@ impl<'a> Signer<'a> { + } + } + +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + fn len_intern(&self) -> Result<usize, ErrorStack> { + unsafe { + let mut len = 0; +@@ -360,7 +360,7 @@ impl<'a> Signer<'a> { + /// OpenSSL documentation at [`EVP_DigestSign`]. + /// + /// [`EVP_DigestSign`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestSign.html +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn sign_oneshot( + &mut self, + sig_buf: &mut [u8], +@@ -382,7 +382,7 @@ impl<'a> Signer<'a> { + /// Returns the signature. + /// + /// This is a simple convenience wrapper over `len` and `sign_oneshot`. +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn sign_oneshot_to_vec(&mut self, data_buf: &[u8]) -> Result<Vec<u8>, ErrorStack> { + let mut sig_buf = vec![0; self.len()?]; + let len = self.sign_oneshot(&mut sig_buf, data_buf)?; +@@ -596,7 +596,7 @@ impl<'a> Verifier<'a> { + /// OpenSSL documentation at [`EVP_DigestVerify`]. + /// + /// [`EVP_DigestVerify`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestVerify.html +- #[cfg(ossl111)] ++ #[cfg(any(boringssl, ossl111))] + pub fn verify_oneshot(&mut self, signature: &[u8], buf: &[u8]) -> Result<bool, ErrorStack> { + unsafe { + let r = ffi::EVP_DigestVerify( +diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs +index c75bbc0c..beff5fc2 100644 +--- a/openssl/src/symm.rs ++++ b/openssl/src/symm.rs +@@ -119,6 +119,7 @@ impl Cipher { + unsafe { Cipher(ffi::EVP_aes_128_cfb1()) } + } + ++ #[cfg(not(boringssl))] + pub fn aes_128_cfb128() -> Cipher { + unsafe { Cipher(ffi::EVP_aes_128_cfb128()) } + } +@@ -164,6 +165,7 @@ impl Cipher { + unsafe { Cipher(ffi::EVP_aes_192_cfb1()) } + } + ++ #[cfg(not(boringssl))] + pub fn aes_192_cfb128() -> Cipher { + unsafe { Cipher(ffi::EVP_aes_192_cfb128()) } + } +@@ -214,6 +216,7 @@ impl Cipher { + unsafe { Cipher(ffi::EVP_aes_256_cfb1()) } + } + ++ #[cfg(not(boringssl))] + pub fn aes_256_cfb128() -> Cipher { + unsafe { Cipher(ffi::EVP_aes_256_cfb128()) } + } +@@ -242,12 +245,12 @@ impl Cipher { + unsafe { Cipher(ffi::EVP_aes_256_ocb()) } + } + +- #[cfg(not(osslconf = "OPENSSL_NO_BF"))] ++ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))] + pub fn bf_cbc() -> Cipher { + unsafe { Cipher(ffi::EVP_bf_cbc()) } + } + +- #[cfg(not(osslconf = "OPENSSL_NO_BF"))] ++ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))] + pub fn bf_ecb() -> Cipher { + unsafe { Cipher(ffi::EVP_bf_ecb()) } + } +diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs +index edd54aa8..a03a8aa6 100644 +--- a/openssl/src/x509/mod.rs ++++ b/openssl/src/x509/mod.rs +@@ -353,6 +353,19 @@ impl X509Builder { + unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) } + } + ++ /// Signs the certificate with a private key but without a digest. ++ /// ++ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null ++ /// message digest. ++ #[cfg(boringssl)] ++ #[corresponds(X509_sign)] ++ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> ++ where ++ T: HasPrivate, ++ { ++ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) } ++ } ++ + /// Consumes the builder, returning the certificate. + pub fn build(self) -> X509 { + self.0 +@@ -880,13 +893,13 @@ impl X509NameBuilder { + pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> { + unsafe { + let field = CString::new(field).unwrap(); +- assert!(value.len() <= c_int::max_value() as usize); ++ assert!(value.len() <= isize::max_value() as usize); + cvt(ffi::X509_NAME_add_entry_by_txt( + self.0.as_ptr(), + field.as_ptr() as *mut _, + ffi::MBSTRING_UTF8, + value.as_ptr(), +- value.len() as c_int, ++ value.len() as isize, + -1, + 0, + )) +@@ -907,13 +920,13 @@ impl X509NameBuilder { + ) -> Result<(), ErrorStack> { + unsafe { + let field = CString::new(field).unwrap(); +- assert!(value.len() <= c_int::max_value() as usize); ++ assert!(value.len() <= isize::max_value() as usize); + cvt(ffi::X509_NAME_add_entry_by_txt( + self.0.as_ptr(), + field.as_ptr() as *mut _, + ty.as_raw(), + value.as_ptr(), +- value.len() as c_int, ++ value.len() as isize, + -1, + 0, + )) +@@ -928,13 +941,13 @@ impl X509NameBuilder { + /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_NAME_add_entry_by_NID.html + pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> { + unsafe { +- assert!(value.len() <= c_int::max_value() as usize); ++ assert!(value.len() <= isize::max_value() as usize); + cvt(ffi::X509_NAME_add_entry_by_NID( + self.0.as_ptr(), + field.as_raw(), + ffi::MBSTRING_UTF8, + value.as_ptr() as *mut _, +- value.len() as c_int, ++ value.len() as isize, + -1, + 0, + )) +@@ -954,13 +967,13 @@ impl X509NameBuilder { + ty: Asn1Type, + ) -> Result<(), ErrorStack> { + unsafe { +- assert!(value.len() <= c_int::max_value() as usize); ++ assert!(value.len() <= isize::max_value() as usize); + cvt(ffi::X509_NAME_add_entry_by_NID( + self.0.as_ptr(), + field.as_raw(), + ty.as_raw(), + value.as_ptr() as *mut _, +- value.len() as c_int, ++ value.len() as isize, + -1, + 0, + )) +@@ -1260,6 +1273,29 @@ impl X509ReqBuilder { + } + } + ++ /// Sign the request using a private key without a digest. ++ /// ++ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null ++ /// message digest. ++ /// ++ /// This corresponds to [`X509_REQ_sign`]. ++ /// ++ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html ++ #[cfg(boringssl)] ++ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack> ++ where ++ T: HasPrivate, ++ { ++ unsafe { ++ cvt(ffi::X509_REQ_sign( ++ self.0.as_ptr(), ++ key.as_ptr(), ++ ptr::null(), ++ )) ++ .map(|_| ()) ++ } ++ } ++ + /// Returns the `X509Req`. + pub fn build(self) -> X509Req { + self.0 +-- +2.42.0.rc2.253.gd59a3bf2b4-goog +
diff --git a/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch b/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch deleted file mode 100644 index 030e46e..0000000 --- a/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch +++ /dev/null
@@ -1,25 +0,0 @@ -From 097eaa7166ad1f6298c41bc66e094a15a9a4e73e Mon Sep 17 00:00:00 2001 -From: Nabil Wadih <nwadih@google.com> -Date: Tue, 6 Jun 2023 15:57:04 -0700 -Subject: [PATCH 2/2] fix boringssl dsa build errors - ---- - openssl/src/dsa.rs | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/openssl/src/dsa.rs b/openssl/src/dsa.rs -index c550f654..ffebdf8a 100644 ---- a/openssl/src/dsa.rs -+++ b/openssl/src/dsa.rs -@@ -283,7 +283,7 @@ impl<T> fmt::Debug for Dsa<T> { - } - - cfg_if! { -- if #[cfg(any(ossl110, libressl273))] { -+ if #[cfg(any(ossl110, libressl273, boringssl))] { - use ffi::{DSA_get0_key, DSA_get0_pqg, DSA_set0_key, DSA_set0_pqg}; - } else { - #[allow(bad_style)] --- -2.41.0.162.gfafddb0af9-goog -
diff --git a/nearby/src/crypto_ffi.rs b/nearby/src/crypto_ffi.rs index 3118b8a..20d1d5d 100644 --- a/nearby/src/crypto_ffi.rs +++ b/nearby/src/crypto_ffi.rs
@@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::support::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; -use crate::BuildBoringSslOptions; use anyhow::anyhow; +use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; use owo_colors::OwoColorize as _; use semver::{Version, VersionReq}; use std::{ @@ -22,7 +21,15 @@ path::{Path, PathBuf}, }; -pub fn build_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow::Result<()> { +use crate::CargoOptions; + +pub fn boringssl_check_everything(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { + check_boringssl(root, cargo_options)?; + check_openssl(root, cargo_options)?; + Ok(()) +} + +pub fn build_boringssl(root: &Path) -> anyhow::Result<()> { let bindgen_version_req = VersionReq::parse(">=0.61.0")?; let bindgen_version = get_bindgen_version()?; @@ -30,23 +37,16 @@ return Err(anyhow!("Bindgen does not match expected version: {bindgen_version_req}")); } - let mut vendor_dir = - root.parent().ok_or_else(|| anyhow!("project root dir no parent dir"))?.to_path_buf(); - vendor_dir.push("boringssl-build"); + let vendor_dir = root + .parent() + .ok_or_else(|| anyhow!("project root dir no parent dir"))? + .join("boringssl-build"); fs::create_dir_all(&vendor_dir)?; - let mut build_dir = clone_repo_if_needed( - &vendor_dir, - "boringssl", - "https://boringssl.googlesource.com/boringssl", - )?; - - run_cmd_shell_with_color::<YellowStderr>( - &build_dir, - format!("git checkout {}", &options.commit_hash), - )?; - - build_dir.push("build"); + let build_dir = root + .parent() + .ok_or_else(|| anyhow!("project root dir no parent dir"))? + .join("third_party/boringssl/build"); fs::create_dir_all(&build_dir)?; let target = run_cmd_shell_with_color::<YellowStderr>(&vendor_dir, "rustc -vV")? @@ -70,19 +70,23 @@ Ok(()) } -pub fn check_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow::Result<()> { +pub fn check_boringssl(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { log::info!("Checking boringssl"); - build_boringssl(root, options)?; + build_boringssl(root)?; - let mut bssl_dir = root.to_path_buf(); - bssl_dir.push("crypto/crypto_provider_boringssl"); + let bssl_dir = root.join("crypto/crypto_provider_boringssl"); - run_cmd_shell(&bssl_dir, "cargo check")?; + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; + + run_cmd_shell(&bssl_dir, format!("cargo check {locked_arg}"))?; run_cmd_shell(&bssl_dir, "cargo fmt --check")?; run_cmd_shell(&bssl_dir, "cargo clippy --all-targets")?; - run_cmd_shell(&bssl_dir, "cargo test -- --color=always")?; + run_cmd_shell(&bssl_dir, format!("cargo test {locked_arg} -- --color=always"))?; run_cmd_shell(&bssl_dir, "cargo doc --no-deps")?; + + run_cmd_shell(root, "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_boringssl")?; + Ok(()) } @@ -100,7 +104,7 @@ run_cmd_shell_with_color::<YellowStderr>( &repo_dir, - "git checkout 11797d9ecb73e94b7f55a49274318abc9dc074d2", + "git checkout 7df56869c5e1e32369091ab106750d644d3aa0c4", )?; run_cmd_shell_with_color::<YellowStderr>(&repo_dir, "git branch -f BASE_COMMIT")?; run_cmd_shell_with_color::<YellowStderr>( @@ -121,16 +125,20 @@ Ok(()) } -pub fn check_openssl(root: &Path) -> anyhow::Result<()> { +pub fn check_openssl(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { log::info!("Checking rust openssl"); prepare_patched_rust_openssl(root)?; + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; // test the openssl crate with the boringssl feature run_cmd_shell( root, - concat!( - "cargo --config .cargo/config-boringssl.toml test -p crypto_provider_openssl ", - "--features=boringssl -- --color=always" + format!( + concat!( + "cargo --config .cargo/config-boringssl.toml test {locked_arg} -p crypto_provider_openssl ", + "--features=boringssl -- --color=always" + ), + locked_arg=locked_arg ), )?;
diff --git a/nearby/src/ffi.rs b/nearby/src/ffi.rs index 44479be..01fe58a 100644 --- a/nearby/src/ffi.rs +++ b/nearby/src/ffi.rs
@@ -12,56 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; +use crate::CargoOptions; +use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; use std::{fs, path}; // wrapper for checking all ffi related things -pub fn check_everything(root: &path::Path) -> anyhow::Result<()> { - check_np_ffi(root)?; - check_ldt_ffi(root)?; - check_cmake_projects(root)?; +pub fn check_everything(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { + check_np_ffi_rust(root, cargo_options)?; + check_ldt_ffi_rust(root)?; + check_ldt_cmake(root, cargo_options)?; + check_np_ffi_cmake(root, cargo_options)?; Ok(()) } -pub fn check_np_ffi(root: &path::Path) -> anyhow::Result<()> { +pub fn check_np_ffi_rust(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { log::info!("Checking np_c_ffi cargo build"); - let mut ffi_dir = root.to_path_buf(); - ffi_dir.push("presence/np_c_ffi"); + let ffi_dir = root.join("presence/np_c_ffi"); + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; for cargo_cmd in [ "fmt --check", - // Default build, RustCrypto + no_std - "build --release", - "clippy --release", + // Default build, RustCrypto + format!("check {locked_arg} --quiet").as_str(), + // Build with BoringSSL for crypto + format!("check {locked_arg} --no-default-features --features=boringssl").as_str(), + "clippy", + "deny check", + "doc --quiet --no-deps", ] { run_cmd_shell(&ffi_dir, format!("cargo {}", cargo_cmd))?; } Ok(()) } -pub fn check_ldt_ffi(root: &path::Path) -> anyhow::Result<()> { +pub fn check_ldt_ffi_rust(root: &path::Path) -> anyhow::Result<()> { log::info!("Checking LFT ffi cargo build"); - let mut ffi_dir = root.to_path_buf(); - ffi_dir.push("presence/ldt_np_adv_ffi"); + let ffi_dir = root.to_path_buf().join("presence/ldt_np_adv_ffi"); for cargo_cmd in [ "fmt --check", // Default build, RustCrypto + no_std - "build --release", + "check --quiet", // Turn on std, still using RustCrypto - "build --features=std", + "check --quiet --features=std", // Turn off default features and try to build with std", - "build --no-default-features --features=std", - // Turn off RustCrypto and use openssl - "build --no-default-features --features=openssl", + "check --quiet --no-default-features --features=std", // Turn off RustCrypto and use boringssl - "--config .cargo/config-boringssl.toml build --no-default-features --features=boringssl", - "doc --no-deps", + "check --quiet --no-default-features --features=boringssl", + "doc --quiet --no-deps", "clippy --release", "clippy --features=std", "clippy --no-default-features --features=openssl", "clippy --no-default-features --features=std", - // TODO also clippy for boringssl? "deny check", ] { run_cmd_shell(&ffi_dir, format!("cargo {}", cargo_cmd))?; @@ -70,44 +72,91 @@ Ok(()) } -pub fn check_cmake_projects(root: &path::Path) -> anyhow::Result<()> { - log::info!("Checking CMake build and tests (for ffi c/c++ code)"); - let mut build_dir = root.to_path_buf(); - build_dir.push("presence/cmake-build"); +pub fn check_np_ffi_cmake(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { + log::info!("Checking CMake build and tests for np ffi c/c++ code"); + let build_dir = root.to_path_buf().join("presence/cmake-build"); fs::create_dir_all(&build_dir)?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake .. -DENABLE_TESTS=true")?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?; + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; - // run the np_cpp_ffi unit tests - let mut np_cpp_tests_dir = build_dir.clone(); - np_cpp_tests_dir.push("np_cpp_ffi/tests"); - run_cmd_shell_with_color::<YellowStderr>(&np_cpp_tests_dir, "ctest")?; + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake -G Ninja -DENABLE_TESTS=true -DCMAKE_BUILD_TYPE=Release ..", + )?; + + // verify sample and benchmarks build + let np_ffi_crate_dir = root.to_path_buf().join("presence/np_c_ffi"); + run_cmd_shell(&np_ffi_crate_dir, format!("cargo build {locked_arg} --release"))?; + run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target np_cpp_sample")?; + run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target np_ffi_bench")?; + + // Run tests with different crypto backends + let tests_dir = build_dir.to_path_buf().join("np_cpp_ffi/tests"); + for build_config in [ + // test with default build settings (rustcrypto) + format!("build {locked_arg} --quiet --release"), + // test with boringssl + format!("build {locked_arg} --quiet --no-default-features --features=boringssl"), + ] { + let _ = run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "rm np_cpp_ffi/tests/np_ffi_tests", + ); + run_cmd_shell(&np_ffi_crate_dir, format!("cargo {}", build_config))?; + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake --build . --target np_ffi_tests", + )?; + run_cmd_shell_with_color::<YellowStderr>(&tests_dir, "ctest")?; + } + + Ok(()) +} + +pub fn check_ldt_cmake(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> { + log::info!("Checking CMake build and tests for ldt c/c++ code"); + let build_dir = root.to_path_buf().join("presence/cmake-build"); + fs::create_dir_all(&build_dir)?; + + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; + + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake -G Ninja -DENABLE_TESTS=true -DCMAKE_BUILD_TYPE=Release ..", + )?; + + // verify sample and benchmarks build + let ldt_ffi_crate_dir = root.to_path_buf().join("presence/ldt_np_adv_ffi"); + run_cmd_shell(&ldt_ffi_crate_dir, format!("cargo build {locked_arg} --release"))?; + run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target ldt_c_sample")?; + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake --build . --target ldt_benchmarks", + )?; // Run the LDT ffi unit tests. These are rebuilt and tested against all of the different // Cargo build configurations based on the feature flags. - let mut ldt_tests_dir = build_dir.clone(); - ldt_tests_dir.push("ldt_np_c_sample/tests"); - - let mut ldt_ffi_crate_dir = root.to_path_buf(); - ldt_ffi_crate_dir.push("presence/ldt_np_adv_ffi"); - + let ldt_tests_dir = build_dir.to_path_buf().join("ldt_np_c_sample/tests"); for build_config in [ // test with default build settings (rustcrypto, no_std) - "build --release", + format!("build {locked_arg} --quiet --release"), // test with std and default features - "build --features std --release", + format!("build {locked_arg} --quiet --features std --release"), // test with boringssl crypto feature flag - "--config .cargo/config-boringssl.toml build --no-default-features --features boringssl --release", - // test with openssl feature flag - "build --no-default-features --features openssl --release", + format!("build {locked_arg} --quiet --no-default-features --features boringssl --release"), // test without defaults and std feature flag - "build --no-default-features --features std --release", + format!("build {locked_arg} --quiet --no-default-features --features std --release"), ] { run_cmd_shell(&ldt_ffi_crate_dir, format!("cargo {}", build_config))?; // Force detection of updated `ldt_np_adv_ffi` static lib - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "rm -rf ldt_np_c_sample/tests/*")?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?; + let _ = run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "rm ldt_np_c_sample/tests/ldt_ffi_tests", + ); + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake --build . --target ldt_ffi_tests", + )?; run_cmd_shell_with_color::<YellowStderr>(&ldt_tests_dir, "ctest")?; }
diff --git a/nearby/src/file_header/license.rs b/nearby/src/file_header/license.rs deleted file mode 100644 index b463ad0..0000000 --- a/nearby/src/file_header/license.rs +++ /dev/null
@@ -1,45 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Support for license-oriented usage of `file_header`. -use super::*; -use chrono::Datelike as _; - -/// The Apache 2 license for the current year and provided `copyright_holder`. -pub fn apache_2(copyright_holder: &str) -> Header<impl HeaderChecker> { - Header::new( - asl2_checker(), - format!( - r#"Copyright {} {} - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License."#, - chrono::prelude::Utc::now().year(), - copyright_holder - ), - ) -} - -pub(crate) fn asl2_checker() -> impl HeaderChecker { - SingleLineChecker::new("Licensed under the Apache License, Version 2.0".to_string(), 10) -}
diff --git a/nearby/src/file_header/mod.rs b/nearby/src/file_header/mod.rs deleted file mode 100644 index 275a7d0..0000000 --- a/nearby/src/file_header/mod.rs +++ /dev/null
@@ -1,559 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Tools for checking for, or adding, headers (e.g. licenses, etc) in files. - -use std::{ - fs, - io::{self, BufRead as _, Write as _}, - iter::FromIterator, - path, thread, -}; - -pub mod license; - -/// A file header to check for or add to files. -#[derive(Clone)] -pub struct Header<C: HeaderChecker> { - checker: C, - header: String, -} - -impl<C: HeaderChecker> Header<C> { - /// Construct a new `Header` with the `checker` used to determine if the header is already - /// present, and the plain `header` text to add (without any applicable comment syntax, etc). - pub fn new(checker: C, header: String) -> Self { - Self { checker, header } - } - - /// Return true if the file has the desired header, false otherwise. - pub fn header_present(&self, input: &mut impl io::Read) -> io::Result<bool> { - self.checker.check(input) - } - - /// Add the header, with appropriate formatting for the type of file indicated by `p`'s - /// extension, if the header is not already present. - /// Returns true if the header was added. - pub fn add_header_if_missing(&self, p: &path::Path) -> Result<bool, AddHeaderError> { - let err_mapper = |e| AddHeaderError::IoError(p.to_path_buf(), e); - let contents = fs::read_to_string(p).map_err(err_mapper)?; - - if self.header_present(&mut contents.as_bytes()).map_err(err_mapper)? { - return Ok(false); - } - - let mut effective_header = header_delimiters(p) - .ok_or_else(|| AddHeaderError::UnknownExtension(p.to_path_buf())) - .map(|d| wrap_header(&self.header, d))?; - - let mut after_header = contents.as_str(); - // check for a magic first line - if let Some((first_line, rest)) = contents.split_once('\n') { - if MAGIC_FIRST_LINES.iter().any(|l| first_line.contains(l)) { - let mut first_line = first_line.to_string(); - first_line.push('\n'); - effective_header.insert_str(0, &first_line); - after_header = rest; - } - } - - // write the license - let mut f = - fs::OpenOptions::new().write(true).truncate(true).open(p).map_err(err_mapper)?; - f.write_all(effective_header.as_bytes()).map_err(err_mapper)?; - // newline to separate the header from previous contents - f.write_all("\n".as_bytes()).map_err(err_mapper)?; - f.write_all(after_header.as_bytes()).map_err(err_mapper)?; - - Ok(true) - } -} - -/// Errors that can occur when adding a header -#[derive(Debug, thiserror::Error)] -pub enum AddHeaderError { - #[error("I/O error at {0:?}: {1}")] - IoError(path::PathBuf, io::Error), - #[error("Unknown file extension: {0:?}")] - UnknownExtension(path::PathBuf), -} - -/// Checks for headers in files, like licenses or author attribution. -pub trait HeaderChecker: Send + Clone { - /// Return true if the file has the desired header, false otherwise. - fn check(&self, file: &mut impl io::Read) -> io::Result<bool>; -} - -/// Checks for a in the first several lines of each file. -#[derive(Clone)] -pub struct SingleLineChecker { - /// Pattern to do a substring match on in each of the first `max_lines` lines of the file - pattern: String, - /// Number of lines to search through - max_lines: usize, -} - -impl SingleLineChecker { - /// Construct a `SingleLineChecker` that looks for `pattern` in the first `max_lines` of a file. - pub(crate) fn new(pattern: String, max_lines: usize) -> Self { - Self { pattern, max_lines } - } -} - -impl HeaderChecker for SingleLineChecker { - fn check(&self, input: &mut impl io::Read) -> io::Result<bool> { - let mut reader = io::BufReader::new(input); - let mut lines_read = 0; - // reuse buffer to minimize allocation - let mut line = String::new(); - // only read the first bit of the file - while lines_read < self.max_lines { - line.clear(); - let bytes = reader.read_line(&mut line)?; - if bytes == 0 { - // EOF - return Ok(false); - } - lines_read += 1; - - if line.contains(&self.pattern) { - return Ok(true); - } - } - - Ok(false) - } -} - -#[derive(Copy, Clone)] -enum CheckStatus { - MisMatchedHeader, - BinaryFile, -} - -#[derive(Clone)] -struct FileResult { - path: path::PathBuf, - status: CheckStatus, -} - -#[derive(Clone, Default)] -pub struct FileResults { - pub mismatched_files: Vec<path::PathBuf>, - pub binary_files: Vec<path::PathBuf>, -} - -impl FileResults { - pub fn has_failure(&self) -> bool { - !self.mismatched_files.is_empty() || !self.binary_files.is_empty() - } -} - -impl FromIterator<FileResult> for FileResults { - fn from_iter<I>(iter: I) -> FileResults - where - I: IntoIterator<Item = FileResult>, - { - let mut results = FileResults::default(); - for result in iter { - match result.status { - CheckStatus::MisMatchedHeader => results.mismatched_files.push(result.path), - CheckStatus::BinaryFile => results.binary_files.push(result.path), - } - } - results - } -} - -/// Recursively check for `header` in every file in `root` that matches `path_predicate`. -/// -/// Returns a [`FileResults`] object containing the paths without headers detected. -pub fn check_headers_recursively( - root: &path::Path, - path_predicate: impl Fn(&path::Path) -> bool, - header: Header<impl HeaderChecker + 'static>, - num_threads: usize, -) -> Result<FileResults, CheckHeadersRecursivelyError> { - let (path_tx, path_rx) = crossbeam::channel::unbounded::<path::PathBuf>(); - let (result_tx, result_rx) = crossbeam::channel::unbounded(); - - // spawn a few threads to handle files in parallel - let handles = (0..num_threads) - .map(|_| { - let path_rx = path_rx.clone(); - let result_tx = result_tx.clone(); - let header = header.clone(); - thread::spawn(move || { - for p in path_rx { - match fs::File::open(&p).and_then(|mut f| header.header_present(&mut f)) { - Ok(header_present) => { - if header_present { - // no op - } else { - let res = - FileResult { path: p, status: CheckStatus::MisMatchedHeader }; - result_tx.send(Ok(res)).unwrap(); - } - } - Err(e) if e.kind() == io::ErrorKind::InvalidData => { - // Binary file - add to ignore in license.rs - let res = FileResult { path: p, status: CheckStatus::BinaryFile }; - result_tx.send(Ok(res)).unwrap(); - } - Err(e) => result_tx - .send(Err(CheckHeadersRecursivelyError::IoError(p, e))) - .unwrap(), - } - } - - // no more files - }) - }) - .collect::<Vec<thread::JoinHandle<()>>>(); - // make sure result channel closes when threads complete - drop(result_tx); - - find_files(root, path_predicate, path_tx)?; - - let res: FileResults = result_rx.into_iter().collect::<Result<_, _>>()?; - - for h in handles { - h.join().unwrap(); - } - - Ok(res) -} - -/// Errors that can occur when checking for headers recursively -#[derive(Debug, thiserror::Error)] -pub enum CheckHeadersRecursivelyError { - #[error("I/O error at {0:?}: {1}")] - IoError(path::PathBuf, io::Error), - #[error("Walkdir error: {0}")] - WalkdirError(#[from] walkdir::Error), -} - -/// Add the provided `header` to any file in `root` that matches `path_predicate` and that doesn't -/// already have a header as determined by `checker`. -/// Returns a list of paths that had headers added. -pub fn add_headers_recursively( - root: &path::Path, - path_predicate: impl Fn(&path::Path) -> bool, - header: Header<impl HeaderChecker>, -) -> Result<Vec<path::PathBuf>, AddHeadersRecursivelyError> { - // likely no need for threading since adding headers is only done occasionally - let (path_tx, path_rx) = crossbeam::channel::unbounded::<path::PathBuf>(); - find_files(root, path_predicate, path_tx)?; - - path_rx - .into_iter() - // keep the errors, or the ones with added headers - .filter_map(|p| { - match header.add_header_if_missing(&p).map_err(|e| match e { - AddHeaderError::IoError(p, e) => AddHeadersRecursivelyError::IoError(p, e), - AddHeaderError::UnknownExtension(e) => { - AddHeadersRecursivelyError::UnknownExtension(e) - } - }) { - Ok(added) => { - if added { - Some(Ok(p)) - } else { - None - } - } - Err(e) => Some(Err(e)), - } - }) - .collect::<Result<Vec<_>, _>>() -} - -/// Errors that can occur when adding a header recursively -#[derive(Debug, thiserror::Error)] -pub enum AddHeadersRecursivelyError { - #[error("I/O error at {0:?}: {1}")] - IoError(path::PathBuf, io::Error), - #[error("Walkdir error: {0}")] - WalkdirError(#[from] walkdir::Error), - #[error("Unknown file extension: {0:?}")] - UnknownExtension(path::PathBuf), -} - -/// Find all files starting from `root` that do not match the globs in `ignore`, publishing the -/// resulting paths into `dest`. -fn find_files( - root: &path::Path, - path_predicate: impl Fn(&path::Path) -> bool, - dest: crossbeam::channel::Sender<path::PathBuf>, -) -> Result<(), walkdir::Error> { - for r in walkdir::WalkDir::new(root).into_iter() { - let entry = r?; - if entry.path().is_dir() || !path_predicate(entry.path()) { - continue; - } - dest.send(entry.into_path()).unwrap() - } - - Ok(()) -} - -/// Prepare a header for inclusion in a particular file syntax by wrapping it with -/// comment characters as per the provided `delim`. -fn wrap_header(orig_header: &str, delim: HeaderDelimiters) -> String { - let mut out = String::new(); - - if !delim.first_line.is_empty() { - out.push_str(delim.first_line); - out.push('\n'); - } - - // assumes header uses \n - for line in orig_header.split('\n') { - out.push_str(delim.content_line_prefix); - out.push_str(line); - // Remove any trailing whitespaces (excluding newlines) from `content_line_prefix + line`. - // For example, if `content_line_prefix` is `// ` and `line` is empty, the resulting string - // should be truncated to `//`. - out.truncate(out.trim_end_matches([' ', '\t']).len()); - out.push('\n'); - } - - if !delim.last_line.is_empty() { - out.push_str(delim.last_line); - out.push('\n'); - } - - out -} - -/// Returns the header prefix line, content line prefix, and suffix line for the extension of the -/// provided path, or `None` if the extension is not recognized. -fn header_delimiters(p: &path::Path) -> Option<HeaderDelimiters> { - match p - .extension() - // if the extension isn't UTF-8, oh well - .and_then(|os_str| os_str.to_str()) - .unwrap_or("") - { - "c" | "h" | "gv" | "java" | "scala" | "kt" | "kts" => Some(("/*", " * ", " */")), - "js" | "mjs" | "cjs" | "jsx" | "tsx" | "css" | "scss" | "sass" | "ts" => { - Some(("/**", " * ", " */")) - } - "cc" | "cpp" | "cs" | "go" | "hcl" | "hh" | "hpp" | "m" | "mm" | "proto" | "rs" - | "swift" | "dart" | "groovy" | "v" | "sv" => Some(("", "// ", "")), - "py" | "sh" | "yaml" | "yml" | "dockerfile" | "rb" | "gemfile" | "tcl" | "tf" | "bzl" - | "pl" | "pp" | "build" => Some(("", "# ", "")), - "el" | "lisp" => Some(("", ";; ", "")), - "erl" => Some(("", "% ", "")), - "hs" | "lua" | "sql" | "sdl" => Some(("", "-- ", "")), - "html" | "xml" | "vue" | "wxi" | "wxl" | "wxs" => Some(("<!--", " ", "-->")), - "php" => Some(("", "// ", "")), - "ml" | "mli" | "mll" | "mly" => Some(("(**", " ", "*)")), - // also handle whole filenames if extensions didn't match - _ => match p.file_name().and_then(|os_str| os_str.to_str()).unwrap_or("") { - "Dockerfile" => Some(("", "# ", "")), - _ => None, - }, - } - .map(|(first_line, content_line_prefix, last_line)| HeaderDelimiters { - first_line, - content_line_prefix, - last_line, - }) -} - -/// Delimiters to use around and inside a header for a particular file syntax. -#[derive(Clone, Copy)] -struct HeaderDelimiters { - /// Line to prepend before the header - first_line: &'static str, - /// Prefix before each line of the header itself - content_line_prefix: &'static str, - /// Line to append after the header - last_line: &'static str, -} - -const MAGIC_FIRST_LINES: [&str; 8] = [ - "#!", // shell script - "<?xml", // XML declaratioon - "<!doctype", // HTML doctype - "# encoding:", // Ruby encoding - "# frozen_string_literal:", // Ruby interpreter instruction - "<?php", // PHP opening tag - "# escape", // Dockerfile directive https://docs.docker.com/engine/reference/builder/#parser-directives - "# syntax", // Dockerfile directive https://docs.docker.com/engine/reference/builder/#parser-directives -]; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn single_line_checker_finds_header_when_present() { - let input = r#"foo - some license - bar"#; - - assert!(test_header().checker.check(&mut input.as_bytes()).unwrap()); - } - - #[test] - fn single_line_checker_doesnt_find_header_when_missing() { - let input = r#"foo - wrong license - bar"#; - - assert!(!test_header().checker.check(&mut input.as_bytes()).unwrap()); - } - - #[test] - fn single_line_checker_throws_error_when_missing_and_file_is_non_utf8() { - let input = b"foo - \x00\xff - bar"; - - assert_eq!( - io::ErrorKind::InvalidData, - test_header().checker.check(&mut input.as_slice()).unwrap_err().kind() - ); - } - - #[test] - fn single_line_checker_doesnt_panic_when_file_is_non_utf8() { - let inputs: [&'static [u8]; 3] = [ - b"foo - \x00\xff - bar", - b"foo - some license - \x00\xff - bar", - b"foo - \x00\xff - some license - bar", - ]; - - for mut input in inputs { - // Output is not defined for non-utf-8 files, but we should handle them with grace - let _ = test_header().checker.check(&mut input); - } - } - - #[test] - fn adds_header_with_empty_delimiters() { - let file = tempfile::Builder::new().suffix(".rs").tempfile().unwrap(); - fs::write(file.path(), r#"not a license"#).unwrap(); - - test_header().add_header_if_missing(file.path()).unwrap(); - - assert_eq!( - "// some license etc etc etc - -not a license", - fs::read_to_string(file.path()).unwrap() - ); - } - - #[test] - fn adds_header_with_nonempty_delimiters() { - let file = tempfile::Builder::new().suffix(".c").tempfile().unwrap(); - fs::write(file.path(), r#"not a license"#).unwrap(); - - test_header().add_header_if_missing(file.path()).unwrap(); - - assert_eq!( - "/* - * some license etc etc etc - */ - -not a license", - fs::read_to_string(file.path()).unwrap() - ); - } - - #[test] - fn adds_header_trim_trailing_whitespace() { - let file = tempfile::Builder::new().suffix(".c").tempfile().unwrap(); - fs::write(file.path(), r#"not a license"#).unwrap(); - - test_header_with_blank_lines_and_trailing_whitespace() - .add_header_if_missing(file.path()) - .unwrap(); - - assert_eq!( - "/* - * some license - * line with trailing whitespace. - * - * etc - */ - -not a license", - fs::read_to_string(file.path()).unwrap() - ); - } - - #[test] - fn doesnt_add_header_when_already_present() { - let file = tempfile::Builder::new().suffix(".rs").tempfile().unwrap(); - let initial_content = r#" - // some license etc etc etc already present - not a license"#; - fs::write(file.path(), initial_content).unwrap(); - - test_header().add_header_if_missing(file.path()).unwrap(); - - assert_eq!(initial_content, fs::read_to_string(file.path()).unwrap()); - } - - #[test] - fn adds_header_after_magic_first_line() { - let file = tempfile::Builder::new().suffix(".xml").tempfile().unwrap(); - fs::write( - file.path(), - r#"<?xml version="1.0" encoding="UTF-8"?> -<root /> -"#, - ) - .unwrap(); - - test_header().add_header_if_missing(file.path()).unwrap(); - - assert_eq!( - r#"<?xml version="1.0" encoding="UTF-8"?> -<!-- - some license etc etc etc ---> - -<root /> -"#, - fs::read_to_string(file.path()).unwrap() - ); - } - - fn test_header() -> Header<SingleLineChecker> { - Header::new( - SingleLineChecker::new("some license".to_string(), 100), - r#"some license etc etc etc"#.to_string(), - ) - } - - fn test_header_with_blank_lines_and_trailing_whitespace() -> Header<SingleLineChecker> { - Header::new( - SingleLineChecker::new("some license".to_string(), 100), - "some license\nline with trailing whitespace. \n\netc".to_string(), - ) - } -}
diff --git a/nearby/src/fuzzers.rs b/nearby/src/fuzzers.rs index 6c84582..192d59c 100644 --- a/nearby/src/fuzzers.rs +++ b/nearby/src/fuzzers.rs
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{run_cmd_shell_with_color, YellowStderr}; +use cmd_runner::{run_cmd_shell_with_color, YellowStderr}; use std::{fs, path}; pub(crate) fn run_rust_fuzzers(root: &path::Path) -> anyhow::Result<()> { @@ -70,8 +70,11 @@ fs::remove_dir_all(&build_dir)?; } fs::create_dir_all(&build_dir)?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake ../.. -DENABLE_FUZZ=true")?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "make")?; + run_cmd_shell_with_color::<YellowStderr>( + &build_dir, + "cmake -G Ninja ../.. -DENABLE_FUZZ=true", + )?; + run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?; fs::remove_dir_all(&build_dir)?; Ok(())
diff --git a/nearby/src/jni.rs b/nearby/src/jni.rs index 903ac6d..22a1da2 100644 --- a/nearby/src/jni.rs +++ b/nearby/src/jni.rs
@@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::support::run_cmd_shell; +use cmd_runner::run_cmd_shell; use std::path; // This has to happen after both boringssl has been built and prepare rust openssl patches has been run. @@ -20,6 +20,11 @@ for feature in ["opensslbssl", "boringssl"] { run_cmd_shell(root, format!("cargo --config .cargo/config-boringssl.toml build -p ldt_np_jni --no-default-features --features={}", feature))?; } + Ok(()) +} +pub fn run_kotlin_tests(root: &path::Path) -> anyhow::Result<()> { + let kotlin_lib_path = root.to_path_buf().join("presence/ldt_np_jni/java/LdtNpJni"); + run_cmd_shell(&kotlin_lib_path, "./gradlew :test")?; Ok(()) }
diff --git a/nearby/src/license.rs b/nearby/src/license.rs index 657a62e..e479526 100644 --- a/nearby/src/license.rs +++ b/nearby/src/license.rs
@@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::file_header::{self, check_headers_recursively}; +use chrono::Datelike; +use file_header::{check_headers_recursively, license::spdx::*}; use std::path; pub(crate) fn check_license_headers(root: &path::Path) -> anyhow::Result<()> { @@ -21,11 +22,14 @@ let results = check_headers_recursively( root, |p| !ignore.is_match(p), - file_header::license::apache_2("Google LLC"), + APACHE_2_0.build_header(YearCopyrightOwnerValue::new( + u32::try_from(chrono::Utc::now().year())?, + "Google LLC".to_string(), + )), 4, )?; - for path in results.mismatched_files.iter() { + for path in results.no_header_files.iter() { eprintln!("Header not present: {path:?}"); } @@ -48,7 +52,10 @@ for p in file_header::add_headers_recursively( root, |p| !ignore.is_match(p), - file_header::license::apache_2("Google LLC"), + APACHE_2_0.build_header(YearCopyrightOwnerValue::new( + u32::try_from(chrono::Utc::now().year())?, + "Google LLC".to_string(), + )), )? { println!("Added header: {:?}", p); } @@ -91,7 +98,7 @@ "**/.DS_Store", "**/fuzz/corpus/**", "**/.*.swp", - "**/Session.vim", + "**/*.vim", "**/*.properties", "**/third_party/**", "**/*.png", @@ -99,5 +106,7 @@ "**/node_modules/**", "**/.angular/**", "**/.editorconfig", + "**/*.class", + "**/fuzz/artifacts/**", ] }
diff --git a/nearby/src/main.rs b/nearby/src/main.rs index 4c1104f..eaf3ed2 100644 --- a/nearby/src/main.rs +++ b/nearby/src/main.rs
@@ -15,17 +15,15 @@ extern crate core; use clap::Parser as _; +use cmd_runner::run_cmd_shell; use env_logger::Env; use std::{env, path}; -use support::*; mod crypto_ffi; mod ffi; -mod file_header; mod fuzzers; mod jni; mod license; -mod support; mod ukey2; fn main() -> anyhow::Result<()> { @@ -37,28 +35,29 @@ ); match cli.subcommand { - Subcommand::CheckEverything { ref check_options, ref bssl_options } => { - check_everything(&root_dir, check_options, bssl_options)? + Subcommand::CheckEverything { ref check_options } => { + check_everything(&root_dir, check_options)? } Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?, - Subcommand::FfiCheckEverything => ffi::check_everything(&root_dir)?, - Subcommand::BuildBoringssl(ref bssl_options) => { - crypto_ffi::build_boringssl(&root_dir, bssl_options)? + Subcommand::FfiCheckEverything(ref options) => ffi::check_everything(&root_dir, options)?, + Subcommand::BoringsslCheckEverything(ref options) => { + crypto_ffi::boringssl_check_everything(&root_dir, options)? } - Subcommand::CheckBoringssl(ref bssl_options) => { - crypto_ffi::check_boringssl(&root_dir, bssl_options)? - } + Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(&root_dir)?, + Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(&root_dir, options)?, Subcommand::PrepareRustOpenssl => crypto_ffi::prepare_patched_rust_openssl(&root_dir)?, - Subcommand::CheckOpenssl => crypto_ffi::check_openssl(&root_dir)?, + Subcommand::CheckOpenssl(ref options) => crypto_ffi::check_openssl(&root_dir, options)?, Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(&root_dir)?, Subcommand::BuildFfiFuzzers => fuzzers::build_ffi_fuzzers(&root_dir)?, Subcommand::CheckLicenseHeaders => license::check_license_headers(&root_dir)?, Subcommand::AddLicenseHeaders => license::add_license_headers(&root_dir)?, - Subcommand::CheckLdtFfi => ffi::check_ldt_ffi(&root_dir)?, - Subcommand::CheckUkey2Ffi => ukey2::check_ukey2_ffi(&root_dir)?, + Subcommand::CheckLdtFfi => ffi::check_ldt_ffi_rust(&root_dir)?, + Subcommand::CheckUkey2Ffi(ref options) => ukey2::check_ukey2_ffi(&root_dir, options)?, Subcommand::CheckLdtJni => jni::check_ldt_jni(&root_dir)?, - Subcommand::CheckNpFfi => ffi::check_np_ffi(&root_dir)?, - Subcommand::CheckCmakeProjects => ffi::check_cmake_projects(&root_dir)?, + Subcommand::CheckNpFfi(ref options) => ffi::check_np_ffi_rust(&root_dir, options)?, + Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(&root_dir, options)?, + Subcommand::CheckNpFfiCmake(ref options) => ffi::check_np_ffi_cmake(&root_dir, options)?, + Subcommand::RunKotlinTests => jni::run_kotlin_tests(&root_dir)?, } Ok(()) @@ -73,14 +72,21 @@ // ensure formatting is correct (Check for it first because it is fast compared to running tests) fmt_command, // make sure everything compiles - "cargo check --workspace --all-targets", + "cargo check --workspace --all-targets --quiet", // run all the tests //TODO: re-enable the openssl tests, this was potentially failing due to UB code in the // upstream rust-openssl crate's handling of empty slices. This repros consistently when // using the rust-openssl crate backed by openssl-sys on Ubuntu 20.04. "cargo test --workspace --quiet --exclude crypto_provider_openssl -- --color=always", + // Test ukey2 builds with different crypto providers + "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_rustcrypto", + "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_openssl", // ensure the docs are valid (cross-references to other code, etc) - "RUSTDOCFLAGS='--deny warnings' cargo doc --workspace --no-deps", + concat!( + "RUSTDOCFLAGS='--deny warnings -Z unstable-options --enable-index-page --generate-link-to-definition' ", + "cargo +nightly doc --quiet --workspace --no-deps --document-private-items ", + "--target-dir target/dist_docs", + ), "cargo clippy --all-targets --workspace -- --deny warnings", "cargo deny --workspace check", ] { @@ -89,18 +95,15 @@ Ok(()) } -pub fn check_everything( - root: &path::Path, - check_options: &CheckOptions, - bssl_options: &BuildBoringSslOptions, -) -> anyhow::Result<()> { +pub fn check_everything(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> { license::check_license_headers(root)?; check_workspace(root, check_options)?; - crypto_ffi::check_boringssl(root, bssl_options)?; - crypto_ffi::check_openssl(root)?; - ffi::check_everything(root)?; + crypto_ffi::check_boringssl(root, &check_options.cargo_options)?; + crypto_ffi::check_openssl(root, &check_options.cargo_options)?; + ffi::check_everything(root, &check_options.cargo_options)?; jni::check_ldt_jni(root)?; - ukey2::check_ukey2_ffi(root)?; + jni::run_kotlin_tests(root)?; + ukey2::check_ukey2_ffi(root, &check_options.cargo_options)?; fuzzers::run_rust_fuzzers(root)?; fuzzers::build_ffi_fuzzers(root)?; @@ -119,56 +122,58 @@ CheckEverything { #[command(flatten)] check_options: CheckOptions, - #[command(flatten)] - bssl_options: BuildBoringSslOptions, }, /// Checks everything included in the top level workspace CheckWorkspace(CheckOptions), + /// Checks everything related to the boringssl version (equivalent of running check-boringssl + /// + check-openssl) + BoringsslCheckEverything(CargoOptions), /// Clones boringssl and uses bindgen to generate the rust crate - BuildBoringssl(BuildBoringSslOptions), + BuildBoringssl, /// Run crypto provider tests using boringssl backend - CheckBoringssl(BuildBoringSslOptions), + CheckBoringssl(CargoOptions), /// Applies AOSP specific patches to the 3p `openssl` crate so that it can use a boringssl /// backend PrepareRustOpenssl, /// Run crypto provider tests using openssl crate with boringssl backend - CheckOpenssl, + CheckOpenssl(CargoOptions), /// Build and run pure Rust fuzzers for 10000 runs RunRustFuzzers, /// Build FFI fuzzers BuildFfiFuzzers, /// Builds and runs tests for all C/C++ projects. This is a combination of CheckNpFfi, /// CheckLdtFfi, and CheckCmakeBuildAndTests - FfiCheckEverything, + FfiCheckEverything(CargoOptions), /// Builds the crate checks the cbindgen generation of C/C++ bindings - CheckNpFfi, + CheckNpFfi(CargoOptions), /// Builds ldt_np_adv_ffi crate with all possible different sets of feature flags CheckLdtFfi, /// Checks the CMake build and runs all of the C/C++ tests - CheckCmakeProjects, + CheckLdtCmake(CargoOptions), + /// Checks the CMake build and runs all of the C/C++ tests + CheckNpFfiCmake(CargoOptions), /// Checks the workspace 3rd party crates and makes sure they have a valid license CheckLicenseHeaders, /// Generate new headers for any files that are missing them AddLicenseHeaders, /// Builds and runs tests for the UKEY2 FFI - CheckUkey2Ffi, + CheckUkey2Ffi(CargoOptions), /// Checks the build of ldt_jni wrapper with non default features, ie rust-openssl, and boringssl CheckLdtJni, + /// Runs the kotlin tests of the LDT Jni API + RunKotlinTests, } #[derive(clap::Args, Debug, Clone, Default)] pub struct CheckOptions { #[arg(long, help = "reformat files with cargo fmt")] reformat: bool, + #[command(flatten)] + cargo_options: CargoOptions, } #[derive(clap::Args, Debug, Clone, Default)] -pub struct BuildBoringSslOptions { - #[arg( - long, - // the commit after this one causes failures in rust-openssl - default_value = "d995d82ad53133017e34b009e9c6912b2ef6aeb7", - help = "Commit hash to use when checking out boringssl" - )] - commit_hash: String, +pub struct CargoOptions { + #[arg(long, help = "whether to run cargo with --locked")] + locked: bool, }
diff --git a/nearby/src/ukey2.rs b/nearby/src/ukey2.rs index f0e1081..e548ab0 100644 --- a/nearby/src/ukey2.rs +++ b/nearby/src/ukey2.rs
@@ -12,26 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; +use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr}; use std::{fs, path}; -pub(crate) fn check_ukey2_ffi(root: &path::Path) -> anyhow::Result<()> { +use crate::CargoOptions; + +pub(crate) fn check_ukey2_ffi( + root: &path::Path, + cargo_options: &CargoOptions, +) -> anyhow::Result<()> { log::info!("Checking Ukey2 ffi"); - let mut ffi_dir = root.to_path_buf(); - ffi_dir.push("connections/ukey2/ukey2_c_ffi"); + let ffi_dir = root.join("connections/ukey2/ukey2_c_ffi"); + + let locked_arg = if cargo_options.locked { "--locked" } else { "" }; // Default build, RustCrypto - run_cmd_shell(&ffi_dir, "cargo build --release --lib")?; + run_cmd_shell(&ffi_dir, format!("cargo build {locked_arg} --quiet --release --lib"))?; // OpenSSL - run_cmd_shell(&ffi_dir, "cargo build --no-default-features --features=openssl")?; + run_cmd_shell( + &ffi_dir, + format!("cargo build {locked_arg} --quiet --no-default-features --features=openssl"), + )?; - run_cmd_shell(&ffi_dir, "cargo doc --no-deps")?; + run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?; run_cmd_shell(&ffi_dir, "cargo clippy --no-default-features --features=openssl")?; run_cmd_shell(&ffi_dir, "cargo deny check")?; - let mut ffi_build_dir = ffi_dir.to_path_buf(); - ffi_build_dir.push("cpp/build"); + let ffi_build_dir = ffi_dir.join("cpp/build"); fs::create_dir_all(&ffi_build_dir)?; run_cmd_shell_with_color::<YellowStderr>(&ffi_build_dir, "cmake ..")?; run_cmd_shell_with_color::<YellowStderr>(&ffi_build_dir, "cmake --build .")?;
diff --git a/nearby/presence/handle_map/Cargo.toml b/nearby/util/handle_map/Cargo.toml similarity index 69% rename from nearby/presence/handle_map/Cargo.toml rename to nearby/util/handle_map/Cargo.toml index 8f00bf7..ee9dbf3 100644 --- a/nearby/presence/handle_map/Cargo.toml +++ b/nearby/util/handle_map/Cargo.toml
@@ -5,14 +5,12 @@ publish.workspace = true [dependencies] -hashbrown.workspace = true -lock_api.workspace = true -portable-atomic.workspace = true -spin.workspace = true +lock_adapter.workspace = true crypto_provider.workspace = true [dev-dependencies] criterion.workspace = true +lazy_static.workspace = true [[bench]] name = "benches"
diff --git a/nearby/presence/handle_map/benches/benches.rs b/nearby/util/handle_map/benches/benches.rs similarity index 100% rename from nearby/presence/handle_map/benches/benches.rs rename to nearby/util/handle_map/benches/benches.rs
diff --git a/nearby/util/handle_map/src/declare_handle_map.rs b/nearby/util/handle_map/src/declare_handle_map.rs new file mode 100644 index 0000000..dbdc77f --- /dev/null +++ b/nearby/util/handle_map/src/declare_handle_map.rs
@@ -0,0 +1,161 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `declare_handle_map!` macro + +#[macro_export] +/// ```ignore +/// declare_handle_map! { +/// mod $handle_module_name { +/// #[dimensions = $map_dimension_provider] +/// type $handle_type_name: HandleLike<Object = $wrapped_type>; +/// } +/// } +/// ``` +/// +/// Declares a new public module with name `handle_module_name` which includes a new type +/// `handle_type_name` which is `#[repr(C)]` and represents FFI-accessible handles +/// to values of type `wrapped_type`. +/// +/// Internal to the generated module, a new static `SingletonHandleMap` is created, where the +/// maximum number of active handles and the number of shards are given by +/// the dimensions returned by evaluation of the `map_dimension_provider` expression. +/// +/// Note: `map_dimension_provider` will be evaluated within the defined module's scope, +/// so you will likely need to use `super` to refer to definitions in the enclosing scope. +/// +/// # Example +/// The following code defines an FFI-safe type `StringHandle` which references +/// the `String` data-type, and uses it to define a (contrived) +/// function `sample` which will print "Hello World". +/// +/// ``` +/// #[macro_use]/// +/// extern crate lazy_static; +/// +/// use core::ops::Deref; +/// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike}; +/// +/// fn get_string_handle_map_dimensions() -> HandleMapDimensions { +/// HandleMapDimensions { +/// num_shards: 8, +/// max_active_handles: 100, +/// } +/// } +/// +/// declare_handle_map! { +/// mod string_handle { +/// #[dimensions = super::get_string_handle_map_dimensions()] +/// type StringHandle: HandleLike<Object = String>; +/// } +/// } +/// +/// use string_handle::StringHandle; +/// +/// fn main() { +/// // Note: this method could panic if there are +/// // more than 99 outstanding handles. +/// +/// // Allocate a new string-handle pointing to the string "Hello" +/// let handle = StringHandle::allocate(|| { "Hello".to_string() }).unwrap(); +/// { +/// // Obtain a write-guard on the contents of our handle +/// let mut handle_write_guard = handle.get_mut().unwrap(); +/// handle_write_guard.push_str(" World"); +/// // Write guard is auto-dropped at the end of this block. +/// } +/// { +/// // Obtain a read-guard on the contents of our handle. +/// // Note that we had to ensure that the write-guard was +/// // dropped prior to doing this, or else execution +/// // could potentially hang. +/// let handle_read_guard = handle.get().unwrap(); +/// println!("{}", handle_read_guard.deref()); +/// } +/// // Clean up the data behind the created handle +/// handle.deallocate().unwrap(); +/// } +/// +/// ``` +macro_rules! declare_handle_map { + ( + mod $handle_module_name:ident { + #[dimensions = $map_dimension_provider:expr] + type $handle_type_name:ident: HandleLike<Object = $wrapped_type:ty>; + } + ) => { + #[doc = ::core::concat!( + "Macro-generated (via `handle_map::declare_handle_map!`) module which", + " defines the `", ::core::stringify!($handle_module_name), "::", + ::core::stringify!($handle_type_name), "` FFI-transmissible handle type ", + " which references values of type `", ::core::stringify!($wrapped_type), "`." + )] + pub mod $handle_module_name { + + lazy_static! { + static ref GLOBAL_HANDLE_MAP: $crate::HandleMap<$wrapped_type> = + $crate::HandleMap::with_dimensions($map_dimension_provider); + } + + #[doc = ::core::concat!( + "A `#[repr(C)]` handle to a value of type `", + ::core::stringify!($wrapped_type), "`." + )] + #[repr(C)] + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct $handle_type_name { + handle_id: u64, + } + + impl $handle_type_name { + /// Cast the given raw Handle to this HandleLike + pub fn from_handle(handle: $crate::Handle) -> Self { + Self { handle_id: handle.get_id() } + } + + /// Get this HandleLike as a raw Handle. + pub fn get_as_handle(&self) -> $crate::Handle { + $crate::Handle::from_id(self.handle_id) + } + } + impl $crate::HandleLike for $handle_type_name { + type Object = $wrapped_type; + fn allocate( + initial_value_provider: impl FnOnce() -> $wrapped_type, + ) -> Result<Self, $crate::HandleMapFullError> { + GLOBAL_HANDLE_MAP + .allocate(initial_value_provider) + .map(|derived_handle| Self { handle_id: derived_handle.get_id() }) + } + fn get( + &self, + ) -> Result<$crate::ObjectReadGuardImpl<$wrapped_type>, $crate::HandleNotPresentError> + { + GLOBAL_HANDLE_MAP.get(self.get_as_handle()) + } + fn get_mut( + &self, + ) -> Result< + $crate::ObjectReadWriteGuardImpl<$wrapped_type>, + $crate::HandleNotPresentError, + > { + GLOBAL_HANDLE_MAP.get_mut(self.get_as_handle()) + } + fn deallocate(self) -> Result<$wrapped_type, $crate::HandleNotPresentError> { + GLOBAL_HANDLE_MAP.deallocate(self.get_as_handle()) + } + } + } + }; +}
diff --git a/nearby/util/handle_map/src/guard.rs b/nearby/util/handle_map/src/guard.rs new file mode 100644 index 0000000..4a96bb5 --- /dev/null +++ b/nearby/util/handle_map/src/guard.rs
@@ -0,0 +1,143 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::Handle; +use core::ops::{Deref, DerefMut}; +use lock_adapter::std::RwMapping; +use std::collections::HashMap; +use std::marker::PhantomData; + +/// A RAII read lock guard for an object in a [`HandleMap`](crate::HandleMap) +/// pointed-to by a given [`Handle`]. When this struct is +/// dropped, the underlying read lock on the associated +/// shard will be dropped. +pub struct ObjectReadGuardImpl<'a, T: 'a> { + pub(crate) guard: lock_adapter::std::MappedRwLockReadGuard< + 'a, + <Self as ObjectReadGuard>::Arg, + <Self as ObjectReadGuard>::Ret, + <Self as ObjectReadGuard>::Mapping, + >, +} + +/// Trait implemented for an ObjectReadGuard which defines the associated types of the guard and a +/// mapping for how the guard is retrieved from its parent object +pub trait ObjectReadGuard: Deref<Target = Self::Ret> { + /// The mapping which defines how a guard is retrieved for `Self::Ret` from `Self::Arg` + type Mapping: RwMapping<Arg = Self::Arg, Ret = Self::Ret>; + /// The argument type input to the mapping functions + type Arg; + /// The Return type of the mapping functions + type Ret; +} + +impl<'a, T> Deref for ObjectReadGuardImpl<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +pub struct ObjectReadGuardMapping<'a, T> { + pub(crate) handle: Handle, + pub(crate) _marker: PhantomData<&'a T>, +} + +impl<'a, T> RwMapping for ObjectReadGuardMapping<'a, T> { + type Arg = HashMap<Handle, T>; + type Ret = T; + + fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret { + // We know that the entry exists, since we've locked the + // shard and already checked that it exists prior to + // handing out this new, mapped read-lock. + arg.get(&self.handle).unwrap() + } + + fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret { + // We know that the entry exists, since we've locked the + // shard and already checked that it exists prior to + // handing out this new, mapped read-lock. + arg.get_mut(&self.handle).unwrap() + } +} + +impl<'a, T> ObjectReadGuard for ObjectReadGuardImpl<'a, T> { + type Mapping = ObjectReadGuardMapping<'a, T>; + type Arg = HashMap<Handle, T>; + type Ret = T; +} + +/// A RAII read-write lock guard for an object in a [`HandleMap`](crate::HandleMap) +/// pointed-to by a given [`Handle`]. When this struct is +/// dropped, the underlying read-write lock on the associated +/// shard will be dropped. +pub struct ObjectReadWriteGuardImpl<'a, T: 'a> { + pub(crate) guard: lock_adapter::std::MappedRwLockWriteGuard< + 'a, + <Self as ObjectReadWriteGuard>::Arg, + <Self as ObjectReadWriteGuard>::Ret, + <Self as ObjectReadWriteGuard>::Mapping, + >, +} + +/// Trait implemented for an object read guard which defines the associated types of the guard and a +/// mapping for how the guard is retrieved from its parent object +pub trait ObjectReadWriteGuard: Deref<Target = Self::Ret> + DerefMut<Target = Self::Ret> { + /// The mapping which defines how a guard is retrieved for `Ret` from `Arg` + type Mapping: RwMapping<Arg = Self::Arg, Ret = Self::Ret>; + /// The argument type input to the mapping functions + type Arg; + /// The Return type of the mapping functions + type Ret; +} + +pub struct ObjectReadWriteGuardMapping<'a, T> { + pub(crate) handle: Handle, + pub(crate) _marker: PhantomData<&'a T>, +} + +impl<'a, T> Deref for ObjectReadWriteGuardImpl<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.guard.deref() + } +} + +impl<'a, T> DerefMut for ObjectReadWriteGuardImpl<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.guard.deref_mut() + } +} + +impl<'a, T> ObjectReadWriteGuard for ObjectReadWriteGuardImpl<'a, T> { + type Mapping = ObjectReadWriteGuardMapping<'a, T>; + type Arg = HashMap<Handle, T>; + type Ret = T; +} + +impl<'a, T> RwMapping for ObjectReadWriteGuardMapping<'a, T> { + type Arg = HashMap<Handle, T>; + type Ret = T; + + fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret { + arg.get(&self.handle).unwrap() + } + + fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret { + arg.get_mut(&self.handle).unwrap() + } +}
diff --git a/nearby/util/handle_map/src/lib.rs b/nearby/util/handle_map/src/lib.rs new file mode 100644 index 0000000..bb420e3 --- /dev/null +++ b/nearby/util/handle_map/src/lib.rs
@@ -0,0 +1,242 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A thread-safe implementation of a map for managing object handles, +//! a safer alternative to raw pointers for FFI interop. + +#![forbid(unsafe_code)] +#![deny(missing_docs)] + +use std::boxed::Box; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; +use std::vec::Vec; + +pub mod declare_handle_map; +mod guard; +pub(crate) mod shard; + +#[cfg(test)] +mod tests; + +pub use guard::{ObjectReadGuardImpl, ObjectReadWriteGuardImpl}; + +use shard::{HandleMapShard, ShardAllocationError}; + +/// An individual handle to be given out by a [`HandleMap`]. +/// This representation is untyped, and just a wrapper +/// around a handle-id, in contrast to implementors of `HandleLike`. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Handle { + handle_id: u64, +} + +impl From<&Handle> for Handle { + fn from(handle: &Handle) -> Self { + *handle + } +} + +impl Handle { + /// Constructs a handle wrapping the given ID. + /// + /// No validity checks are done on the wrapped ID + /// to ensure that the given ID is active in + /// any specific handle-map, and the type + /// of the handle is not represented. + /// + /// As a result, this method is only useful for + /// allowing access to handles from an FFI layer. + pub fn from_id(handle_id: u64) -> Self { + Self { handle_id } + } + + /// Gets the ID for this handle. + /// + /// Since the underlying handle is un-typed,` + /// this method is only suitable for + /// transmitting handles across an FFI layer. + pub fn get_id(&self) -> u64 { + self.handle_id + } + + /// Derives the shard index from the handle id + fn get_shard_index(&self, num_shards: u8) -> usize { + (self.handle_id % (num_shards as u64)) as usize + } +} + +/// Error raised when attempting to allocate into a full handle-map. +#[derive(Debug)] +pub struct HandleMapFullError; + +/// Error raised when the entry for a given [`Handle`] doesn't exist. +#[derive(Debug)] +pub struct HandleNotPresentError; + +/// FFI-transmissible structure expressing the dimensions +/// (max # of allocatable slots, number of shards) of a handle-map +/// to be used upon initialization. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct HandleMapDimensions { + /// The number of shards which are employed + /// by the associated handle-map. + pub num_shards: u8, + /// The maximum number of active handles which may be + /// stored within the associated handle-map. + pub max_active_handles: u32, +} + +/// A thread-safe mapping from "handle"s [like pointers, but safer] +/// to underlying structures, supporting allocations, reads, writes, +/// and deallocations of objects behind handles. +pub struct HandleMap<T: Send + Sync> { + /// The dimensions of this handle-map + dimensions: HandleMapDimensions, + + /// The individually-lockable "shards" of the handle-map, + /// among which the keys will be roughly uniformly-distributed. + handle_map_shards: Box<[HandleMapShard<T>]>, + + /// An atomically-incrementing counter which tracks the + /// next handle ID which allocations will attempt to use. + new_handle_id_counter: AtomicU64, + + /// An atomic integer roughly tracking the number of + /// currently-outstanding allocated entries in this + /// handle-map among all [`HandleMapShard`]s. + outstanding_allocations_counter: AtomicU32, +} + +impl<T: Send + Sync> HandleMap<T> { + /// Creates a new handle-map with the given `HandleMapDimensions`. + pub fn with_dimensions(dimensions: HandleMapDimensions) -> Self { + let mut handle_map_shards = Vec::with_capacity(dimensions.num_shards as usize); + for _ in 0..dimensions.num_shards { + handle_map_shards.push(HandleMapShard::default()); + } + let handle_map_shards = handle_map_shards.into_boxed_slice(); + Self { + dimensions, + handle_map_shards, + new_handle_id_counter: AtomicU64::new(0), + outstanding_allocations_counter: AtomicU32::new(0), + } + } +} + +impl<T: Send + Sync> HandleMap<T> { + /// Allocates a new object within the given handle-map, returning + /// a handle to the location it was stored at. This operation + /// may fail if attempting to allocate over the `dimensions.max_active_handles` + /// limit imposed on the handle-map, in which case this method + /// will return a `HandleMapFullError`. + pub fn allocate( + &self, + initial_value_provider: impl FnOnce() -> T, + ) -> Result<Handle, HandleMapFullError> { + let mut initial_value_provider = initial_value_provider; + loop { + // Increment the new-handle-ID counter using relaxed memory ordering, + // since the only invariant that we want to enforce is that concurrently-running + // threads always get distinct new handle-ids. + let new_handle_id = self.new_handle_id_counter.fetch_add(1, Ordering::Relaxed); + let new_handle = Handle::from_id(new_handle_id); + let shard_index = new_handle.get_shard_index(self.dimensions.num_shards); + + // Now, check the shard to see if we can actually allocate into it. + let shard_allocate_result = self.handle_map_shards[shard_index].try_allocate( + new_handle, + initial_value_provider, + &self.outstanding_allocations_counter, + self.dimensions.max_active_handles, + ); + match shard_allocate_result { + Ok(_) => { + return Ok(new_handle); + } + Err(ShardAllocationError::ExceedsAllocationLimit) => { + return Err(HandleMapFullError); + } + Err(ShardAllocationError::EntryOccupied(thrown_back_provider)) => { + // We need to do the whole thing again with a new ID + initial_value_provider = thrown_back_provider; + } + } + } + } + + /// Gets a read-only reference to an object within the given handle-map, + /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`]. + pub fn get(&self, handle: Handle) -> Result<ObjectReadGuardImpl<T>, HandleNotPresentError> { + let shard_index = handle.get_shard_index(self.dimensions.num_shards); + self.handle_map_shards[shard_index].get(handle) + } + + /// Gets a read+write reference to an object within the given handle-map, + /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`]. + pub fn get_mut( + &self, + handle: Handle, + ) -> Result<ObjectReadWriteGuardImpl<T>, HandleNotPresentError> { + let shard_index = handle.get_shard_index(self.dimensions.num_shards); + self.handle_map_shards[shard_index].get_mut(handle) + } + + /// Removes the object pointed to by the given handle in + /// the handle-map, returning the removed object if it + /// exists. Otherwise, returns [`HandleNotPresentError`]. + pub fn deallocate(&self, handle: Handle) -> Result<T, HandleNotPresentError> { + let shard_index = handle.get_shard_index(self.dimensions.num_shards); + self.handle_map_shards[shard_index] + .deallocate(handle, &self.outstanding_allocations_counter) + } + + /// Gets the actual number of elements stored in the entire map. + /// Only suitable for single-threaded sections of tests. + #[cfg(test)] + pub(crate) fn len(&self) -> usize { + self.handle_map_shards.iter().map(|s| s.len()).sum() + } + + /// Sets the new-handle-id counter to the given value. + /// Only suitable for tests. + #[cfg(test)] + pub(crate) fn set_new_handle_id_counter(&mut self, value: u64) { + self.new_handle_id_counter = AtomicU64::new(value); + } +} + +/// Externally-facing trait for things which behave like handle-map handles +/// with a globally-defined handle-map for the type. +pub trait HandleLike: Sized { + /// The underlying object type pointed-to by this handle + type Object: Send + Sync; + + /// Tries to allocate a new handle using the given provider + /// to construct the underlying stored object as a new + /// entry into the global handle table for this type. + fn allocate( + initial_value_provider: impl FnOnce() -> Self::Object, + ) -> Result<Self, HandleMapFullError>; + + /// Gets a RAII read-guard on the contents behind this handle. + fn get(&self) -> Result<ObjectReadGuardImpl<Self::Object>, HandleNotPresentError>; + + /// Gets a RAII read-write guard on the contents behind this handle. + fn get_mut(&self) -> Result<ObjectReadWriteGuardImpl<Self::Object>, HandleNotPresentError>; + + /// Deallocates the contents behind this handle. + fn deallocate(self) -> Result<Self::Object, HandleNotPresentError>; +}
diff --git a/nearby/util/handle_map/src/shard.rs b/nearby/util/handle_map/src/shard.rs new file mode 100644 index 0000000..da3bd57 --- /dev/null +++ b/nearby/util/handle_map/src/shard.rs
@@ -0,0 +1,185 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ops::{Deref, DerefMut}; +use lock_adapter::std::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use lock_adapter::RwLock as _; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use std::collections::HashMap; +use std::marker::PhantomData; +use std::sync::atomic::{AtomicU32, Ordering}; + +use crate::guard::{ + ObjectReadGuardImpl, ObjectReadGuardMapping, ObjectReadWriteGuardImpl, + ObjectReadWriteGuardMapping, +}; +use crate::{Handle, HandleNotPresentError}; + +// Bunch o' type aliases to make talking about them much easier in the shard code. +type ShardMapType<T> = HashMap<Handle, T>; +type ShardReadWriteLock<T> = RwLock<ShardMapType<T>>; +type ShardReadGuard<'a, T> = RwLockReadGuard<'a, ShardMapType<T>>; +type ShardReadWriteGuard<'a, T> = RwLockWriteGuard<'a, ShardMapType<T>>; + +/// Internal error enum for failed allocations into a given shard. +pub(crate) enum ShardAllocationError<T, F: FnOnce() -> T> { + /// Error for when the entry for the handle is occupied, + /// in which case we spit out the object-provider to try again + /// with a new handle-id. + EntryOccupied(F), + /// Error for when we would exceed the maximum number of allocations. + ExceedsAllocationLimit, +} + +/// An individual handle-map shard, which is ultimately +/// just a hash-map behind a lock. +pub(crate) struct HandleMapShard<T: Send + Sync> { + data: RwLock<ShardMapType<T>>, +} + +impl<T: Send + Sync> Default for HandleMapShard<T> { + fn default() -> Self { + Self { data: RwLock::new(HashMap::new()) } + } +} + +impl<T: Send + Sync> HandleMapShard<T> { + pub fn get(&self, handle: Handle) -> Result<ObjectReadGuardImpl<T>, HandleNotPresentError> { + let map_read_guard = ShardReadWriteLock::<T>::read(&self.data); + let read_only_map_ref = map_read_guard.deref(); + if read_only_map_ref.contains_key(&handle) { + let object_read_guard = ShardReadGuard::<T>::map( + map_read_guard, + ObjectReadGuardMapping { handle, _marker: PhantomData }, + ); + Ok(ObjectReadGuardImpl { guard: object_read_guard }) + } else { + // Auto-drop the read guard, and return an error + Err(HandleNotPresentError) + } + } + /// Gets a read-write guard on the entire shard map if an entry for the given + /// handle exists, but if not, yield [`HandleNotPresentError`]. + fn get_read_write_guard_if_entry_exists( + &self, + handle: Handle, + ) -> Result<ShardReadWriteGuard<T>, HandleNotPresentError> { + let contains_key = { + let map_ref = self.data.read(); + map_ref.contains_key(&handle) + }; + if contains_key { + // If we know that the entry exists, and we're currently + // holding a read-lock, we know that we're safe to request + // an upgrade to a write lock, since only one write or + // upgradable read lock can be outstanding at any one time. + let write_guard = self.data.write(); + Ok(write_guard) + } else { + // Auto-drop the read guard, we don't need to allow a write. + Err(HandleNotPresentError) + } + } + + pub fn get_mut( + &self, + handle: Handle, + ) -> Result<ObjectReadWriteGuardImpl<T>, HandleNotPresentError> { + let map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?; + // Expose only the pointed-to object with a mapped read-write guard + let object_read_write_guard = ShardReadWriteGuard::<T>::map( + map_read_write_guard, + ObjectReadWriteGuardMapping { handle, _marker: PhantomData }, + ); + Ok(ObjectReadWriteGuardImpl { guard: object_read_write_guard }) + } + + pub fn deallocate( + &self, + handle: Handle, + outstanding_allocations_counter: &AtomicU32, + ) -> Result<T, HandleNotPresentError> { + let mut map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?; + // We don't need to worry about double-decrements, since the above call + // got us an upgradable read guard for our read, which means it's the only + // outstanding upgradeable guard on the shard. See `spin` documentation. + // Remove the pointed-to object from the map, and return it, + // releasing the lock when the guard goes out of scope. + let removed_object = map_read_write_guard.deref_mut().remove(&handle).unwrap(); + // Decrement the allocations counter. Release ordering because we want + // to ensure that clearing the map entry never gets re-ordered to after when + // this counter gets decremented. + let _ = outstanding_allocations_counter.fetch_sub(1, Ordering::Release); + Ok(removed_object) + } + + pub fn try_allocate<F>( + &self, + handle: Handle, + object_provider: F, + outstanding_allocations_counter: &AtomicU32, + max_active_handles: u32, + ) -> Result<(), ShardAllocationError<T, F>> + where + F: FnOnce() -> T, + { + let mut read_write_guard = self.data.write(); + match read_write_guard.entry(handle) { + Occupied(_) => { + // We've already allocated for that handle-id, so yield + // the object provider back to the caller. + Err(ShardAllocationError::EntryOccupied(object_provider)) + } + Vacant(vacant_entry) => { + // An entry is open, but we haven't yet checked the allocations count. + // Try to increment the total allocations count atomically. + // Use acquire ordering on a successful bump, because we don't want + // to invoke the allocation closure before we have a guaranteed slot. + // On the other hand, upon failure, we don't care about ordering + // of surrounding operations, and so we use a relaxed ordering there. + let allocation_count_bump_result = outstanding_allocations_counter.fetch_update( + Ordering::Acquire, + Ordering::Relaxed, + |old_total_allocations| { + if old_total_allocations >= max_active_handles { + None + } else { + Some(old_total_allocations + 1) + } + }, + ); + match allocation_count_bump_result { + Ok(_) => { + // We're good to actually allocate + let object = object_provider(); + vacant_entry.insert(object); + Ok(()) + } + Err(_) => { + // The allocation would cause us to exceed the allowed allocations, + // so release all locks and error. + Err(ShardAllocationError::ExceedsAllocationLimit) + } + } + } + } + } + /// Gets the actual number of elements stored in this shard. + /// Only suitable for single-threaded sections of tests. + #[cfg(test)] + pub fn len(&self) -> usize { + let guard = ShardReadWriteLock::<T>::read(&self.data); + guard.deref().len() + } +}
diff --git a/nearby/presence/handle_map/src/tests.rs b/nearby/util/handle_map/src/tests.rs similarity index 99% rename from nearby/presence/handle_map/src/tests.rs rename to nearby/util/handle_map/src/tests.rs index d97657f..a0774d6 100644 --- a/nearby/presence/handle_map/src/tests.rs +++ b/nearby/util/handle_map/src/tests.rs
@@ -13,7 +13,8 @@ // limitations under the License. use crate::*; -use hashbrown::HashSet; +use core::ops::{Deref, DerefMut}; +use std::collections::HashSet; use std::sync::Arc; use std::thread;
diff --git a/nearby/connections/ukey2/lock_adapter/Cargo.toml b/nearby/util/lock_adapter/Cargo.toml similarity index 91% rename from nearby/connections/ukey2/lock_adapter/Cargo.toml rename to nearby/util/lock_adapter/Cargo.toml index 713f224..eae1fc9 100644 --- a/nearby/connections/ukey2/lock_adapter/Cargo.toml +++ b/nearby/util/lock_adapter/Cargo.toml
@@ -8,6 +8,6 @@ spin = { workspace = true, optional = true } [features] -default = ["spin"] -spin = ["dep:spin"] +default = ["std"] std = [] +spin = ["dep:spin"]
diff --git a/nearby/connections/ukey2/lock_adapter/src/lib.rs b/nearby/util/lock_adapter/src/lib.rs similarity index 72% rename from nearby/connections/ukey2/lock_adapter/src/lib.rs rename to nearby/util/lock_adapter/src/lib.rs index 098031b..3d75c0d 100644 --- a/nearby/connections/ukey2/lock_adapter/src/lib.rs +++ b/nearby/util/lock_adapter/src/lib.rs
@@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! An abstraction layer for Rust synchronization primitives which provides both no_std and std library +//! based implementations + +#![forbid(unsafe_code)] +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] /// A Spinlock-based implementation of Mutex using the `spin` crate that can be used in `no_std` @@ -58,3 +63,23 @@ /// Creates a new mutex in an unlocked state ready for use. fn new(value: T) -> Self; } + +/// A reader-writer lock. This type of lock allows a number of readers or at most one writer at +/// any point in time. +pub trait RwLock<T> { + /// RAII structure used to release the shared read access of a lock when dropped. + type RwLockReadGuard<'a> + where + Self: 'a; + + /// RAII structure used to release the exclusive write access of a lock when dropped. + type RwLockWriteGuard<'a> + where + Self: 'a; + + /// Locks this RwLock with shared read access, blocking the current thread until it can be acquired. + fn read(&self) -> Self::RwLockReadGuard<'_>; + + /// Locks this RwLock with exclusive write access, blocking the current thread until it can be acquired. + fn write(&self) -> Self::RwLockWriteGuard<'_>; +}
diff --git a/nearby/util/lock_adapter/src/spin.rs b/nearby/util/lock_adapter/src/spin.rs new file mode 100644 index 0000000..e894cef --- /dev/null +++ b/nearby/util/lock_adapter/src/spin.rs
@@ -0,0 +1,60 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::NoPoisonMutex; + +/// A mutual exclusion primitive useful for protecting shared data +pub struct Mutex<T>(spin::Mutex<T>); + +impl<T> NoPoisonMutex<T> for Mutex<T> { + type MutexGuard<'a> = spin::MutexGuard<'a, T> where T: 'a; + + fn lock(&self) -> Self::MutexGuard<'_> { + self.0.lock() + } + + fn try_lock(&self) -> Option<Self::MutexGuard<'_>> { + self.0.try_lock() + } + + fn new(value: T) -> Self { + Self(spin::Mutex::new(value)) + } +} + +/// A reader-writer lock +/// This type of lock allows a number of readers or at most one writer at any point in time. +/// The write portion of this lock typically allows modification of the underlying data (exclusive access) +/// and the read portion of this lock typically allows for read-only access (shared access). +pub struct RwLock<T>(spin::RwLock<T>); + +impl<T> RwLock<T> { + /// Creates a new instance of an `RwLock<T>` which is unlocked. + pub const fn new(inner: T) -> Self { + Self(spin::RwLock::new(inner)) + } +} + +impl<T> crate::RwLock<T> for RwLock<T> { + type RwLockReadGuard<'a> = spin::RwLockReadGuard<'a, T> where T: 'a; + type RwLockWriteGuard<'a> = spin::RwLockWriteGuard<'a, T> where T: 'a; + + fn read(&self) -> Self::RwLockReadGuard<'_> { + self.0.read() + } + + fn write(&self) -> Self::RwLockWriteGuard<'_> { + self.0.write() + } +}
diff --git a/nearby/util/lock_adapter/src/std.rs b/nearby/util/lock_adapter/src/std.rs new file mode 100644 index 0000000..c950d85 --- /dev/null +++ b/nearby/util/lock_adapter/src/std.rs
@@ -0,0 +1,175 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::NoPoisonMutex; +use std::ops::{Deref, DerefMut}; + +/// A mutual exclusion primitive useful for protecting shared data +pub struct Mutex<T>(std::sync::Mutex<T>); + +impl<T> NoPoisonMutex<T> for Mutex<T> { + type MutexGuard<'a> = std::sync::MutexGuard<'a, T> where T: 'a; + + fn lock(&self) -> Self::MutexGuard<'_> { + self.0.lock().unwrap_or_else(|poison| poison.into_inner()) + } + + fn try_lock(&self) -> Option<Self::MutexGuard<'_>> { + match self.0.try_lock() { + Ok(guard) => Some(guard), + Err(std::sync::TryLockError::Poisoned(guard)) => Some(guard.into_inner()), + Err(std::sync::TryLockError::WouldBlock) => None, + } + } + + fn new(value: T) -> Self { + Self(std::sync::Mutex::new(value)) + } +} + +/// A reader-writer lock +/// This type of lock allows a number of readers or at most one writer at any point in time. +/// The write portion of this lock typically allows modification of the underlying data (exclusive access) +/// and the read portion of this lock typically allows for read-only access (shared access). +pub struct RwLock<T>(std::sync::RwLock<T>); + +impl<T> RwLock<T> { + /// Creates a new instance of an `RwLock<T>` which is unlocked. + pub const fn new(value: T) -> Self { + Self(std::sync::RwLock::new(value)) + } +} + +impl<T> crate::RwLock<T> for RwLock<T> { + type RwLockReadGuard<'a> = RwLockReadGuard<'a, T> where T: 'a; + + type RwLockWriteGuard<'a> = RwLockWriteGuard<'a, T> where T: 'a; + + fn read(&self) -> Self::RwLockReadGuard<'_> { + RwLockReadGuard(self.0.read().unwrap_or_else(|e| e.into_inner())) + } + + fn write(&self) -> Self::RwLockWriteGuard<'_> { + RwLockWriteGuard(self.0.write().unwrap_or_else(|e| e.into_inner())) + } +} + +/// RAII structure used to release the shared read access of a lock when dropped. +pub struct RwLockReadGuard<'a, T>(std::sync::RwLockReadGuard<'a, T>); + +impl<'a, T> RwLockReadGuard<'a, T> { + /// Make a new MappedRwLockReadGuard for a component of the locked data. + pub fn map<U, M>(s: Self, mapping: M) -> MappedRwLockReadGuard<'a, T, U, M> + where + M: RwMapping<Arg = T, Ret = U>, + { + MappedRwLockReadGuard { mapping, guard: s } + } +} + +impl<'a, T> Deref for RwLockReadGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +/// An RAII read lock guard returned by RwLockReadGuard::map, which can point to a subfield of the protected data. +pub struct MappedRwLockReadGuard<'a, T, U, M> +where + M: RwMapping<Arg = T, Ret = U>, +{ + mapping: M, + guard: RwLockReadGuard<'a, T>, +} + +impl<'a, T, U, M> Deref for MappedRwLockReadGuard<'a, T, U, M> +where + M: RwMapping<Arg = T, Ret = U>, +{ + type Target = U; + + fn deref(&self) -> &Self::Target { + self.mapping.map(&*self.guard) + } +} + +/// RAII structure used to release the exclusive write access of a lock when dropped. +pub struct RwLockWriteGuard<'a, T>(std::sync::RwLockWriteGuard<'a, T>); + +impl<'a, T> RwLockWriteGuard<'a, T> { + /// Make a new MappedRwLockWriteGuard for a component of the locked data. + pub fn map<U, M>(s: Self, mapping: M) -> MappedRwLockWriteGuard<'a, T, U, M> + where + M: RwMapping<Arg = T, Ret = U>, + { + MappedRwLockWriteGuard { mapping, guard: s } + } +} + +impl<'a, T> Deref for RwLockWriteGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl<'a, T> DerefMut for RwLockWriteGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.deref_mut() + } +} + +/// An RAII read lock guard returned by RwLockWriteGuard::map, which can point to a subfield of the protected data. +pub struct MappedRwLockWriteGuard<'a, T, U, M> +where + M: RwMapping<Arg = T, Ret = U>, +{ + mapping: M, + guard: RwLockWriteGuard<'a, T>, +} + +impl<'a, P, T, M> Deref for MappedRwLockWriteGuard<'a, P, T, M> +where + M: RwMapping<Arg = P, Ret = T>, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + self.mapping.map(&*self.guard) + } +} + +impl<'a, P, T, M> DerefMut for MappedRwLockWriteGuard<'a, P, T, M> +where + M: RwMapping<Arg = P, Ret = T>, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.mapping.map_mut(&mut *self.guard) + } +} + +/// Mapping functions which define how to map from one locked data type to a component of that locked data +pub trait RwMapping { + /// The original locked data type + type Arg; + /// The returned mapped locked data type which is a component of the original locked data + type Ret; + /// Maps from Arg into Ret + fn map<'a>(&self, arg: &'a Self::Arg) -> &'a Self::Ret; + /// Mutably maps from Arg into Ret + fn map_mut<'a>(&self, arg: &'a mut Self::Arg) -> &'a mut Self::Ret; +}
diff --git a/remoteauth/Cargo.lock b/remoteauth/Cargo.lock new file mode 100644 index 0000000..55841b9 --- /dev/null +++ b/remoteauth/Cargo.lock
@@ -0,0 +1,469 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "build-scripts" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "cmd-runner", + "env_logger", + "log", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "clap" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "cmd-runner" +version = "0.1.0" +dependencies = [ + "anyhow", + "owo-colors", + "shell-escape", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "ctap_protocol" +version = "0.1.0" +dependencies = [ + "anyhow", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "platform" +version = "0.1.0" +dependencies = [ + "anyhow", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "remote_auth_protocol" +version = "0.1.0" +dependencies = [ + "anyhow", +] + +[[package]] +name = "rustix" +version = "0.38.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/remoteauth/Cargo.toml b/remoteauth/Cargo.toml new file mode 100644 index 0000000..20b2ec3 --- /dev/null +++ b/remoteauth/Cargo.toml
@@ -0,0 +1,27 @@ +[workspace] +members = [ + "ctap_protocol", + "platform", + "remote_auth_protocol", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +publish = false + +[workspace.dependencies] +anyhow = "1.0.72" + +[package] +name = "build-scripts" +version.workspace = true +edition.workspace = true +publish.workspace = true + +[dependencies] +anyhow.workspace = true +clap = { version = "4.0.25", features = ["derive"] } +cmd-runner = { path = "../cmd-runner" } +env_logger = "0.10.0" +log = "0.4.17" \ No newline at end of file
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/ctap_protocol/Cargo.toml similarity index 80% copy from nearby/crypto/bssl-crypto/Cargo.toml copy to remoteauth/ctap_protocol/Cargo.toml index bfe3964..8a06840 100644 --- a/nearby/crypto/bssl-crypto/Cargo.toml +++ b/remoteauth/ctap_protocol/Cargo.toml
@@ -1,5 +1,5 @@ [package] -name = "bssl-crypto" +name = "ctap_protocol" version.workspace = true edition.workspace = true publish.workspace = true @@ -7,3 +7,4 @@ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow.workspace = true
diff --git a/remoteauth/ctap_protocol/src/command.rs b/remoteauth/ctap_protocol/src/command.rs new file mode 100644 index 0000000..e512755 --- /dev/null +++ b/remoteauth/ctap_protocol/src/command.rs
@@ -0,0 +1,227 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # CTAP Protocol +//! +//! This crate represents CTAP messages and turns them into a binary representation to be sent to a +//! remote device. + +use anyhow::anyhow; + +/// The Rust representation of CTAP values (or identifiers). +#[derive(Debug, PartialEq)] +pub enum Value { + AuthenticatorMakeCredential = 0x01, + AuthenticatorGetAssertion = 0x02, + AuthenticatorGetInfo = 0x04, + AuthenticatorClientPIN = 0x06, + AuthenticatorReset = 0x07, + AuthenticatorGetNextAssertion = 0x08, +} + +/// The Rust representation of CTAP parameters. +#[derive(Debug, PartialEq)] +pub enum Parameters { + AuthenticatorMakeCredential, + AuthenticatorGetAssertion, + AuthenticatorClientPIN, +} + +impl From<Parameters> for Vec<u8> { + fn from(message: Parameters) -> Vec<u8> { + // TODO: serialize parameters correctly + match message { + Parameters::AuthenticatorMakeCredential => vec![], + Parameters::AuthenticatorGetAssertion => vec![], + Parameters::AuthenticatorClientPIN => vec![], + } + } +} + +pub struct Command { + pub value: Value, + pub parameters: Option<Parameters>, +} + +impl From<Command> for Vec<u8> { + /// Converts the given CTAP command into its binary representation. + fn from(message: Command) -> Vec<u8> { + let mut result = vec![message.value as u8]; + if let Some(p) = message.parameters { + result.append(&mut p.into()); + } + result + } +} + +impl TryFrom<Vec<u8>> for Command { + type Error = anyhow::Error; + /// Convert a binary message to its Rust representation. + fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> { + if bytes.is_empty() { + Err(anyhow!("Binary message was empty.")) + } else { + match bytes[0] { + _x if _x == Value::AuthenticatorMakeCredential as u8 => Ok(Self { + value: Value::AuthenticatorMakeCredential, + parameters: Some(Parameters::AuthenticatorMakeCredential), + }), + _x if _x == Value::AuthenticatorGetAssertion as u8 => Ok(Self { + value: Value::AuthenticatorGetAssertion, + parameters: Some(Parameters::AuthenticatorGetAssertion), + }), + _x if _x == Value::AuthenticatorGetInfo as u8 => Ok(Self { + value: Value::AuthenticatorGetInfo, + parameters: None, + }), + _x if _x == Value::AuthenticatorClientPIN as u8 => Ok(Self { + value: Value::AuthenticatorClientPIN, + parameters: Some(Parameters::AuthenticatorClientPIN), + }), + _x if _x == Value::AuthenticatorReset as u8 => Ok(Self { + value: Value::AuthenticatorReset, + parameters: None, + }), + _x if _x == Value::AuthenticatorGetNextAssertion as u8 => Ok(Self { + value: Value::AuthenticatorGetNextAssertion, + parameters: None, + }), + _ => Err(anyhow!("Unknown message type.")), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::bail; + + #[test] + fn translate_message_to_bytes() { + let mut message = Command { + value: Value::AuthenticatorReset, + parameters: None, + }; + let mut bytes: Vec<u8> = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorReset as u8]); + + message = Command { + value: Value::AuthenticatorMakeCredential, + parameters: None, + }; + bytes = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorMakeCredential as u8]); + + message = Command { + value: Value::AuthenticatorGetAssertion, + parameters: None, + }; + bytes = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorGetAssertion as u8]); + + message = Command { + value: Value::AuthenticatorGetInfo, + parameters: None, + }; + bytes = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorGetInfo as u8]); + + message = Command { + value: Value::AuthenticatorClientPIN, + parameters: None, + }; + bytes = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorClientPIN as u8]); + + message = Command { + value: Value::AuthenticatorGetNextAssertion, + parameters: None, + }; + bytes = message.into(); + + assert_eq!(bytes, vec![Value::AuthenticatorGetNextAssertion as u8]); + } + + #[test] + fn translate_bytes_to_message() -> anyhow::Result<()> { + let mut bytes = vec![Value::AuthenticatorReset as u8]; + let mut message: Command = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorReset); + assert_eq!(message.parameters, None); + + bytes = vec![Value::AuthenticatorMakeCredential as u8]; + message = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorMakeCredential); + assert_eq!( + message.parameters, + Some(Parameters::AuthenticatorMakeCredential) + ); + + bytes = vec![Value::AuthenticatorGetAssertion as u8]; + message = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorGetAssertion); + assert_eq!( + message.parameters, + Some(Parameters::AuthenticatorGetAssertion) + ); + + bytes = vec![Value::AuthenticatorGetInfo as u8]; + message = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorGetInfo); + assert_eq!(message.parameters, None); + + bytes = vec![Value::AuthenticatorClientPIN as u8]; + message = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorClientPIN); + assert_eq!(message.parameters, Some(Parameters::AuthenticatorClientPIN)); + + bytes = vec![Value::AuthenticatorGetNextAssertion as u8]; + message = bytes.try_into()?; + assert_eq!(message.value, Value::AuthenticatorGetNextAssertion); + assert_eq!(message.parameters, None); + Ok(()) + } + + #[test] + fn translate_empty_bytes() -> anyhow::Result<()> { + let bytes = vec![]; + let res: anyhow::Result<Command> = bytes.try_into(); + match res { + Err(e) => { + assert_eq!(e.to_string(), "Binary message was empty."); + Ok(()) + } + _ => bail!("Should not parse empty bytes"), + } + } + + #[test] + fn translate_unknown_message() -> anyhow::Result<()> { + let bytes = vec![0x10]; + let res: anyhow::Result<Command> = bytes.try_into(); + match res { + Err(e) => { + assert_eq!(e.to_string(), "Unknown message type."); + Ok(()) + } + _ => bail!("Should not parse unknown message."), + } + } +}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/remoteauth/ctap_protocol/src/lib.rs similarity index 78% rename from nearby/crypto/bssl-crypto/src/lib.rs rename to remoteauth/ctap_protocol/src/lib.rs index 89e6968..b42193e 100644 --- a/nearby/crypto/bssl-crypto/src/lib.rs +++ b/remoteauth/ctap_protocol/src/lib.rs
@@ -12,5 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the -//! `build-boringssl` subcommand of the top level crate. +//! # CTAP Protocol +//! +//! This crate represents CTAP messages and turns them into a binary representation to be sent to a +//! remote device. + +pub mod command;
diff --git a/remoteauth/deny.toml b/remoteauth/deny.toml new file mode 100644 index 0000000..2bf5920 --- /dev/null +++ b/remoteauth/deny.toml
@@ -0,0 +1,219 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + # criterion 0.4.0 depends on a version of atty w/unaligned reads +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +unused-allowed-license = "allow" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-3-Clause", + "BSD-2-Clause", + "ISC", + "Unicode-DFS-2016", + "OpenSSL", + "Unlicense" +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[[licenses.clarify]] +name = "ring" +version = "*" +expression = "MIT AND ISC AND OpenSSL" +license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "allow" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] \ No newline at end of file
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/platform/Cargo.toml similarity index 82% copy from nearby/crypto/bssl-crypto/Cargo.toml copy to remoteauth/platform/Cargo.toml index bfe3964..0136c27 100644 --- a/nearby/crypto/bssl-crypto/Cargo.toml +++ b/remoteauth/platform/Cargo.toml
@@ -1,5 +1,5 @@ [package] -name = "bssl-crypto" +name = "platform" version.workspace = true edition.workspace = true publish.workspace = true @@ -7,3 +7,4 @@ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow.workspace = true
diff --git a/remoteauth/platform/src/lib.rs b/remoteauth/platform/src/lib.rs new file mode 100644 index 0000000..798c756 --- /dev/null +++ b/remoteauth/platform/src/lib.rs
@@ -0,0 +1,29 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod listeners; + +use crate::listeners::SendRequestListener; + +/// # RemoteAuth Platform +/// This trait represents the capabilities that RemoteAuth requires from the platform. +pub trait Platform { + /// Send a binary message to the remote with the given connection id and return the response. + fn send_request( + &self, + connection_id: i32, + request: &[u8], + listener: Box<dyn SendRequestListener + Send>, + ); +}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/remoteauth/platform/src/listeners.rs similarity index 71% copy from nearby/crypto/bssl-crypto/src/lib.rs copy to remoteauth/platform/src/listeners.rs index 89e6968..e354fea 100644 --- a/nearby/crypto/bssl-crypto/src/lib.rs +++ b/remoteauth/platform/src/listeners.rs
@@ -12,5 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the -//! `build-boringssl` subcommand of the top level crate. +/// SendRequestListener handles the result or an error from calling send_request in the Platform. +pub trait SendRequestListener { + fn on_response(&mut self, response: Vec<u8>); + fn on_error(&mut self, error_code: i32); +}
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/remote_auth_protocol/Cargo.toml similarity index 78% copy from nearby/crypto/bssl-crypto/Cargo.toml copy to remoteauth/remote_auth_protocol/Cargo.toml index bfe3964..e9fd6d2 100644 --- a/nearby/crypto/bssl-crypto/Cargo.toml +++ b/remoteauth/remote_auth_protocol/Cargo.toml
@@ -1,5 +1,5 @@ [package] -name = "bssl-crypto" +name = "remote_auth_protocol" version.workspace = true edition.workspace = true publish.workspace = true @@ -7,3 +7,4 @@ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow.workspace = true
diff --git a/remoteauth/remote_auth_protocol/src/lib.rs b/remoteauth/remote_auth_protocol/src/lib.rs new file mode 100644 index 0000000..bc8315b --- /dev/null +++ b/remoteauth/remote_auth_protocol/src/lib.rs
@@ -0,0 +1,42 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod remote_auth_service; + +/// Struct representing the remote device. +pub struct RemoteDevice { + pub id: i32, +} + +/// Trait to be implemented by anything that wants to be notified when remote devices are discovered +/// or lost. +pub trait DeviceDiscoveryListener { + fn on_discovered(&mut self, remote_device: &RemoteDevice); + fn on_lost(&mut self, remote_device: &RemoteDevice); + fn on_timeout(&mut self); +} + +/// Trait to be implemented by the struct responsible for scanning for remote devices. +pub trait DiscoveryPublisher<'a, T: DeviceDiscoveryListener> { + /// Add a listener to the list and return a handle to that listener so it can be removed later. + fn add_listener(&mut self, listener: &'a mut T) -> i32; + /// Remove the listener with the given handle. + fn remove_listener(&mut self, listener_handle: i32); + /// Notify all listeners that a remote device has been discovered. + fn device_discovered(&mut self, remote_device: &RemoteDevice); + /// Notify all listeners that a previously discovered remote device has been lost. + fn device_lost(&mut self, remote_device: &RemoteDevice); + /// Notify all listeners that timeout has occurred. + fn timed_out(&mut self); +}
diff --git a/remoteauth/remote_auth_protocol/src/remote_auth_service.rs b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs new file mode 100644 index 0000000..5da33c7 --- /dev/null +++ b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs
@@ -0,0 +1,255 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{DeviceDiscoveryListener, DiscoveryPublisher, RemoteDevice}; + +pub struct RemoteAuthService<'a, T: DeviceDiscoveryListener> { + listeners: Vec<&'a mut T>, +} + +impl<'a, T: DeviceDiscoveryListener + PartialEq> RemoteAuthService<'a, T> { + pub fn new() -> RemoteAuthService<'a, T> { + RemoteAuthService { + listeners: Vec::new(), + } + } + + pub fn is_remote_auth_supported() -> bool { + true + } +} + +impl<'a, T: DeviceDiscoveryListener + PartialEq> DiscoveryPublisher<'a, T> + for RemoteAuthService<'a, T> +{ + fn add_listener(&mut self, listener: &'a mut T) -> i32 { + self.listeners.push(listener); + (self.listeners.len() - 1) as i32 + } + + fn remove_listener(&mut self, listener_id: i32) { + let listener_index = listener_id as usize; + if self.listeners.len() > listener_index { + self.listeners.remove(listener_index); + } + } + + fn device_discovered(&mut self, remote_device: &RemoteDevice) { + for listener in self.listeners.iter_mut() { + listener.on_discovered(remote_device); + } + } + + fn device_lost(&mut self, remote_device: &RemoteDevice) { + for listener in self.listeners.iter_mut() { + listener.on_lost(remote_device); + } + } + + fn timed_out(&mut self) { + for listener in self.listeners.iter_mut() { + listener.on_timeout(); + } + } +} + +impl<'a, T: DeviceDiscoveryListener + PartialEq> Default for RemoteAuthService<'a, T> { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(PartialEq)] + struct TestListener { + pub remote_ids: Vec<i32>, + pub got_timeout: bool, + } + + impl TestListener { + pub fn new() -> TestListener { + TestListener { + remote_ids: vec![], + got_timeout: false, + } + } + } + + impl DeviceDiscoveryListener for TestListener { + fn on_discovered(&mut self, remote_device: &RemoteDevice) { + self.remote_ids.push(remote_device.id); + } + + fn on_lost(&mut self, remote_device: &RemoteDevice) { + if let Some(index) = self.remote_ids.iter().position(|i| *i == remote_device.id) { + self.remote_ids.remove(index); + } + } + + fn on_timeout(&mut self) { + self.got_timeout = true; + } + } + + #[test] + fn single_listener_discover_notification() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + ras.add_listener(&mut listener); + ras.device_discovered(&remote_device); + + assert_eq!(listener.remote_ids.len(), 1); + assert_eq!(listener.remote_ids[0], 42); + } + + #[test] + fn all_listeners_discover_notification() { + let mut listener1 = TestListener::new(); + let mut listener2 = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + ras.add_listener(&mut listener1); + ras.add_listener(&mut listener2); + ras.device_discovered(&remote_device); + + assert_eq!(listener1.remote_ids.len(), 1); + assert_eq!(listener2.remote_ids.len(), 1); + assert_eq!(listener1.remote_ids[0], 42); + assert_eq!(listener2.remote_ids[0], 42); + } + + #[test] + fn listeners_discover_notification_for_all_devices() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device1 = RemoteDevice { id: 42 }; + let remote_device2 = RemoteDevice { id: 13 }; + + ras.add_listener(&mut listener); + ras.device_discovered(&remote_device1); + ras.device_discovered(&remote_device2); + + assert_eq!(listener.remote_ids.len(), 2); + assert_eq!(listener.remote_ids[0], 42); + assert_eq!(listener.remote_ids[1], 13); + } + + #[test] + fn single_listener_lost_notification() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + ras.add_listener(&mut listener); + ras.device_discovered(&remote_device); + ras.device_lost(&remote_device); + + assert_eq!(listener.remote_ids.len(), 0); + } + + #[test] + fn all_listeners_lost_notification() { + let mut listener1 = TestListener::new(); + let mut listener2 = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + ras.add_listener(&mut listener1); + ras.add_listener(&mut listener2); + ras.device_discovered(&remote_device); + ras.device_lost(&remote_device); + + assert_eq!(listener1.remote_ids.len(), 0); + assert_eq!(listener2.remote_ids.len(), 0); + } + + #[test] + fn listeners_lost_notification_for_all_devices() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device1 = RemoteDevice { id: 42 }; + let remote_device2 = RemoteDevice { id: 13 }; + let remote_device3 = RemoteDevice { id: 99 }; + + ras.add_listener(&mut listener); + ras.device_discovered(&remote_device1); + ras.device_discovered(&remote_device2); + ras.device_discovered(&remote_device3); + + ras.device_lost(&remote_device2); + ras.device_lost(&remote_device3); + + assert_eq!(listener.remote_ids.len(), 1); + assert_eq!(listener.remote_ids[0], 42); + } + + #[test] + fn single_listener_timeout_notification() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + + ras.add_listener(&mut listener); + ras.timed_out(); + + assert!(listener.got_timeout); + } + + #[test] + fn all_listeners_timeout_notification() { + let mut listener1 = TestListener::new(); + let mut listener2 = TestListener::new(); + let mut ras = RemoteAuthService::new(); + + ras.add_listener(&mut listener1); + ras.add_listener(&mut listener2); + ras.timed_out(); + + assert!(listener1.got_timeout); + assert!(listener2.got_timeout); + } + + #[test] + fn listener_can_be_removed() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + let id = ras.add_listener(&mut listener); + ras.remove_listener(id); + ras.device_discovered(&remote_device); + + assert_eq!(listener.remote_ids.len(), 0); + } + + #[test] + fn listener_can_be_removed_after_notification() { + let mut listener = TestListener::new(); + let mut ras = RemoteAuthService::new(); + let remote_device = RemoteDevice { id: 42 }; + + let id = ras.add_listener(&mut listener); + ras.device_discovered(&remote_device); + ras.remove_listener(id); + ras.device_discovered(&remote_device); + + assert_eq!(listener.remote_ids.len(), 1); + assert_eq!(listener.remote_ids[0], 42); + } +}
diff --git a/remoteauth/src/ctap_protocol.rs b/remoteauth/src/ctap_protocol.rs new file mode 100644 index 0000000..7e35e6c --- /dev/null +++ b/remoteauth/src/ctap_protocol.rs
@@ -0,0 +1,29 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cmd_runner::run_cmd_shell; +use std::path; + +pub(crate) fn check_ctap_protocol(root: &path::Path) -> anyhow::Result<()> { + log::info!("Checking CTAP Protocol"); + let mut ffi_dir = root.to_path_buf(); + ffi_dir.push("ctap_protocol"); + + run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?; + run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?; + run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?; + run_cmd_shell(&ffi_dir, "cargo deny check")?; + + Ok(()) +}
diff --git a/remoteauth/src/main.rs b/remoteauth/src/main.rs new file mode 100644 index 0000000..e0851eb --- /dev/null +++ b/remoteauth/src/main.rs
@@ -0,0 +1,103 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern crate core; + +use clap::Parser as _; +use cmd_runner::run_cmd_shell; +use env_logger::Env; +use std::{env, path}; + +mod ctap_protocol; +mod platform; +mod remote_auth_protocol; + +fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + let cli: Cli = Cli::parse(); + + let root_dir: path::PathBuf = env::var("CARGO_MANIFEST_DIR") + .expect("Must be run via Cargo to establish root directory") + .into(); + + match cli.subcommand { + Subcommand::CheckEverything(ref options) => check_everything(&root_dir, options)?, + Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?, + Subcommand::CheckCtapProtocol => ctap_protocol::check_ctap_protocol(&root_dir)?, + Subcommand::CheckPlatform => platform::check_platform(&root_dir)?, + Subcommand::CheckRemoteAuthProtocol => { + remote_auth_protocol::check_remote_auth_protocol(&root_dir)? + } + } + + Ok(()) +} + +pub fn check_everything(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> { + check_workspace(root, check_options)?; + ctap_protocol::check_ctap_protocol(root)?; + platform::check_platform(root)?; + remote_auth_protocol::check_remote_auth_protocol(root)?; + Ok(()) +} + +pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()> { + log::info!("Running cargo checks on workspace"); + + let fmt_command = if options.reformat { + "cargo fmt" + } else { + "cargo fmt --check" + }; + + for cargo_cmd in [ + fmt_command, + "cargo check --workspace --all-targets --quiet", + "cargo test --workspace --quiet -- --color=always", + "cargo doc --quiet --no-deps", + "cargo deny --workspace check", + "cargo clippy --all-targets --workspace -- --deny warnings", + ] { + run_cmd_shell(root, cargo_cmd)?; + } + + Ok(()) +} + +#[derive(clap::Parser)] +struct Cli { + #[clap(subcommand)] + subcommand: Subcommand, +} + +#[derive(clap::Subcommand, Debug, Clone)] +#[allow(clippy::enum_variant_names)] +enum Subcommand { + /// Checks everything in remoteauth + CheckEverything(CheckOptions), + /// Checks everything included in the top level workspace + CheckWorkspace(CheckOptions), + /// Build and run tests for the CTAP Protocol + CheckCtapProtocol, + /// Builds and run tests for the Platform + CheckPlatform, + /// Builds and run tests for the Remote Auth Protocol + CheckRemoteAuthProtocol, +} + +#[derive(clap::Args, Debug, Clone, Default)] +pub struct CheckOptions { + #[arg(long, help = "reformat files with cargo fmt")] + reformat: bool, +}
diff --git a/remoteauth/src/platform.rs b/remoteauth/src/platform.rs new file mode 100644 index 0000000..481b4f7 --- /dev/null +++ b/remoteauth/src/platform.rs
@@ -0,0 +1,29 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cmd_runner::run_cmd_shell; +use std::path; + +pub(crate) fn check_platform(root: &path::Path) -> anyhow::Result<()> { + log::info!("Checking Platform"); + let mut ffi_dir = root.to_path_buf(); + ffi_dir.push("platform"); + + run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?; + run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?; + run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?; + run_cmd_shell(&ffi_dir, "cargo deny check")?; + + Ok(()) +}
diff --git a/remoteauth/src/remote_auth_protocol.rs b/remoteauth/src/remote_auth_protocol.rs new file mode 100644 index 0000000..e47517d --- /dev/null +++ b/remoteauth/src/remote_auth_protocol.rs
@@ -0,0 +1,29 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cmd_runner::run_cmd_shell; +use std::path; + +pub(crate) fn check_remote_auth_protocol(root: &path::Path) -> anyhow::Result<()> { + log::info!("Checking Remote Auth Protocol"); + let mut ffi_dir = root.to_path_buf(); + ffi_dir.push("remote_auth_protocol"); + + run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?; + run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?; + run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?; + run_cmd_shell(&ffi_dir, "cargo deny check")?; + + Ok(()) +}