diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-02-02 23:56:40 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-02-02 23:56:40 +0000 |
commit | e51d81bbdaeeed4543c7ef218b9bb34eeb754da5 (patch) | |
tree | 130a48b0d282dd0c937e3a60bbc796ba57d12073 | |
parent | a0ad49e1207936fb7dd1e6477f9d6f3e7bb9737a (diff) | |
parent | 45cc9975196e343faec1c131b490acb797af39d3 (diff) | |
download | beto-rust-simpleperf-release.tar.gz |
Snap for 11400057 from 45cc9975196e343faec1c131b490acb797af39d3 to simpleperf-releasesimpleperf-release
Change-Id: I3154c5242809c26f0daa8d40710845c7b72bbfe9
316 files changed, 20152 insertions, 11158 deletions
@@ -21,7 +21,7 @@ protobuf-compiler pkg-config libdbus-1-dev libssl-dev ninja-build 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 cargo-prefetch \ 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 index cc81352..fa2f1f4 100644 --- a/nearby/src/support.rs +++ b/cmd-runner/src/lib.rs @@ -33,7 +33,12 @@ pub fn run_cmd_shell_with_color<C: TermColors>( 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 @@ where 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 @@ fn run<C: TermColors>( }, ); - 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/.gitignore b/nearby/.gitignore index b7d2235..49cb45e 100644 --- a/nearby/.gitignore +++ b/nearby/.gitignore @@ -3,3 +3,4 @@ target/ /*.mdb /auth_token.txt .DS_Store +presence/cmake-build-debug diff --git a/nearby/Android.bp b/nearby/Android.bp index 548f005..1739424 100644 --- a/nearby/Android.bp +++ b/nearby/Android.bp @@ -26,6 +26,7 @@ rust_library_rlib { rustlibs: [ "libhex", "librand", + "libtinyvec", ], apex_available: [ "//apex_available:platform", @@ -56,7 +57,7 @@ rust_library_rlib { crate_name: "lock_adapter", cargo_env_compat: true, cargo_pkg_version: "0.1.0", - srcs: ["connections/ukey2/lock_adapter/src/lib.rs"], + srcs: ["util/lock_adapter/src/lib.rs"], edition: "2021", features: [ "std", @@ -68,20 +69,38 @@ rust_library_rlib { } rust_library_rlib { - name: "libcrypto_provider_openssl", + name: "libcrypto_provider_default", host_supported: true, - crate_name: "crypto_provider_openssl", + crate_name: "crypto_provider_default", cargo_env_compat: true, cargo_pkg_version: "0.1.0", - cfgs: ["soong"], - srcs: ["crypto/crypto_provider_openssl/src/lib.rs"], + srcs: ["crypto/crypto_provider_default/src/lib.rs"], edition: "2021", features: ["boringssl"], rustlibs: [ "libcfg_if", "libcrypto_provider", + "libcrypto_provider_boringssl", + ], + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], +} + +rust_library_rlib { + name: "libcrypto_provider_boringssl", + host_supported: true, + crate_name: "crypto_provider_boringssl", + cargo_env_compat: true, + cargo_pkg_version: "0.1.0", + srcs: ["crypto/crypto_provider_boringssl/src/lib.rs"], + edition: "2021", + rustlibs: [ + "libcfg_if", + "libcrypto_provider", "libcrypto_provider_stubs", - "libopenssl", + "libbssl_crypto", "librand", ], apex_available: [ @@ -105,7 +124,7 @@ rust_ffi_shared { ], rlibs: [ "libcfg_if", - "libcrypto_provider_openssl", + "libcrypto_provider_default", "liblazy_static", "liblock_adapter", "liblog_rust", @@ -162,7 +181,7 @@ rust_ffi_shared { // common projects like libjni and libprotobuf. rlibs: [ "libcfg_if", - "libcrypto_provider_openssl", + "libcrypto_provider_default", "libjni", "liblazy_static", "liblock_adapter", @@ -213,7 +232,6 @@ rust_library_rlib { "librand", "libukey2_proto", ], - proc_macros: ["libderive_getters"], apex_available: [ "//apex_available:platform", "//apex_available:anyapex", diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock index 76582bf..3a3934a 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,36 +31,39 @@ dependencies = [ ] [[package]] -name = "aes-gcm-siv" -version = "0.11.1" +name = "aes-gcm" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", "cipher", "ctr", - "polyval", + "ghash", "subtle", - "zeroize", ] [[package]] -name = "ahash" -version = "0.8.3" +name = "aes-gcm-siv" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ - "cfg-if", - "once_cell", - "version_check", + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", ] [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -81,12 +75,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -109,58 +97,57 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "array_ref" @@ -171,38 +158,12 @@ name = "array_view" version = "0.1.0" [[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -210,15 +171,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.2" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9" [[package]] name = "base64ct" @@ -234,9 +189,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "blake2" @@ -268,6 +223,13 @@ dependencies = [ [[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" @@ -277,9 +239,9 @@ checksum = "312d12393c060384f2e6ed14c7b4be37b3dd90249857485613c1a91b9a1abb5c" [[package]] name = "bstr" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -290,41 +252,42 @@ name = "build-scripts" version = "0.1.0" dependencies = [ "anyhow", - "base64 0.21.2", "chrono", - "clap 4.3.19", + "clap", + "cmd-runner", "crossbeam", "env_logger", + "file-header", "globset", "log", "owo-colors", - "reqwest", + "regex", "semver", + "serde_json", "shell-escape", "tempfile", "thiserror", - "tinytemplate", "walkdir", "which", ] [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[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 = "cast" @@ -343,9 +306,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cesu8" @@ -361,14 +327,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -410,65 +376,52 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap", - "textwrap", -] - -[[package]] -name = "clap" -version = "4.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", - "clap_lex 0.5.0", + "clap_lex", "strsim", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] -name = "clap_lex" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +name = "cmd-runner" +version = "0.1.0" +dependencies = [ + "anyhow", + "owo-colors", + "shell-escape", +] [[package]] name = "colorchoice" @@ -488,21 +441,21 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -518,19 +471,19 @@ dependencies = [ [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", - "clap 3.2.25", + "clap", "criterion-plot", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -554,11 +507,10 @@ dependencies = [ [[package]] name = "crossbeam" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ - "cfg-if", "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", @@ -568,62 +520,52 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -652,6 +594,7 @@ dependencies = [ "hex-literal", "rand", "rand_ext", + "tinyvec", ] [[package]] @@ -660,7 +603,6 @@ version = "0.1.0" dependencies = [ "bssl-crypto", "crypto_provider", - "crypto_provider_stubs", ] [[package]] @@ -685,7 +627,7 @@ dependencies = [ "hex-literal", "openssl", "ouroboros", - "rstest 0.17.0", + "rstest", ] [[package]] @@ -694,6 +636,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -731,7 +674,7 @@ dependencies = [ "hex-literal", "rand", "rand_ext", - "rstest 0.17.0", + "rstest", "rstest_reuse", "test_helper", "wycheproof", @@ -748,9 +691,9 @@ dependencies = [ [[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", @@ -765,37 +708,26 @@ dependencies = [ [[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.28", + "syn 2.0.48", ] [[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", ] [[package]] -name = "derive-getters" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0122f262bf9c9a367829da84f808d9fb128c10ef283bbe7b0922a77cf07b2747" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -814,9 +746,9 @@ dependencies = [ [[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", @@ -824,15 +756,16 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core 0.6.4", "serde", "sha2", + "subtle", "zeroize", ] @@ -844,9 +777,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -862,19 +795,10 @@ dependencies = [ ] [[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" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -885,30 +809,19 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "ff" @@ -922,25 +835,32 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] -name = "flate2" -version = "1.0.26" +name = "file-header" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "b5568149106e77ae33bc3a2c3ef3839cbe63ffa4a8dd4a81612a6f9dfdbc2e9f" dependencies = [ - "crc32fast", - "miniz_oxide", + "crossbeam", + "lazy_static", + "license", + "thiserror", + "walkdir", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "flate2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] [[package]] name = "foreign-types" @@ -958,110 +878,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -1074,9 +890,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -1084,22 +900,32 @@ dependencies = [ ] [[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 = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1114,25 +940,6 @@ dependencies = [ ] [[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" @@ -1143,11 +950,8 @@ name = "handle_map" version = "0.1.0" dependencies = [ "criterion", - "crypto_provider", - "hashbrown 0.14.0", - "lock_api", - "portable-atomic", - "spin 0.9.8", + "lazy_static", + "lock_adapter", ] [[package]] @@ -1157,22 +961,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64 0.13.1", + "base64", "byteorder", "crossbeam-channel", "flate2", @@ -1188,18 +982,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -1215,9 +1000,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1232,95 +1017,32 @@ dependencies = [ ] [[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" +name = "home" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "bytes", - "http", - "pin-project-lite", + "windows-sys 0.52.0", ] [[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" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1333,32 +1055,16 @@ dependencies = [ ] [[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]] -name = "init_with" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0175f63815ce00183bf755155ad0cb48c65226c5d17a724e369c25418d2b7699" - -[[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1369,20 +1075,14 @@ dependencies = [ ] [[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" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1396,9 +1096,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jni" @@ -1424,9 +1124,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1446,9 +1146,9 @@ version = "0.1.0" dependencies = [ "aes", "anyhow", - "base64 0.21.2", + "base64", "blake2", - "clap 4.3.19", + "clap", "criterion", "crypto_provider", "crypto_provider_default", @@ -1473,7 +1173,7 @@ version = "0.1.0" dependencies = [ "anyhow", "array_view", - "base64 0.21.2", + "base64", "criterion", "crypto_provider", "crypto_provider_default", @@ -1513,15 +1213,26 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[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" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_adapter" @@ -1532,9 +1243,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1542,30 +1253,15 @@ dependencies = [ [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "minimal-lexical" @@ -1583,17 +1279,6 @@ dependencies = [ ] [[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" @@ -1613,7 +1298,6 @@ dependencies = [ "crypto_provider", "crypto_provider_default", "hex", - "init_with", "lazy_static", "ldt", "ldt_np_adv", @@ -1622,6 +1306,7 @@ dependencies = [ "np_hkdf", "rand", "rand_ext", + "serde", "serde_json", "sink", "strum", @@ -1632,6 +1317,17 @@ dependencies = [ ] [[package]] +name = "np_adv_dynamic" +version = "0.1.0" +dependencies = [ + "array_view", + "crypto_provider", + "np_adv", + "sink", + "thiserror", +] + +[[package]] name = "np_ed25519" version = "0.1.0" dependencies = [ @@ -1649,8 +1345,12 @@ dependencies = [ "crypto_provider", "crypto_provider_default", "handle_map", + "lazy_static", + "ldt_np_adv", + "lock_adapter", "np_adv", - "spin 0.9.8", + "np_adv_dynamic", + "np_hkdf", ] [[package]] @@ -1672,9 +1372,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -1693,37 +1393,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.2", - "libc", -] - -[[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" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -1739,11 +1420,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1760,16 +1441,16 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ - "bssl-sys", + "bssl-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc", "libc", "pkg-config", @@ -1777,12 +1458,6 @@ dependencies = [ ] [[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - -[[package]] name = "ouroboros" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1803,7 +1478,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] @@ -1823,24 +1498,6 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.3.0" -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" - -[[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1852,15 +1509,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "platforms" -version = "3.0.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "plotters" @@ -1903,12 +1560,6 @@ dependencies = [ ] [[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" @@ -1916,9 +1567,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primeorder" -version = "0.13.2" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] @@ -1949,9 +1600,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -2009,9 +1660,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2081,9 +1732,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -2091,30 +1742,28 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[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 1.3.2", ] [[package]] name = "regex" -version = "1.9.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -2124,9 +1773,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -2135,133 +1784,65 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "reqwest" -version = "0.11.18" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" -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", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] -name = "ring" -version = "0.16.20" +name = "relative-path" +version = "1.9.2" 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", -] +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" [[package]] -name = "rstest" -version = "0.16.0" +name = "reword" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf" +checksum = "fe272098dce9ed76b479995953f748d1851261390b08f8a0ff619c885a1f0765" dependencies = [ - "futures", - "futures-timer", - "rstest_macros 0.16.0", - "rustc_version", + "unicode-segmentation", ] [[package]] name = "rstest" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" -dependencies = [ - "rstest_macros 0.17.0", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.16.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ - "cfg-if", - "proc-macro2", - "quote", + "rstest_macros", "rustc_version", - "syn 1.0.109", - "unicode-ident", ] [[package]] name = "rstest_macros" -version = "0.17.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", + "glob", "proc-macro2", "quote", + "regex", + "relative-path", "rustc_version", - "syn 1.0.109", + "syn 2.0.48", "unicode-ident", ] [[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.48", ] [[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" @@ -2272,46 +1853,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[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", + "windows-sys 0.52.0", ] [[package]] @@ -2322,9 +1872,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -2342,16 +1892,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -2366,35 +1906,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.179" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -2402,22 +1942,10 @@ dependencies = [ ] [[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" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2432,9 +1960,12 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] [[package]] name = "sink" @@ -2444,25 +1975,6 @@ dependencies = [ ] [[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" @@ -2479,9 +1991,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -2501,21 +2013,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[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.48", ] [[package]] @@ -2531,15 +2043,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.28" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -2548,22 +2059,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -2577,29 +2088,23 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", ] [[package]] @@ -2617,102 +2122,19 @@ name = "tinyvec" 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" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ukey2_c_ffi" version = "0.1.0" dependencies = [ "cfg-if", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", + "crypto_provider_default", "lazy_static", "lock_adapter", "log", @@ -2729,11 +2151,10 @@ dependencies = [ "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 +2164,7 @@ name = "ukey2_jni" version = "0.1.0" dependencies = [ "cfg-if", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", + "crypto_provider_default", "jni", "lazy_static", "lock_adapter", @@ -2769,13 +2189,10 @@ name = "ukey2_rs" version = "0.1.0" dependencies = [ "crypto_provider", - "crypto_provider_openssl", - "crypto_provider_rustcrypto", - "derive-getters", + "crypto_provider_default", "log", "num-bigint", "rand", - "rstest 0.16.0", "sha2", "ukey2_proto", ] @@ -2784,32 +2201,23 @@ dependencies = [ name = "ukey2_shell" version = "0.1.0" dependencies = [ - "clap 4.3.19", + "clap", "crypto_provider_rustcrypto", "ukey2_connections", "ukey2_rs", ] [[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" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[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 +2230,6 @@ dependencies = [ ] [[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" @@ -2858,24 +2249,15 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[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" @@ -2883,9 +2265,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2893,36 +2275,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", "wasm-bindgen-shared", ] [[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" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2930,61 +2300,43 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", ] [[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" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix", ] [[package]] @@ -3005,9 +2357,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[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", ] @@ -3019,12 +2371,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.52.0", ] [[package]] @@ -3038,11 +2390,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.52.0", ] [[package]] @@ -3062,17 +2414,32 @@ dependencies = [ [[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 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3083,9 +2450,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -3095,9 +2468,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -3107,9 +2486,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -3119,9 +2504,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -3131,9 +2522,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[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_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -3143,9 +2540,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[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 = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -3155,26 +2558,23 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[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 = "winreg" -version = "0.10.1" +name = "windows_x86_64_msvc" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[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", "hex", "serde", "serde_json", @@ -3182,9 +2582,9 @@ dependencies = [ [[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", @@ -3207,7 +2607,7 @@ dependencies = [ "aes", "anyhow", "array_ref", - "base64 0.21.2", + "base64", "crypto_provider", "crypto_provider_default", "hex", @@ -3217,11 +2617,12 @@ dependencies = [ "rand_pcg", "regex", "test_helper", + "wycheproof", "xts-mode", ] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml index 8ce92ea..236b74f 100644 --- a/nearby/Cargo.toml +++ b/nearby/Cargo.toml @@ -1,13 +1,11 @@ [workspace] members = [ - "connections/ukey2/lock_adapter", "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,12 +14,12 @@ members = [ "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", "presence/ldt_tbc", "presence/np_adv", + "presence/np_adv_dynamic", "presence/np_ed25519", "presence/np_ffi_core", "presence/np_hkdf", @@ -29,6 +27,8 @@ members = [ "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 @@ -40,18 +40,35 @@ exclude = [ "presence/np_c_ffi", ] +[workspace.lints.rust] +unsafe_code = "deny" +missing_docs = "deny" +trivial_casts = "deny" +trivial_numeric_casts = "deny" +unused_extern_crates = "deny" +unused_import_braces = "deny" +unused_results = "deny" + +[workspace.lints.clippy] +indexing_slicing = "deny" +unwrap_used = "deny" +panic = "deny" +expect_used = "deny" + + [workspace.dependencies] # 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" } @@ -62,65 +79,67 @@ ldt = { path = "presence/ldt" } ldt_np_adv = { path = "presence/ldt_np_adv" } ldt_tbc = { path = "presence/ldt_tbc" } np_adv = { path = "presence/np_adv" } +np_adv_dynamic = { path = "presence/np_adv_dynamic" } np_ed25519 = { path = "presence/np_ed25519" } np_ffi_core = { path = "presence/np_ffi_core" } sink = { path = "presence/sink" } # 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"] } -ctr = "0.9.1" -hashbrown = "0.14.0" +sha2 = { version = "0.10.8", default-features = false } +aes = "0.8.3" +cbc = { version = "0.1.2", features = ["block-padding"] } +ctr = "0.9.2" hkdf = "0.12.3" hmac = "0.12.1" -ed25519-dalek = { version = "2.0.0", default-features = false } -ed25519 = "2.2.0" -aes-gcm = "0.10.1" +ed25519-dalek = { version = "2.1.0", default-features = false } +ed25519 = "2.2.3" +aes-gcm = "0.10.3" hex = "0.4.3" -serde_json = { version = "1.0.96", features = [ +serde = { version = "1.0.193" } +serde_json = { version = "1.0.108", features = [ "alloc", ], default-features = false } -base64 = "0.21.0" +base64 = "0.21.5" 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" -jni = "0.21.1" -lock_api = "0.4.9" +sec1 = "0.7.3" +protobuf = "=3.2.0" +protobuf-codegen = "=3.2.0" +reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "rustls-tls"] } +jni = "0.20.0" +lock_api = "0.4.11" spin = { version = "0.9.8", features = ["once", "lock_api", "rwlock"] } -anyhow = "1.0.64" -log = "0.4.17" -env_logger = "0.10.0" -criterion = { version = "0.4.0", features = ["html_reports"] } -clap = { version = "4.0.25", features = ["derive"] } +anyhow = "1.0.75" +log = "0.4.20" +env_logger = "0.10.1" +criterion = { version = "0.5.1", features = ["html_reports"] } +clap = { version = "4.4.11", features = ["derive"] } lazy_static = { version = "1.4.0", features = ["spin_no_std"] } hex-literal = "0.4.1" -openssl = "0.10.48" +openssl = "0.10.61" cfg-if = "1.0.0" -blake2 = "0.10.4" -hdrhistogram = "7.5.0" -regex = "1.7.0" -tokio = { version = "1.20.3", features = ["full"] } +blake2 = "0.10.6" +hdrhistogram = "7.5.4" +regex = "1.10.2" +tokio = { version = "1.35.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"] } -tempfile = "3.5.0" -thiserror = "1.0.40" +rstest = { version = "0.18.2", default-features = false } +rstest_reuse = "0.6.0" +wycheproof = "0.5.1" +chrono = { version = "0.4.31", default-features = false, features = ["clock"] } +tempfile = "3.8.1" +thiserror = "1.0.51" tinyvec = { version = "1.6.0", features = ["rustc_1_55"] } -mlua = "0.8.8" -strum = { version = "0.24.1", default-features=false } -strum_macros = { version = "0.24.2", default-features=false } +mlua = "0.9.2" +strum = { version = "0.25.0", default-features=false } +strum_macros = { version = "0.25.3", default-features=false } owo-colors = "3.5.0" +rhai = { version = "1.16.3", features = ["sync"] } [workspace.package] version = "0.1.0" @@ -155,21 +174,22 @@ rust-version = "1.71.0" [dependencies] clap.workspace = true +cmd-runner = { path = "../cmd-runner" } anyhow.workspace = true shell-escape = "0.1.5" owo-colors.workspace = true -reqwest = { version = "0.11.17", default-features = false, features = ["blocking", "rustls-tls"] } 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 +regex = "1.10.2" [dev-dependencies] tempfile.workspace = true diff --git a/nearby/cargo2android.json b/nearby/cargo2android.json deleted file mode 100644 index 84e42b8..0000000 --- a/nearby/cargo2android.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "patch": "patches/Android.bp.patch", - "device": true, - "run": true, - "features": "openssl,boringssl", - "onefile": true, - "patch": "patches/Android.bp.patch" -} 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..2ac6f3a 100644 --- a/nearby/connections/ukey2/ukey2/Cargo.toml +++ b/nearby/connections/ukey2/ukey2/Cargo.toml @@ -4,18 +4,24 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +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 ukey2_proto.workspace = true log.workspace = true -derive-getters = "0.2.0" -num-bigint = "0.4.3" +num-bigint = "0.4.4" [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/lib.rs b/nearby/connections/ukey2/ukey2/src/lib.rs index d8f2270..dc17b83 100644 --- a/nearby/connections/ukey2/ukey2/src/lib.rs +++ b/nearby/connections/ukey2/ukey2/src/lib.rs @@ -18,15 +18,6 @@ //! establish a secure channel. //! //! For a full description of the protocol, see <https://github.com/google/ukey2>. -#![forbid(unsafe_code)] -#![deny( - missing_docs, - trivial_casts, - trivial_numeric_casts, - unused_extern_crates, - unused_import_braces, - unused_results -)] mod proto_adapter; mod state_machine; diff --git a/nearby/connections/ukey2/ukey2/src/proto_adapter.rs b/nearby/connections/ukey2/ukey2/src/proto_adapter.rs index de3c4b9..2986276 100644 --- a/nearby/connections/ukey2/ukey2/src/proto_adapter.rs +++ b/nearby/connections/ukey2/ukey2/src/proto_adapter.rs @@ -17,7 +17,6 @@ use crypto_provider::elliptic_curve::EcdhProvider; use crypto_provider::p256::{P256EcdhProvider, P256PublicKey, P256}; use crypto_provider::CryptoProvider; -use derive_getters::Getters; use ukey2_proto::ukey2_all_proto::{securemessage, ukey}; /// For generated proto types for UKEY2 messages @@ -79,23 +78,44 @@ pub(crate) enum MessageType { ClientFinish, } -#[derive(Getters)] pub(crate) struct ClientInit { version: i32, commitments: Vec<CipherCommitment>, next_protocol: String, } +impl ClientInit { + pub fn version(&self) -> i32 { + self.version + } + + pub fn commitments(&self) -> &[CipherCommitment] { + &self.commitments + } + + pub fn next_protocol(&self) -> &str { + &self.next_protocol + } +} + #[allow(dead_code)] -#[derive(Getters)] pub(crate) struct ServerInit { version: i32, random: [u8; 32], handshake_cipher: HandshakeCipher, - #[getter(skip)] pub(crate) public_key: Vec<u8>, } +impl ServerInit { + pub fn version(&self) -> i32 { + self.version + } + + pub fn handshake_cipher(&self) -> HandshakeCipher { + self.handshake_cipher + } +} + pub(crate) struct ClientFinished { pub(crate) public_key: Vec<u8>, } @@ -119,12 +139,22 @@ impl HandshakeCipher { } } -#[derive(Clone, Getters)] +#[derive(Clone)] pub(crate) struct CipherCommitment { cipher: HandshakeCipher, commitment: Vec<u8>, } +impl CipherCommitment { + pub fn cipher(&self) -> HandshakeCipher { + self.cipher + } + + pub fn commitment(&self) -> &[u8] { + &self.commitment + } +} + pub(crate) enum GenericPublicKey<C: CryptoProvider> { Ec256(<C::P256 as EcdhProvider<P256>>::PublicKey), // Other public key types are not supported @@ -256,6 +286,7 @@ impl<C: CryptoProvider> IntoAdapter<GenericPublicKey<C>> for securemessage::Gene /// representation. If the input byte array is not positive or cannot be fit into 32 byte unsigned /// int range, then `None` is returned. fn positive_twos_complement_to_32_byte_unsigned(twos_complement: &[u8]) -> Option<[u8; 32]> { + #[allow(clippy::indexing_slicing)] if !twos_complement.is_empty() && (twos_complement[0] & 0x80) == 0 { let mut twos_complement_iter = twos_complement.iter().rev(); let mut result = [0_u8; 32]; diff --git a/nearby/connections/ukey2/ukey2/src/state_machine.rs b/nearby/connections/ukey2/ukey2/src/state_machine.rs index c7cefc3..d2021e6 100644 --- a/nearby/connections/ukey2/ukey2/src/state_machine.rs +++ b/nearby/connections/ukey2/ukey2/src/state_machine.rs @@ -43,7 +43,8 @@ impl SendAlert { error_message: self.msg, ..Default::default() }; - alert_message.to_wrapped_msg().write_to_bytes().unwrap() + #[allow(clippy::expect_used)] + alert_message.to_wrapped_msg().write_to_bytes().expect("Writing to proto should succeed") } } diff --git a/nearby/connections/ukey2/ukey2/src/tests.rs b/nearby/connections/ukey2/ukey2/src/tests.rs index b58cee6..92c0358 100644 --- a/nearby/connections/ukey2/ukey2/src/tests.rs +++ b/nearby/connections/ukey2/ukey2/src/tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use crate::{ proto_adapter::{IntoAdapter as _, MessageType, ToWrappedMessage as _}, ukey2_handshake::HandshakeCipher, @@ -21,42 +23,41 @@ use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey}; 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 +67,24 @@ fn advance_from_init_to_finish_client_test<C: CryptoProvider>( // 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 +120,23 @@ fn advance_from_init_to_complete_server_x25519_test<C: CryptoProvider>( // 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..0b2be0e 100644 --- a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs +++ b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs @@ -12,6 +12,9 @@ // 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. +#![allow(clippy::expect_used)] +// TODO: remove this and convert all unwraps to expects +#![allow(clippy::unwrap_used)] pub(crate) use crate::proto_adapter::{ CipherCommitment, ClientFinished, ClientInit, GenericPublicKey, HandshakeCipher, @@ -89,7 +92,7 @@ impl WireCompatibilityLayer for HandshakeImplementation { HandshakeCipher::P256Sha512 => { let p256_key = <C::P256 as P256EcdhProvider>::PublicKey::from_bytes(key.as_slice()) - .unwrap(); + .expect(""); let (x, y) = p256_key.to_affine_coordinates().unwrap(); let bigboi_x = num_bigint::BigInt::from_biguint( num_bigint::Sign::Plus, @@ -133,7 +136,7 @@ impl WireCompatibilityLayer for HandshakeImplementation { match public_key { GenericPublicKey::Ec256(key) => { debug_assert_eq!(cipher, HandshakeCipher::P256Sha512); - Some(key.to_bytes()) + Some(key.to_bytes().to_vec()) } } } @@ -167,7 +170,7 @@ impl<C: CryptoProvider> Ukey2ServerStage1<C> { client_init: ClientInit, client_init_msg_bytes: Vec<u8>, ) -> Result<Ukey2ServerStage2<C>, ClientInitError> { - if client_init.version() != &1 { + if client_init.version() != 1 { return Err(ClientInitError::BadVersion); } @@ -188,7 +191,7 @@ impl<C: CryptoProvider> Ukey2ServerStage1<C> { // proto enum uses the priority as the numeric value .max_by_key(|c| c.cipher().as_proto() as i32) .ok_or(ClientInitError::BadHandshakeCipher)?; - match *commitment.cipher() { + match commitment.cipher() { // pick in priority order HandshakeCipher::Curve25519Sha512 => { let secret = ServerKeyPair::Curve25519( @@ -267,9 +270,12 @@ impl<C: CryptoProvider> Ukey2ServerStage2<C> { 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 +398,7 @@ impl<C: CryptoProvider> Ukey2ClientStage1<C> { ); 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 +417,7 @@ impl<C: CryptoProvider> Ukey2ClientStage1<C> { 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"), @@ -471,7 +477,7 @@ impl<C: CryptoProvider> Ukey2ClientStage1<C> { server_init: ServerInit, server_init_bytes: Vec<u8>, ) -> Result<Ukey2Client, ServerInitError> { - if server_init.version() != &1 { + if server_init.version() != 1 { return Err(ServerInitError::BadVersion); } @@ -480,7 +486,7 @@ impl<C: CryptoProvider> Ukey2ClientStage1<C> { .commitment_ciphers .iter() .fold(None, |accum, c| { - if server_init.handshake_cipher() == c { + if server_init.handshake_cipher() == *c { match accum { None => Some(c), Some(_) => accum, diff --git a/nearby/connections/ukey2/ukey2/tests/tests.rs b/nearby/connections/ukey2/ukey2/tests/tests.rs index 31ba416..85b972c 100644 --- a/nearby/connections/ukey2/ukey2/tests/tests.rs +++ b/nearby/connections/ukey2/ukey2/tests/tests.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crypto_provider_rustcrypto::RustCrypto; +#![allow(clippy::unwrap_used)] + +use crypto_provider_default::CryptoProviderImpl; use rand::{rngs::StdRng, SeedableRng}; use std::collections::hash_set; use ukey2_rs::*; @@ -22,10 +24,12 @@ fn full_integration_state_machine() { 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 +41,18 @@ fn full_integration_state_machine() { 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 +61,12 @@ fn full_integration_state_machine_public_key_in_protobuf() { 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 +78,17 @@ fn full_integration_state_machine_public_key_in_protobuf() { 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 @@ publish.workspace = true 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.workspace = true 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..a8b7b9d 100644 --- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt +++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt @@ -14,12 +14,16 @@ cmake_minimum_required(VERSION 3.14) +project(Ukey2) + enable_testing() include_directories( ${CMAKE_SOURCE_DIR}/ukey2_c_ffi/cpp/) +include(GoogleTest) include(ExternalProject) + set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/target/tmp) ExternalProject_Add( ukey2_c_ffi @@ -33,11 +37,9 @@ ExternalProject_Add( set(CMAKE_CXX_STANDARD 20) if(UNIX) - add_compile_options(-Wall -Werror -Wextra -Wimplicit-fallthrough -Wextra-semi + add_compile_options(-Wall -Wextra -Wimplicit-fallthrough -Wextra-semi -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi - -Wno-unneeded-internal-declaration - -Wno-ignored-pragma-optimize - -Wno-bitfield-constant-conversion -Wno-deprecated-this-capture -Wshadow + -Wshadow -Wsign-compare) elseif(MSVC) add_compile_options(-W4 -MD) @@ -81,5 +83,4 @@ elseif(MSVC) ) endif() -include(GoogleTest) gtest_discover_tests(ffi_test) 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 @@ use ukey2_connections::{ 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..b1c34a1 100644 --- a/nearby/connections/ukey2/ukey2_connections/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_connections/Cargo.toml @@ -4,20 +4,30 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +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" } crypto_provider.workspace = true rand = { workspace = true, features = ["std", "std_rng"] } ukey2_proto.workspace = true -nom = { version = "7.1.1", features = ["alloc"] } -bytes = "1.2.1" -criterion = "0.4.0" +nom = { version = "7.1.3", features = ["alloc"] } +bytes = "1.5.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..f816165 100644 --- a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs +++ b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs, clippy::expect_used, clippy::unwrap_used)] + use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; 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 +65,18 @@ fn criterion_benchmark(c: &mut Criterion) { 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)); + let _ = 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| { + let _ = 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 c4aad27..167e0de 100644 --- a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock +++ b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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,6 +69,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] name = "anyhow" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -70,17 +90,6 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -99,6 +108,12 @@ 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" @@ -124,9 +139,9 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[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 = "cast" @@ -197,26 +212,30 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ - "bitflags", - "clap_lex", - "indexmap", - "textwrap", + "clap_builder", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_builder" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ - "os_str_bytes", + "anstyle", + "clap_lex", ] [[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] name = "const-oid" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -233,19 +252,19 @@ dependencies = [ [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", "clap", "criterion-plot", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -337,7 +356,7 @@ dependencies = [ name = "crypto_provider" version = "0.1.0" dependencies = [ - "bytes", + "tinyvec", ] [[package]] @@ -346,6 +365,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -375,9 +395,9 @@ dependencies = [ [[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", @@ -397,7 +417,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn", ] [[package]] @@ -411,17 +431,6 @@ dependencies = [ ] [[package]] -name = "derive-getters" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0122f262bf9c9a367829da84f808d9fb128c10ef283bbe7b0922a77cf07b2747" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "derive_arbitrary" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -429,7 +438,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn", ] [[package]] @@ -454,14 +463,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "sha2", + "subtle", ] [[package]] @@ -497,7 +507,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -558,6 +568,16 @@ dependencies = [ ] [[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" @@ -582,15 +602,6 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" @@ -648,9 +659,20 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix 0.38.13", + "windows-sys 0.52.0", ] [[package]] @@ -687,12 +709,6 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -716,10 +732,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[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" @@ -754,9 +776,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -775,9 +797,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -788,7 +810,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", ] @@ -811,12 +833,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - -[[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1014,7 +1030,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1061,12 +1077,25 @@ version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", - "windows-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys 0.4.12", + "windows-sys 0.48.0", ] [[package]] @@ -1126,7 +1155,7 @@ checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn", ] [[package]] @@ -1142,9 +1171,9 @@ dependencies = [ [[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", @@ -1165,17 +1194,6 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" @@ -1195,17 +1213,11 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix", - "windows-sys", + "rustix 0.37.23", + "windows-sys 0.48.0", ] [[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] name = "thiserror" version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1222,7 +1234,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn", ] [[package]] @@ -1236,6 +1248,12 @@ dependencies = [ ] [[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" @@ -1279,7 +1297,6 @@ name = "ukey2_rs" version = "0.1.0" dependencies = [ "crypto_provider", - "derive-getters", "log", "num-bigint", "rand", @@ -1345,7 +1362,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn", "wasm-bindgen-shared", ] @@ -1367,7 +1384,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1436,7 +1453,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1445,13 +1471,28 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" 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", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1461,46 +1502,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[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/lib.rs b/nearby/connections/ukey2/ukey2_connections/src/lib.rs index 3a441bb..b3ed937 100644 --- a/nearby/connections/ukey2/ukey2_connections/src/lib.rs +++ b/nearby/connections/ukey2/ukey2_connections/src/lib.rs @@ -24,7 +24,9 @@ //! from the handshake context once the handshake is complete, and controls the encryption and //! decryption of the payload messages. -#![deny(missing_docs)] +#![allow(clippy::expect_used)] +//TODO: remove this and fix instances of unwrap +#![allow(clippy::unwrap_used, clippy::panic)] mod crypto_utils; mod d2d_connection_context_v1; diff --git a/nearby/connections/ukey2/ukey2_connections/src/tests.rs b/nearby/connections/ukey2/ukey2_connections/src/tests.rs index 35a1e70..d6acecc 100644 --- a/nearby/connections/ukey2/ukey2_connections/src/tests.rs +++ b/nearby/connections/ukey2/ukey2_connections/src/tests.rs @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crypto_provider_rustcrypto::RustCryptoImpl; -use rand::SeedableRng; -use rand::{rngs::StdRng, CryptoRng, RngCore}; -use rstest::rstest; +#![allow(clippy::indexing_slicing)] use crypto_provider::CryptoProvider; -use crypto_provider_openssl::Openssl; -use crypto_provider_rustcrypto::RustCrypto; +use crypto_provider_default::CryptoProviderImpl; +use rand::SeedableRng; +use rand::{rngs::StdRng, CryptoRng, RngCore}; use ukey2_rs::HandshakeImplementation; use crate::{ @@ -28,27 +26,25 @@ use crate::{ 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 +55,36 @@ fn crypto_test_encrypt_seeded<C: CryptoProvider>( ); } -#[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 +122,17 @@ where (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 +146,104 @@ fn send_receive_message_seeded<C: CryptoProvider>( 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 +253,7 @@ fn test_unique_session<C: CryptoProvider>( 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..08566dd 100644 --- a/nearby/connections/ukey2/ukey2_jni/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_jni/Cargo.toml @@ -4,16 +4,18 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [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 +24,10 @@ log = { workspace = true, features = ["std"] } [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/java/build.gradle.kts b/nearby/connections/ukey2/ukey2_jni/java/build.gradle.kts index 58c58fc..78b0ebc 100644 --- a/nearby/connections/ukey2/ukey2_jni/java/build.gradle.kts +++ b/nearby/connections/ukey2/ukey2_jni/java/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { implementation("com.google.code.findbugs:jsr305:3.0.2") implementation(kotlin("stdlib")) testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.2") } kotlin { @@ -51,10 +52,10 @@ kotlin { } tasks.jmh { - jvmArgs.value(mutableListOf("-Djava.library.path=../../../../target/release")) + jvmArgs.value(mutableListOf("-Djava.library.path=$projectDir/../../../../target/release")) } tasks.test { useJUnitPlatform() - jvmArgs = mutableListOf("-Djava.library.path=../../../../target/debug") + jvmArgs = mutableListOf("-Djava.library.path=$projectDir/../../../../target/debug") } diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/Ukey2Benchmark.java b/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/Ukey2Benchmark.java deleted file mode 100644 index eb063cc..0000000 --- a/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/Ukey2Benchmark.java +++ /dev/null @@ -1,83 +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. - */ - -package com.google.security.cryptauth.lib.securegcm; - -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.profile.GCProfiler; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.Random; - -/** - * Benchmark for encoding and decoding UKEY2 messages over the JNI, analogous to - * `ukey2_benches.rs`. The parameters and the operations also roughly matches the that of the Rust - * Criterion benchmark. That said, since the benchmark infrastructure is different, there will - * inevitably be differences the skews the number in certain ways – comparison of numbers from the - * different benchmarks should compared on order-of-magnitudes only. To get the JNI overhead, for - * example, it would be better use this JMH infra to measure a call into a no-op Rust function, - * which is a more apples-to-apples comparison. - * - * To run this benchmark, run - * cargo build -p ukey2_jni --release && ./gradlew jmh - */ -@State(Scope.Benchmark) -@OutputTimeUnit(TimeUnit.SECONDS) -@BenchmarkMode(Mode.Throughput) -public class Ukey2Benchmark { - - @State(Scope.Thread) - public static class ConnectionState { - D2DConnectionContextV1 connContext; - D2DConnectionContextV1 serverConnContext; - @Param({"10", "1024"}) - int sizeKibs; - byte[] inputBytes; - - @Setup - public void setup() throws Exception { - D2DHandshakeContext initiatorContext = - new D2DHandshakeContext(D2DHandshakeContext.Role.Initiator); - D2DHandshakeContext serverContext = - new D2DHandshakeContext(D2DHandshakeContext.Role.Responder); - serverContext.parseHandshakeMessage(initiatorContext.getNextHandshakeMessage()); - initiatorContext.parseHandshakeMessage(serverContext.getNextHandshakeMessage()); - serverContext.parseHandshakeMessage(initiatorContext.getNextHandshakeMessage()); - connContext = initiatorContext.toConnectionContext(); - serverConnContext = serverContext.toConnectionContext(); - Random random = new Random(); - inputBytes = new byte[sizeKibs * 1024]; - random.nextBytes(inputBytes); - } - } - - @Benchmark - @Fork(3) - @Warmup(iterations = 2, time = 500, timeUnit = TimeUnit.MILLISECONDS) - @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) - public void encodeAndDecode(ConnectionState state, Blackhole blackhole) throws Exception { - byte[] encoded = state.connContext.encodeMessageToPeer(state.inputBytes, null); - byte[] decoded = state.serverConnContext.decodeMessageFromPeer(encoded, null); - blackhole.consume(decoded); - } -} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/ukey2/Ukey2Benchmark.java b/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/ukey2/Ukey2Benchmark.java new file mode 100644 index 0000000..9cca229 --- /dev/null +++ b/nearby/connections/ukey2/ukey2_jni/java/src/jmh/java/com/google/security/cryptauth/lib/securegcm/ukey2/Ukey2Benchmark.java @@ -0,0 +1,78 @@ +/* + * 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.security.cryptauth.lib.securegcm.ukey2; + +import com.google.security.cryptauth.lib.securegcm.ukey2.D2DHandshakeContext.Role; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.concurrent.TimeUnit; +import java.util.Random; + +/** + * Benchmark for encoding and decoding UKEY2 messages over the JNI, analogous to `ukey2_benches.rs`. + * The parameters and the operations also roughly matches the that of the Rust Criterion benchmark. + * That said, since the benchmark infrastructure is different, there will inevitably be differences + * the skews the number in certain ways – comparison of numbers from the different benchmarks should + * compared on order-of-magnitudes only. To get the JNI overhead, for example, it would be better + * use this JMH infra to measure a call into a no-op Rust function, which is a more apples-to-apples + * comparison. + * + * <p>To run this benchmark, run cargo build -p ukey2_jni --release && ./gradlew jmh + */ +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.SECONDS) +@BenchmarkMode(Mode.Throughput) +public class Ukey2Benchmark { + + @State(Scope.Thread) + public static class ConnectionState { + D2DConnectionContextV1 connContext; + D2DConnectionContextV1 serverConnContext; + + @Param({"10", "1024"}) + int sizeKibs; + + byte[] inputBytes; + + @Setup + public void setup() throws Exception { + D2DHandshakeContext initiatorContext = + new D2DHandshakeContext(Role.INITIATOR); + D2DHandshakeContext serverContext = + new D2DHandshakeContext(Role.RESPONDER); + serverContext.parseHandshakeMessage(initiatorContext.getNextHandshakeMessage()); + initiatorContext.parseHandshakeMessage(serverContext.getNextHandshakeMessage()); + serverContext.parseHandshakeMessage(initiatorContext.getNextHandshakeMessage()); + connContext = initiatorContext.toConnectionContext(); + serverConnContext = serverContext.toConnectionContext(); + Random random = new Random(); + inputBytes = new byte[sizeKibs * 1024]; + random.nextBytes(inputBytes); + } + } + + @Benchmark + @Fork(3) + @Warmup(iterations = 2, time = 500, timeUnit = TimeUnit.MILLISECONDS) + @Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) + public void encodeAndDecode(ConnectionState state, Blackhole blackhole) throws Exception { + byte[] encoded = state.connContext.encodeMessageToPeer(state.inputBytes, null); + byte[] decoded = state.serverConnContext.decodeMessageFromPeer(encoded, null); + blackhole.consume(decoded); + } +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java deleted file mode 100644 index 7874cd9..0000000 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DConnectionContextV1.java +++ /dev/null @@ -1,123 +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. - */ - -package com.google.security.cryptauth.lib.securegcm; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class D2DConnectionContextV1 { - - static { - System.loadLibrary("ukey2_jni"); - } - - private static native byte[] encode_message_to_peer(long contextPtr, byte[] payload, byte[] associatedData) throws BadHandleException; - - private static native byte[] decode_message_from_peer(long contextPtr, byte[] message, byte[] associatedData) throws CryptoException; - - private static native byte[] get_session_unique(long contextPtr) throws BadHandleException; - - private static native int get_sequence_number_for_encoding(long contextPtr) throws BadHandleException; - - private static native int get_sequence_number_for_decoding(long contextPtr) throws BadHandleException; - - private static native byte[] save_session(long contextPtr) throws BadHandleException; - - private static native long from_saved_session(byte[] savedSessionInfo); - - private final long contextPtr; - - /** - * Java wrapper for D2DConnectionContextV1 to interact with the underlying Rust implementation - * - * @param contextPtr the handle to the Rust implementation. - */ - D2DConnectionContextV1(@Nonnull long contextPtr) { - this.contextPtr = contextPtr; - } - - /** - * Encode a message to the connection peer using session keys derived from the handshake. - * - * @param payload The message to be encrypted. - * @return The encrypted/encoded message. - */ - @Nonnull - public byte[] encodeMessageToPeer(@Nonnull byte[] payload, @Nullable byte[] associatedData) throws BadHandleException { - return encode_message_to_peer(contextPtr, payload, associatedData); - } - - /** - * Decodes/decrypts a message from the connection peer. - * - * @param message The message received over the connection. - * @return The decoded message from the connection peer. - */ - @Nonnull - public byte[] decodeMessageFromPeer(@Nonnull byte[] message, @Nullable byte[] associatedData) throws CryptoException { - return decode_message_from_peer(contextPtr, message, associatedData); - } - - /** - * A unique session identifier derived from session-specific information - * - * @return The session unique identifier - */ - @Nonnull - public byte[] getSessionUnique() throws BadHandleException { - return get_session_unique(contextPtr); - } - - /** - * Returns the encoding sequence number. - * - * @return the encoding sequence number. - */ - public int getSequenceNumberForEncoding() throws BadHandleException { - return get_sequence_number_for_encoding(contextPtr); - } - - /** - * Returns the decoding sequence number. - * - * @return the decoding sequence number. - */ - public int getSequenceNumberForDecoding() throws BadHandleException { - return get_sequence_number_for_decoding(contextPtr); - } - - /** - * Serializes the current session in a form usable by {@link D2DConnectionContextV1#fromSavedSession} - * - * @return a byte array representing the current session. - */ - @Nonnull - public byte[] saveSession() throws BadHandleException { - return save_session(contextPtr); - } - - /** - * Reconstructs and returns the session originally serialized by {@link D2DConnectionContextV1#saveSession} - * - * @param savedSessionInfo the byte array from saveSession() - * @return a D2DConnectionContextV1 session with the same properties as the context saved. - */ - public static D2DConnectionContextV1 fromSavedSession(@Nonnull byte[] savedSessionInfo) { - return new D2DConnectionContextV1(from_saved_session(savedSessionInfo)); - } - -} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java deleted file mode 100644 index 39f7aa9..0000000 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/D2DHandshakeContext.java +++ /dev/null @@ -1,122 +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. - */ - -package com.google.security.cryptauth.lib.securegcm; - -import javax.annotation.Nonnull; - -public class D2DHandshakeContext { - static { - System.loadLibrary("ukey2_jni"); - } - - public enum Role { - INITIATOR, - RESPONDER, - } - - private final long contextPtr; - - private static native boolean is_handshake_complete(long contextPtr) throws BadHandleException; - - private static native long create_context(boolean isClient); - - private static native byte[] get_next_handshake_message(long contextPtr) throws BadHandleException; - - private static native void parse_handshake_message(long contextPtr, byte[] message) throws BadHandleException, HandshakeException; - - private static native byte[] get_verification_string(long contextPtr, int length) throws BadHandleException, HandshakeException; - - private static native long to_connection_context(long contextPtr) throws HandshakeException; - - public D2DHandshakeContext(@Nonnull Role role) { - this.contextPtr = create_context(role == Role.INITIATOR); - } - - /** - * Convenience constructor that creates a UKEY2 D2DHandshakeContext for the initiator role. - * - * @return a D2DHandshakeContext for the role of initiator in the handshake. - */ - public static D2DHandshakeContext forInitiator() { - return new D2DHandshakeContext(Role.INITIATOR); - } - - /** - * Convenience constructor that creates a UKEY2 D2DHandshakeContext for the initiator role. - * - * @return a D2DHandshakeContext for the role of responder/server in the handshake. - */ - public static D2DHandshakeContext forResponder() { - return new D2DHandshakeContext(Role.RESPONDER); - } - - /** - * Function that checks if the handshake is completed. - * - * @return true/false depending on if the handshake is complete. - */ - public boolean isHandshakeComplete() throws BadHandleException { - return is_handshake_complete(contextPtr); - } - - /** - * Gets the next handshake message in the exchange. - * - * @return handshake message encoded in a SecureMessage. - */ - @Nonnull - public byte[] getNextHandshakeMessage() throws BadHandleException { - return get_next_handshake_message(contextPtr); - } - - /** - * Parses the handshake message. - * - * @param message - handshake message from the other side. - */ - @Nonnull - public void parseHandshakeMessage(@Nonnull byte[] message) throws BadHandleException, HandshakeException { - parse_handshake_message(contextPtr, message); - } - - /** - * Returns an authentication string suitable for authenticating the handshake out-of-band. Note - * that the authentication string can be short (e.g., a 6 digit visual confirmation code). Note: - * this should only be called when {#isHandshakeComplete} returns true. - * This code is analogous to the authentication string described in the spec. - * - * @param length - The length of the returned verification string. - * @return - The returned verification string as a byte array. - * @throws BadHandleException - Thrown if the handle is no longer valid, for example after calling {@link D2DHandshakeContext#toConnectionContext} - * @throws HandshakeException - Thrown if the handshake is not complete when this function is called. - */ - @Nonnull - public byte[] getVerificationString(int length) throws BadHandleException, HandshakeException { - return get_verification_string(contextPtr, length); - } - - /** - * Function to create a secure communication channel from the handshake after confirming the auth string generated by - * the handshake out-of-band (i.e. via a user-facing UI). - * - * @return a new {@link D2DConnectionContextV1} with the next protocol specified when creating the D2DHandshakeContext. - * @throws HandshakeException if the handsshake is not complete when this function is called. - */ - public D2DConnectionContextV1 toConnectionContext() throws HandshakeException { - return new D2DConnectionContextV1(to_connection_context(contextPtr)); - } -} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/BadHandleException.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException.java index 2efd7c4..78f0e5e 100644 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/BadHandleException.java +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException.java @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.security.cryptauth.lib.securegcm; +package com.google.security.cryptauth.lib.securegcm.ukey2; /** - * Represents an unrecoverable error (invalid handle) that has occurred during the handshake/connection. + * Represents an unrecoverable error (invalid handle) that has occurred during the + * handshake/connection. */ public class BadHandleException extends Exception { public BadHandleException(String message) { @@ -29,4 +30,4 @@ public class BadHandleException extends Exception { public BadHandleException(String message, Exception e) { super(message, e); } -}
\ No newline at end of file +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/CryptoException.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/CryptoException.java index 6abeb53..11b5c9b 100644 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/CryptoException.java +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/CryptoException.java @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.security.cryptauth.lib.securegcm; +package com.google.security.cryptauth.lib.securegcm.ukey2; -/** - * Represents an unrecoverable error that has occurred during the handshake procedure. - */ +/** Represents an unrecoverable error that has occurred during the handshake procedure. */ public class CryptoException extends Exception { public CryptoException(String message) { super(message); @@ -29,4 +27,4 @@ public class CryptoException extends Exception { public CryptoException(String message, Exception e) { super(message, e); } -}
\ No newline at end of file +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DConnectionContextV1.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DConnectionContextV1.java new file mode 100644 index 0000000..9ce2517 --- /dev/null +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DConnectionContextV1.java @@ -0,0 +1,139 @@ +/* + * 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.security.cryptauth.lib.securegcm.ukey2; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class D2DConnectionContextV1 { + + static { + var path = System.getProperty("java.library.path"); + + if (path == null) { + throw new RuntimeException("Path isn't set."); + } + + var paths = java.util.List.of(path.split(";")); + paths.forEach(System.out::println); + + System.loadLibrary("ukey2_jni"); + } + + private static native byte[] encode_message_to_peer( + long contextPtr, byte[] payload, byte[] associatedData) throws BadHandleException; + + private static native byte[] decode_message_from_peer( + long contextPtr, byte[] message, byte[] associatedData) throws CryptoException; + + private static native byte[] get_session_unique(long contextPtr) throws BadHandleException; + + private static native int get_sequence_number_for_encoding(long contextPtr) + throws BadHandleException; + + private static native int get_sequence_number_for_decoding(long contextPtr) + throws BadHandleException; + + private static native byte[] save_session(long contextPtr) throws BadHandleException; + + private static native long from_saved_session(byte[] savedSessionInfo); + + private final long contextPtr; + + /** + * Java wrapper for D2DConnectionContextV1 to interact with the underlying Rust implementation + * + * @param contextPtr the handle to the Rust implementation. + */ + D2DConnectionContextV1(@Nonnull long contextPtr) { + this.contextPtr = contextPtr; + } + + /** + * Encode a message to the connection peer using session keys derived from the handshake. + * + * @param payload The message to be encrypted. + * @return The encrypted/encoded message. + */ + @Nonnull + public byte[] encodeMessageToPeer(@Nonnull byte[] payload, @Nullable byte[] associatedData) + throws BadHandleException { + return encode_message_to_peer(contextPtr, payload, associatedData); + } + + /** + * Decodes/decrypts a message from the connection peer. + * + * @param message The message received over the connection. + * @return The decoded message from the connection peer. + */ + @Nonnull + public byte[] decodeMessageFromPeer(@Nonnull byte[] message, @Nullable byte[] associatedData) + throws CryptoException { + return decode_message_from_peer(contextPtr, message, associatedData); + } + + /** + * A unique session identifier derived from session-specific information + * + * @return The session unique identifier + */ + @Nonnull + public byte[] getSessionUnique() throws BadHandleException { + return get_session_unique(contextPtr); + } + + /** + * Returns the encoding sequence number. + * + * @return the encoding sequence number. + */ + public int getSequenceNumberForEncoding() throws BadHandleException { + return get_sequence_number_for_encoding(contextPtr); + } + + /** + * Returns the decoding sequence number. + * + * @return the decoding sequence number. + */ + public int getSequenceNumberForDecoding() throws BadHandleException { + return get_sequence_number_for_decoding(contextPtr); + } + + /** + * Serializes the current session in a form usable by {@link + * D2DConnectionContextV1#fromSavedSession} + * + * @return a byte array representing the current session. + */ + @Nonnull + public byte[] saveSession() throws BadHandleException { + return save_session(contextPtr); + } + + /** + * Reconstructs and returns the session originally serialized by {@link + * D2DConnectionContextV1#saveSession} + * + * @param savedSessionInfo the byte array from saveSession() + * @return a D2DConnectionContextV1 session with the same properties as the context saved. + */ + public static D2DConnectionContextV1 fromSavedSession(@Nonnull byte[] savedSessionInfo) { + return new D2DConnectionContextV1(from_saved_session(savedSessionInfo)); + } +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DHandshakeContext.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DHandshakeContext.java new file mode 100644 index 0000000..429295e --- /dev/null +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/D2DHandshakeContext.java @@ -0,0 +1,129 @@ +/* + * 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.security.cryptauth.lib.securegcm.ukey2; + +import javax.annotation.Nonnull; + +public class D2DHandshakeContext { + static { + System.loadLibrary("ukey2_jni"); + } + + public enum Role { + INITIATOR, + RESPONDER, + } + + private final long contextPtr; + + private static native boolean is_handshake_complete(long contextPtr) throws BadHandleException; + + private static native long create_context(boolean isClient); + + private static native byte[] get_next_handshake_message(long contextPtr) + throws BadHandleException; + + private static native void parse_handshake_message(long contextPtr, byte[] message) + throws BadHandleException, HandshakeException; + + private static native byte[] get_verification_string(long contextPtr, int length) + throws BadHandleException, HandshakeException; + + private static native long to_connection_context(long contextPtr) throws HandshakeException; + + public D2DHandshakeContext(@Nonnull Role role) { + this.contextPtr = create_context(role == Role.INITIATOR); + } + + /** + * Convenience constructor that creates a UKEY2 D2DHandshakeContext for the initiator role. + * + * @return a D2DHandshakeContext for the role of initiator in the handshake. + */ + public static D2DHandshakeContext forInitiator() { + return new D2DHandshakeContext(Role.INITIATOR); + } + + /** + * Convenience constructor that creates a UKEY2 D2DHandshakeContext for the initiator role. + * + * @return a D2DHandshakeContext for the role of responder/server in the handshake. + */ + public static D2DHandshakeContext forResponder() { + return new D2DHandshakeContext(Role.RESPONDER); + } + + /** + * Function that checks if the handshake is completed. + * + * @return true/false depending on if the handshake is complete. + */ + public boolean isHandshakeComplete() throws BadHandleException { + return is_handshake_complete(contextPtr); + } + + /** + * Gets the next handshake message in the exchange. + * + * @return handshake message encoded in a SecureMessage. + */ + @Nonnull + public byte[] getNextHandshakeMessage() throws BadHandleException { + return get_next_handshake_message(contextPtr); + } + + /** + * Parses the handshake message. + * + * @param message - handshake message from the other side. + */ + @Nonnull + public void parseHandshakeMessage(@Nonnull byte[] message) + throws BadHandleException, HandshakeException { + parse_handshake_message(contextPtr, message); + } + + /** + * Returns an authentication string suitable for authenticating the handshake out-of-band. Note + * that the authentication string can be short (e.g., a 6 digit visual confirmation code). Note: + * this should only be called when {#isHandshakeComplete} returns true. This code is analogous to + * the authentication string described in the spec. + * + * @param length - The length of the returned verification string. + * @return - The returned verification string as a byte array. + * @throws BadHandleException - Thrown if the handle is no longer valid, for example after calling + * {@link D2DHandshakeContext#toConnectionContext} + * @throws HandshakeException - Thrown if the handshake is not complete when this function is + * called. + */ + @Nonnull + public byte[] getVerificationString(int length) throws BadHandleException, HandshakeException { + return get_verification_string(contextPtr, length); + } + + /** + * Function to create a secure communication channel from the handshake after confirming the auth + * string generated by the handshake out-of-band (i.e. via a user-facing UI). + * + * @return a new {@link D2DConnectionContextV1} with the next protocol specified when creating the + * D2DHandshakeContext. + * @throws HandshakeException if the handsshake is not complete when this function is called. + */ + public D2DConnectionContextV1 toConnectionContext() throws HandshakeException { + return new D2DConnectionContextV1(to_connection_context(contextPtr)); + } +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException.java index 17928e9..20d3112 100644 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/HandshakeException.java +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException.java @@ -12,21 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.security.cryptauth.lib.securegcm; +package com.google.security.cryptauth.lib.securegcm.ukey2; -/** - * Represents an unrecoverable error that has occurred during the handshake procedure. - */ +/** Represents an unrecoverable error that has occurred during the handshake procedure. */ public class HandshakeException extends Exception { - public HandshakeException(String message) { - super(message); - } + public HandshakeException(String message) { + super(message); + } - public HandshakeException(Exception e) { - super(e); - } + public HandshakeException(Exception e) { + super(e); + } - public HandshakeException(String message, Exception e) { - super(message, e); - } -}
\ No newline at end of file + public HandshakeException(String message, Exception e) { + super(message, e); + } +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/SessionRestoreException.java b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/SessionRestoreException.java index c780973..026f8c5 100644 --- a/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/SessionRestoreException.java +++ b/nearby/connections/ukey2/ukey2_jni/java/src/main/java/com/google/security/cryptauth/lib/securegcm/ukey2/SessionRestoreException.java @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.security.cryptauth.lib.securegcm; +package com.google.security.cryptauth.lib.securegcm.ukey2; -/** - * Represents an unrecoverable error that has occurred during the handshake procedure. - */ +/** Represents an unrecoverable error that has occurred during the handshake procedure. */ public class SessionRestoreException extends Exception { public SessionRestoreException(String message) { super(message); @@ -29,4 +27,4 @@ public class SessionRestoreException extends Exception { public SessionRestoreException(String message, Exception e) { super(message, e); } -}
\ No newline at end of file +} diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/TestUkey2Protocol.kt b/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/TestUkey2Protocol.kt deleted file mode 100644 index 79cbd15..0000000 --- a/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/TestUkey2Protocol.kt +++ /dev/null @@ -1,182 +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. - */ - -/* - * This Java source file was generated by the Gradle 'init' task. - */ -package com.google.security.cryptauth.lib.securegcm - -import java.nio.charset.StandardCharsets -import org.junit.jupiter.api.Assertions.assertArrayEquals -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows - -// Driver code -// Tests exception handling and the handshake routine, as well as encrypting/decrypting short message between the server and initiator contexts. -@Suppress("UNUSED_VARIABLE") -class TestUkey2Protocol { - @Test - fun testHandshake() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - assertFalse(initiatorContext.isHandshakeComplete) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - assertFalse(serverContext.isHandshakeComplete) - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - assertTrue(initiatorContext.isHandshakeComplete) - assertTrue(serverContext.isHandshakeComplete) - } - } - - @Test - fun testSendReceiveMessage() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - val connContext = initiatorContext.toConnectionContext() - val serverConnContext = serverContext.toConnectionContext() - val initialShareString = "Nearby sharing to server" - val encoded = connContext.encodeMessageToPeer( - initialShareString.toByteArray( - StandardCharsets.UTF_8 - ), null - ) - val response = - String(serverConnContext.decodeMessageFromPeer(encoded, null), StandardCharsets.UTF_8) - assertEquals(response, initialShareString) - } - } - - @Test - fun testSaveRestoreSession() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - val connContext = initiatorContext.toConnectionContext() - val serverConnContext = serverContext.toConnectionContext() - val initiatorSavedSession = connContext.saveSession() - val restored = D2DConnectionContextV1.fromSavedSession(initiatorSavedSession) - assertArrayEquals(connContext.sessionUnique, restored.sessionUnique) - val initialShareString = "Nearby sharing to server" - val encoded = serverConnContext.encodeMessageToPeer( - initialShareString.toByteArray( - StandardCharsets.UTF_8 - ), null - ) - val response = String(restored.decodeMessageFromPeer(encoded, null), StandardCharsets.UTF_8) - assertEquals(response, initialShareString) - } - } - - @Test - fun testSaveRestoreBadSession() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - val deriveInitiatorSavedSession = { - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - val connContext = initiatorContext.toConnectionContext() - val serverConnContext = serverContext.toConnectionContext() - connContext.saveSession() - } - } - assertThrows<SessionRestoreException> { - val unused = D2DConnectionContextV1.fromSavedSession(deriveInitiatorSavedSession().copyOfRange(0, 20)) - } - } - - @Test - fun tryReuseHandshakeContext() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - val connContext = initiatorContext.toConnectionContext() - val serverConnContext = serverContext.toConnectionContext() - } - assertThrows<BadHandleException> { - val unused = serverContext.nextHandshakeMessage - } - } - - @Test - fun testSendReceiveMessageWithAssociatedData() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - val associatedData = "Associated data.".toByteArray() - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - val connContext = initiatorContext.toConnectionContext() - val serverConnContext = serverContext.toConnectionContext() - val initialShareString = "Nearby sharing to server" - val encoded = connContext.encodeMessageToPeer( - initialShareString.toByteArray( - StandardCharsets.UTF_8 - ), associatedData - ) - val response = - String(serverConnContext.decodeMessageFromPeer(encoded, associatedData), StandardCharsets.UTF_8) - assertEquals(response, initialShareString) - } - } - - @Test - fun testVerificationString() { - val initiatorContext = - D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) - val serverContext = - D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) - assertDoesNotThrow { - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) - serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) - } - assert(serverContext.isHandshakeComplete) - assert(initiatorContext.isHandshakeComplete) - assertArrayEquals(serverContext.getVerificationString(32), initiatorContext.getVerificationString(32)) - } -}
\ No newline at end of file diff --git a/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/ukey2/TestUkey2Protocol.kt b/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/ukey2/TestUkey2Protocol.kt new file mode 100644 index 0000000..f770977 --- /dev/null +++ b/nearby/connections/ukey2/ukey2_jni/java/src/test/java/com/google/security/cryptauth/lib/securegcm/ukey2/TestUkey2Protocol.kt @@ -0,0 +1,167 @@ +/* + * 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. + */ + +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package com.google.security.cryptauth.lib.securegcm.ukey2 + +import java.nio.charset.StandardCharsets +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows + +// Driver code +// Tests exception handling and the handshake routine, as well as encrypting/decrypting short +// message between the server and initiator contexts. +@Suppress("UNUSED_VARIABLE") +class TestUkey2Protocol { + @Test + fun testHandshake() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + println("got initial context") + assertFalse(initiatorContext.isHandshakeComplete) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + assertFalse(serverContext.isHandshakeComplete) + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + assertTrue(initiatorContext.isHandshakeComplete) + assertTrue(serverContext.isHandshakeComplete) + } + } + + @Test + fun testSendReceiveMessage() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + val connContext = initiatorContext.toConnectionContext() + val serverConnContext = serverContext.toConnectionContext() + val initialShareString = "Nearby sharing to server" + val encoded = + connContext.encodeMessageToPeer( + initialShareString.toByteArray(StandardCharsets.UTF_8), null) + val response = + String(serverConnContext.decodeMessageFromPeer(encoded, null), StandardCharsets.UTF_8) + assertEquals(response, initialShareString) + } + } + + @Test + fun testSaveRestoreSession() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + val connContext = initiatorContext.toConnectionContext() + val serverConnContext = serverContext.toConnectionContext() + val initiatorSavedSession = connContext.saveSession() + val restored = D2DConnectionContextV1.fromSavedSession(initiatorSavedSession) + assertArrayEquals(connContext.sessionUnique, restored.sessionUnique) + val initialShareString = "Nearby sharing to server" + val encoded = + serverConnContext.encodeMessageToPeer( + initialShareString.toByteArray(StandardCharsets.UTF_8), null) + val response = String(restored.decodeMessageFromPeer(encoded, null), StandardCharsets.UTF_8) + assertEquals(response, initialShareString) + } + } + + @Test + fun testSaveRestoreBadSession() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + val deriveInitiatorSavedSession = { + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + val connContext = initiatorContext.toConnectionContext() + val serverConnContext = serverContext.toConnectionContext() + connContext.saveSession() + } + } + assertThrows<SessionRestoreException> { + val unused = + D2DConnectionContextV1.fromSavedSession(deriveInitiatorSavedSession().copyOfRange(0, 20)) + } + } + + @Test + fun tryReuseHandshakeContext() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + val connContext = initiatorContext.toConnectionContext() + val serverConnContext = serverContext.toConnectionContext() + } + assertThrows<BadHandleException> { + val unused = serverContext.nextHandshakeMessage + } + } + + @Test + fun testSendReceiveMessageWithAssociatedData() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + val associatedData = "Associated data.".toByteArray() + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + val connContext = initiatorContext.toConnectionContext() + val serverConnContext = serverContext.toConnectionContext() + val initialShareString = "Nearby sharing to server" + val encoded = + connContext.encodeMessageToPeer( + initialShareString.toByteArray(StandardCharsets.UTF_8), associatedData) + val response = + String( + serverConnContext.decodeMessageFromPeer(encoded, associatedData), + StandardCharsets.UTF_8) + assertEquals(response, initialShareString) + } + } + + @Test + fun testVerificationString() { + val initiatorContext = D2DHandshakeContext(D2DHandshakeContext.Role.INITIATOR) + val serverContext = D2DHandshakeContext(D2DHandshakeContext.Role.RESPONDER) + assertDoesNotThrow { + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + initiatorContext.parseHandshakeMessage(serverContext.nextHandshakeMessage) + serverContext.parseHandshakeMessage(initiatorContext.nextHandshakeMessage) + } + assert(serverContext.isHandshakeComplete) + assert(initiatorContext.isHandshakeComplete) + assertArrayEquals( + serverContext.getVerificationString(32), initiatorContext.getVerificationString(32)) + } +} diff --git a/nearby/connections/ukey2/ukey2_jni/src/lib.rs b/nearby/connections/ukey2/ukey2_jni/src/lib.rs index 0e11793..c500ddd 100644 --- a/nearby/connections/ukey2/ukey2_jni/src/lib.rs +++ b/nearby/connections/ukey2/ukey2_jni/src/lib.rs @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! JNI bindings for the ukey2 rust implementation + +#![allow(unsafe_code, clippy::expect_used)] +//TODO: remove this and fix instances of unwrap/panic +#![allow(clippy::unwrap_used, clippy::panic)] + use std::collections::HashMap; use jni::objects::JClass; @@ -34,13 +40,7 @@ use ukey2_connections::{ 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>; @@ -58,14 +58,28 @@ fn generate_handle() -> u64 { } pub(crate) fn insert_handshake_handle(item: D2DBox) -> u64 { - let handle = generate_handle(); - HANDLE_MAPPING.lock().insert(handle, item); + let mut handle = generate_handle(); + let mut map = HANDLE_MAPPING.lock(); + while map.contains_key(&handle) { + handle = generate_handle(); + } + + let result = map.insert(handle, item); + // result should always be None since we checked that handle map does not contain the key already + assert!(result.is_none()); handle } pub(crate) fn insert_conn_handle(item: ConnectionBox) -> u64 { - let handle = generate_handle(); - CONNECTION_HANDLE_MAPPING.lock().insert(handle, item); + let mut handle = generate_handle(); + let mut map = CONNECTION_HANDLE_MAPPING.lock(); + while map.contains_key(&handle) { + handle = generate_handle(); + } + + let result = map.insert(handle, item); + // result should always be None since we checked that handle map does not contain the key already + assert!(result.is_none()); handle } @@ -77,10 +91,11 @@ enum JniError { HandshakeError(HandshakeError), } -// D2DHandshakeContext +/// Tells the caller whether the handshake has completed or not. If the handshake is complete, +/// the caller may call `to_connection_context`to obtain a connection context. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_is_1handshake_1complete( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_is_1handshake_1complete( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jboolean { @@ -88,14 +103,15 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands if let Some(ctx) = HANDLE_MAPPING.lock().get(&(context_handle as u64)) { is_complete = ctx.is_handshake_complete(); } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); } is_complete as jboolean } +/// Creates a new handshake context #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_create_1context( +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_create_1context( _: JNIEnv, _: JClass, is_client: jboolean, @@ -113,9 +129,10 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands } } +/// Constructs the next message that should be sent in the handshake. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_get_1next_1handshake_1message( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1next_1handshake_1message( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray { @@ -123,7 +140,7 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands let next_message = if let Some(ctx) = HANDLE_MAPPING.lock().get(&(context_handle as u64)) { ctx.get_next_handshake_message() } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); None }; @@ -135,11 +152,12 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands } } -#[no_mangle] +/// Parses a handshake message and advances the internal state of the context. +// Safety: We know the message pointer is safe as it is coming directly from the JVM. #[allow(clippy::not_unsafe_ptr_arg_deref)] -/// Safety: We know the message pointer is safe as it is coming directly from the JVM. -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_parse_1handshake_1message( - mut env: JNIEnv, +#[no_mangle] +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_parse_1handshake_1message( + env: JNIEnv, _: JClass, context_handle: jlong, message: jbyteArray, @@ -148,14 +166,14 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands let result = if let Some(ctx) = HANDLE_MAPPING.lock().get_mut(&(context_handle as u64)) { ctx.handle_handshake_message(rust_buffer.as_slice()).map_err(JniError::HandleMessageError) } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); Err(JniError::BadHandle) }; if let Err(e) = result { if !env.exception_check().unwrap() { env.throw_new( - "com/google/security/cryptauth/lib/securegcm/HandshakeException", + "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException", match e { JniError::BadHandle => "Bad handle", JniError::DecodeError(_) => "Unable to decode message", @@ -168,9 +186,11 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands } } +/// Returns the `CompletedHandshake` using the results from this handshake context. May only +/// be called if `is_handshake_complete` returns true. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_get_1verification_1string( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_get_1verification_1string( + env: JNIEnv, _: JClass, context_handle: jlong, length: jint, @@ -181,14 +201,14 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands .map_err(|_| JniError::HandshakeError(HandshakeError::HandshakeNotComplete)) .map(|h| h.auth_string::<CryptoProvider>().derive_vec(length as usize).unwrap()) } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); Err(JniError::BadHandle) }; if let Err(e) = result { if !env.exception_check().unwrap() { env.throw_new( - "com/google/security/cryptauth/lib/securegcm/HandshakeException", + "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException", match e { JniError::BadHandle => "Bad handle", JniError::DecodeError(_) => "Unable to decode message", @@ -205,9 +225,11 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands } } +/// Creates a [`D2DConnectionContextV1`] using the results of the handshake. May only be called +/// if `is_handshake_complete` returns true. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHandshakeContext_to_1connection_1context( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DHandshakeContext_to_1connection_1context( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jlong { @@ -218,7 +240,7 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands }; if let Err(error) = conn_context { env.throw_new( - "com/google/security/cryptauth/lib/securegcm/HandshakeException", + "com/google/security/cryptauth/lib/securegcm/ukey2/HandshakeException", match error { JniError::BadHandle => "Bad context handle", JniError::HandshakeError(_) => "Handshake not complete", @@ -228,18 +250,19 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DHands .expect("failed to find error class"); return -1; } else { - HANDLE_MAPPING.lock().remove(&(context_handle as u64)); + let _ = HANDLE_MAPPING.lock().remove(&(context_handle as u64)); } insert_conn_handle(Box::new(conn_context.unwrap())) as jlong } -// D2DConnectionContextV1 -#[no_mangle] +/// Once initiator and responder have exchanged public keys, use this method to encrypt and +/// sign a payload. Both initiator and responder devices can use this message. +// Safety: We know the payload and associated_data pointers are safe as they are coming directly +// from the JVM. #[allow(clippy::not_unsafe_ptr_arg_deref)] -/// Safety: We know the payload and associated_data pointers are safe as they are coming directly -/// from the JVM. -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_encode_1message_1to_1peer( - mut env: JNIEnv, +#[no_mangle] +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_encode_1message_1to_1peer( + env: JNIEnv, _: JClass, context_handle: jlong, payload: jbyteArray, @@ -268,18 +291,21 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne if let Ok(ret_vec) = result { env.byte_array_from_slice(ret_vec.as_slice()).expect("unable to create jByteArray") } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); empty_array } } -#[no_mangle] +/// Once `InitiatorHello` and `ResponderHello` (and payload) are exchanged, use this method to +/// decrypt and verify a message received from the other device. Both initiator and responder +/// devices can use this message. +// Safety: We know the message and associated_data pointers are safe as they are coming directly +// from the JVM. #[allow(clippy::not_unsafe_ptr_arg_deref)] -/// Safety: We know the message and associated_data pointers are safe as they are coming directly -/// from the JVM. -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_decode_1message_1from_1peer( - mut env: JNIEnv, +#[no_mangle] +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_decode_1message_1from_1peer( + env: JNIEnv, _: JClass, context_handle: jlong, message: jbyteArray, @@ -308,7 +334,7 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne env.byte_array_from_slice(message.as_slice()).expect("unable to create jByteArray") } else { env.throw_new( - "com/google/security/cryptauth/lib/securegcm/CryptoException", + "com/google/security/cryptauth/lib/securegcm/ukey2/CryptoException", match result.unwrap_err() { JniError::BadHandle => "Bad context handle", JniError::DecodeError(e) => match e { @@ -324,39 +350,43 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne } } +/// Returns the last sequence number used to encode a message. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1sequence_1number_1for_1encoding( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1encoding( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jint { if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) { - ctx.get_sequence_number_for_encoding() as jint + ctx.get_sequence_number_for_encoding() } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); - -1 as jint + -1 } } +/// Returns the last sequence number used to decode a message. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1sequence_1number_1for_1decoding( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1sequence_1number_1for_1decoding( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jint { if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) { - ctx.get_sequence_number_for_decoding() as jint + ctx.get_sequence_number_for_decoding() } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); - -1 as jint + -1 } } +/// Creates a saved session that can later be used for resumption. The session data may be +/// persisted, but it must be stored in a secure location. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_save_1session( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_save_1session( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray { @@ -364,17 +394,18 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne if let Some(ctx) = CONNECTION_HANDLE_MAPPING.lock().get(&(context_handle as u64)) { env.byte_array_from_slice(ctx.save_session().as_slice()).expect("unable to save session") } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); empty_array } } +/// Creates a connection context from a saved session. +// Safety: We know the session_info pointer is safe because it is coming directly from the JVM. #[no_mangle] #[allow(clippy::not_unsafe_ptr_arg_deref)] -/// Safety: We know the session_info pointer is safe because it is coming directly from the JVM. -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_from_1saved_1session( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_from_1saved_1session( + env: JNIEnv, _: JClass, session_info: jbyteArray, ) -> jlong { @@ -385,7 +416,7 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne D2DConnectionContextV1::from_saved_session::<CryptoProvider>(session_info_rust.as_slice()); if ctx.is_err() { env.throw_new( - "com/google/security/cryptauth/lib/securegcm/SessionRestoreException", + "com/google/security/cryptauth/lib/securegcm/ukey2/SessionRestoreException", match ctx.err().unwrap() { DeserializeError::BadDataLength => "DeserializeError: bad session_info length", DeserializeError::BadProtocolVersion => "DeserializeError: bad protocol version", @@ -400,9 +431,12 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne insert_conn_handle(conn_context_final) as jlong } +/// Returns a cryptographic digest (SHA256) of the session keys prepended by the SHA256 hash +/// of the ASCII string "D2D". Since the server and client share the same session keys, the +/// resulting session unique is also the same. #[no_mangle] -pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConnectionContextV1_get_1session_1unique( - mut env: JNIEnv, +pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_ukey2_D2DConnectionContextV1_get_1session_1unique( + env: JNIEnv, _: JClass, context_handle: jlong, ) -> jbyteArray { @@ -411,7 +445,7 @@ pub extern "system" fn Java_com_google_security_cryptauth_lib_securegcm_D2DConne env.byte_array_from_slice(ctx.get_session_unique::<CryptoProvider>().as_slice()) .expect("unable to get unique session id") } else { - env.throw_new("com/google/security/cryptauth/lib/securegcm/BadHandleException", "") + env.throw_new("com/google/security/cryptauth/lib/securegcm/ukey2/BadHandleException", "") .expect("failed to find error class"); empty_array } diff --git a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/device_to_device_messages.rs b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/device_to_device_messages.rs index 3c2da35..71c247b 100644 --- a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/device_to_device_messages.rs +++ b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/device_to_device_messages.rs @@ -13,7 +13,7 @@ // limitations under the License. // This file is generated by rust-protobuf 3.2.0. Do not edit -// .proto file is parsed by protoc 3.19.1 +// .proto file is parsed by protoc 3.21.12 // @generated // https://github.com/rust-lang/rust-clippy/issues/702 diff --git a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securegcm.rs b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securegcm.rs index 3231440..70a927d 100644 --- a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securegcm.rs +++ b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securegcm.rs @@ -13,7 +13,7 @@ // limitations under the License. // This file is generated by rust-protobuf 3.2.0. Do not edit -// .proto file is parsed by protoc 3.19.1 +// .proto file is parsed by protoc 3.21.12 // @generated // https://github.com/rust-lang/rust-clippy/issues/702 diff --git a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securemessage.rs b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securemessage.rs index 161e0be..d83d137 100644 --- a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securemessage.rs +++ b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/securemessage.rs @@ -13,7 +13,7 @@ // limitations under the License. // This file is generated by rust-protobuf 3.2.0. Do not edit -// .proto file is parsed by protoc 3.19.1 +// .proto file is parsed by protoc 3.21.12 // @generated // https://github.com/rust-lang/rust-clippy/issues/702 diff --git a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/ukey.rs b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/ukey.rs index 5370207..9be6b3d 100644 --- a/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/ukey.rs +++ b/nearby/connections/ukey2/ukey2_proto/src/ukey2_all_proto/ukey.rs @@ -13,7 +13,7 @@ // limitations under the License. // This file is generated by rust-protobuf 3.2.0. Do not edit -// .proto file is parsed by protoc 3.19.1 +// .proto file is parsed by protoc 3.21.12 // @generated // https://github.com/rust-lang/rust-clippy/issues/702 diff --git a/nearby/connections/ukey2/ukey2_shell/Cargo.toml b/nearby/connections/ukey2/ukey2_shell/Cargo.toml index d1f48e4..2ac12fa 100644 --- a/nearby/connections/ukey2/ukey2_shell/Cargo.toml +++ b/nearby/connections/ukey2/ukey2_shell/Cargo.toml @@ -4,11 +4,11 @@ version.workspace = true edition.workspace = true publish.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints] +workspace = true [dependencies] crypto_provider_rustcrypto = { workspace = true, features = [ "alloc" ] } ukey2_rs = { version = "0.1.0", path = "../ukey2" } ukey2_connections = { version = "0.1.0", path = "../ukey2_connections" } - -clap = { version = "4.0.17", default-features = false, features = ["std", "derive"] } +clap = { workspace = true, features = ["std", "derive"] } diff --git a/nearby/connections/ukey2/ukey2_shell/src/main.rs b/nearby/connections/ukey2/ukey2_shell/src/main.rs index e4bcc0d..3eca7b0 100644 --- a/nearby/connections/ukey2/ukey2_shell/src/main.rs +++ b/nearby/connections/ukey2/ukey2_shell/src/main.rs @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Provides a sample ukey2 shell app which can be run from the command line + +#![allow(clippy::expect_used)] +//TODO: remove this and fix instances of unwrap +#![allow(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)] + use std::io::{Read, Write}; use std::process::exit; @@ -191,9 +197,9 @@ fn main() { let args = Ukey2Cli::parse(); let shell = Ukey2Shell::new(args.verification_string_length); if args.mode == MODE_INITIATOR { - shell.run_as_initiator(); + let _ = shell.run_as_initiator(); } else if args.mode == MODE_RESPONDER { - shell.run_as_responder(); + let _ = shell.run_as_responder(); } else { exit(1); } diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml index 60f8626..1a588bb 100644 --- a/nearby/crypto/crypto_provider/Cargo.toml +++ b/nearby/crypto/crypto_provider/Cargo.toml @@ -4,11 +4,17 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +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 +23,7 @@ default = ["alloc"] std = [] alloc = [] test_vectors = [] +raw_private_key_permit = [] [[bench]] name = "hmac_bench" diff --git a/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs b/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs index f401327..70c65cf 100644 --- a/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs +++ b/nearby/crypto/crypto_provider/benches/constant_time_eq_bench.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs, clippy::indexing_slicing)] + use criterion::{ criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, Criterion, }; @@ -33,7 +35,7 @@ fn constant_time_eq_equals(c: &mut Criterion) { fn add_benches<C: CryptoProvider>(group: &mut BenchmarkGroup<WallTime>, rng: &mut ThreadRng) { const TEST_LEN: usize = 1000; for i in (0..=TEST_LEN).step_by(100) { - group.bench_function( + let _ = group.bench_function( &format!( "constant_time_eq impl {} differ by {:04} bytes", std::any::type_name::<C>(), diff --git a/nearby/crypto/crypto_provider/benches/hkdf_bench.rs b/nearby/crypto/crypto_provider/benches/hkdf_bench.rs index 4c11d0d..e73f8cf 100644 --- a/nearby/crypto/crypto_provider/benches/hkdf_bench.rs +++ b/nearby/crypto/crypto_provider/benches/hkdf_bench.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs, clippy::expect_used)] + use criterion::{criterion_group, criterion_main, Criterion}; use hex_literal::hex; @@ -26,15 +28,16 @@ fn hkdf_sha256_operations<C: CryptoProvider>(c: &mut Criterion) { let salt = hex!("000102030405060708090a0b0c"); let info = hex!("f0f1f2f3f4f5f6f7f8f9"); - c.bench_function(&format!("bench hkdf with salt {}", std::any::type_name::<C>()), |b| { - b.iter(|| { - let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm); - let mut okm = [0u8; 42]; - hk.expand(&info, &mut okm).expect("42 is a valid length for Sha256 to output"); + let _ = + c.bench_function(&format!("bench hkdf with salt {}", std::any::type_name::<C>()), |b| { + b.iter(|| { + let hk = C::HkdfSha256::new(Some(&salt[..]), &ikm); + let mut okm = [0u8; 42]; + hk.expand(&info, &mut okm).expect("42 is a valid length for Sha256 to output"); + }); }); - }); - c.bench_function(&format!("bench hkdf no salt {}", std::any::type_name::<C>()), |b| { + let _ = c.bench_function(&format!("bench hkdf no salt {}", std::any::type_name::<C>()), |b| { b.iter(|| { let hk = C::HkdfSha256::new(None, &ikm); let mut okm = [0u8; 42]; diff --git a/nearby/crypto/crypto_provider/benches/hmac_bench.rs b/nearby/crypto/crypto_provider/benches/hmac_bench.rs index c7ccf1a..9bcfde0 100644 --- a/nearby/crypto/crypto_provider/benches/hmac_bench.rs +++ b/nearby/crypto/crypto_provider/benches/hmac_bench.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs)] + use criterion::{criterion_group, criterion_main, Criterion}; use crypto_provider::hmac::Hmac; @@ -26,7 +28,7 @@ fn hmac_sha256_operations<C: CryptoProvider>(c: &mut Criterion) { let key: [u8; 32] = rand_ext::random_bytes::<32, C>(&mut rng); let update_data: [u8; 16] = rand_ext::random_bytes::<16, C>(&mut rng); - c.bench_function("bench for hmac sha256 single update", |b| { + let _ = c.bench_function("bench for hmac sha256 single update", |b| { b.iter(|| { let mut hmac = C::HmacSha256::new_from_key(key); hmac.update(&update_data); @@ -40,7 +42,7 @@ fn hmac_sha512_operations<C: CryptoProvider>(c: &mut Criterion) { let key: [u8; 64] = rand_ext::random_bytes::<64, C>(&mut rng); let update_data: [u8; 16] = random_bytes::<16, C>(&mut rng); - c.bench_function("bench for hmac sha512 single update", |b| { + let _ = c.bench_function("bench for hmac sha512 single update", |b| { b.iter(|| { let mut hmac = C::HmacSha512::new_from_key(key); hmac.update(&update_data); 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/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..82a857b 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,54 @@ pub type AesCbcIv = [u8; 16]; /// 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`). + #[allow(clippy::expect_used)] + 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 +85,65 @@ pub enum DecryptionError { /// 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() { + let _ = 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 @@ pub trait AesCtr { /// 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 @@ use core::{array, fmt}; 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 @@ pub type RawPublicKey = [u8; PUBLIC_KEY_LENGTH]; /// 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 @@ pub trait KeyPair: Sized { 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 @@ pub trait EphemeralSecret<C: Curve>: Send { /// 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 @@ pub trait EphemeralSecret<C: Curve>: Send { /// 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 @@ pub trait PublicKey<E: Curve>: Sized + PartialEq + Debug { /// 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..214175d 100644 --- a/nearby/crypto/crypto_provider/src/lib.rs +++ b/nearby/crypto/crypto_provider/src/lib.rs @@ -11,12 +11,11 @@ // 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.' -#![no_std] -#![forbid(unsafe_code)] -#![deny(missing_docs)] //! Crypto abstraction trait only crate, which provides traits for cryptographic primitives +#![no_std] + use core::fmt::Debug; use crate::aes::{Aes128Key, Aes256Key}; @@ -48,6 +47,8 @@ pub mod aead; /// 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 +81,13 @@ pub trait CryptoProvider: Clone + Debug + PartialEq + Eq + Send { /// 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 @@ use core::fmt::Debug; 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 @@ pub trait P256PublicKey: Sized + PartialEq + Debug { /// ("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 @@ pub trait P256PublicKey: Sized + PartialEq + Debug { 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..11ec2e3 100644 --- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock +++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock @@ -3,14 +3,30 @@ version = 3 [[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[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 +37,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_boringssl" @@ -28,18 +47,10 @@ version = "0.1.0" 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 +67,9 @@ dependencies = [ [[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", @@ -66,6 +77,12 @@ dependencies = [ ] [[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -79,24 +96,27 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[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 = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "ppv-lite86" @@ -106,18 +126,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -172,10 +192,45 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + +[[package]] name = "rstest" -version = "0.17.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" dependencies = [ "rstest_macros", "rustc_version", @@ -183,28 +238,31 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.17.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" dependencies = [ "cfg-if", + "glob", "proc-macro2", "quote", + "regex", + "relative-path", "rustc_version", - "syn 1.0.109", + "syn", "unicode-ident", ] [[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", ] [[package]] @@ -218,41 +276,41 @@ dependencies = [ [[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.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.162" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" dependencies = [ "itoa", "ryu", @@ -261,20 +319,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.15" +version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ "proc-macro2", "quote", @@ -290,10 +337,16 @@ dependencies = [ ] [[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" + +[[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "wasi" @@ -303,9 +356,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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 @@ publish = false [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 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 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 @@ use crypto_provider::aes::{ 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 @@ impl crypto_provider::ed25519::KeyPair for KeyPair { 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 @@ pub mod hmac; /// 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 @@ impl CryptoRng for BoringSslRng { 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/Cargo.toml b/nearby/crypto/crypto_provider_default/Cargo.toml index 2a155bf..4c906e4 100644 --- a/nearby/crypto/crypto_provider_default/Cargo.toml +++ b/nearby/crypto/crypto_provider_default/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] crypto_provider.workspace = true crypto_provider_rustcrypto = {workspace = true, optional = true} diff --git a/nearby/crypto/crypto_provider_default/src/lib.rs b/nearby/crypto/crypto_provider_default/src/lib.rs index b880ba8..52da9d4 100644 --- a/nearby/crypto/crypto_provider_default/src/lib.rs +++ b/nearby/crypto/crypto_provider_default/src/lib.rs @@ -16,8 +16,6 @@ //! feature flag. #![no_std] -#![forbid(unsafe_code)] -#![deny(missing_docs)] cfg_if::cfg_if! { if #[cfg(feature = "rustcrypto")] { diff --git a/nearby/crypto/crypto_provider_openssl/Cargo.toml b/nearby/crypto/crypto_provider_openssl/Cargo.toml index 84d4380..19ef3e4 100644 --- a/nearby/crypto/crypto_provider_openssl/Cargo.toml +++ b/nearby/crypto/crypto_provider_openssl/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] crypto_provider = { workspace = true, features = ["alloc", "std"] } crypto_provider_stubs.workspace = true diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs index f5d5b00..857be74 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 @@ -68,7 +71,7 @@ impl AesEncryptCipher for Aes128Cipher { let mut crypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Encrypt, self.0.as_slice(), None).unwrap(); crypter.pad(false); - crypter.update(block, &mut output).unwrap(); + let _ = crypter.update(block, &mut output).unwrap(); block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]); } } @@ -80,7 +83,7 @@ impl AesDecryptCipher for Aes128Cipher { let mut crypter = Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, self.0.as_slice(), None).unwrap(); crypter.pad(false); - crypter.update(block, &mut output).unwrap(); + let _ = crypter.update(block, &mut output).unwrap(); block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]); } } @@ -103,7 +106,7 @@ impl AesEncryptCipher for Aes256Cipher { let mut crypter = Crypter::new(Cipher::aes_256_ecb(), Mode::Encrypt, self.0.as_slice(), None).unwrap(); crypter.pad(false); - crypter.update(block, &mut output).unwrap(); + let _ = crypter.update(block, &mut output).unwrap(); block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]); } } @@ -115,7 +118,7 @@ impl AesDecryptCipher for Aes256Cipher { let mut crypter = Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, self.0.as_slice(), None).unwrap(); crypter.pad(false); - crypter.update(block, &mut output).unwrap(); + let _ = crypter.update(block, &mut output).unwrap(); block.copy_from_slice(&output[..crypto_provider::aes::BLOCK_SIZE]); } } @@ -126,7 +129,23 @@ pub struct OpenSslAesCbcPkcs7; 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 @@ impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for OpenSslAesCbcPkcs7 { ) .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 @@ impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr128 { 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 @@ impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr256 { 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 @@ impl crypto_provider::ed25519::KeyPair for KeyPair { 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 @@ impl crypto_provider::ed25519::KeyPair for KeyPair { 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/hkdf_openssl.rs b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs index d429a4d..b726f5f 100644 --- a/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs +++ b/nearby/crypto/crypto_provider_openssl/src/hkdf_openssl.rs @@ -42,7 +42,9 @@ impl<H: OpenSslHash> crypto_provider::hkdf::Hkdf for Hkdf<H> { let md = H::get_md(); ctx.derive_init().expect("hkdf derive init should not fail"); ctx.set_hkdf_md(md).expect("hkdf set md should not fail"); - self.salt.as_ref().map(|salt| ctx.set_hkdf_salt(salt.as_slice())); + let _ = self.salt.as_ref().map(|salt| { + ctx.set_hkdf_salt(salt.as_slice()).expect("setting the salt is infallible") + }); ctx.set_hkdf_key(self.ikm.as_slice()).expect("should be able to set key"); ctx.add_hkdf_info(&info_components.concat()).expect("should be able to add info"); ctx.derive(Some(okm)).map_err(|_| InvalidLength).map(|_| ()) diff --git a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs index 9b86726..5272995 100644 --- a/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs +++ b/nearby/crypto/crypto_provider_openssl/src/hmac_boringssl.rs @@ -50,7 +50,7 @@ impl<const N: usize, H: Hash<N>> crypto_provider::hmac::Hmac<N> for Hmac<H> { fn finalize(mut self) -> [u8; N] { let mut buf = [0_u8; N]; - self.ctx.finalize(&mut buf).expect("wrong length"); + let _ = self.ctx.finalize(&mut buf).expect("wrong length"); buf } diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs index d8157d2..04b4e4c 100644 --- a/nearby/crypto/crypto_provider_openssl/src/lib.rs +++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs @@ -12,10 +12,13 @@ // 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. -#![deny(missing_docs, clippy::indexing_slicing, clippy::panic)] //! Crate which provides impls for CryptoProvider backed by openssl +// This crate treats allocation errors as handleable, which leads to unwraps everywhere, so they +// have to be allowed here. This will be fixed when we can migrate over to the new boringssl bindings +#![allow(clippy::expect_used, clippy::unwrap_used)] + use cfg_if::cfg_if; pub use openssl; use openssl::hash::MessageDigest; @@ -82,6 +85,8 @@ impl crypto_provider::CryptoProvider for Openssl { 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 +118,7 @@ mod tests { 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 @@ impl crypto_provider::p256::P256PublicKey for P256PublicKey { 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 @@ impl EphemeralSecret<P256> for P256EphemeralSecret { 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 @@ impl EphemeralSecret<P256> for P256EphemeralSecret { 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 @@ mod tests { 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/sha2.rs b/nearby/crypto/crypto_provider_openssl/src/sha2.rs index 92e663e..68fee79 100644 --- a/nearby/crypto/crypto_provider_openssl/src/sha2.rs +++ b/nearby/crypto/crypto_provider_openssl/src/sha2.rs @@ -38,7 +38,8 @@ impl crypto_provider::sha2::Sha256 for OpenSslSha256 { mdctx.digest_init(Self::get_md()).unwrap(); mdctx.digest_update(input).unwrap(); let mut buf = [0_u8; 32]; - mdctx.digest_final(&mut buf).unwrap(); + let size = mdctx.digest_final(&mut buf).unwrap(); + debug_assert_eq!(size, 32); buf } } @@ -63,7 +64,8 @@ impl crypto_provider::sha2::Sha512 for OpenSslSha512 { mdctx.digest_init(Self::get_md()).unwrap(); mdctx.digest_update(input).unwrap(); let mut buf = [0_u8; 64]; - mdctx.digest_final(&mut buf).unwrap(); + let size = mdctx.digest_final(&mut buf).unwrap(); + debug_assert_eq!(size, 64); buf } } 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 PartialEq for X25519PublicKey { 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 @@ impl EphemeralSecret<X25519> for X25519PrivateKey { 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..14e8ad9 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml +++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml @@ -4,17 +4,27 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [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 +41,12 @@ crypto_provider_rustcrypto = { path = ".", features = ["std"] } [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}; - -pub struct AesGcmSiv128(Aes128GcmSiv); - -impl AesGcmSiv for AesGcmSiv128 {} - -impl Aead for AesGcmSiv128 { - const TAG_SIZE: usize = 16; - type Nonce = [u8; 12]; - type Key = Aes128Key; - - 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) - } +// 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 _; + +// CryptoProvider traits and types +use crypto_provider::aead::{Aead, AeadError, AeadInit}; + +pub struct AesGcmSiv<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>( + aes_gcm_siv::AesGcmSiv<A>, +); + +impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcmSiv + for AesGcmSiv<A> +{ +} - 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]; + + #[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 new(key: &Self::Key) -> Self { - Self(Aes256GcmSiv::new(key.as_slice().into())) + 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 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) + #[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(&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) + 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 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 @@ impl crypto_provider::aes::ctr::AesCtr for AesCtr128 { } } - 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 @@ impl crypto_provider::aes::ctr::AesCtr for AesCtr256 { } } - 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 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 @@ use crypto_provider::aes::{ }; /// 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 @@ impl crypto_provider::ed25519::KeyPair for KeyPair { 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 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::typenum::{IsLess, Le, NonZero}; 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 @@ where + 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 @@ where 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 @@ where 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 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::digest::{HashMarker, OutputSizeUser}; 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 @@ where + 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 @@ impl crypto_provider::hmac::Hmac<64> for Hmac<sha2::Sha512> { #[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..2985dec 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs +++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs @@ -11,20 +11,14 @@ // 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. + #![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] //! Crate which provides impls for CryptoProvider backed by RustCrypto crates use core::{fmt::Debug, marker::PhantomData}; +pub use aes; use cfg_if::cfg_if; pub use hkdf; pub use hmac; @@ -35,17 +29,17 @@ use subtle::ConstantTimeEq; /// 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 +48,11 @@ cfg_if! { /// 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 +72,24 @@ impl<R: CryptoRng + SeedableRng + RngCore> RustCryptoImpl<R> { 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 +121,7 @@ mod testing; 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 @@ impl crypto_provider::p256::P256PublicKey for P256PublicKey { 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 @@ impl<R: CryptoRng + SeedableRng + RngCore + Send> EphemeralSecret<P256> for P256 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 @@ impl<R: CryptoRng + SeedableRng + RngCore + Send> EphemeralSecret<P256> for P256 } } - 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 @@ mod tests { 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 index 977eb83..977eb83 100644 --- a/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs +++ b/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 @@ impl<R: CryptoRng + RngCore + SeedableRng + Send> EphemeralSecret<X25519> 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 @@ impl<R: CryptoRng + RngCore + SeedableRng + Send> EphemeralSecret<X25519> } } - 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 @@ pub struct X25519PublicKey(x25519_dalek::PublicKey); 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 @@ edition.workspace = true 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 @@ impl crypto_provider::CryptoProvider for CryptoProviderStubs { 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 @@ impl AesCbcPkcs7Padded for AesCbcPkcs7PaddedStubs { 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 @@ impl AesCbcPkcs7Padded for AesCbcPkcs7PaddedStubs { ) -> 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 @@ impl EphemeralSecret<X25519> for EphSecretStubs { 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 @@ impl EphemeralSecret<P256> for EphSecretStubs { 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 @@ pub struct EcdhPubKey; 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 @@ impl P256PublicKey for PublicKeyStubs { unimplemented!() } - fn to_sec1_bytes(&self) -> Vec<u8> { + fn to_sec1_bytes(&self, _point_compression: PointCompression) -> ArrayVec<[u8; 65]> { unimplemented!() } @@ -294,6 +317,12 @@ impl crypto_provider::sha2::Sha512 for Sha2Stubs { 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 @@ impl AesCtr for Aes128Stubs { 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 AesCtr for Aes128Stubs { 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 @@ impl AesCtr for Aes256Stubs { 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 AesCtr for Aes256Stubs { 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 @@ impl KeyPair for KeyPairStubs { 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 @@ edition.workspace = true 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 @@ rand.workspace = true 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..49c9513 100644 --- a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock +++ b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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" @@ -47,9 +61,9 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "arbitrary" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] @@ -62,9 +76,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -86,9 +100,9 @@ dependencies = [ [[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" @@ -101,11 +115,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -126,24 +141,24 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", @@ -165,6 +180,9 @@ dependencies = [ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_default" @@ -193,6 +211,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -239,9 +258,9 @@ dependencies = [ [[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", @@ -255,20 +274,20 @@ dependencies = [ [[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.26", + "syn 2.0.48", ] [[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", @@ -276,13 +295,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.48", ] [[package]] @@ -298,30 +317,31 @@ dependencies = [ [[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 = [ "signature", ] [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "sha2", + "subtle", ] [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -348,9 +368,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "foreign-types" @@ -380,9 +400,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -390,6 +410,16 @@ dependencies = [ ] [[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" @@ -408,9 +438,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -436,24 +466,24 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libfuzzer-sys" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", @@ -462,9 +492,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -474,9 +504,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags", "cfg-if", @@ -495,14 +525,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.48", ] [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -512,9 +542,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4361ddfdd108bddc19367ba805a3a43773d8bc3e407ac30e6c364cd264dd52e" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" dependencies = [ "aliasable", "ouroboros_macro", @@ -523,15 +553,15 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b81e113ed913910f05ef45c9344f67588fe6395f92d906eedf9ee5d54279922" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.48", ] [[package]] @@ -546,15 +576,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "platforms" -version = "3.0.2" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "polyval" @@ -576,9 +606,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primeorder" -version = "0.13.2" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] @@ -609,18 +639,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.31" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -677,15 +707,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[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", @@ -694,9 +724,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" [[package]] name = "static_assertions" @@ -722,9 +752,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -732,16 +762,22 @@ dependencies = [ ] [[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" +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.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "universal-hash" @@ -773,9 +809,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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", @@ -783,6 +819,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 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 hex_literal::hex; 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 @@ pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::Phan 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_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<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) { +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 @@ pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::Phan 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 @@ pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::Phan #[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 @@ fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>( #[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 @@ pub fn aes_256_cbc_test_encrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { 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 @@ pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { 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 @@ pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) { #[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 @@ pub fn aes_128_ctr_test_encrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::Pha 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 @@ pub fn aes_128_ctr_test_decrypt<A: AesCtr<Key = Aes128Key>>(_marker: marker::Pha 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 @@ pub fn aes_256_ctr_test_encrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::Pha 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 @@ pub fn aes_256_ctr_test_decrypt<A: AesCtr<Key = Aes256Key>>(_marker: marker::Pha 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 @@ extern crate std; 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 @@ use wycheproof::TestResult; // 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 @@ where .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 @@ where 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 @@ where } } +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 @@ where { 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 @@ pub mod x25519; /// 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 @@ pub fn to_bytes_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { 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 @@ pub fn to_bytes_random_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { 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 @@ pub fn public_key_to_affine_coordinates_zero_top_byte_test<E: EcdhProviderForP25 /// 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 @@ pub fn wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { }; 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 @@ pub fn wycheproof_p256_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) { #[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 index 0dcd164..0dcd164 100644 --- 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 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 @@ where 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 @@ pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData< 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 @@ pub fn x25519_to_bytes_random_test<E: EcdhProviderForX25519Test>(_: PhantomData< /// 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 @@ pub fn wycheproof_x25519_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) { 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/crypto/rand_core_05_adapter/Cargo.toml b/nearby/crypto/rand_core_05_adapter/Cargo.toml index 0a8fc47..1a02526 100644 --- a/nearby/crypto/rand_core_05_adapter/Cargo.toml +++ b/nearby/crypto/rand_core_05_adapter/Cargo.toml @@ -4,9 +4,10 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] rand.workspace = true # an older rand_core used by ed25519-dalek so we can adapt newer rand to it rand_core05 = { package = "rand_core", version = "0.5.1" } - -[dev-dependencies] diff --git a/nearby/crypto/rand_core_05_adapter/src/lib.rs b/nearby/crypto/rand_core_05_adapter/src/lib.rs index 6484983..9de0020 100644 --- a/nearby/crypto/rand_core_05_adapter/src/lib.rs +++ b/nearby/crypto/rand_core_05_adapter/src/lib.rs @@ -13,15 +13,8 @@ // limitations under the License. //! Adapter for using rand_core 0.5 RNGs with code that expects rand_core 0.5 RNGs. + #![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] /// A trivial adapter to expose rand 1.0/rand_core 0.6 rngs to ed25519-dalek's rand_core 0.5 types, /// which we import under a separate name so there's no clash. diff --git a/nearby/presence/CMakeLists.txt b/nearby/presence/CMakeLists.txt index 15143a1..9670c34 100644 --- a/nearby/presence/CMakeLists.txt +++ b/nearby/presence/CMakeLists.txt @@ -17,7 +17,7 @@ cmake_minimum_required(VERSION 3.14) project(NearbyProtocol) # required for designated initializers on MSVC -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) # root directory of repo set(BETO_CORE_ROOT ${CMAKE_SOURCE_DIR}/../..) @@ -32,15 +32,6 @@ if (UNIX) 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 -MD) -endif () - find_package(OpenSSL REQUIRED) if (OPENSSL_FOUND) message(STATUS "OpenSSL Found: ${OPENSSL_VERSION}") @@ -48,6 +39,10 @@ if (OPENSSL_FOUND) 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 @@ if (ENABLE_TESTS) 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 @@ if (ENABLE_TESTS) ) 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/Cargo.toml b/nearby/presence/array_ref/Cargo.toml index f912c41..b74168b 100644 --- a/nearby/presence/array_ref/Cargo.toml +++ b/nearby/presence/array_ref/Cargo.toml @@ -4,8 +4,10 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] -[dependencies] 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/array_ref/src/lib.rs b/nearby/presence/array_ref/src/lib.rs index 3ff84fc..769d3ef 100644 --- a/nearby/presence/array_ref/src/lib.rs +++ b/nearby/presence/array_ref/src/lib.rs @@ -12,8 +12,6 @@ // 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. -#![forbid(unsafe_code)] -#![deny(missing_docs)] //! Crate exposing macros to take array references of slices diff --git a/nearby/presence/array_view/Cargo.toml b/nearby/presence/array_view/Cargo.toml index 48693c4..8ac53d2 100644 --- a/nearby/presence/array_view/Cargo.toml +++ b/nearby/presence/array_view/Cargo.toml @@ -4,11 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] - -[dependencies] - -[dev-dependencies] - diff --git a/nearby/presence/array_view/src/lib.rs b/nearby/presence/array_view/src/lib.rs index c57e959..e2ea8d4 100644 --- a/nearby/presence/array_view/src/lib.rs +++ b/nearby/presence/array_view/src/lib.rs @@ -14,14 +14,6 @@ //! A no_std friendly array wrapper to expose a variable length prefix of the array. #![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] #[cfg(feature = "std")] extern crate std; 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..337dd0d 100644 --- a/nearby/presence/ldt/Cargo.toml +++ b/nearby/presence/ldt/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] @@ -13,7 +16,7 @@ crypto_provider.workspace = true 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 @@ -21,7 +24,7 @@ xts_aes.workspace = true rand.workspace = true rand_pcg.workspace = true base64.workspace = true -serde_json = {workspace = true, features = ["std"]} +serde_json = { workspace = true, features = ["std"] } anyhow.workspace = true hex.workspace = true diff --git a/nearby/presence/ldt/benches/ldt_scan.rs b/nearby/presence/ldt/benches/ldt_scan.rs index dac1374..b1711ad 100644 --- a/nearby/presence/ldt/benches/ldt_scan.rs +++ b/nearby/presence/ldt/benches/ldt_scan.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs, unused_results, clippy::indexing_slicing, clippy::unwrap_used)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_rustcrypto::RustCrypto; diff --git a/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs b/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs index 517e7ec..7e7c943 100644 --- a/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs +++ b/nearby/presence/ldt/examples/gen_ldt_xor_pad_test_vectors.rs @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Generates LDT test vectors which can be used to verify implementations + +#![allow(clippy::unwrap_used)] + use crypto_provider::aes::BLOCK_SIZE; use crypto_provider::{aes, CryptoProvider, CryptoRng}; use crypto_provider_rustcrypto::RustCrypto; diff --git a/nearby/presence/ldt/examples/ldt_benchmark.rs b/nearby/presence/ldt/examples/ldt_benchmark.rs index df87c28..101e17b 100644 --- a/nearby/presence/ldt/examples/ldt_benchmark.rs +++ b/nearby/presence/ldt/examples/ldt_benchmark.rs @@ -14,6 +14,8 @@ //! A manual benchmark for more interactive parameter-twiddling. +#![allow(clippy::unwrap_used, clippy::indexing_slicing)] + use clap::Parser as _; use crypto_provider_rustcrypto::RustCrypto; use ldt::{LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Swap, XorPadder}; diff --git a/nearby/presence/ldt/examples/ldt_prp.rs b/nearby/presence/ldt/examples/ldt_prp.rs index f8e055c..7734952 100644 --- a/nearby/presence/ldt/examples/ldt_prp.rs +++ b/nearby/presence/ldt/examples/ldt_prp.rs @@ -20,6 +20,9 @@ //! //! The output shows how many times a change to the first n bytes wasn't detected, as well as a //! histogram of how many bits were flipped in the entire plaintext. + +#![allow(clippy::unwrap_used, clippy::indexing_slicing)] + use clap::{self, Parser as _}; use crypto_provider::aes::BLOCK_SIZE; use crypto_provider::{CryptoProvider, CryptoRng}; diff --git a/nearby/presence/ldt/fuzz/Cargo.lock b/nearby/presence/ldt/fuzz/Cargo.lock index 539197a..7262f74 100644 --- a/nearby/presence/ldt/fuzz/Cargo.lock +++ b/nearby/presence/ldt/fuzz/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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" @@ -158,7 +172,7 @@ dependencies = [ name = "crypto_provider" version = "0.1.0" dependencies = [ - "bytes", + "tinyvec", ] [[package]] @@ -167,6 +181,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -196,9 +211,9 @@ dependencies = [ [[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", @@ -264,14 +279,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "sha2", + "subtle", ] [[package]] @@ -332,6 +348,16 @@ dependencies = [ ] [[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" @@ -553,9 +579,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[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", @@ -586,6 +612,12 @@ dependencies = [ ] [[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" @@ -621,9 +653,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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..8596ad2 100644 --- a/nearby/presence/ldt/src/lib.rs +++ b/nearby/presence/ldt/src/lib.rs @@ -15,8 +15,6 @@ //! Provides an implementation of [LDT](https://eprint.iacr.org/2017/841.pdf). #![no_std] -#![forbid(unsafe_code)] -#![deny(clippy::indexing_slicing, clippy::unwrap_used, clippy::panic, clippy::expect_used)] #[cfg(feature = "std")] extern crate std; @@ -31,7 +29,6 @@ use ldt_tbc::{TweakableBlockCipherDecrypter, TweakableBlockCipherEncrypter}; /// `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 +121,7 @@ where // 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 +144,23 @@ where // |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 +219,23 @@ pub trait Mix { /// 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 +254,7 @@ pub trait Padder<const B: usize, T: TweakableBlockCipher<B>> { 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/tests/ldt_roundtrip.rs b/nearby/presence/ldt/tests/ldt_roundtrip.rs index b5f930e..251cefe 100644 --- a/nearby/presence/ldt/tests/ldt_roundtrip.rs +++ b/nearby/presence/ldt/tests/ldt_roundtrip.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use crypto_provider::aes::BLOCK_SIZE; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; diff --git a/nearby/presence/ldt/tests/ldt_test_vectors.rs b/nearby/presence/ldt/tests/ldt_test_vectors.rs index 477243d..03b5d3f 100644 --- a/nearby/presence/ldt/tests/ldt_test_vectors.rs +++ b/nearby/presence/ldt/tests/ldt_test_vectors.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used, clippy::indexing_slicing)] + use anyhow::anyhow; use crypto_provider_default::CryptoProviderImpl; use ldt::{DefaultPadder, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Swap, XorPadder}; @@ -26,7 +28,7 @@ fn aluykx_test_vectors() -> Result<(), anyhow::Error> { ); let mut file = fs::File::open(full_path)?; let mut data = String::new(); - file.read_to_string(&mut data)?; + let _ = file.read_to_string(&mut data)?; let test_cases = match serde_json::de::from_str(&data)? { serde_json::Value::Array(a) => a, _ => return Err(anyhow!("bad json")), @@ -73,7 +75,7 @@ fn xor_pad_test_vectors() -> Result<(), anyhow::Error> { test_helper::get_data_file("presence/ldt/resources/test/ldt-xor-pad-testvectors.json"); let mut file = fs::File::open(full_path)?; let mut data = String::new(); - file.read_to_string(&mut data)?; + let _ = file.read_to_string(&mut data)?; let test_cases = match serde_json::de::from_str(&data)? { serde_json::Value::Array(a) => a, _ => return Err(anyhow!("bad json")), diff --git a/nearby/presence/ldt/tests/tests.rs b/nearby/presence/ldt/tests/tests.rs index b9e061a..25938fb 100644 --- a/nearby/presence/ldt/tests/tests.rs +++ b/nearby/presence/ldt/tests/tests.rs @@ -70,7 +70,8 @@ fn normal_pad_max_len() { fn normal_pad_too_big_panics() { let padder = DefaultPadder; let input = [0x99; 16]; - <DefaultPadder as Padder<16, XtsAes128<CryptoProviderImpl>>>::pad_tweak(&padder, &input); + let _ = + <DefaultPadder as Padder<16, XtsAes128<CryptoProviderImpl>>>::pad_tweak(&padder, &input); } #[test] @@ -129,7 +130,7 @@ fn xor_pad_too_big_panics() { let padder = [0x24; BLOCK_SIZE].into(); // need 1 byte for padding, and 2 more for salt let input = [0x99; 16]; - <XorPadder<BLOCK_SIZE> as Padder<BLOCK_SIZE, XtsAes128<CryptoProviderImpl>>>::pad_tweak( + let _ = <XorPadder<BLOCK_SIZE> as Padder<BLOCK_SIZE, XtsAes128<CryptoProviderImpl>>>::pad_tweak( &padder, &input, ); } diff --git a/nearby/presence/ldt_np_adv/Cargo.toml b/nearby/presence/ldt_np_adv/Cargo.toml index 02592ce..3f19bdd 100644 --- a/nearby/presence/ldt_np_adv/Cargo.toml +++ b/nearby/presence/ldt_np_adv/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] @@ -17,14 +20,14 @@ xts_aes.workspace = true 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/benches/ldt_adv_scan.rs b/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs index d7d7fad..7c0b4aa 100644 --- a/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs +++ b/nearby/presence/ldt_np_adv/benches/ldt_adv_scan.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use ldt_np_adv::*; @@ -30,7 +32,7 @@ fn ldt_adv_scan<C: CryptoProvider>(c: &mut Criterion) { let mut rng = rand_pcg::Pcg64::from_seed(seed); for &len in &[1_usize, 10, 1000] { - c.bench_function(&format!("Scan adv with fresh ciphers/{len}"), |b| { + let _ = c.bench_function(&format!("Scan adv with fresh ciphers/{len}"), |b| { let configs = random_configs::<C, _>(&mut rng, len); let payload_len = rng.gen_range(crypto_provider::aes::BLOCK_SIZE..=LDT_XTS_AES_MAX_LEN); let payload = random_vec(&mut rng, payload_len); @@ -42,7 +44,7 @@ fn ldt_adv_scan<C: CryptoProvider>(c: &mut Criterion) { black_box(find_matching_item::<C>(&ciphers, salt, &payload)) }); }); - c.bench_function(&format!("Scan adv with existing ciphers/{len}"), |b| { + let _ = c.bench_function(&format!("Scan adv with existing ciphers/{len}"), |b| { let configs = random_configs::<C, _>(&mut rng, len); let payload_len = rng.gen_range(crypto_provider::aes::BLOCK_SIZE..=LDT_XTS_AES_MAX_LEN); let payload = random_vec(&mut rng, payload_len); @@ -64,7 +66,7 @@ fn find_matching_item<C: CryptoProvider>( payload: &[u8], ) { let padder = salt_padder::<16, C>(salt); - ciphers + let _ = ciphers .iter() .enumerate() .filter_map(|(index, item)| { diff --git a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock index 7b5ba15..3bfbfb9 100644 --- a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock +++ b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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" @@ -162,7 +176,7 @@ dependencies = [ name = "crypto_provider" version = "0.1.0" dependencies = [ - "bytes", + "tinyvec", ] [[package]] @@ -171,6 +185,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -200,9 +215,9 @@ dependencies = [ [[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", @@ -268,14 +283,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "sha2", + "subtle", ] [[package]] @@ -336,6 +352,16 @@ dependencies = [ ] [[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" @@ -580,9 +606,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[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", @@ -613,6 +639,12 @@ dependencies = [ ] [[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" @@ -648,9 +680,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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..542d6a5 100644 --- a/nearby/presence/ldt_np_adv/src/lib.rs +++ b/nearby/presence/ldt_np_adv/src/lib.rs @@ -14,14 +14,6 @@ //! Nearby Presence-specific usage of LDT. #![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] #[cfg(feature = "std")] extern crate std; @@ -41,10 +33,13 @@ use xts_aes::XtsAes128; /// 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 +99,20 @@ type LdtXtsAes128Decrypter<C> = LdtDecryptCipher<{ BLOCK_SIZE }, XtsAes128<C>, S /// 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 +139,10 @@ where /// /// 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/src/np_adv_test_vectors.rs b/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs index 90d09b1..cf176dd 100644 --- a/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs +++ b/nearby/presence/ldt_np_adv/src/np_adv_test_vectors.rs @@ -33,7 +33,7 @@ fn np_adv_test_vectors() -> Result<(), anyhow::Error> { test_helper::get_data_file("presence/ldt_np_adv/resources/test/np_adv_test_vectors.json"); let mut file = fs::File::open(full_path)?; let mut data = String::new(); - file.read_to_string(&mut data)?; + let _ = file.read_to_string(&mut data)?; let test_cases = match serde_json::de::from_str(&data)? { serde_json::Value::Array(a) => a, _ => return Err(anyhow!("bad json")), 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..d8664ea 100644 --- a/nearby/presence/ldt_np_adv_ffi/Cargo.lock +++ b/nearby/presence/ldt_np_adv_ffi/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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" @@ -73,9 +87,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -98,6 +112,13 @@ dependencies = [ [[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 @@ dependencies = [ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_boringssl" @@ -184,7 +208,6 @@ version = "0.1.0" dependencies = [ "bssl-crypto", "crypto_provider", - "crypto_provider_stubs", ] [[package]] @@ -204,6 +227,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -240,9 +264,9 @@ dependencies = [ [[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,15 +323,16 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", + "subtle", "zeroize", ] @@ -384,6 +409,16 @@ dependencies = [ ] [[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" @@ -528,9 +563,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ "bitflags", "cfg-if", @@ -554,9 +589,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -742,9 +777,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[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", @@ -767,9 +802,9 @@ checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" [[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", @@ -841,6 +876,12 @@ dependencies = [ ] [[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 +923,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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/src/lib.rs b/nearby/presence/ldt_np_adv_ffi/src/lib.rs index b6f7a58..1534857 100644 --- a/nearby/presence/ldt_np_adv_ffi/src/lib.rs +++ b/nearby/presence/ldt_np_adv_ffi/src/lib.rs @@ -20,8 +20,8 @@ clippy::panic, clippy::expect_used )] -// TODO: Remove usage of `lang_items` when ffi is no longer alloc #![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)] @@ -130,7 +130,7 @@ extern "C" fn NpLdtEncryptClose(handle: NpLdtEncryptHandle) -> i32 { get_enc_handle_map() .remove(&handle.handle) .ok_or(CloseCipherError::InvalidHandle) - .map(|_| 0) + .map(|_| SUCCESS) }) } @@ -140,14 +140,11 @@ extern "C" fn NpLdtDecryptClose(handle: NpLdtDecryptHandle) -> i32 { 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 @@ target_link_libraries( 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 @@ target_link_libraries( "${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 @@ target_link_libraries( 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 @@ extern "C" { #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/Cargo.toml b/nearby/presence/ldt_np_jni/Cargo.toml index 6803d56..2551c14 100644 --- a/nearby/presence/ldt_np_jni/Cargo.toml +++ b/nearby/presence/ldt_np_jni/Cargo.toml @@ -4,12 +4,15 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] ldt.workspace = true ldt_np_adv.workspace = true np_hkdf.workspace = true crypto_provider.workspace = true -crypto_provider_default = {workspace = true, default-features = false} +crypto_provider_default = { workspace = true, default-features = false } cfg-if.workspace = true jni.workspace = true 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 createEncryptionCipher(byte[] keySeed); + + /** + * Create a new LDT-XTS-AES128 Decryption cipher using the "swap" mix function. + * + * @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 long createDecryptionCipher(byte[] keySeed, byte[] hmacTag); + + /** + * Close the native resources for an LdtEncryptCipher instance. + * @param ldtEncryptHandle a ldt handle returned from {@link LdtNpJni#createEncryptionCipher}. */ - static native long createLdtCipher(byte[] key_seed, byte[] metadata_key_hmac_tag); + static native void closeEncryptCipher(long ldtEncryptHandle); /** - * Close the native resources for an Ldt instance. + * Close the native resources for an LdtDecryptCipher instance. * - * @param ldtHandle An ldt handle returned from {@link LdtNpJni#createLdtCipher}. - * @return 0 on success, <0 for any error + * @param ldtDecryptHandle a ldt handle returned from {@link LdtNpJni#createDecryptionCipher}. */ - static native int closeLdtCipher(long ldtHandle); + static native void closeDecryptCipher(long ldtDecryptHandle); /** - * Encrypt the data in place. + * Encrypt a 16-31 byte buffer 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 + * @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 */ - static native int encrypt(long ldtHandle, char salt, byte[] data); + @EncryptErrorCode + static native int encrypt(long ldtEncryptHandle, char salt, byte[] data); /** - * Decrypt the data in place using the default LDT tweak padding scheme. + * 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 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 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. */ - static native int decrypt_and_verify(long ldtHandle, char salt, byte[] data); + @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() + + + 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() - LdtCipher.decrypt_and_verify(salt, data) - Assertions.assertArrayEquals(plaintext, data) + 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..e232c1e 100644 --- a/nearby/presence/ldt_np_jni/src/lib.rs +++ b/nearby/presence/ldt_np_jni/src/lib.rs @@ -20,18 +20,19 @@ //! - <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)] +#![allow(unsafe_code)] // Allow using Box in no_std extern crate alloc; 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,202 @@ use crypto_provider_default::CryptoProviderImpl; 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; + +type LdtAdvDecrypter = LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>; +type LdtAdvEncrypter = LdtEncrypterXtsAes128<CryptoProviderImpl>; + +/// 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 on failure, or the non-zero handle as a jlong/i64 on success. +#[no_mangle] +extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createEncryptionCipher( + env: JNIEnv, + _class: JClass, + java_key_seed: 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 hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new( + #[allow(clippy::expect_used)] + key_seed.as_slice().try_into().expect("Length is checked above"), + ); -// TODO: split this into separate APIs for encrypt and decrypt -struct Ldt { - ldt_enc: LdtEncrypterXtsAes128<CryptoProviderImpl>, - ldt_dec: LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>, + let cipher = LdtAdvEncrypter::new(&hkdf_key_seed.legacy_ldt_key()); + box_to_handle(cipher).map_err(|_| CREATE_ERROR) + }) } -/// Create an LDT cipher. +/// Create a LDT Decryption 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_createDecryptionCipher( env: JNIEnv, _class: JClass, - key_seed: jbyteArray, - metadata_key_hmac_tag: jbyteArray, + java_key_seed: JByteArray, + java_hmac_tag: 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(), - ); - - 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 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) + 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( + #[allow(clippy::expect_used)] + key_seed.as_slice().try_into().expect("Length is checked above"), + ); + + #[allow(clippy::expect_used)] + 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) + }) } -/// Close an LDT cipher. +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] -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_closeEncryptCipher( _env: JNIEnv, _class: JClass, - ldt_handle: jlong, -) -> jint { + handle: jlong, +) { + // create the box, let it be dropped + 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::<Ldt>(ldt_handle); - // success -- are there any meaningful error condtions we can even detect? - 0 + 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`. @@ -201,8 +255,7 @@ fn with_handle<T, U, F: FnMut(&T) -> U>(handle: jlong, mut f: F) -> U { let ret = f(&boxed); // don't consume the box -- need to keep the handle alive - Box::leak(boxed); - + let _ = Box::leak(boxed); ret } @@ -243,77 +296,56 @@ fn box_to_handle<T>(thing: T) -> Result<jlong, ()> { .map(|ptr_64: u64| ptr_64 as jlong) } -/// Extract data suitable for Ldt128 cipher ops from a JNI jbyteArray. +/// Expand the NP salt to the size needed to be an LDT XorPadder. /// -/// 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)?; +/// Returns a XorPadder containing the HKDF of the salt. +fn expand_np_salt_to_padder(np_salt: jchar) -> XorPadder<{ crypto_provider::aes::BLOCK_SIZE }> { + let salt_bytes = np_salt.to_be_bytes(); + ldt_np_adv::salt_padder::<16, CryptoProviderImpl>(salt_bytes.into()) +} - Ok((data_len, jbyte_array_to_u8_array(buf))) +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()) } -/// 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 +trait JniError { + fn to_jni_error_code(&self) -> jint; } -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 +#[derive(Debug)] +enum EncryptError { + /// Data is the wrong length + DataLen, + /// JNI op failed + JniOp, } -/// Expand the NP salt to the size needed to be an LDT XorPadder. -/// -/// Returns a XorPadder containing the HKDF of the salt. -fn expand_np_salt_to_padder(np_salt: jchar) -> XorPadder<{ crypto_provider::aes::BLOCK_SIZE }> { - let salt_bytes = np_salt.to_be_bytes(); - ldt_np_adv::salt_padder::<16, CryptoProviderImpl>(salt_bytes.into()) +impl JniError for EncryptError { + fn to_jni_error_code(&self) -> jint { + match self { + Self::DataLen => -1, + Self::JniOp => -2, + } + } } #[derive(Debug)] -enum CipherOpError { - /// The mac did not match the provided tag - MacMisMatch, +enum DecryptError { /// Data is the wrong length DataLen, + /// The mac did not match the provided tag + MacMisMatch, /// JNI op failed JniOp, } -impl CipherOpError { +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/ldt_tbc/Cargo.toml b/nearby/presence/ldt_tbc/Cargo.toml index 0177f87..8941051 100644 --- a/nearby/presence/ldt_tbc/Cargo.toml +++ b/nearby/presence/ldt_tbc/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] diff --git a/nearby/presence/ldt_tbc/src/lib.rs b/nearby/presence/ldt_tbc/src/lib.rs index d24da7e..aefeb17 100644 --- a/nearby/presence/ldt_tbc/src/lib.rs +++ b/nearby/presence/ldt_tbc/src/lib.rs @@ -12,14 +12,6 @@ // 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. -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] //! Defining traits for an LDT specific Tweakable Block Cipher diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml index 7f0d5d7..9b576a6 100644 --- a/nearby/presence/np_adv/Cargo.toml +++ b/nearby/presence/np_adv/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] array_view = { path = "../array_view" } ldt_np_adv.workspace = true @@ -14,29 +17,41 @@ xts_aes.workspace = true crypto_provider.workspace = true strum.workspace = true strum_macros.workspace = true -nom = { version = "7.1.1", default-features = false, features = ["alloc"] } +nom = { version = "7.1.3", default-features = false } 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"] } +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 bc2789d..b52f53a 100644 --- a/nearby/presence/np_adv/benches/deser_adv.rs +++ b/nearby/presence/np_adv/benches/deser_adv.rs @@ -12,17 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow( + missing_docs, + unused_results, + clippy::unwrap_used, + clippy::expect_used, + clippy::indexing_slicing, + clippy::panic +)] + use core::marker::PhantomData; 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, @@ -34,9 +44,10 @@ use np_adv::{ 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; @@ -63,17 +74,18 @@ pub fn deser_adv_v1_encrypted(c: &mut Criterion) { // 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(MicEncryptedSectionEncoder::new_random_salt( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, EncryptedIdentityDataElementType::Private, - &identity.extended_metadata_key, - &hkdf, + &broadcast_cm, )) .unwrap(); @@ -82,12 +94,10 @@ pub fn deser_adv_v1_encrypted(c: &mut Criterion) { } VerificationMode::Signature => { let mut sb = adv_builder - .section_builder(SignedEncryptedSectionEncoder::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(); @@ -99,22 +109,11 @@ pub fn deser_adv_v1_encrypted(c: &mut Criterion) { 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() + ) }, ); } @@ -124,31 +123,23 @@ pub fn deser_adv_v1_encrypted(c: &mut Criterion) { } pub fn deser_adv_v1_plaintext(c: &mut Criterion) { - let _rng = rand::rngs::StdRng::from_entropy(); - - 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(AdvertisementType::Plaintext); + c.bench_function("Deser V1 plaintext: sections=1", |b| { + let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Plaintext); - // take the first n identities, one section per identity - for _ in 0..num_sections { - let mut sb = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap(); + let mut sb = adv_builder.section_builder(PublicSectionEncoder::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) { @@ -165,17 +156,17 @@ pub fn deser_adv_v0_encrypted(c: &mut Criterion) { .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(); @@ -184,46 +175,31 @@ pub fn deser_adv_v0_encrypted(c: &mut Criterion) { 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( @@ -232,9 +208,10 @@ pub fn deser_adv_v0_plaintext(c: &mut Criterion) { 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"), ); @@ -247,22 +224,20 @@ pub fn deser_adv_v0_plaintext(c: &mut Criterion) { /// 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<_>>(); @@ -270,34 +245,55 @@ where // 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<_>>(); @@ -305,12 +301,41 @@ where // 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: SectionEncoder>(sb: &mut SectionBuilder<I>) { +fn add_des<I: SectionEncoder>( + sb: &mut SectionBuilder<&mut np_adv::extended::serialize::AdvBuilder, 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(); } @@ -325,20 +350,29 @@ criterion_main!(benches); 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>, } @@ -347,77 +381,23 @@ impl<C: CryptoProvider> V1Identity<C> { 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/array_vec.rs b/nearby/presence/np_adv/src/array_vec.rs new file mode 100644 index 0000000..bcc387e --- /dev/null +++ b/nearby/presence/np_adv/src/array_vec.rs @@ -0,0 +1,205 @@ +// 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 an [`ArrayVecOption`] wrapper implementation over [`ArrayVec`], +//! which stores all the elements in an `Option` in order to satisfy +//! `ArrayVec`'s requirement that the elements must implement `Default`. + +use tinyvec::{ArrayVec, ArrayVecIterator}; +#[cfg(any(test, feature = "alloc"))] +extern crate alloc; +#[cfg(any(test, feature = "alloc"))] +use alloc::vec::Vec; + +/// A wrapper of [`ArrayVec`] that stores it values as [`Option`], in order to +/// satisfy `ArrayVec`'s requirement that the elements must implement `Default`. +/// The implementation guarantees that any items in the wrapped `ArrayVec` +/// within `0..len` is `Some`, and therefore will not panic when unwrapped. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ArrayVecOption<A, const N: usize>(ArrayVec<[Option<A>; N]>); + +// Cannot derive due to https://github.com/rust-lang/rust/issues/74462 +impl<A, const N: usize> Default for ArrayVecOption<A, N> { + fn default() -> Self { + Self(Default::default()) + } +} + +/// Iterator type returned by `ArrayVecOption.iter()`, which can be used as +/// `impl Iterator<Item = &A>` +pub type ArrayVecOptionRefIter<'a, A> = + core::iter::Map<core::slice::Iter<'a, Option<A>>, fn(&'a Option<A>) -> &'a A>; + +/// Iterator type returned by `ArrayVecOption.into_iter()`, which can be used as +/// `impl Iterator<Item = A>` +pub type ArrayVecOptionIntoIter<A, const N: usize> = + core::iter::Map<ArrayVecIterator<[Option<A>; N]>, fn(Option<A>) -> A>; + +impl<A, const N: usize> ArrayVecOption<A, N> { + /// Returns an iterator over this vec. + pub fn iter(&self) -> ArrayVecOptionRefIter<A> { + self.0 + .iter() + .map(|v| v.as_ref().expect("ArrayVecOption value before .len() should not be None")) + } + + /// The length of the vec (in elements). + pub fn len(&self) -> usize { + self.0.len() + } + + /// Checks if the length is 0. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns the first element of the vec, or `None` if it is empty. + pub fn first(&self) -> Option<&A> { + self.iter().next() + } + + /// Place an element onto the end of the vec. + /// + /// # Panics + /// * If the length of the vec would overflow the capacity. + pub fn push(&mut self, value: A) { + self.0.push(Some(value)) + } + + /// Returns a reference to an element at the given index. + pub fn get(&self, index: usize) -> Option<&A> { + self.0.get(index).and_then(|opt| opt.as_ref()) + } + + /// Sorts the slice with a key extraction function, but might not preserve + /// the order of equal elements. + /// + /// This sort is unstable (i.e., may reorder equal elements), in-place + /// (i.e., does not allocate), and *O*(*m* \* *n* \* log(*n*)) worst-case, + /// where the key function is *O*(*m*). + pub fn sort_unstable_by_key<K: Ord>(&mut self, f: impl Fn(&A) -> K) { + self.0.sort_unstable_by_key(|a| { + f(a.as_ref().expect("Iterated values in ArrayVecOption should never be None")) + }) + } + + /// Remove an element, swapping the end of the vec into its place. + pub fn swap_remove(&mut self, index: usize) -> A { + self.0.swap_remove(index).expect("since index is is bounds, the value at index will always be Some which is safe to unwrap") + } + + /// Converts this vector into a regular `Vec`, unwrapping all of the + /// `Option` in the process. + #[cfg(any(test, feature = "alloc"))] + pub fn into_vec(self) -> Vec<A> { + self.into_iter().collect() + } +} + +impl<A, const N: usize> IntoIterator for ArrayVecOption<A, N> { + type Item = A; + type IntoIter = ArrayVecOptionIntoIter<A, N>; + fn into_iter(self) -> Self::IntoIter { + self.0 + .into_iter() + .map(|v| v.expect("ArrayVecOption value before .len() should not be None")) + } +} + +// Implement `FromIterator` to enable `iter.collect::<ArrayVecOption<_>>()` +impl<A, const N: usize> FromIterator<A> for ArrayVecOption<A, N> { + fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self { + Self(iter.into_iter().map(Some).collect()) + } +} + +impl<A, const N: usize> core::ops::Index<usize> for ArrayVecOption<A, N> { + type Output = A; + fn index(&self, index: usize) -> &Self::Output { + self.0[index].as_ref().expect("This panics if provided index is out of bounds") + } +} + +#[cfg(test)] +mod test { + extern crate std; + use super::ArrayVecOption; + use std::vec; + + #[test] + fn test_default_array_vec() { + let vec = ArrayVecOption::<u32, 5>::default(); + assert_eq!(0, vec.len()); + assert_eq!(None, vec.iter().next()); + assert!(vec.is_empty()); + assert_eq!(None, vec.first()); + assert_eq!(None, vec.get(0)); + assert_eq!(vec![0_u32; 0], vec.into_vec()); + } + + #[test] + fn test_array_vec_with_contents() { + let mut vec = ArrayVecOption::<u32, 5>::default(); + vec.push(1); + vec.push(2); + vec.push(3); + vec.push(4); + vec.push(5); + assert_eq!(5, vec.len()); + let mut iter = vec.iter(); + assert_eq!(Some(&1_u32), iter.next()); + assert_eq!(Some(&2_u32), iter.next()); + assert_eq!(Some(&3_u32), iter.next()); + assert_eq!(Some(&4_u32), iter.next()); + assert_eq!(Some(&5_u32), iter.next()); + assert_eq!(None, iter.next()); + assert!(!vec.is_empty()); + assert_eq!(Some(&1_u32), vec.first()); + assert_eq!(Some(&5_u32), vec.get(4)); + assert_eq!(vec![1_u32, 2, 3, 4, 5], vec.clone().into_vec()); + + let _ = vec.swap_remove(2); + assert_eq!(vec![1_u32, 2, 5, 4], vec.clone().into_vec()); + } + + #[test] + #[should_panic] + fn test_array_vec_push_overflow() { + let mut vec = ArrayVecOption::<u32, 5>::default(); + vec.push(1); + vec.push(2); + vec.push(3); + vec.push(4); + vec.push(5); + vec.push(6); + } + + #[test] + fn test_sort() { + let mut vec = ArrayVecOption::<u32, 5>::default(); + vec.push(3); + vec.push(1); + vec.push(4); + vec.push(1); + vec.push(2); + vec.sort_unstable_by_key(|k| *k); + assert_eq!(vec![1_u32, 1, 2, 3, 4], vec.clone().into_vec()); + } + + #[test] + fn test_collect() { + let vec: ArrayVecOption<u32, 5> = [5_u32, 4, 3, 2, 1].into_iter().collect(); + assert_eq!(vec![5_u32, 4, 3, 2, 1], vec.clone().into_vec()); + } +} 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..902d55a --- /dev/null +++ b/nearby/presence/np_adv/src/credential/book.rs @@ -0,0 +1,544 @@ +// 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")] +extern crate alloc; +#[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 core::fmt::Debug; -use crypto_provider::CryptoProvider; +use crate::MetadataKey; + +use crate::credential::v0::{V0DiscoveryCredential, V0ProtocolVersion}; -use self::{ - simple::{SimpleV0Credential, SimpleV1Credential}, - source::OwnedBothCredentialSource, - v0::MinimumFootprintV0CryptoMaterial, - v1::MinimumFootprintV1CryptoMaterial, -}; +use core::convert::Infallible; +use core::fmt::Debug; +use crypto_provider::{CryptoProvider, CryptoRng}; -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; +/// [`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, +} - /// Returns the crypto material associated with the credential. - /// - /// Used to decrypt encrypted advertisement content. - fn crypto_material(&self) -> &Self::CryptoMaterial; +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, } -/// An owned credential store that contains minimum footprint crypto materials -pub type MinimumFootprintCredentialSource<T> = OwnedBothCredentialSource< - SimpleV0Credential<MinimumFootprintV0CryptoMaterial, T>, - SimpleV1Credential<MinimumFootprintV1CryptoMaterial, T>, ->; +#[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. - -use super::*; -use alloc::vec::Vec; - -/// 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; - - /// Iterate over the available credentials - fn iter(&self) -> Self::Iterator<'_>; -} - -/// Trait for combined credential sources able to yield credential sources for both V0 and V1. -pub trait BothCredentialSource<C0, C1> +//! Definitions of traits for structures which supply +//! credentials for discovering advertisements/advertisement +//! sections for a _particular_ protocol version. + +use crate::credential::{ + DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion, + ReferencedMatchedCredential, +}; +use core::borrow::Borrow; + +/// 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 - 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, which allows borrowing a discovery credential. + type Crypto: DiscoveryCryptoMaterial<V> + Borrow<V::DiscoveryCredential>; - /// 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() - } - - /// 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() - } + /// Iterate over the available credentials + fn iter(&'a self) -> Self::Iterator; } -/// A simple [CredentialSource] that just iterates over a provided slice of credentials -pub struct SliceCredentialSource<'c, C: MatchableCredential> { - credentials: &'c [C], -} +/// 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 + Self: 'a, +{ + /// The kind of data yielded to the caller upon a successful + /// identity-match. + type Matched: MatchedCredential; -impl<'c, C: MatchableCredential> SliceCredentialSource<'c, C> { - /// Construct the credential source from the provided credentials. - pub fn new(credentials: &'c [C]) -> Self { - Self { credentials } - } -} + /// The kind of crypto-material yielded from the wrapped + /// iterator. + type Crypto: DiscoveryCryptoMaterial<V>; -impl<'c, C: MatchableCredential> CredentialSource<C> for SliceCredentialSource<'c, C> { - type Iterator<'i> = core::slice::Iter<'i, C> - where Self: 'i; + /// 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)>; - 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>, + /// Iterate over the available credentials + fn iter(&'a self) -> Self::Iterator; } -impl<C: MatchableCredential> OwnedCredentialSource<C> { - /// Constructs an owned credential source from the given credentials - pub fn new(credentials: Vec<C>) -> Self { - Self { credentials } - } -} +// 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<C: MatchableCredential> CredentialSource<C> for OwnedCredentialSource<C> { - type Iterator<'i> = core::slice::Iter<'i, C> - where Self: 'i; +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(&'_ self) -> Self::Iterator<'_> { - self.credentials.iter() + fn iter(&'a self) -> Self::Iterator { + self.0.iter() } } -/// An owned credential source for both v0 and v1 credentials, -pub struct OwnedBothCredentialSource<C0, C1> -where - C0: V0Credential, - C1: V1Credential, -{ - v0_source: OwnedCredentialSource<C0>, - v1_source: OwnedCredentialSource<C1>, +/// 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<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<'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<C0, C1> BothCredentialSource<C0, C1> for 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>, { - type V0Source = OwnedCredentialSource<C0>; - type V1Source = OwnedCredentialSource<C1>; - - fn v0(&self) -> &Self::V0Source { - &self.v0_source - } - fn v1(&self) -> &Self::V1Source { - &self.v1_source + 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>), + >; + + 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..8b12bfc --- /dev/null +++ b/nearby/presence/np_adv/src/credential/tests.rs @@ -0,0 +1,208 @@ +// 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. + +extern crate alloc; + +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 @@ impl V0CryptoMaterial for MinimumFootprintV0CryptoMaterial { } } -/// [`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 @@ impl V0CryptoMaterial for PrecalculatedV0CryptoMaterial { ) } } + +// 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 5c17d27..386799f 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, ed25519::PublicKey, 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( + key_seed: [u8; 32], + expected_unsigned_metadata_key_hmac: [u8; 32], + expected_signed_metadata_key_hmac: [u8; 32], + pub_key: ed25519::RawPublicKey, + ) -> Self { + Self { + key_seed, + expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, + pub_key, + } + } + + /// 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,11 +160,18 @@ impl SignedSectionIdentityResolutionMaterial { 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. @@ -74,8 +197,14 @@ impl UnsignedSectionIdentityResolutionMaterial { 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 } @@ -94,51 +223,6 @@ impl UnsignedSectionIdentityResolutionMaterial { } } -/// Wrapper enum around signed/unsigned identity resolution material -/// which may be borrowed. Used only in implementation details -/// of identity-resolution. -pub(crate) enum BorrowableIdentityResolutionMaterial<S, U> -where - S: Borrow<SignedSectionIdentityResolutionMaterial>, - U: Borrow<UnsignedSectionIdentityResolutionMaterial>, -{ - Signed(S), - Unsigned(U), -} - -impl<S, U> BorrowableIdentityResolutionMaterial<S, U> -where - S: Borrow<SignedSectionIdentityResolutionMaterial>, - U: Borrow<UnsignedSectionIdentityResolutionMaterial>, -{ - pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial { - match self { - Self::Signed(x) => x.borrow().as_raw_resolution_material(), - Self::Unsigned(x) => x.borrow().as_raw_resolution_material(), - } - } - pub(crate) fn signed_from_crypto_material<'a, C, P>(crypto_material: &'a C) -> Self - where - C: V1CryptoMaterial< - SignedIdentityResolverReference<'a> = S, - UnsignedIdentityResolverReference<'a> = U, - >, - P: CryptoProvider, - { - Self::Signed(crypto_material.signed_identity_resolution_material::<P>()) - } - pub(crate) fn unsigned_from_crypto_material<'a, C, P>(crypto_material: &'a C) -> Self - where - C: V1CryptoMaterial< - SignedIdentityResolverReference<'a> = S, - UnsignedIdentityResolverReference<'a> = U, - >, - P: CryptoProvider, - { - Self::Unsigned(crypto_material.unsigned_identity_resolution_material::<P>()) - } -} - /// Crypto materials for V1 signed sections which are not employed in identity resolution, /// but may be necessary to verify a signed section. #[derive(Clone)] @@ -184,88 +268,54 @@ impl UnsignedSectionVerificationMaterial { // 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() @@ -277,67 +327,126 @@ impl V1CryptoMaterial for PrecalculatedV1CryptoMaterial { } } -/// [`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<'a> DiscoveryCryptoMaterial<V1> for &'a V1DiscoveryCredential { + fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] { + (*self).metadata_nonce::<C>() + } } -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> V1DiscoveryCryptoMaterial for &'a V1DiscoveryCredential { + 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>() } } -impl V1CryptoMaterial for MinimumFootprintV1CryptoMaterial { - type SignedIdentityResolverReference<'a> = SignedSectionIdentityResolutionMaterial - where Self: 'a; - type UnsignedIdentityResolverReference<'a> = UnsignedSectionIdentityResolutionMaterial - where Self: 'a; +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, - ) -> 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>() + } +} + +/// 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 = pub_key.to_bytes(); + + 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/deser_v0_tests.rs b/nearby/presence/np_adv/src/deser_v0_tests.rs index 7f4d888..f6643de 100644 --- a/nearby/presence/np_adv/src/deser_v0_tests.rs +++ b/nearby/presence/np_adv/src/deser_v0_tests.rs @@ -12,33 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use rand::{seq::SliceRandom as _, SeedableRng as _}; extern crate std; 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, PlaintextIdentityMode, PublicIdentity, V0AdvContents, V0Credential, + HasIdentityMatch, PlaintextIdentityMode, PublicIdentity, V0AdvertisementContents, }; use array_view::ArrayView; use core::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 _; @@ -50,10 +55,18 @@ fn v0_all_identities_resolvable() { 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 arena = deserialization_arena!(); + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]); + let contents = deser_v0::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book); assert_adv_equals(&adv_config, &contents); } @@ -67,24 +80,31 @@ fn v0_only_non_matching_identities_available() { 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 arena = deserialization_arena!(); + let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>( + &credentials, + &[], + ); + let contents = deser_v0::<_, CryptoProviderImpl>(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); } } } @@ -98,43 +118,48 @@ fn v0_no_creds_available_error_if_encrypted() { 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 arena = deserialization_arena!(); + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]); + let contents = deser_v0::<_, CryptoProviderImpl>(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); assert_eq!(adv_config.plaintext_mode.unwrap(), p.identity()); assert_eq!( - vec![&PlainDataElement::Actions(de)], - p.data_elements().collect::<Vec<_>>() + vec![PlainDataElement::Actions(de)], + p.data_elements().collect::<Result<Vec<_>, _>>().unwrap() ) } _ => 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 @@ -143,15 +168,15 @@ fn assert_adv_equals<'m>( let de = ActionsDataElement::from(action_bits); assert_eq!( - vec![&PlainDataElement::Actions(de)], - wmc.contents().data_elements().collect::<Vec<_>>() + vec![PlainDataElement::Actions(de)], + wmc.contents().data_elements().collect::<Result<Vec<_>, _>>().unwrap() ); assert_eq!( adv_config.identity.unwrap().identity_type, wmc.contents().identity_type() ); assert_eq!( - &adv_config.identity.unwrap().legacy_metadata_key, + adv_config.identity.unwrap().legacy_metadata_key, wmc.contents().metadata_key() ); } @@ -160,16 +185,19 @@ fn assert_adv_equals<'m>( } } -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: DeserializationArena<'adv>, + adv: &'adv [u8], + cred_book: &'adv B, +) -> V0AdvertisementContents<'adv, 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,7 +207,7 @@ fn adv_random_identity<'a, R: rand::Rng>( ) -> (ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, AdvConfig<'a>) { let identity = identities.choose(&mut rng).unwrap(); if rng.gen_bool(0.5) { - let mut adv_builder = AdvBuilder::new(PublicIdentity::default()); + let mut adv_builder = AdvBuilder::new(PublicIdentity); add_de(&mut adv_builder); ( @@ -187,11 +215,14 @@ fn adv_random_identity<'a, R: rand::Rng>( 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]>()), - identity.legacy_metadata_key, - LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&identity.hkdf().legacy_ldt_key()), + &broadcast_cm, )); add_de(&mut adv_builder); @@ -214,7 +245,7 @@ where struct TestIdentity<C: CryptoProvider> { identity_type: EncryptedIdentityDataElementType, key_seed: [u8; 32], - legacy_metadata_key: [u8; 14], + legacy_metadata_key: ShortMetadataKey, _marker: PhantomData<C>, } @@ -227,20 +258,17 @@ impl<C: CryptoProvider> TestIdentity<C> { .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 8076e99..e0cabc9 100644 --- a/nearby/presence/np_adv/src/deser_v1_tests.rs +++ b/nearby/presence/np_adv/src/deser_v1_tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use core::marker::PhantomData; use rand::{rngs::StdRng, seq::SliceRandom as _, Rng as _, SeedableRng as _}; @@ -19,12 +21,17 @@ extern crate std; 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, @@ -33,8 +40,8 @@ use crate::{ PublicSectionEncoder, SectionBuilder, SectionEncoder, SignedEncryptedSectionEncoder, }, }, - AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, CredentialSource, - PlaintextIdentityMode, Section, V1AdvContents, V1Credential, V1DeserializedSection, + AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, HasIdentityMatch, MetadataKey, + PlaintextIdentityMode, Section, V1AdvertisementContents, V1DeserializedSection, }; use crypto_provider::{CryptoProvider, CryptoRng}; use std::{collections, prelude::rust_2021::*, vec}; @@ -55,11 +62,20 @@ fn v1_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); + let creds = identities + .iter() + .map(|i| MatchableCredential { + discovery_credential: i.discovery_credential(), + match_data: EmptyMatchedCredential, + }) + .collect::<Vec<_>>(); + + let 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>(&cred_source, &adv); + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); assert_eq!( v1_error, AdvDeserializationError::ParseError { @@ -68,7 +84,7 @@ fn v1_plaintext() { } ); //assert a adv deserialization error } else { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(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()) @@ -90,11 +106,19 @@ fn v1_all_identities_resolvable_ciphertext() { 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| 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 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>(&cred_source, &adv); + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); assert_eq!( v1_error, AdvDeserializationError::ParseError { @@ -103,7 +127,7 @@ fn v1_all_identities_resolvable_ciphertext() { } ); //assert a adv deserialization error } else { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(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()) @@ -133,12 +157,18 @@ fn v1_only_non_matching_identities_available_ciphertext() { .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 arena = deserialization_arena!(); // check if the section header would be 0 + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); if section_configs.is_empty() { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); assert_eq!( v1_error, AdvDeserializationError::ParseError { @@ -147,7 +177,7 @@ fn v1_only_non_matching_identities_available_ciphertext() { } ); //assert a adv deserialization error } else { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book); // all encrypted identity sections are invalid let encrypted_section_count = section_configs.iter().filter(|sc| sc.identity.is_some()).count(); @@ -178,13 +208,15 @@ fn v1_no_creds_available_ciphertext() { &mut adv_builder, ); let adv = adv_builder.into_advertisement(); - let cred_source: SliceCredentialSource< - '_, - SimpleV1Credential<MinimumFootprintV1CryptoMaterial, [u8; 32]>, - > = SliceCredentialSource::new(&[]); + let arena = deserialization_arena!(); // check if the section header would be 0 + let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::< + 0, + 0, + CryptoProviderImpl, + >(&[], &[]); if section_configs.is_empty() { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); assert_eq!( v1_error, AdvDeserializationError::ParseError { @@ -193,7 +225,7 @@ fn v1_no_creds_available_ciphertext() { } ); //assert a adv deserialization error } else { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book); // all encrypted identity sections are invalid let encrypted_section_count = section_configs.iter().filter(|sc| sc.identity.is_some()).count(); @@ -240,12 +272,20 @@ fn v1_only_some_matching_identities_available_ciphertext() { 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 arena = deserialization_arena!(); + + let cred_book = + CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds); + // check if the section header would be 0 if section_configs.is_empty() { - let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_error = deser_v1_error::<_, CryptoProviderImpl>(arena, &adv, &cred_book); assert_eq!( v1_error, AdvDeserializationError::ParseError { @@ -254,7 +294,7 @@ fn v1_only_some_matching_identities_available_ciphertext() { } ); //assert a adv deserialization error } else { - let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, &adv); + let v1_contents = deser_v1::<_, CryptoProviderImpl>(arena, &adv, &cred_book); let affected_sections: Vec<_> = section_configs .iter() @@ -282,9 +322,9 @@ fn v1_only_some_matching_identities_available_ciphertext() { } } -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 { @@ -294,7 +334,7 @@ fn assert_section_equals<'m, C: CryptoProvider>( 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"), @@ -306,14 +346,17 @@ fn assert_section_equals<'m, C: CryptoProvider>( 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!( @@ -325,32 +368,35 @@ fn assert_section_equals<'m, C: CryptoProvider>( } } -fn deser_v1_error<'s, C, S, P>( - cred_source: &'s S, - adv: &'s EncodedAdvertisement, +fn deser_v1_error<'a, B, P>( + arena: 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: &'s EncodedAdvertisement, -) -> V1AdvContents<'s, SimpleMatchedCredential<'s, [u8; 32]>> +fn deser_v1<'adv, B, P>( + arena: 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 @@ -392,13 +438,15 @@ fn fill_encrypted_adv<'a, R: rand::Rng, C: CryptoProvider>( for _ in 0..rng.gen_range(0..=6) { let chosen_index = rng.gen_range(0..identities.len()); let identity = &identities[chosen_index]; + + let broadcast_cm = identity.broadcast_credential(); + let res = if rng.gen_bool(0.5) { adv_builder - .section_builder(MicEncryptedSectionEncoder::new_random_salt( + .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( @@ -410,12 +458,10 @@ fn fill_encrypted_adv<'a, R: rand::Rng, C: CryptoProvider>( }) } else { adv_builder - .section_builder(SignedEncryptedSectionEncoder::new_random_salt( + .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( @@ -440,7 +486,7 @@ fn fill_encrypted_adv<'a, R: rand::Rng, C: CryptoProvider>( 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>, } @@ -453,33 +499,27 @@ impl<C: CryptoProvider> TestIdentity<C> { .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: SectionEncoder, R: rand::Rng>( - mut sb: SectionBuilder<I>, + mut sb: SectionBuilder<&mut AdvBuilder, I>, rng: &mut R, ) -> Vec<GenericDataElement> { let mut des = Vec::new(); 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..9b58cbf --- /dev/null +++ b/nearby/presence/np_adv/src/deserialization_arena.rs @@ -0,0 +1,119 @@ +// 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 { + buffer: &mut $crate::deserialization_arena::DeserializationArena::new_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(crate) struct DeserializationArenaAllocator<'a> { + #[doc(hidden)] + slice: &'a mut [u8], +} + +impl<'a> DeserializationArenaAllocator<'a> { + /// 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. While it's deterministic (not UB), + // semantically this should be treated as a write only slice. + Ok(allocated) + } +} + +/// 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 buffer: &'a mut [u8; BLE_ADV_SVC_CONTENT_LEN], +} + +impl<'a> DeserializationArena<'a> { + #[doc(hidden)] // Exposed for use by `deserialization_arena!` only. + pub fn new_buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] { + [0; BLE_ADV_SVC_CONTENT_LEN] + } + + /// Convert this arena into an allocator that can start allocating. + pub(crate) fn into_allocator(self) -> DeserializationArenaAllocator<'a> { + DeserializationArenaAllocator { slice: self.buffer } + } +} + +/// 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(crate) 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!().buffer.len()); + } + + #[test] + fn test_allocation() { + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); + assert_eq!(Ok(&mut [0_u8; 100][..]), allocator.allocate(100)); + assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, allocator.slice.len()); + } + + #[test] + fn test_allocation_overflow() { + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); + assert_eq!(Err(ArenaOutOfSpace), allocator.allocate(u8::MAX)); + } + + #[test] + fn test_allocation_twice_overflow() { + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); + assert_eq!(Ok(&mut [0_u8; 128][..]), allocator.allocate(128)); + assert_eq!(Err(ArenaOutOfSpace), allocator.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 @@ impl WriteDataElement for GenericDataElement { } /// 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 a0f9a2f..6f085cd 100644 --- a/nearby/presence/np_adv/src/extended/data_elements/tests.rs +++ b/nearby/presence/np_adv/src/extended/data_elements/tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; diff --git a/nearby/presence/np_adv/src/extended/de_type.rs b/nearby/presence/np_adv/src/extended/de_type.rs index 105d15e..975df90 100644 --- a/nearby/presence/np_adv/src/extended/de_type.rs +++ b/nearby/presence/np_adv/src/extended/de_type.rs @@ -36,7 +36,7 @@ impl DeType { impl From<u8> for DeType { fn from(value: u8) -> Self { - DeType { code: value as u32 } + DeType { code: value.into() } } } @@ -46,6 +46,12 @@ impl From<u32> for DeType { } } +impl From<DeType> for u32 { + fn from(value: DeType) -> Self { + value.code + } +} + pub(crate) trait ExtendedDataElementType: Sized { /// A type code for use in the DE header. fn type_code(&self) -> DeType; 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 2fe6f69..bf78b7d 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 @@ -12,24 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; +use crate::deserialization_arena; use crate::extended::data_elements::TxPowerDataElement; +use crate::extended::deserialize::DataElementParseError; use crate::extended::serialize::{AdvertisementType, SingleTypeDataElement, WriteDataElement}; use crate::shared_data::TxPower; use crate::{ - credential::v1::MinimumFootprintV1CryptoMaterial, + credential::{v1::V1, SimpleBroadcastCryptoMaterial}, de_type::EncryptedIdentityDataElementType, extended::{ deserialize::{ encrypted_section::{ - DeserializationError, EncryptedSectionContents, - IdentityResolutionOrDeserializationError, MicVerificationError, + EncryptedSectionContents, IdentityResolutionOrDeserializationError, + MicVerificationError, }, parse_sections, - test_stubs::{HkdfCryptoMaterial, IntermediateSectionExt}, - CiphertextSection, OffsetDataElement, RawV1Salt, + test_stubs::IntermediateSectionExt, + CiphertextSection, DataElement, RawV1Salt, }, serialize::{AdvBuilder, CapacityLimitedVec, MicEncryptedSectionEncoder}, }, @@ -42,20 +46,20 @@ use std::{prelude::rust_2021::*, vec}; #[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(AdvertisementType::Encrypted); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncryptedSectionEncoder::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(); @@ -88,19 +92,20 @@ fn deserialize_mic_encrypted_correct_keys() { let keypair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate(); // deserializing to Section works - 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 discovery_credential = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, keypair.private_key()) + .derive_v1_discovery_credential::<CryptoProviderImpl>(); + let identity_resolution_material = - crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>(); + discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>(); let verification_material = - crypto_material.unsigned_verification_material::<CryptoProviderImpl>(); + discovery_credential.unsigned_verification_material::<CryptoProviderImpl>(); + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); let section = contents .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut allocator, &identity_resolution_material, &verification_material, ) @@ -114,27 +119,28 @@ fn deserialize_mic_encrypted_correct_keys() { 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<_>>() ); @@ -173,18 +179,23 @@ fn deserialize_mic_encrypted_correct_keys() { contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>(); let identity_match = identity_resolution_contents .try_match::<CryptoProviderImpl>( - identity_resolution_material.as_raw_resolution_material(), + &identity_resolution_material.into_raw_resolution_material(), ) .unwrap(); - let decrypted = contents.contents.decrypt_ciphertext::<CryptoProviderImpl>(identity_match); + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); + let decrypted = contents + .contents + .decrypt_ciphertext::<CryptoProviderImpl>(&mut allocator, identity_match) + .unwrap(); 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); + let _ = txpower_de.write_de_contents(&mut expected); assert_eq!(metadata_key, decrypted.metadata_key_plaintext); - assert_eq!(&expected, decrypted.plaintext_contents.as_slice()); + assert_eq!(&expected, decrypted.plaintext_contents); } } @@ -240,7 +251,9 @@ fn deserialize_mic_encrypted_incorrect_expected_metadata_key_hmac_error() { fn deserialize_mic_encrypted_incorrect_salt_error() { // bad salt -> bad iv -> bad metadata key plaintext do_bad_deserialize_tampered( - IdentityResolutionOrDeserializationError::IdentityMatchingError, + DeserializeError::IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError::IdentityMatchingError, + ), |_| {}, |adv| adv[23..39].copy_from_slice(&[0xFF; 16]), ); @@ -250,7 +263,7 @@ fn deserialize_mic_encrypted_incorrect_salt_error() { 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( - DeserializationError::DeParseError.into(), + DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd), |sec| sec.try_push(0xFF).unwrap(), |_| {}, ); @@ -260,7 +273,9 @@ fn deserialize_mic_encrypted_de_that_wont_parse() { fn deserialize_mic_encrypted_tampered_mic_error() { // flip the a bit in the first MIC byte do_bad_deserialize_tampered( - MicVerificationError::MicMismatch.into(), + DeserializeError::IdentityResolutionOrDeserializationError( + MicVerificationError::MicMismatch.into(), + ), |_| {}, |adv| { let mic_start = adv.len() - 16; @@ -273,7 +288,9 @@ fn deserialize_mic_encrypted_tampered_mic_error() { fn deserialize_mic_encrypted_tampered_payload_error() { // flip the last payload bit do_bad_deserialize_tampered( - MicVerificationError::MicMismatch.into(), + DeserializeError::IdentityResolutionOrDeserializationError( + MicVerificationError::MicMismatch.into(), + ), |_| {}, |adv| *adv.last_mut().unwrap() ^= 0x01, ); @@ -288,7 +305,7 @@ fn do_bad_deserialize_params<C: CryptoProvider>( 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; @@ -296,12 +313,13 @@ fn do_bad_deserialize_params<C: CryptoProvider>( 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(MicEncryptedSectionEncoder::new_for_testing( + .section_builder(MicEncryptedSectionEncoder::<C>::new( identity_type, section_salt, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -336,7 +354,7 @@ fn do_bad_deserialize_params<C: CryptoProvider>( .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 = @@ -352,6 +370,7 @@ fn do_bad_deserialize_params<C: CryptoProvider>( error, contents .try_resolve_identity_and_deserialize::<C>( + &mut deserialization_arena!().into_allocator(), &identity_resolution_material, &verification_material, ) @@ -359,28 +378,33 @@ fn do_bad_deserialize_params<C: CryptoProvider>( ); } -fn do_bad_deserialize_tampered< - S: Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>), - A: Fn(&mut Vec<u8>), ->( - expected_error: IdentityResolutionOrDeserializationError<MicVerificationError>, - 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(AdvertisementType::Encrypted); + let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key); + let mut section_builder = adv_builder - .section_builder(MicEncryptedSectionEncoder::new_for_testing( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, section_salt, - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -413,22 +437,35 @@ fn do_bad_deserialize_tampered< 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 discovery_credential = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()) + .derive_v1_discovery_credential::<CryptoProviderImpl>(); + let identity_resolution_material = - crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>(); + discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>(); let verification_material = - crypto_material.unsigned_verification_material::<CryptoProviderImpl>(); - - assert_eq!( - expected_error, - contents - .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( - &identity_resolution_material, - &verification_material, - ) - .unwrap_err() - ); + discovery_credential.unsigned_verification_material::<CryptoProviderImpl>(); + + match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut deserialization_arena!().into_allocator(), + &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 7dc411e..eb2fe01 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,18 +14,23 @@ use crate::{ credential::v1::*, + deserialization_arena::DeserializationArenaAllocator, 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(feature = "devtools")] + +#[cfg(any(feature = "devtools", test))] +extern crate alloc; +#[cfg(any(feature = "devtools", test))] use alloc::vec::Vec; +#[cfg(feature = "devtools")] use array_view::ArrayView; use core::fmt::Debug; use crypto_provider::{ @@ -35,6 +40,8 @@ use crypto_provider::{ }; use np_hkdf::v1_salt::V1Salt; +use super::ArenaOutOfSpace; + #[cfg(test)] mod mic_decrypt_tests; #[cfg(test)] @@ -69,7 +76,7 @@ impl SectionIdentityResolutionContents { 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.decrypt(&mut decrypt_buf[..]); + cipher.apply_keystream(&mut decrypt_buf[..]); let metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C> = identity_resolution_material.metadata_key_hmac_key.into(); @@ -77,7 +84,7 @@ impl SectionIdentityResolutionContents { metadata_key_hmac_key.verify_hmac(&decrypt_buf[..], expected_metadata_key_hmac).ok().map( move |_| IdentityMatch { cipher, - metadata_key_plaintext: decrypt_buf, + metadata_key_plaintext: MetadataKey(decrypt_buf), nonce: self.nonce, }, ) @@ -88,7 +95,7 @@ impl SectionIdentityResolutionContents { /// against some particular V1 identity-resolution crypto-materials. pub(crate) struct IdentityMatch<C: CryptoProvider> { /// Decrypted metadata key ciphertext - metadata_key_plaintext: [u8; METADATA_KEY_LEN], + 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 @@ -98,25 +105,25 @@ pub(crate) struct IdentityMatch<C: CryptoProvider> { } /// 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 { - pub(crate) metadata_key_plaintext: [u8; METADATA_KEY_LEN], +pub(crate) struct RawDecryptedSection<'adv> { + pub(crate) metadata_key_plaintext: MetadataKey, pub(crate) nonce: AesCtrNonce, - pub(crate) plaintext_contents: ArrayView<u8, MAX_SECTION_CONTENTS_LEN>, + pub(crate) plaintext_contents: &'adv [u8], } #[cfg(feature = "devtools")] -impl RawDecryptedSection { +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); - result.extend_from_slice(self.plaintext_contents.as_slice()); - // Won't panic because of the involved lengths - ArrayView::try_from_slice(&result).unwrap() + result.extend_from_slice(&self.metadata_key_plaintext.0); + result.extend_from_slice(self.plaintext_contents); + ArrayView::try_from_slice(&result).expect("Won't panic because of the involved lengths") } } @@ -173,8 +180,10 @@ impl<'a> EncryptedSectionContents<'a> { &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(); + let metadata_key_ciphertext: [u8; METADATA_KEY_LEN] = self.all_ciphertext + [..METADATA_KEY_LEN] + .try_into() + .expect("slice will always fit into same size array"); SectionIdentityResolutionContents { nonce, metadata_key_ciphertext } } @@ -183,33 +192,37 @@ impl<'a> EncryptedSectionContents<'a> { /// and returns the raw bytes of the decrypted plaintext. pub(crate) fn decrypt_ciphertext<C: CryptoProvider>( &self, + arena: &mut DeserializationArenaAllocator<'a>, mut identity_match: IdentityMatch<C>, - ) -> RawDecryptedSection { + ) -> Result<RawDecryptedSection<'a>, ArenaOutOfSpace> { // Fill decrypt_buf with the ciphertext after the metadata key - let mut decrypt_buf = tinyvec::ArrayVec::<[u8; MAX_SECTION_CONTENTS_LEN]>::new(); - decrypt_buf.extend_from_slice(&self.all_ciphertext[METADATA_KEY_LEN..]); + 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.decrypt(&mut decrypt_buf); + identity_match.cipher.apply_keystream(decrypt_buf); - let plaintext_contents = to_array_view(decrypt_buf); - - RawDecryptedSection { + Ok(RawDecryptedSection { metadata_key_plaintext: identity_match.metadata_key_plaintext, nonce: identity_match.nonce, - plaintext_contents, - } + 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, + allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &SectionIdentityResolutionMaterial, - ) -> Option<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>> { + ) -> 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>(identity_match); - decrypted_section.to_raw_bytes() + let decrypted_section = self.decrypt_ciphertext::<P>(allocator, identity_match)?; + Ok(decrypted_section.to_raw_bytes()) }) } } @@ -225,13 +238,14 @@ impl<'a> SignatureEncryptedSection<'a> { /// with some paired verification material for the matched identity. pub(crate) fn try_deserialize<P>( &self, + arena: &mut DeserializationArenaAllocator<'a>, identity_match: IdentityMatch<P>, verification_material: &SignedSectionVerificationMaterial, - ) -> Result<DecryptedSection, DeserializationError<SignatureVerificationError>> + ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>> where P: CryptoProvider, { - let raw_decrypted = self.contents.decrypt_ciphertext(identity_match); + 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; @@ -241,13 +255,8 @@ impl<'a> SignatureEncryptedSection<'a> { } // should not panic due to above check - let (non_identity_des, sig) = remaining - .as_slice() - .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(|_| DeserializationError::DeParseError)?; + let (non_identity_des, sig) = + remaining.split_at(remaining.len() - crypto_provider::ed25519::SIGNATURE_LENGTH); // All implementations only check for 64 bytes, and this will always result in a 64 byte signature. let expected_signature = @@ -279,16 +288,20 @@ impl<'a> SignatureEncryptedSection<'a> { VerificationMode::Signature, metadata_key, salt.into(), - SectionContents::new(self.contents.section_header, &ref_des), + // 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, + allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &SignedSectionIdentityResolutionMaterial, - ) -> Option<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>> { + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { self.contents.try_resolve_identity_and_decrypt::<P>( + allocator, identity_resolution_material.as_raw_resolution_material(), ) } @@ -297,6 +310,7 @@ impl<'a> SignatureEncryptedSection<'a> { #[cfg(test)] pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>( &self, + allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &SignedSectionIdentityResolutionMaterial, verification_material: &SignedSectionVerificationMaterial, ) -> Result< @@ -308,9 +322,9 @@ impl<'a> SignatureEncryptedSection<'a> { match section_identity_resolution_contents .try_match::<P>(identity_resolution_material.as_raw_resolution_material()) { - Some(identity_match) => { - self.try_deserialize(identity_match, verification_material).map_err(|e| e.into()) - } + Some(identity_match) => self + .try_deserialize(allocator, identity_match, verification_material) + .map_err(|e| e.into()), None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError), } } @@ -353,10 +367,16 @@ impl<V: VerificationError> From<V> for IdentityResolutionOrDeserializationError< /// detailed. #[derive(Debug, PartialEq, Eq)] pub(crate) enum DeserializationError<V: VerificationError> { - /// Parsing of decrypted DEs failed - DeParseError, /// 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> { @@ -396,13 +416,14 @@ impl<'a> MicEncryptedSection<'a> { /// Returns an error if the credential is incorrect or if the section data is malformed. pub(crate) fn try_deserialize<P>( &self, + allocator: &mut DeserializationArenaAllocator<'a>, identity_match: IdentityMatch<P>, verification_material: &UnsignedSectionVerificationMaterial, - ) -> Result<DecryptedSection, DeserializationError<MicVerificationError>> + ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>> where P: CryptoProvider, { - let raw_decrypted = self.contents.decrypt_ciphertext(identity_match); + let raw_decrypted = self.contents.decrypt_ciphertext(allocator, identity_match)?; let metadata_key = raw_decrypted.metadata_key_plaintext; let nonce = raw_decrypted.nonce; let remaining_des = raw_decrypted.plaintext_contents; @@ -421,10 +442,6 @@ impl<'a> MicEncryptedSection<'a> { .verify_truncated_left(&self.mic.mic) .map_err(|_e| MicVerificationError::MicMismatch)?; - // offset 2 for encryption info and identity DEs - let (_, ref_des) = parse_non_identity_des(2)(remaining_des.as_slice()) - .map_err(|_| DeserializationError::DeParseError)?; - let salt = self.contents.salt::<P>(); Ok(DecryptedSection::new( @@ -432,7 +449,8 @@ impl<'a> MicEncryptedSection<'a> { VerificationMode::Mic, metadata_key, salt.into(), - SectionContents::new(self.contents.section_header, &ref_des), + // offset 2 for encryption info and identity DEs + SectionContents::new(self.contents.section_header, remaining_des, 2), )) } @@ -440,9 +458,11 @@ impl<'a> MicEncryptedSection<'a> { #[cfg(feature = "devtools")] pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>( &self, + allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, - ) -> Option<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>> { + ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> { self.contents.try_resolve_identity_and_decrypt::<P>( + allocator, identity_resolution_material.as_raw_resolution_material(), ) } @@ -451,6 +471,7 @@ impl<'a> MicEncryptedSection<'a> { #[cfg(test)] pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>( &self, + allocator: &mut DeserializationArenaAllocator<'a>, identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial, verification_material: &UnsignedSectionVerificationMaterial, ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>> @@ -460,9 +481,9 @@ impl<'a> MicEncryptedSection<'a> { match section_identity_resolution_contents .try_match::<P>(identity_resolution_material.as_raw_resolution_material()) { - Some(identity_match) => { - self.try_deserialize(identity_match, verification_material).map_err(|e| e.into()) - } + Some(identity_match) => self + .try_deserialize(allocator, identity_match, verification_material) + .map_err(|e| e.into()), None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError), } } 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 f8d622c..af70228 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 @@ -12,10 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + 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::{ @@ -24,14 +29,12 @@ use crate::{ extended::{ deserialize::{ encrypted_section::{ - DeserializationError, EncryptedSectionContents, - IdentityResolutionOrDeserializationError, SignatureEncryptedSection, - SignatureVerificationError, + EncryptedSectionContents, IdentityResolutionOrDeserializationError, + SignatureEncryptedSection, SignatureVerificationError, }, parse_sections, test_stubs::IntermediateSectionExt, - CiphertextSection, EncryptedIdentityMetadata, OffsetDataElement, SectionContents, - VerificationMode, + CiphertextSection, DataElement, EncryptedIdentityMetadata, VerificationMode, }, section_signature_payload::*, serialize::{ @@ -40,9 +43,8 @@ use crate::{ }, 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; @@ -54,7 +56,7 @@ type KeyPair = np_ed25519::KeyPair<CryptoProviderImpl>; #[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); @@ -64,13 +66,14 @@ fn deserialize_signature_encrypted_correct_keys() { 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(SignedEncryptedSectionEncoder::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(); @@ -126,11 +129,11 @@ fn deserialize_signature_encrypted_correct_keys() { // plaintext is correct { - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new( 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_pair.public(), + 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().to_bytes(), ); let signed_identity_resolution_material = crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>(); @@ -138,16 +141,18 @@ fn deserialize_signature_encrypted_correct_keys() { contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>(); let identity_match = identity_resolution_contents .try_match::<CryptoProviderImpl>( - signed_identity_resolution_material.as_raw_resolution_material(), + &signed_identity_resolution_material.into_raw_resolution_material(), ) .unwrap(); - let decrypted = contents.contents.decrypt_ciphertext(identity_match); + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); + let decrypted = + contents.contents.decrypt_ciphertext(&mut allocator, identity_match).unwrap(); let mut expected = Vec::new(); - // battery de expected.extend_from_slice(txpower_de.de_header().serialize().as_slice()); - txpower_de.write_de_contents(&mut expected); + let _ = txpower_de.write_de_contents(&mut expected); let nonce: AesCtrNonce = section_salt.derive(Some(1.into())).unwrap(); @@ -169,24 +174,27 @@ fn deserialize_signature_encrypted_correct_keys() { expected.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes()); assert_eq!(metadata_key, decrypted.metadata_key_plaintext); assert_eq!(nonce, decrypted.nonce); - assert_eq!(&expected, decrypted.plaintext_contents.as_slice()); + assert_eq!(&expected, decrypted.plaintext_contents); } // deserialization to Section works { - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new( 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_pair.public(), + 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().to_bytes(), ); let signed_identity_resolution_material = crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>(); let signed_verification_material = crypto_material.signed_verification_material::<CryptoProviderImpl>(); + let arena = deserialization_arena!(); + let mut allocator = arena.into_allocator(); let section = contents .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut allocator, &signed_identity_resolution_material, &signed_verification_material, ) @@ -200,27 +208,23 @@ fn deserialize_signature_encrypted_correct_keys() { 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<_>>() ); @@ -279,7 +283,9 @@ fn deserialize_signature_encrypted_incorrect_pub_key_error() { fn deserialize_signature_encrypted_incorrect_salt_error() { // bad salt -> bad iv -> bad metadata key plaintext do_bad_deserialize_tampered( - IdentityResolutionOrDeserializationError::IdentityMatchingError, + DeserializeError::IdentityResolutionOrDeserializationError( + IdentityResolutionOrDeserializationError::IdentityMatchingError, + ), None, |_| {}, |adv_mut| adv_mut[5..21].copy_from_slice(&[0xFF; 16]), @@ -289,7 +295,9 @@ fn deserialize_signature_encrypted_incorrect_salt_error() { #[test] fn deserialize_signature_encrypted_tampered_signature_error() { do_bad_deserialize_tampered( - SignatureVerificationError::SignatureMismatch.into(), + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMismatch.into(), + ), None, |_| {}, // flip a bit in the middle of the signature @@ -303,7 +311,9 @@ fn deserialize_signature_encrypted_tampered_signature_error() { #[test] fn deserialize_signature_encrypted_tampered_ciphertext_error() { do_bad_deserialize_tampered( - SignatureVerificationError::SignatureMismatch.into(), + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMismatch.into(), + ), None, |_| {}, // flip a bit outside of the signature @@ -318,7 +328,9 @@ fn deserialize_signature_encrypted_tampered_ciphertext_error() { fn deserialize_signature_encrypted_missing_signature_de_error() { let section_len = 19 + 2 + 16 + 1 + 1; do_bad_deserialize_tampered( - SignatureVerificationError::SignatureMissing.into(), + DeserializeError::IdentityResolutionOrDeserializationError( + SignatureVerificationError::SignatureMissing.into(), + ), Some(section_len), |_| {}, |adv_mut| { @@ -333,7 +345,7 @@ fn deserialize_signature_encrypted_missing_signature_de_error() { #[test] fn deserialize_signature_encrypted_des_wont_parse() { do_bad_deserialize_tampered( - DeserializationError::DeParseError.into(), + DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd), Some(19 + 2 + 16 + 1 + 1 + 64 + 1), // add an impossible DE |section| section.try_push(0xFF).unwrap(), @@ -350,7 +362,7 @@ fn do_bad_deserialize_params<C: CryptoProvider>( 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; @@ -359,13 +371,14 @@ fn do_bad_deserialize_params<C: CryptoProvider>( 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(SignedEncryptedSectionEncoder::new( identity_type, section_salt, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -402,7 +415,9 @@ fn do_bad_deserialize_params<C: CryptoProvider>( .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) }), }); @@ -414,6 +429,7 @@ fn do_bad_deserialize_params<C: CryptoProvider>( error, contents .try_resolve_identity_and_deserialize::<C>( + &mut deserialization_arena!().into_allocator(), &signed_identity_resolution_material, &signed_verification_material, ) @@ -421,34 +437,40 @@ fn do_bad_deserialize_params<C: CryptoProvider>( ); } +#[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: IdentityResolutionOrDeserializationError<SignatureVerificationError>, +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(AdvertisementType::Encrypted); + let broadcast_cm = + SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key()); + let mut section_builder = adv_builder .section_builder(SignedEncryptedSectionEncoder::new( identity_type, section_salt, - &metadata_key, - &key_pair, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -504,24 +526,33 @@ fn do_bad_deserialize_tampered< contents ); - let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>( + let crypto_material = V1DiscoveryCredential::new( 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_pair.public(), + 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().to_bytes(), ); let identity_resolution_material = crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>(); let verification_material = crypto_material.signed_verification_material::<CryptoProviderImpl>(); - assert_eq!( - expected_error, - contents - .try_resolve_identity_and_deserialize::<CryptoProviderImpl>( - &identity_resolution_material, - &verification_material, - ) - .unwrap_err() - ); + match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>( + &mut deserialization_arena!().into_allocator(), + &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 beb205f..6ec7881 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs @@ -14,12 +14,14 @@ //! Deserialization for V1 advertisement contents +#[cfg(any(test, feature = "alloc"))] extern crate alloc; +#[cfg(any(test, feature = "alloc", feature = "devtools"))] use alloc::vec::Vec; use core::array::TryFromSliceError; -use core::slice; +use core::fmt::Debug; use nom::{branch, bytes, combinator, error, multi, number, sequence}; use strum::IntoEnumIterator as _; @@ -27,8 +29,13 @@ use array_view::ArrayView; use crypto_provider::CryptoProvider; use np_hkdf::v1_salt::{self, V1Salt}; +use crate::array_vec::ArrayVecOption; #[cfg(any(feature = "devtools", test))] -use crate::credential::v1::V1CryptoMaterial; +use crate::credential::v1::V1DiscoveryCryptoMaterial; +use crate::credential::v1::V1; +use crate::deserialization_arena::ArenaOutOfSpace; +#[cfg(any(feature = "devtools", test))] +use crate::deserialization_arena::DeserializationArenaAllocator; #[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}; @@ -41,9 +48,9 @@ use crate::{ 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, }; pub(crate) mod encrypted_section; @@ -62,19 +69,32 @@ mod test_stubs; pub(crate) fn parse_sections( adv_header: V1Header, adv_body: &[u8], -) -> Result<Vec<IntermediateSection>, nom::Err<error::Error<&[u8]>>> { +) -> Result< + ArrayVecOption<IntermediateSection, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>, + nom::Err<error::Error<&[u8]>>, +> { combinator::all_consuming(branch::alt(( // Public advertisement - multi::many_m_n( + multi::fold_many_m_n( 1, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, IntermediateSection::parser_public_section(), + ArrayVecOption::default, + |mut acc, item| { + acc.push(item); + acc + }, ), // Encrypted advertisement - multi::many_m_n( + multi::fold_many_m_n( 1, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, IntermediateSection::parser_encrypted_with_header(adv_header), + ArrayVecOption::default, + |mut acc, item| { + acc.push(item); + acc + }, ), )))(adv_body) .map(|(_rem, sections)| sections) @@ -82,17 +102,16 @@ pub(crate) fn parse_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_public_section() -> 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| { @@ -103,18 +122,23 @@ impl<'a> IntermediateSection<'a> { // - Public Identity DE, all other DEs 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) + // 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) } @@ -226,61 +250,59 @@ impl<'a> IntermediateSection<'a> { } #[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 } - } - - fn data_elements(&'_ self) -> DataElements<'_> { - DataElements { de_iter: self.data_elements.iter(), de_data: &self.de_data } +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 } } -} - -/// 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 } } @@ -293,11 +315,11 @@ impl PlaintextSection { } } -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() } } @@ -327,21 +349,21 @@ impl<C: CryptoProvider> From<V1Salt<C>> for RawV1Salt { /// Fully-parsed and verified decrypted contents from an encrypted section. #[derive(Debug, PartialEq, Eq)] -pub struct DecryptedSection { +pub struct DecryptedSection<'adv> { identity_type: EncryptedIdentityDataElementType, verification_mode: VerificationMode, - metadata_key: [u8; METADATA_KEY_LEN], + metadata_key: MetadataKey, salt: RawV1Salt, - contents: SectionContents, + contents: SectionContents<'adv>, } -impl DecryptedSection { +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 } } @@ -356,22 +378,24 @@ impl DecryptedSection { 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 + } +} + +impl<'adv> Section<'adv, DataElementParseError> for DecryptedSection<'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() } } @@ -386,14 +410,14 @@ impl<'a> CiphertextSection<'a> { /// and if successful, tries to decrypt this section. #[cfg(test)] pub(crate) fn try_resolve_identity_and_deserialize<C, P>( - &self, + &'a self, + allocator: &mut DeserializationArenaAllocator<'a>, crypto_material: &C, - ) -> Result<DecryptedSection, SectionDeserializeError> + ) -> Result<DecryptedSection<'a>, SectionDeserializeError> where - C: V1CryptoMaterial, + C: V1DiscoveryCryptoMaterial, P: CryptoProvider, { - use core::borrow::Borrow; match self { CiphertextSection::SignatureEncryptedIdentity(contents) => { let identity_resolution_material = @@ -402,7 +426,8 @@ impl<'a> CiphertextSection<'a> { contents .try_resolve_identity_and_deserialize::<P>( - identity_resolution_material.borrow(), + allocator, + &identity_resolution_material, &verification_material, ) .map_err(|e| e.into()) @@ -414,7 +439,8 @@ impl<'a> CiphertextSection<'a> { contents .try_resolve_identity_and_deserialize::<P>( - identity_resolution_material.borrow(), + allocator, + &identity_resolution_material, &verification_material, ) .map_err(|e| e.into()) @@ -424,21 +450,24 @@ impl<'a> CiphertextSection<'a> { /// Try decrypting into some raw bytes given some raw unsigned crypto-material. #[cfg(feature = "devtools")] - pub(crate) fn try_resolve_identity_and_decrypt<C: V1CryptoMaterial, P: CryptoProvider>( + pub(crate) fn try_resolve_identity_and_decrypt< + C: V1DiscoveryCryptoMaterial, + P: CryptoProvider, + >( &self, + allocator: &mut DeserializationArenaAllocator<'a>, crypto_material: &C, - ) -> Option<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>> { - use core::borrow::Borrow; + ) -> 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>(identity_resolution_material.borrow()) + x.try_resolve_identity_and_decrypt::<P>(allocator, &identity_resolution_material) } CiphertextSection::MicEncryptedIdentity(x) => { let identity_resolution_material = crypto_material.unsigned_identity_resolution_material::<P>(); - x.try_resolve_identity_and_decrypt::<P>(identity_resolution_material.borrow()) + x.try_resolve_identity_and_decrypt::<P>(allocator, &identity_resolution_material) } } } @@ -459,6 +488,8 @@ pub enum SectionDeserializeError { IncorrectCredential, /// Section data is malformed ParseError, + /// The given arena is not large enough to hold the decrypted contents + ArenaOutOfSpace, } #[cfg(test)] @@ -492,10 +523,10 @@ impl From<IdentityResolutionOrDeserializationError<SignatureVerificationError>> impl From<DeserializationError<MicVerificationError>> for SectionDeserializeError { fn from(mic_deserialization_error: DeserializationError<MicVerificationError>) -> Self { match mic_deserialization_error { - DeserializationError::DeParseError => Self::ParseError, DeserializationError::VerificationError(MicVerificationError::MicMismatch) => { Self::IncorrectCredential } + DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace, } } } @@ -505,43 +536,78 @@ impl From<DeserializationError<SignatureVerificationError>> for SectionDeseriali signature_deserialization_error: DeserializationError<SignatureVerificationError>, ) -> Self { match signature_deserialization_error { - DeserializationError::DeParseError - | DeserializationError::VerificationError( + DeserializationError::VerificationError( SignatureVerificationError::SignatureMissing, ) => Self::ParseError, DeserializationError::VerificationError( SignatureVerificationError::SignatureMismatch, ) => Self::IncorrectCredential, + DeserializationError::ArenaOutOfSpace => Self::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) +/// 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 + } + } } } @@ -657,56 +723,14 @@ impl<'d> ProtoDataElement<'d> { 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 } } } @@ -730,7 +754,7 @@ pub enum VerificationMode { pub struct EncryptedSectionIdentity { identity_type: EncryptedIdentityDataElementType, validation_mode: VerificationMode, - metadata_key: [u8; METADATA_KEY_LEN], + metadata_key: MetadataKey, } impl EncryptedSectionIdentity { @@ -743,52 +767,37 @@ impl EncryptedSectionIdentity { 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)] @@ -836,8 +845,11 @@ impl EncryptionInfo { } fn salt(&self) -> RawV1Salt { - // should never panic - RawV1Salt(self.bytes[Self::TOTAL_DE_LEN - 16..].try_into().ok().unwrap()) + RawV1Salt( + self.bytes[Self::TOTAL_DE_LEN - 16..] + .try_into() + .expect("a 16 byte slice will always fit into a 16 byte array"), + ) } } @@ -882,7 +894,7 @@ impl PublicIdentity { 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) 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 dd88f74..41eb054 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + 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}; @@ -35,31 +38,29 @@ fn parse_adv_ext_public_identity() { // de 2 byte header, type 6, len 1 adv_body.extend_from_slice(&[0x81, 0x06, 0x01]); + let parsed_sections = + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap().into_vec(); assert_eq!( - Ok(vec![PlaintextSection::new( + vec![IntermediateSection::from(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(),]), - parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + 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!( + &expected_des[..], + &parsed_sections[0].as_plaintext().unwrap().collect_data_elements().unwrap() ); } @@ -127,54 +128,50 @@ fn parse_adv_ext_identity() { 0x11, 0x11, ], }; - assert_eq!( - Ok(vec![ - SignatureEncryptedSection { - contents: EncryptedSectionContents { - section_header: 47, - adv_header, - encryption_info: encryption_info.clone(), - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x01], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Private, - }, - // skip section header + encryption info + identity header -> end of section - all_ciphertext: &adv_body[1 + 19 + 2..48], + let expected_sections = [ + SignatureEncryptedSection { + contents: EncryptedSectionContents { + section_header: 47, + adv_header, + encryption_info: encryption_info.clone(), + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x01], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Private, }, - } - .into(), - SignatureEncryptedSection { - contents: EncryptedSectionContents { - section_header: 48, - adv_header, - encryption_info: encryption_info.clone(), - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x02], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Trusted, - }, - all_ciphertext: &adv_body[48 + 1 + 19 + 2..97], + // skip section header + encryption info + identity header -> end of section + all_ciphertext: &adv_body[1 + 19 + 2..48], + }, + }, + SignatureEncryptedSection { + contents: EncryptedSectionContents { + section_header: 48, + adv_header, + encryption_info: encryption_info.clone(), + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x02], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Trusted, }, - } - .into(), - SignatureEncryptedSection { - contents: EncryptedSectionContents { - section_header: 49, - adv_header, - encryption_info, - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x04], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Provisioned, - }, - all_ciphertext: &adv_body[97 + 1 + 19 + 2..], + all_ciphertext: &adv_body[48 + 1 + 19 + 2..97], + }, + }, + SignatureEncryptedSection { + contents: EncryptedSectionContents { + section_header: 49, + adv_header, + encryption_info, + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x04], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Provisioned, }, - } - .into() - ]), - parse_sections(adv_header, &adv_body) - ); + all_ciphertext: &adv_body[97 + 1 + 19 + 2..], + }, + }, + ]; + let parsed_sections = parse_sections(adv_header, &adv_body).unwrap(); + assert_eq!(parsed_sections.into_vec(), expected_sections.map(IntermediateSection::from)); } #[test] @@ -256,57 +253,53 @@ fn parse_adv_ext_mic_identity() { 0x11, 0x11, ], }; - assert_eq!( - Ok(vec![ - MicEncryptedSection { - contents: EncryptedSectionContents { - section_header: 63, - adv_header, - encryption_info: encryption_info.clone(), - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x01], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Private, - }, - // skip section header + encryption info + identity header -> end of ciphertext - all_ciphertext: &adv_body[1 + 19 + 2..64 - 16], + let expected_sections = [ + MicEncryptedSection { + contents: EncryptedSectionContents { + section_header: 63, + adv_header, + encryption_info: encryption_info.clone(), + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x01], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Private, }, - mic: SectionMic::from([0x33; 16]), - } - .into(), - MicEncryptedSection { - contents: EncryptedSectionContents { - section_header: 64, - adv_header, - encryption_info: encryption_info.clone(), - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x02], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Trusted, - }, - all_ciphertext: &adv_body[64 + 1 + 19 + 2..129 - 16], + // skip section header + encryption info + identity header -> end of ciphertext + all_ciphertext: &adv_body[1 + 19 + 2..64 - 16], + }, + mic: SectionMic::from([0x33; 16]), + }, + MicEncryptedSection { + contents: EncryptedSectionContents { + section_header: 64, + adv_header, + encryption_info: encryption_info.clone(), + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x02], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Trusted, }, - mic: SectionMic::from([0x66; 16]), - } - .into(), - MicEncryptedSection { - contents: EncryptedSectionContents { - section_header: 65, - adv_header, - encryption_info, - identity: EncryptedIdentityMetadata { - header_bytes: [0x90, 0x04], - offset: 1_usize.into(), - identity_type: EncryptedIdentityDataElementType::Provisioned, - }, - all_ciphertext: &adv_body[129 + 1 + 19 + 2..195 - 16], + all_ciphertext: &adv_body[64 + 1 + 19 + 2..129 - 16], + }, + mic: SectionMic::from([0x66; 16]), + }, + MicEncryptedSection { + contents: EncryptedSectionContents { + section_header: 65, + adv_header, + encryption_info, + identity: EncryptedIdentityMetadata { + header_bytes: [0x90, 0x04], + offset: 1_u8.into(), + identity_type: EncryptedIdentityDataElementType::Provisioned, }, - mic: SectionMic::from([0x99; 16]), - } - .into(), - ]), - parse_sections(adv_header, &adv_body) - ); + all_ciphertext: &adv_body[129 + 1 + 19 + 2..195 - 16], + }, + mic: SectionMic::from([0x99; 16]), + }, + ]; + let parsed_sections = parse_sections(adv_header, &adv_body).unwrap(); + assert_eq!(parsed_sections.into_vec(), &expected_sections.map(IntermediateSection::from)); } #[test] @@ -441,11 +434,8 @@ fn public_identity_not_first_de_error() { adv_body.push(0x03); assert_eq!( - Err(nom::Err::Error(error::Error { - input: &adv_body[1..], - code: error::ErrorKind::Verify - })), - parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Verify }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() ); } @@ -469,12 +459,15 @@ fn invalid_public_section() { adv_body.push(0x03); } if add_des { - adv_body[0] += 3; - adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); + adv_body[0] += 1; + adv_body.extend_from_slice(&[0x81]); } if remove_section_len { - adv_body.remove(0); + let _ = adv_body.remove(0); } + + let ordered_adv = adv_body.clone(); + if shuffle { adv_body.shuffle(&mut rng); } @@ -484,8 +477,8 @@ fn invalid_public_section() { // * 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 || (shuffle && adv_body.len() > 2) { - parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + if remove_section_len || !add_public_identity || (ordered_adv != adv_body) { + let _ = parse_sections(V1Header { header_byte: 0x20 }, &adv_body) .expect_err("Expected to fail because of missing section length or identity"); } } @@ -504,12 +497,11 @@ fn public_identity_after_public_identity_error() { // 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!( - Err(nom::Err::Error(error::Error { - input: &adv_body[1..], - code: error::ErrorKind::Verify - })), - parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + DataElementParseError::DuplicateIdentityDataElement, + sections[0].as_plaintext().unwrap().collect_data_elements().unwrap_err() ); } @@ -526,12 +518,12 @@ fn salt_public_identity_error() { adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); assert_eq!( - Err(nom::Err::Error(error::Error { + 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) + }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() ); } @@ -553,12 +545,12 @@ fn salt_mic_public_identity_error() { adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]); assert_eq!( - Err(nom::Err::Error(error::Error { + 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) + }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() ); } @@ -566,8 +558,8 @@ fn salt_mic_public_identity_error() { fn parse_adv_no_identity() { let adv_body = vec![0x55, 0x01, 0x02, 0x03, 0x04, 0x05]; assert_eq!( - Err(nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Eof })), - parse_sections(V1Header { header_byte: 0x20 }, &adv_body) + nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Eof }), + parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err() ); } @@ -685,8 +677,8 @@ fn parse_adv_signature_encrypted_plaintext_mix() { let adv_header = V1Header { header_byte: 0x20 }; assert_eq!( - Err(nom::Err::Error(error::Error { input: &adv_body[11..], code: error::ErrorKind::Eof })), - parse_sections(adv_header, &adv_body) + nom::Err::Error(error::Error { input: &adv_body[11..], code: error::ErrorKind::Eof }), + parse_sections(adv_header, &adv_body).unwrap_err() ); } @@ -704,8 +696,8 @@ impl<'a> From<MicEncryptedSection<'a>> for IntermediateSection<'a> { } } -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 15df16a..5fa2dbe 100644 --- a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs +++ b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs @@ -12,32 +12,35 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; +use crate::deserialization_arena; +use crate::deserialization_arena::DeserializationArena; 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, MicEncryptedSectionEncoder, PublicSectionEncoder, SectionBuilder, SignedEncryptedSectionEncoder, WriteDataElement, }, MAX_DE_LEN, }, - parse_adv_header, AdvHeader, + parse_adv_header, AdvHeader, WithMatchedCredential, }; +use core::borrow::Borrow; +use core::convert::Into; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; use rand::{seq::SliceRandom as _, Rng as _, SeedableRng as _}; @@ -52,7 +55,6 @@ fn deserialize_public_identity_section() { PublicSectionEncoder::default(), PlaintextIdentityMode::Public, 1, - 1, ); } @@ -71,17 +73,32 @@ fn deserialize_mic_encrypted_rand_identities_finds_correct_one() { // 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 = @@ -89,18 +106,19 @@ fn deserialize_mic_encrypted_rand_identities_finds_correct_one() { 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(MicEncryptedSectionEncoder::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(); @@ -116,29 +134,33 @@ fn deserialize_mic_encrypted_rand_identities_finds_correct_one() { 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_slice(), &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 @@ -159,27 +181,31 @@ fn deserialize_signature_encrypted_rand_identities_finds_correct_one() { // 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 = @@ -187,19 +213,23 @@ fn deserialize_signature_encrypted_rand_identities_finds_correct_one() { 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(SignedEncryptedSectionEncoder::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(); @@ -213,31 +243,35 @@ fn deserialize_signature_encrypted_rand_identities_finds_correct_one() { panic!("incorrect header"); }; + 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_slice(), &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 @@ -260,17 +294,24 @@ fn deserialize_encrypted_no_matching_identities_finds_nothing() { // 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 = @@ -278,30 +319,35 @@ fn deserialize_encrypted_no_matching_identities_finds_nothing() { 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 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 = SignedEncryptedSectionEncoder::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 = MicEncryptedSectionEncoder::new_random_salt( + let identity = MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt( &mut crypto_rng, identity_type, - &metadata_key, - &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(); }; @@ -316,9 +362,10 @@ fn deserialize_encrypted_no_matching_identities_finds_nothing() { 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()); @@ -326,138 +373,30 @@ fn deserialize_encrypted_no_matching_identities_finds_nothing() { } #[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 ); @@ -474,7 +413,7 @@ fn do_deserialize_zero_section_header() { } else { panic!("incorrect header"); }; - parse_sections(v1_header, remaining).expect_err("Expected an error"); + let _ = parse_sections(v1_header, remaining).expect_err("Expected an error"); } #[test] @@ -487,7 +426,7 @@ fn do_deserialize_empty_section() { } else { panic!("incorrect header"); }; - parse_sections(v1_header, remaining).expect_err("Expected an error"); + let _ = parse_sections(v1_header, remaining).expect_err("Expected an error"); } #[test] @@ -526,7 +465,7 @@ fn try_deserialize_over_max_number_of_public_sections() { } else { panic!("incorrect header"); }; - parse_sections(v1_header, remaining) + let _ = parse_sections(v1_header, remaining) .expect_err("Expected an error because number of sections is over limit"); } @@ -542,7 +481,6 @@ pub(crate) fn random_de<R: rand::Rng>(rng: &mut R) -> GenericDataElement { 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(); @@ -550,8 +488,9 @@ fn do_deserialize_section_unencrypted<I: serialize::SectionEncoder>( 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(); @@ -569,37 +508,29 @@ fn do_deserialize_section_unencrypted<I: serialize::SectionEncoder>( 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::SectionEncoder>( +fn fill_section_random_des<'adv, R: rand::Rng, I: serialize::SectionEncoder>( mut rng: &mut R, - section_builder: &mut SectionBuilder<I>, + sink: &'adv mut Vec<u8>, + section_builder: &mut SectionBuilder<&mut AdvBuilder, 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(); @@ -607,55 +538,65 @@ fn fill_section_random_des<R: rand::Rng, I: serialize::SectionEncoder>( 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; + 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_des.push(OffsetDataElement { - offset: (index as usize + de_offset).into(), + 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_len, - start_of_contents: orig_len, + contents: &sink[range], }); - orig_des.push(de); } - (expected_de_data, expected_des, orig_des) + (expected_des, orig_des) } fn total_de_len(des: &[GenericDataElement]) -> usize { des.iter() .map(|de| { let mut buf = vec![]; - de.write_de_contents(&mut buf); + let _ = de.write_de_contents(&mut buf); de.de_header().serialize().len() + buf.len() }) .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>( + arena: 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() { - let crypto_material = c.crypto_material(); - match section.try_resolve_identity_and_deserialize::<_, P>(crypto_material) { - Ok(s) => return Ok(Some((s, c.matched()))), + let mut allocator = arena.into_allocator(); + for (crypto_material, match_data) in cred_source.iter() { + match section + .try_resolve_identity_and_deserialize::<_, P>(&mut allocator, 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) + } }, } } @@ -676,4 +617,6 @@ fn build_dummy_advertisement_sections(number_of_sections: usize) -> AdvBuilder { 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 5d18daa..68b16c3 100644 --- a/nearby/presence/np_adv/src/extended/mod.rs +++ b/nearby/presence/np_adv/src/extended/mod.rs @@ -56,8 +56,8 @@ impl DeLength { /// 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 } } @@ -65,7 +65,7 @@ impl TryFrom<u8> for DeLength { 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 a89843b..d8ec93c 100644 --- a/nearby/presence/np_adv/src/extended/section_signature_payload.rs +++ b/nearby/presence/np_adv/src/extended/section_signature_payload.rs @@ -16,7 +16,8 @@ //! after the included context bytes, and utilities for //! performing signatures and signature verification. -use crate::extended::{deserialize::EncryptionInfo, METADATA_KEY_LEN}; +use crate::extended::deserialize::EncryptionInfo; +use crate::MetadataKey; use crate::NP_SVC_UUID; use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider}; use sink::{Sink, SinkWriter}; @@ -48,7 +49,7 @@ enum AfterIVInfo<'a> { /// 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], [u8; METADATA_KEY_LEN], &'a [u8]), + IdentityHeaderMetadataKeyAndRemainder([u8; 2], MetadataKey, &'a [u8]), } const ADV_SIGNATURE_CONTEXT: np_ed25519::SignatureContext = { @@ -67,7 +68,7 @@ impl<'a> SectionSignaturePayload<'a> { encryption_info: &'a [u8; EncryptionInfo::TOTAL_DE_LEN], nonce_ref: &'a AesCtrNonce, identity_header: [u8; 2], - plaintext_metadata_key: [u8; METADATA_KEY_LEN], + plaintext_metadata_key: MetadataKey, raw_plaintext_remainder: &'a [u8], ) -> Self { Self { @@ -141,7 +142,7 @@ impl<'a> SinkWriter for SectionSignaturePayload<'a> { remainder, ) => { sink.try_extend_from_slice(&identity_header)?; - sink.try_extend_from_slice(&metadata_key)?; + 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 36e4e5f..ee44c75 100644 --- a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; use crate::extended::serialize::section_tests::{fill_section_builder, DummyDataElement}; diff --git a/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs b/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs index eda8b69..cb6c9ef 100644 --- a/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/de_header_tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use super::*; use crate::extended::deserialize; use core::cmp; diff --git a/nearby/presence/np_adv/src/extended/serialize/mod.rs b/nearby/presence/np_adv/src/extended/serialize/mod.rs index c3b9ed3..9d82273 100644 --- a/nearby/presence/np_adv/src/extended/serialize/mod.rs +++ b/nearby/presence/np_adv/src/extended/serialize/mod.rs @@ -54,8 +54,10 @@ //! //! ``` //! 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}; @@ -67,15 +69,20 @@ //! // 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(MicEncryptedSectionEncoder::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(); @@ -106,6 +113,10 @@ //! ``` 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 @@ use crate::{ section_signature_payload::*, to_array_view, DeLength, BLE_ADV_SVC_CONTENT_LEN, NP_ADV_MAX_SECTION_LEN, }, - DeLengthOutOfRange, 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}, @@ -151,6 +161,12 @@ pub struct AdvBuilder { advertisement_type: AdvertisementType, } +impl AsMut<AdvBuilder> for AdvBuilder { + fn as_mut(&mut self) -> &mut AdvBuilder { + self + } +} + impl AdvBuilder { /// Build an [AdvBuilder]. pub fn new(advertisement_type: AdvertisementType) -> Self { @@ -160,17 +176,10 @@ impl AdvBuilder { Self { adv, section_count: 0, advertisement_type } } - /// Create a section builder. - /// - /// 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<SE: SectionEncoder>( - &mut self, - section_encoder: SE, - ) -> Result<SectionBuilder<SE>, AddSectionError> { + fn prepare_section_builder_buffer_and_de_offset<SE: SectionEncoder>( + &self, + ) -> Result<(CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>, DataElementOffset), AddSectionError> + { // section header and identity prefix let prefix_len = 1 + SE::PREFIX_LEN; let minimum_section_len = prefix_len + SE::SUFFIX_LEN; @@ -189,20 +198,53 @@ impl AdvBuilder { return Err(AddSectionError::IncompatibleSectionType); } - let mut section = tinyvec::ArrayVec::new(); + let mut section: tinyvec::ArrayVec<[u8; 249]> = tinyvec::ArrayVec::new(); // placeholder for section header and identity prefix section.resize(prefix_len, 0); - Ok(SectionBuilder { - section: CapacityLimitedVec { - vec: section, - // won't underflow: checked above - capacity: available_len - SE::SUFFIX_LEN, - }, - section_encoder, - adv_builder: self, - next_de_offset: SE::INITIAL_DE_OFFSET, - }) + let section = CapacityLimitedVec { + vec: section, + // won't underflow: checked above + capacity: available_len - SE::SUFFIX_LEN, + }; + let next_de_offset = SE::INITIAL_DE_OFFSET; + Ok((section, next_de_offset)) + } + + /// Create a section builder whose contents may be added to this advertisement. + /// + /// 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<SE: SectionEncoder>( + &mut self, + section_encoder: SE, + ) -> Result<SectionBuilder<&mut AdvBuilder, SE>, AddSectionError> { + let (section, next_de_offset) = + self.prepare_section_builder_buffer_and_de_offset::<SE>()?; + + Ok(SectionBuilder { section, section_encoder, adv_builder: self, next_de_offset }) + } + + /// Create a section builder which actually takes ownership of this advertisement builder. + /// + /// This is unlike `AdvertisementBuilder#section_builder` in that the returned section + /// builder will take ownership of this advertisement builder, if the operation was + /// successful. Otherwise, this advertisement builder will be returned back to the + /// caller unaltered as part of the `Err` arm. + #[allow(clippy::result_large_err)] + pub fn into_section_builder<SE: SectionEncoder>( + self, + section_encoder: SE, + ) -> Result<SectionBuilder<AdvBuilder, SE>, (AdvBuilder, AddSectionError)> { + match self.prepare_section_builder_buffer_and_de_offset::<SE>() { + Ok((section, next_de_offset)) => { + Ok(SectionBuilder { section, section_encoder, adv_builder: self, next_de_offset }) + } + Err(err) => Err((self, err)), + } } /// Convert the builder into an encoded advertisement. @@ -210,6 +252,12 @@ impl AdvBuilder { EncodedAdvertisement { adv: to_array_view(self.adv) } } + /// Gets the current number of sections added to this advertisement + /// builder, not counting any outstanding SectionBuilders. + pub fn section_count(&self) -> usize { + self.section_count + } + /// Add the section, which must have come from a SectionBuilder generated from this, into this /// advertisement. fn add_section(&mut self, section: EncodedSection) { @@ -235,6 +283,22 @@ pub enum AddSectionError { IncompatibleSectionType, } +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") + } + } + } +} + /// An encoded NP V1 advertisement, starting with the NP advertisement header byte. #[derive(Debug, PartialEq, Eq)] pub struct EncodedAdvertisement { @@ -253,24 +317,56 @@ type EncodedSection = ArrayView<u8, NP_ADV_MAX_SECTION_LEN>; /// Accumulates data elements and encodes them into a section. #[derive(Debug)] -pub struct SectionBuilder<'a, SE: SectionEncoder> { +pub struct SectionBuilder<R: AsMut<AdvBuilder>, SE: SectionEncoder> { /// Contains the section header, the identity-specified overhead, and any DEs added pub(crate) section: CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>, section_encoder: SE, - /// mut ref to enforce only one active section builder at a time - adv_builder: &'a mut AdvBuilder, + /// mut ref-able to enforce only one active section builder at a time + adv_builder: R, next_de_offset: DataElementOffset, } -impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { +impl<'a, SE: SectionEncoder> SectionBuilder<&'a mut AdvBuilder, SE> { /// Add this builder to the advertisement that created it. pub fn add_to_advertisement(self) { - let adv_builder = self.adv_builder; + let _ = self.add_to_advertisement_internal(); + } +} + +impl<SE: SectionEncoder> SectionBuilder<AdvBuilder, SE> { + /// Gets the 0-based index of the section currently under construction + /// in the context of the containing advertisement. + pub fn section_index(&self) -> usize { + self.adv_builder.section_count() + } + /// Add this builder to the advertisement that created it, + /// and returns the containing advertisement back to the caller. + pub fn add_to_advertisement(self) -> AdvBuilder { + self.add_to_advertisement_internal() + } +} + +impl<R: AsMut<AdvBuilder>, SE: SectionEncoder> SectionBuilder<R, SE> { + /// Add this builder to the advertisement that created it. + /// Returns the mut-refable to the advertisement builder + /// which the contents of this section builder were added to. + fn add_to_advertisement_internal(mut self) -> R { + let adv_builder = self.adv_builder.as_mut(); + let adv_builder_header_byte = adv_builder.header_byte(); adv_builder.add_section(Self::build_section( + adv_builder_header_byte, self.section.into_inner(), self.section_encoder, - adv_builder, - )) + )); + self.adv_builder + } + + /// Gets the derived salt which will be employed for the next DE offset. + /// + /// Suitable for scenarios (like FFI) where a closure would be inappropriate + /// for DE construction, and interaction with the client is preferred. + pub fn next_de_salt(&self) -> SE::DerivedSalt { + self.section_encoder.de_salt(self.next_de_offset) } /// Add a data element to the section with a closure that returns a `Result`. @@ -280,8 +376,7 @@ impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { &mut self, build_de: F, ) -> Result<(), AddDataElementError<E>> { - let writer = build_de(self.section_encoder.de_salt(self.next_de_offset)) - .map_err(AddDataElementError::BuildDeError)?; + let writer = build_de(self.next_de_salt()).map_err(AddDataElementError::BuildDeError)?; let orig_len = self.section.len(); // since we own the writer, and it's immutable, no race risk writing header w/ len then @@ -304,10 +399,10 @@ impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { 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 ); } @@ -331,9 +426,9 @@ impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { /// /// Implemented without self to avoid partial-move issues. fn build_section( + adv_builder_header_byte: u8, mut section_contents: tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, 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() + SE::SUFFIX_LEN, 0); @@ -346,7 +441,7 @@ impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> { .expect("section length is always <=255 and non-negative"); section_encoder.postprocess( - adv_builder.header_byte(), + adv_builder_header_byte, section_contents[0], &mut section_contents[1..], ); @@ -437,48 +532,44 @@ impl SectionEncoder for PublicSectionEncoder { /// Encrypts the data elements and protects integrity with an np_ed25519 signature /// using key material derived from an NP identity. -pub struct SignedEncryptedSectionEncoder<'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> SignedEncryptedSectionEncoder<'a, C> { - /// Build a [SignedEncryptedSectionEncoder] 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 [SignedEncryptedSectionEncoder] 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> SectionEncoder for SignedEncryptedSectionEncoder<'a, C> { +impl<C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<C> { const PREFIX_LEN: usize = EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN; /// Ed25519 signature @@ -502,7 +593,7 @@ impl<'a, C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<'a, 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)) @@ -515,7 +606,7 @@ impl<'a, C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<'a, before_sig.split_at(EncryptionInfo::TOTAL_DE_LEN); let encryption_info: &[u8; EncryptionInfo::TOTAL_DE_LEN] = - encryption_info.try_into().unwrap(); + encryption_info.try_into().expect("encryption info should always be the correct size"); // we need to sign the 16-byte IV, which doesn't have to actually fit in the adv, but we // don't need a bigger buffer here since we won't be including the 66 bytes for the sig + @@ -531,14 +622,14 @@ impl<'a, C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<'a, 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>; @@ -550,54 +641,55 @@ impl<'a, C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<'a, /// Encrypts the data elements and protects integrity with a MIC using key material derived from /// an NP identity. -pub struct MicEncryptedSectionEncoder<'a, C: CryptoProvider> { +pub struct MicEncryptedSectionEncoder<C: CryptoProvider> { identity_type: EncryptedIdentityDataElementType, salt: V1Salt<C>, - identity_payload: &'a [u8; 16], + metadata_key: MetadataKey, aes_key: Aes128Key, mic_hmac_key: np_hkdf::NpHmacSha256Key<C>, } -impl<'a, C: CryptoProvider> MicEncryptedSectionEncoder<'a, C> { - /// Build a [MicEncryptedSectionEncoder] from the provided identity info with a random salt. - pub fn new_random_salt( +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, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, + crypto_material: &B, ) -> Self { let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into(); - Self::new(identity_type, salt, identity_payload, keys) + Self::new(identity_type, salt, crypto_material) } + /// Build a [MicEncryptedSectionEncoder] from the provided identity info. - pub(crate) fn new( + pub(crate) fn new<B: BroadcastCryptoMaterial<V1>>( identity_type: EncryptedIdentityDataElementType, salt: V1Salt<C>, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, + crypto_material: &B, ) -> Self { - MicEncryptedSectionEncoder { - identity_type, - salt, - identity_payload, - aes_key: keys.aes_key(), - mic_hmac_key: keys.hmac_key(), - } + 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 [MicEncryptedSectionEncoder] from the provided identity info. Exposed outside of this crate for - /// testing purposes only. + + /// 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( + pub fn new_for_testing<B: BroadcastCryptoMaterial<V1>>( identity_type: EncryptedIdentityDataElementType, salt: V1Salt<C>, - identity_payload: &'a [u8; 16], - keys: &impl np_hkdf::UnsignedSectionKeys<C>, + crypto_material: &B, ) -> Self { - Self::new(identity_type, salt, identity_payload, keys) + Self::new(identity_type, salt, crypto_material) } } -impl<'a, C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<'a, C> { +impl<C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<C> { const PREFIX_LEN: usize = EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN; /// Length of mic @@ -625,9 +717,9 @@ impl<'a, C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<'a, C> .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); + 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.identity_payload); + 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)) @@ -636,7 +728,7 @@ impl<'a, C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<'a, C> 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]); + 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 @@ -777,11 +869,12 @@ impl DeHeader { 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 74a7eb0..3a6a5f9 100644 --- a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs +++ b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs @@ -12,9 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; +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; @@ -59,6 +65,7 @@ fn mic_encrypted_identity_section_random_des() { .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(); @@ -66,12 +73,13 @@ fn mic_encrypted_identity_section_random_des() { 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(MicEncryptedSectionEncoder::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( @@ -86,9 +94,7 @@ fn mic_encrypted_identity_section_random_des() { 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 = [ @@ -121,14 +127,14 @@ fn mic_encrypted_identity_section_random_des() { &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); + let _ = de.write_de_contents(&mut plaintext); } - cipher.encrypt(&mut plaintext); + cipher.apply_keystream(&mut plaintext); let ciphertext = plaintext; hmac_input.extend_from_slice(&ciphertext); @@ -160,7 +166,7 @@ fn signature_encrypted_identity_section_random_des() { 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(); @@ -169,13 +175,17 @@ fn signature_encrypted_identity_section_random_des() { 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(SignedEncryptedSectionEncoder::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( @@ -200,9 +210,7 @@ fn signature_encrypted_identity_section_random_des() { + 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 = [ @@ -226,7 +234,7 @@ fn signature_encrypted_identity_section_random_des() { let mut section_body = Vec::new(); for de in extra_des { section_body.extend_from_slice(de.de_header().serialize().as_slice()); - de.write_de_contents(&mut section_body); + let _ = de.write_de_contents(&mut section_body); } let sig_payload = SectionSignaturePayload::from_deserialized_parts( @@ -239,7 +247,7 @@ fn signature_encrypted_identity_section_random_des() { §ion_body, ); - let mut plaintext = metadata_key.as_slice().to_vec(); + 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()); @@ -248,7 +256,7 @@ fn signature_encrypted_identity_section_random_des() { &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]; @@ -268,13 +276,15 @@ fn section_builder_too_full_doesnt_advance_de_index() { 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(MicEncryptedSectionEncoder::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 = @@ -309,7 +319,7 @@ fn section_builder_too_full_doesnt_advance_de_index() { 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 @@ -324,7 +334,7 @@ fn section_builder_too_full_doesnt_advance_de_index() { 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 @@ -363,13 +373,15 @@ fn section_builder_build_de_error_doesnt_advance_de_index() { 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(MicEncryptedSectionEncoder::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 = @@ -398,7 +410,7 @@ fn section_builder_build_de_error_doesnt_advance_de_index() { 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 @@ -413,7 +425,7 @@ fn section_builder_build_de_error_doesnt_advance_de_index() { 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 @@ -429,13 +441,15 @@ fn add_multiple_de_correct_de_offsets_mic_encrypted_identity() { 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(MicEncryptedSectionEncoder::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 = @@ -458,7 +472,7 @@ fn add_multiple_de_correct_de_offsets_mic_encrypted_identity() { 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 @@ -473,7 +487,7 @@ fn add_multiple_de_correct_de_offsets_mic_encrypted_identity() { 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 @@ -489,15 +503,17 @@ fn add_multiple_de_correct_de_offsets_signature_encrypted_identity() { 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(SignedEncryptedSectionEncoder::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 = @@ -520,7 +536,7 @@ fn add_multiple_de_correct_de_offsets_signature_encrypted_identity() { 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 @@ -534,7 +550,7 @@ fn add_multiple_de_correct_de_offsets_signature_encrypted_identity() { &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 @@ -552,16 +568,17 @@ fn signature_encrypted_section_de_lengths_allow_room_for_suffix() { 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(SignedEncryptedSectionEncoder::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(); @@ -614,21 +631,22 @@ fn serialize_max_number_of_public_sections() { } 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 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(MicEncryptedSectionEncoder::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -640,7 +658,7 @@ fn do_mic_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_ 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 = [ @@ -672,14 +690,14 @@ fn do_mic_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_ &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); + let _ = de.write_de_contents(&mut plaintext); } - cipher.encrypt(&mut plaintext); + cipher.apply_keystream(&mut plaintext); let ciphertext = plaintext; hmac_input.extend_from_slice(&ciphertext); @@ -698,7 +716,7 @@ fn do_mic_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_ } 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(); @@ -706,15 +724,16 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); let key_pair = KeyPair::generate(); + 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(SignedEncryptedSectionEncoder::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(); @@ -728,7 +747,7 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( + 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 = [ @@ -748,7 +767,7 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( let mut section_body = Vec::new(); for de in extra_des { section_body.extend_from_slice(de.de_header().serialize().as_slice()); - de.write_de_contents(&mut section_body); + let _ = de.write_de_contents(&mut section_body); } let nonce = section_salt.derive(Some(1.into())).unwrap(); @@ -763,7 +782,7 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( §ion_body, ); - let mut plaintext = metadata_key.as_slice().to_vec(); + 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()); @@ -771,7 +790,7 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( &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]; @@ -786,7 +805,7 @@ fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>( /// Write `section_contents_len` bytes of DE and header into `section_builder` pub(crate) fn fill_section_builder<I: SectionEncoder>( section_contents_len: usize, - section_builder: &mut SectionBuilder<I>, + section_builder: &mut SectionBuilder<&mut AdvBuilder, I>, ) { // DEs can only go up to 127, so we'll need multiple for long sections for _ in 0..(section_contents_len / 100) { @@ -844,9 +863,14 @@ pub(crate) trait SectionBuilderExt { fn into_section(self) -> EncodedSection; } -impl<'a, I: SectionEncoder> SectionBuilderExt for SectionBuilder<'a, I> { +impl<R: AsMut<AdvBuilder>, I: SectionEncoder> SectionBuilderExt for SectionBuilder<R, I> { /// Convenience method for tests - fn into_section(self) -> EncodedSection { - Self::build_section(self.section.into_inner(), self.section_encoder, self.adv_builder) + fn into_section(mut self) -> EncodedSection { + let adv_builder_header_byte = self.adv_builder.as_mut().header_byte(); + Self::build_section( + adv_builder_header_byte, + self.section.into_inner(), + self.section_encoder, + ) } } 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 0853df5..d03ad9b 100644 --- a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs +++ b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs @@ -12,15 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use crate::extended::serialize::AdvertisementType; use crate::{ + credential::{v1::V1, SimpleBroadcastCryptoMaterial}, de_type::EncryptedIdentityDataElementType, extended::serialize::{ section_tests::{DummyDataElement, SectionBuilderExt}, AdvBuilder, MicEncryptedSectionEncoder, }, + MetadataKey, }; use anyhow::anyhow; use crypto_provider::{aes::ctr::AES_CTR_NONCE_LEN, aes::AesKey}; @@ -40,7 +44,7 @@ fn mic_encrypted_test_vectors() -> Result<(), anyhow::Error> { ); let mut file = fs::File::open(full_path)?; let mut data = String::new(); - file.read_to_string(&mut data)?; + let _ = file.read_to_string(&mut data)?; let test_cases = match serde_json::de::from_str(&data)? { serde_json::Value::Array(a) => a, @@ -50,7 +54,7 @@ fn mic_encrypted_test_vectors() -> Result<(), anyhow::Error> { 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"), @@ -66,7 +70,7 @@ fn mic_encrypted_test_vectors() -> Result<(), anyhow::Error> { }) .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(), @@ -81,16 +85,17 @@ fn mic_encrypted_test_vectors() -> Result<(), anyhow::Error> { 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(AdvertisementType::Encrypted); assert_eq!(adv_header_byte, adv_builder.header_byte()); let mut section_builder = adv_builder - .section_builder(MicEncryptedSectionEncoder::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, section_salt, - &metadata_key, - &hkdf, + &broadcast_cm, )) .unwrap(); @@ -128,7 +133,7 @@ fn gen_mic_encrypted_test_vectors() { }) .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 = @@ -136,15 +141,16 @@ fn gen_mic_encrypted_test_vectors() { let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed); + 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(MicEncryptedSectionEncoder::new( + .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new( identity_type, V1Salt::from(*section_salt.as_array_ref()), - &metadata_key, - &key_seed_hkdf, + &broadcast_cm, )) .unwrap(); @@ -157,7 +163,7 @@ fn gen_mic_encrypted_test_vectors() { 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..a2b2d9c --- /dev/null +++ b/nearby/presence/np_adv/src/filter/mod.rs @@ -0,0 +1,296 @@ +// 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::data_elements::DataElementDeserializeError; +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<'cred, B, P>( + v0_creds: B::V0Iterator, + adv: &EncryptedAdvContents, +) -> Result<(DecryptedAdvContents, B::Matched), NoMatch> +where + B: CredentialBook<'cred>, + 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<F, I>(&self, data_elements: impl Fn() -> I) -> Result<(), NoMatch> + where + F: PacketFlavor, + I: Iterator<Item = Result<PlainDataElement<F>, DataElementDeserializeError>>, + { + match &self.contains_tx_power { + None => Ok(()), + Some(c) => { + if c == &data_elements().any(|de| matches!(de, Ok(PlainDataElement::TxPower(_)))) { + Ok(()) + } else { + Err(NoMatch) + } + } + }?; + + match &self.actions_filter { + None => Ok(()), + Some(filter) => { + if let Some(_err) = data_elements().find_map(|result| result.err()) { + return Err(NoMatch); + } + // find if an actions DE exists, if so match on the provided action filter + let actions = data_elements().find_map(|de| match de { + Ok(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..ffdeeb9 --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs @@ -0,0 +1,138 @@ +// 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. + +#![allow(clippy::unwrap_used)] + +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..4a893a2 --- /dev/null +++ b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.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 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 result = filter.match_v0_legible_adv(|| { + [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))] + .into_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, _>(|| [].into_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 result = filter.match_v0_legible_adv(|| { + [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))] + .into_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 mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(ActiveUnlock::from(true)); + + let result = filter.match_v0_legible_adv(|| { + [ + Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone()))), + Ok(PlainDataElement::Actions(action_bits.into())), + ] + .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 mut action_bits = ActionBits::<Ciphertext>::default(); + action_bits.set_action(ActiveUnlock::from(true)); + + let result = filter.match_v0_legible_adv(|| { + [ + Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone()))), + Ok(PlainDataElement::Actions(action_bits.into())), + ] + .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 result = filter.match_v0_legible_adv(|| { + [Ok(PlainDataElement::<Ciphertext>::TxPower(TxPowerDataElement::from(tx_power.clone())))] + .into_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/header_parse_tests.rs b/nearby/presence/np_adv/src/header_parse_tests.rs index 2da1b4a..f6b5533 100644 --- a/nearby/presence/np_adv/src/header_parse_tests.rs +++ b/nearby/presence/np_adv/src/header_parse_tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use super::*; extern crate std; diff --git a/nearby/presence/np_adv/src/legacy/actions/macros.rs b/nearby/presence/np_adv/src/legacy/actions/macros.rs index 8208a57..db241d6 100644 --- a/nearby/presence/np_adv/src/legacy/actions/macros.rs +++ b/nearby/presence/np_adv/src/legacy/actions/macros.rs @@ -48,25 +48,6 @@ macro_rules! boolean_element_action_element_impl_shared { /// Use `plaintext_only`, `ciphertext_only`, or `plaintext_and_ciphertext` to create appropriate /// impls. macro_rules! boolean_element { - ($type_name:ident, $index:expr, plaintext_only) => { - $crate::legacy::actions::macros::boolean_element_struct!($type_name); - $crate::legacy::actions::macros::boolean_element_struct_from_bool!($type_name); - - impl $crate::legacy::actions::ActionElement for $type_name { - $crate::legacy::actions::macros::boolean_element_action_element_impl_shared!( - $type_name, $index - ); - - fn supports_flavor(flavor: $crate::legacy::PacketFlavorEnum) -> bool { - match flavor { - $crate::legacy::PacketFlavorEnum::Plaintext => true, - $crate::legacy::PacketFlavorEnum::Ciphertext => false, - } - } - } - - $crate::legacy::actions::macros::boolean_element_to_plaintext_element!($type_name); - }; ($type_name:ident, $index:expr, ciphertext_only) => { $crate::legacy::actions::macros::boolean_element_struct!($type_name); $crate::legacy::actions::macros::boolean_element_struct_from_bool!($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..e0f4514 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 @@ pub(crate) mod tests; /// 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 @@ pub struct ActionBits<F: PacketFlavor> { 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 { @@ -438,5 +439,5 @@ macros::boolean_element!(NearbyShare, 9, plaintext_and_ciphertext); macros::boolean_element!(InstantTethering, 10, ciphertext_only); macros::boolean_element!(PhoneHub, 11, ciphertext_only); macros::boolean_element!(PresenceManager, 12, ciphertext_only); -macros::boolean_element!(Finder, 13, plaintext_only); -macros::boolean_element!(FastPairSass, 14, plaintext_only); +macros::boolean_element!(Finder, 13, plaintext_and_ciphertext); +macros::boolean_element!(FastPairSass, 14, plaintext_and_ciphertext); diff --git a/nearby/presence/np_adv/src/legacy/actions/tests.rs b/nearby/presence/np_adv/src/legacy/actions/tests.rs index 2e3fda0..6e68211 100644 --- a/nearby/presence/np_adv/src/legacy/actions/tests.rs +++ b/nearby/presence/np_adv/src/legacy/actions/tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use crate::legacy::{ @@ -248,18 +250,6 @@ fn action_bits_try_from_flavor_mismatch_plaintext() { } #[test] -fn action_bits_try_from_flavor_mismatch_ciphertext() { - assert_eq!( - FlavorNotSupported { flavor: PacketFlavorEnum::Ciphertext }, - ActionBits::<Ciphertext>::try_from(ActionType::Finder.all_bits()).unwrap_err() - ); - assert_eq!( - 0xF0000000, - ActionBits::<Ciphertext>::try_from(ActionType::ContextSyncSeqNum.all_bits()).unwrap().bits - ); -} - -#[test] fn actions_de_deser_plaintext_with_ciphertext_action() { assert_eq!( DataElementDeserializeError::FlavorNotSupported { @@ -294,14 +284,14 @@ fn context_sync_seq_num_works() { 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 +300,9 @@ fn has_action_plaintext_works() { 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 +312,10 @@ fn has_action_encrypted_works() { 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..c97cb25 100644 --- a/nearby/presence/np_adv/src/legacy/data_elements.rs +++ b/nearby/presence/np_adv/src/legacy/data_elements.rs @@ -13,6 +13,8 @@ // limitations under the License. //! V0 data elements and core trait impls. +use nom::error::{ErrorKind, FromExternalError}; + use crate::legacy::{ de_type::{DataElementType, PlainDataElementType}, serialize::{DataElementBundle, ToDataElementBundle}, @@ -53,12 +55,44 @@ pub enum DataElementDeserializeError { /// The DE type attempting to be deserialized de_type: DataElementType, }, + /// Only one identity data element is allowed in an advertisement, but a duplicate is found + /// while parsing. + DuplicateIdentityDataElement, + /// There is unexpected data remaining in the incoming payload. + UnexpectedDataRemaining, + /// Parsing error returned from Nom. + NomError(nom::error::ErrorKind), +} + +impl FromExternalError<&[u8], DataElementDeserializeError> for DataElementDeserializeError { + fn from_external_error( + _input: &[u8], + _kind: ErrorKind, + e: DataElementDeserializeError, + ) -> Self { + e + } +} + +impl nom::error::ParseError<&[u8]> for DataElementDeserializeError { + /// Creates an error from the input position and an [ErrorKind] + fn from_error_kind(_input: &[u8], kind: ErrorKind) -> Self { + Self::NomError(kind) + } + + /// Combines an existing error with a new one created from the input + /// position and an [ErrorKind]. This is useful when backtracking + /// through a parse tree, accumulating error context on the way + fn append(_input: &[u8], kind: ErrorKind, _other: Self) -> Self { + Self::NomError(kind) + } } /// 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/de_type/mod.rs b/nearby/presence/np_adv/src/legacy/de_type/mod.rs index 5c7ebfa..304347d 100644 --- a/nearby/presence/np_adv/src/legacy/de_type/mod.rs +++ b/nearby/presence/np_adv/src/legacy/de_type/mod.rs @@ -47,6 +47,7 @@ impl DeTypeCode { } } +/// The DE type code is out of range for v0 DE types. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) struct DeTypeCodeOutOfRange; diff --git a/nearby/presence/np_adv/src/legacy/de_type/tests.rs b/nearby/presence/np_adv/src/legacy/de_type/tests.rs index 59075aa..fc64321 100644 --- a/nearby/presence/np_adv/src/legacy/de_type/tests.rs +++ b/nearby/presence/np_adv/src/legacy/de_type/tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use super::*; diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs index 5112ed7..d6c6b36 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs @@ -16,22 +16,25 @@ //! //! This module only deals with the _contents_ of an advertisement, not the advertisement header. -extern crate alloc; +use core::marker::PhantomData; 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 array_view::ArrayView; use crypto_provider::CryptoProvider; use ldt_np_adv::{LegacySalt, NP_LEGACY_METADATA_KEY_LEN}; -use nom::{bytes, combinator, multi, number, sequence}; +use nom::{bytes, combinator, number, sequence}; + +use super::BLE_ADV_SVC_CONTENT_LEN; #[cfg(test)] mod tests; @@ -40,12 +43,15 @@ mod tests; /// ciphertext. pub(crate) fn deserialize_adv_contents<C: CryptoProvider>( input: &[u8], -) -> Result<IntermediateAdvContents, AdvDeserializeError> { +) -> 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(adv_contents) => { + if adv_contents.data_elements().next().is_none() { + return Err(AdvDeserializeError::NoPublicDataElements); + } + + Ok(IntermediateAdvContents::Plaintext(adv_contents)) + } RawAdvertisement::Ciphertext(eac) => Ok(IntermediateAdvContents::Ciphertext(eac)), }) } @@ -57,50 +63,56 @@ pub(crate) fn deserialize_adv_contents<C: CryptoProvider>( fn parse_raw_adv_contents<C: CryptoProvider>( input: &[u8], ) -> Result<RawAdvertisement, AdvDeserializeError> { - let (_, data_elements) = parse_data_elements(input) - .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError)?; - - if let Some(identity_de_type) = - data_elements.first().and_then(|de| de.de_type.try_as_identity_de_type()) - { - match identity_de_type.as_encrypted_identity_de_type() { - Some(encrypted_de_type) => { - if data_elements.len() == 1 { - match encrypted_de_type { - // TODO handle length=0 provisioned identity DEs - EncryptedIdentityDataElementType::Private - | EncryptedIdentityDataElementType::Trusted - | EncryptedIdentityDataElementType::Provisioned => { - combinator::map( - parse_encrypted_identity_de_contents, - |(salt, payload)| { - RawAdvertisement::Ciphertext(EncryptedAdvContents { - identity_type: encrypted_de_type, - salt_padder: ldt_np_adv::salt_padder::<16, C>(salt), - salt, - ciphertext: payload, - }) - }, - )(data_elements[0].contents) - .map(|(_rem, contents)| contents) - .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError) + if input.is_empty() { + return Err(AdvDeserializeError::MissingIdentity); + } + match parse_de(input) { + Ok((rem, identity_de)) => { + if let Some(identity_de_type) = identity_de.de_type.try_as_identity_de_type() { + match identity_de_type.as_encrypted_identity_de_type() { + Some(encrypted_de_type) => { + if matches!(parse_de(rem), Err(nom::Err::Error(..))) { + match encrypted_de_type { + // TODO handle length=0 provisioned identity DEs + EncryptedIdentityDataElementType::Private + | EncryptedIdentityDataElementType::Trusted + | EncryptedIdentityDataElementType::Provisioned => combinator::map( + parse_encrypted_identity_de_contents, + |(salt, payload)| { + RawAdvertisement::Ciphertext(EncryptedAdvContents { + identity_type: encrypted_de_type, + salt_padder: ldt_np_adv::salt_padder::<16, C>(salt), + salt, + ciphertext: payload, + }) + }, + )( + identity_de.contents, + ) + .map(|(_rem, contents)| contents) + .map_err(|_e| AdvDeserializeError::AdvertisementDeserializeError), + } + } else { + Err(AdvDeserializeError::TooManyTopLevelDataElements) } } - } else { - Err(AdvDeserializeError::TooManyTopLevelDataElements) + // It's an identity de, but not encrypted, so it must be public, and the rest + // must be plain + None => Ok(RawAdvertisement::Plaintext(PlaintextAdvContents { + identity_type: PlaintextIdentityMode::Public, + data: rem, + })), } + } else { + Err(AdvDeserializeError::MissingIdentity) } - // It's an identity de, but not encrypted, so it must be public, and the rest must be - // plain - None => plain_data_elements(&data_elements[1..]).map(|pdes| { - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::Public, - data_elements: pdes, - }) - }), } - } else { - Err(AdvDeserializeError::MissingIdentity) + Err(nom::Err::Error(_)) | Err(nom::Err::Failure(_)) => { + Err(AdvDeserializeError::AdvertisementDeserializeError) + } + Err(nom::Err::Incomplete(_)) => { + panic!("Should not hit Incomplete when using nom::complete parsers") + } } } @@ -109,25 +121,16 @@ fn parse_raw_adv_contents<C: CryptoProvider>( pub(crate) enum AdvDeserializeError { /// Parsing the overall advertisement or DE structure failed AdvertisementDeserializeError, - /// Deserializing an individual DE from its DE contents failed - DataElementDeserializeError(DataElementDeserializeError), /// Must not have any other top level data elements if there is an encrypted identity DE TooManyTopLevelDataElements, - /// Must not have an identity DE inside an identity DE - InvalidDataElementHierarchy, /// Missing identity DE MissingIdentity, -} - -/// Parse an advertisement's contents into raw DEs. -/// -/// Consumes the entire input. -fn parse_data_elements(adv_contents: &[u8]) -> nom::IResult<&[u8], Vec<RawDataElement>> { - combinator::all_consuming(multi::many0(parse_de))(adv_contents) + /// Non-identity DE contents must not be empty + NoPublicDataElements, } /// Parse an individual DE into its header and contents. -fn parse_de(input: &[u8]) -> nom::IResult<&[u8], RawDataElement> { +fn parse_de(input: &[u8]) -> nom::IResult<&[u8], RawDataElement, DataElementDeserializeError> { let (remaining, (de_type, actual_len)) = combinator::map_opt(number::complete::u8, |de_header| { // header: LLLLTTTT @@ -151,22 +154,6 @@ fn parse_de(input: &[u8]) -> nom::IResult<&[u8], RawDataElement> { })(remaining) } -/// Returns `Err`` if any DEs are not of a plain DE type. -fn plain_data_elements<'d, D: AsRef<[RawDataElement<'d>]>>( - data_elements: D, -) -> Result<Vec<RawPlainDataElement<'d>>, AdvDeserializeError> { - data_elements - .as_ref() - .iter() - .map(|de| { - de.de_type - .try_as_plain_de_type() - .map(|de_type| RawPlainDataElement { de_type, contents: de.contents }) - }) - .collect::<Option<Vec<_>>>() - .ok_or(AdvDeserializeError::InvalidDataElementHierarchy) -} - /// Parse legacy encrypted identity DEs (private, trusted, provisioned) into salt and ciphertext /// (encrypted metadata key and at least 2 bytes of DEs). /// @@ -191,6 +178,7 @@ fn parse_encrypted_identity_de_contents( #[derive(Debug, PartialEq, Eq)] struct RawDataElement<'d> { de_type: DataElementType, + /// Byte array payload of the data element, without the DE header. contents: &'d [u8], } @@ -198,30 +186,76 @@ struct RawDataElement<'d> { /// level DE representations. #[derive(Debug, PartialEq, Eq)] enum RawAdvertisement<'d> { - Plaintext(PlaintextAdvRawContents<'d>), + Plaintext(PlaintextAdvContents<'d>), Ciphertext(EncryptedAdvContents<'d>), } -/// A plaintext advertisement's content in raw DEs but without further deserialization. -#[derive(Debug, PartialEq, Eq)] -struct PlaintextAdvRawContents<'d> { - identity_type: PlaintextIdentityMode, - data_elements: Vec<RawPlainDataElement<'d>>, +/// 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<PlainDataElement>>` so the validity of the whole +/// advertisement can be checked before proceeding with further processing. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PlainDeIterator<'d, F> +where + F: PacketFlavor, + actions::ActionsDataElement<F>: DataElement, +{ + /// Data to be parsed, containing a sequence of data elements in serialized + /// form. This should not contain the identity data elements. + data: &'d [u8], + _marker: PhantomData<F>, } -impl<'d> PlaintextAdvRawContents<'d> { - /// Deserialize the DE contents into per-DE-type structures. - /// - /// Returns `Some` if each DE's contents can be successfully deserialized, otherwise `None`. - fn try_deserialize(&self) -> Result<PlaintextAdvContents, DataElementDeserializeError> { - self.data_elements - .iter() - .map(|de| de.try_deserialize::<Plaintext>()) - .collect::<Result<Vec<_>, _>>() - .map(|des| PlaintextAdvContents { - identity_type: self.identity_type, - data_elements: des, - }) +impl<'d, F> PlainDeIterator<'d, F> +where + F: PacketFlavor, + actions::ActionsDataElement<F>: DataElement, +{ + fn raw_de_to_plain_de( + raw_de: RawDataElement<'d>, + ) -> Result<PlainDataElement<F>, DataElementDeserializeError> { + let de_type = raw_de + .de_type + .try_as_plain_de_type() + .ok_or(DataElementDeserializeError::DuplicateIdentityDataElement)?; + (RawPlainDataElement { de_type, contents: raw_de.contents }).try_deserialize() + } +} + +impl<'d, F> Iterator for PlainDeIterator<'d, F> +where + F: PacketFlavor, + actions::ActionsDataElement<F>: DataElement, +{ + type Item = Result<PlainDataElement<F>, DataElementDeserializeError>; + + fn next(&mut self) -> Option<Self::Item> { + let parse_result = nom::combinator::cut(nom::combinator::map_res( + parse_de, + Self::raw_de_to_plain_de, + ))(self.data); + match parse_result { + Ok((rem, de)) => { + self.data = rem; + Some(Ok(de)) + } + Err(nom::Err::Error(_)) => { + panic!("All Errors are turned into Failures with `cut` above"); + } + Err(nom::Err::Failure(DataElementDeserializeError::NomError( + nom::error::ErrorKind::Eof, + ))) => { + if self.data.is_empty() { + None + } else { + Some(Err(DataElementDeserializeError::UnexpectedDataRemaining)) + } + } + Err(nom::Err::Failure(e)) => Some(Err(e)), + Err(nom::Err::Incomplete(_)) => { + panic!("Incomplete unexpected when using nom::complete APIs") + } + } } } @@ -229,6 +263,7 @@ impl<'d> PlaintextAdvRawContents<'d> { #[derive(Debug, PartialEq, Eq)] pub(crate) struct RawPlainDataElement<'d> { de_type: PlainDataElementType, + /// Byte array payload of the data element, without the DE header. contents: &'d [u8], } @@ -281,7 +316,6 @@ impl<'d> EncryptedAdvContents<'d> { .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(), @@ -290,26 +324,15 @@ impl<'d> EncryptedAdvContents<'d> { DecryptError::DeserializeError(AdvDeserializeError::AdvertisementDeserializeError) })?; - let (_remaining, raw_des) = combinator::all_consuming(parse_data_elements)(remaining) - .map_err(|_e| { - DecryptError::DeserializeError(AdvDeserializeError::AdvertisementDeserializeError) - })?; - - plain_data_elements(&raw_des)? - .into_iter() - .map(|de| de.try_deserialize()) - .collect::<Result<Vec<_>, _>>() - .map_err(|e| { - DecryptError::DeserializeError(AdvDeserializeError::DataElementDeserializeError(e)) - }) - .map(|data_elements| { - DecryptedAdvContents::new( - self.identity_type, - metadata_key, - self.salt, - data_elements, - ) - }) + let remaining_arr = ArrayView::try_from_slice(remaining) + .expect("Max remaining = 31 - 14 = 17 bytes < BLE_ADV_SVC_CONTENT_LEN"); + + Ok(DecryptedAdvContents::new( + self.identity_type, + ShortMetadataKey(metadata_key), + self.salt, + remaining_arr, + )) } } @@ -336,26 +359,50 @@ pub enum PlainDataElement<F: PacketFlavor> { TxPower(TxPowerDataElement), } +impl<F: PacketFlavor> PlainDataElement<F> { + /// Returns the DE type as a u8 + #[cfg(feature = "devtools")] + pub fn de_type_code(&self) -> u8 { + match self { + PlainDataElement::Actions(_) => DataElementType::Actions.type_code().as_u8(), + PlainDataElement::TxPower(_) => DataElementType::TxPower.type_code().as_u8(), + } + } + + /// Returns the serialized contents of the DE + #[cfg(feature = "devtools")] + pub fn de_contents(&self) -> alloc::vec::Vec<u8> { + use crate::legacy::serialize::{DataElementBundle, ToDataElementBundle}; + match self { + PlainDataElement::Actions(a) => { + let bundle: DataElementBundle<F> = a.to_de_bundle(); + bundle.contents_as_slice().to_vec() + } + PlainDataElement::TxPower(t) => { + let bundle: DataElementBundle<F> = t.to_de_bundle(); + bundle.contents_as_slice().to_vec() + } + } + } +} + /// The contents of a plaintext advertisement after deserializing DE contents #[derive(Debug, PartialEq, Eq)] -pub struct PlaintextAdvContents { +pub struct PlaintextAdvContents<'d> { identity_type: PlaintextIdentityMode, - data_elements: Vec<PlainDataElement<Plaintext>>, + /// Contents of the advertisement excluding the identity DE + data: &'d [u8], } -impl PlaintextAdvContents { +impl<'d> PlaintextAdvContents<'d> { /// Returns the identity type used for the advertisement pub fn identity(&self) -> PlaintextIdentityMode { self.identity_type } - /// Returns the deserialized data elements - pub fn data_elements(&self) -> impl Iterator<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>> { - self.data_elements + + /// Returns an iterator over the v0 data elements + pub fn data_elements(&self) -> PlainDeIterator<'d, Plaintext> { + PlainDeIterator { data: self.data, _marker: PhantomData } } } @@ -363,20 +410,22 @@ impl PlaintextAdvContents { #[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>>, + /// The decrypted data in this advertisement. This should be a sequence of + /// serialized data elements, excluding the identity DE. + data: ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, } impl DecryptedAdvContents { /// 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>>, + data: ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, ) -> Self { - Self { identity_type, metadata_key, salt, data_elements } + Self { identity_type, metadata_key, salt, data } } /// The type of identity DE used in the advertisement. @@ -384,16 +433,11 @@ impl DecryptedAdvContents { 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>> { - self.data_elements.iter() + pub fn data_elements(&self) -> PlainDeIterator<Ciphertext> { + PlainDeIterator { data: self.data.as_slice(), _marker: PhantomData } } /// The salt used for decryption of this advertisement. @@ -402,12 +446,19 @@ impl DecryptedAdvContents { } } +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)] pub(crate) enum IntermediateAdvContents<'d> { /// Plaintext advertisements - Plaintext(PlaintextAdvContents), + Plaintext(PlaintextAdvContents<'d>), /// Ciphertext advertisements Ciphertext(EncryptedAdvContents<'d>), } diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs index 4cf6041..3dfecd1 100644 --- a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs +++ b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs @@ -12,30 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(unused_results, clippy::unwrap_used)] + +extern crate alloc; extern crate std; use super::*; -use crate::legacy::actions::{ActionBits, ActionsDataElement}; -use crate::shared_data::TxPower; use crate::{ + credential::{v0::V0, SimpleBroadcastCryptoMaterial}, de_type::IdentityDataElementType, legacy::{ - actions, + actions::{self, ActionBits, ActionsDataElement, Finder, NearbyShare}, de_type::DeActualLength, random_data_elements::{random_de_ciphertext, random_de_plaintext}, serialize::{ encode_de_header_actual_len, id_de_type_as_generic_de_type, AdvBuilder, - DataElementBundle, Identity, LdtIdentity, ToDataElementBundle as _, + DataElementBundle, Identity, LdtIdentity, ToDataElementBundle, }, PacketFlavorEnum, BLE_ADV_SVC_CONTENT_LEN, }, - parse_adv_header, shared_data, AdvHeader, PublicIdentity, + parse_adv_header, shared_data, + shared_data::TxPower, + AdvHeader, PublicIdentity, }; +use alloc::vec::Vec; use array_view::ArrayView; use crypto_provider_default::CryptoProviderImpl; -use init_with::InitWith as _; use ldt_np_adv::LdtEncrypterXtsAes128; -use nom::error; +use nom::error::{self, ErrorKind}; use rand_ext::rand::{prelude::SliceRandom, Rng as _}; use std::vec; use strum::IntoEnumIterator as _; @@ -75,22 +79,27 @@ fn parse_raw_adv_3_de_public_identity() { let adv = parse_raw_adv_contents::<CryptoProviderImpl>(&[ 0x03, // public identity 0x15, 0x05, // tx power 5 - 0x36, 0x11, 0x12, 0x13, // actions + 0x26, 0x00, 0x44, // actions ]) .unwrap(); - assert_eq!( - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::Public, - data_elements: vec![ - RawPlainDataElement { de_type: PlainDataElementType::TxPower, contents: &[0x05] }, - RawPlainDataElement { - de_type: PlainDataElementType::Actions, - contents: &[0x11, 0x12, 0x13] - } - ], - }), - adv - ); + match adv { + RawAdvertisement::Plaintext(plaintext) => { + assert_eq!(PlaintextIdentityMode::Public, plaintext.identity_type); + let mut action_bits = ActionBits::default(); + action_bits.set_action(NearbyShare::from(true)); + action_bits.set_action(Finder::from(true)); + assert_eq!( + vec![ + PlainDataElement::<Plaintext>::TxPower(TxPowerDataElement::from( + TxPower::try_from(5).unwrap() + )), + PlainDataElement::Actions(ActionsDataElement::from(action_bits)), + ], + plaintext.data_elements().collect::<Result<Vec<_>, _>>().unwrap(), + ); + } + RawAdvertisement::Ciphertext(_) => panic!("adv should be plaintext"), + } } #[test] @@ -99,13 +108,16 @@ fn parse_raw_adv_0_de_public_identity() { 0x03, // public identity ]) .unwrap(); - assert_eq!( - RawAdvertisement::Plaintext(PlaintextAdvRawContents { - identity_type: PlaintextIdentityMode::Public, - data_elements: vec![], - }), - adv - ); + match adv { + RawAdvertisement::Plaintext(plaintext) => { + assert_eq!(PlaintextIdentityMode::Public, plaintext.identity_type); + assert_eq!( + Vec::<PlainDataElement<Plaintext>>::new(), + plaintext.data_elements().collect::<Result<Vec<_>, _>>().unwrap() + ); + } + RawAdvertisement::Ciphertext(_) => panic!("adv should be plaintext"), + } } #[test] @@ -113,8 +125,8 @@ fn parse_raw_adv_1_de_length_overrun() { // battery uses the header length as is let input = &[0xFB, 0x01, 0x02, 0x03]; assert_eq!( - nom::Err::Error(error::Error { input: input.as_slice(), code: error::ErrorKind::Eof }), - parse_data_elements(input).unwrap_err() + nom::Err::Error(DataElementDeserializeError::NomError(ErrorKind::MapOpt)), + parse_de(input).unwrap_err(), ); } @@ -125,10 +137,15 @@ fn parse_raw_adv_public_identity_containing_public_identity() { 0x03, // another public identity 0x15, 0x03, // tx power de ]; - assert_eq!( - AdvDeserializeError::InvalidDataElementHierarchy, - parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap_err() - ); + match parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap() { + RawAdvertisement::Plaintext(content) => { + assert_eq!( + DataElementDeserializeError::DuplicateIdentityDataElement, + content.data_elements().collect::<Result<Vec<_>, _>>().unwrap_err(), + ); + } + RawAdvertisement::Ciphertext(_) => panic!("Adv should be plaintext"), + } } #[test] @@ -296,35 +313,42 @@ fn parse_de_invalid_de_len_error() { ]; assert_eq!( - nom::Err::Error(error::Error { input: input.as_slice(), code: error::ErrorKind::MapOpt }), + nom::Err::Error(DataElementDeserializeError::NomError(ErrorKind::MapOpt)), parse_de(&input[..]).unwrap_err() ); } #[test] -fn plain_data_elements_matches_plain_des() { +fn raw_de_to_plain_de_matches_plain_des() { assert_eq!( - vec![ - RawPlainDataElement { de_type: PlainDataElementType::TxPower, contents: &[0x01] }, - RawPlainDataElement { de_type: PlainDataElementType::Actions, contents: &[0x02] } - ], - plain_data_elements(&[ - RawDataElement { de_type: DataElementType::TxPower, contents: &[0x01] }, - RawDataElement { de_type: DataElementType::Actions, contents: &[0x02] } - ]) - .unwrap() + PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(1).unwrap())), + PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement { + de_type: DataElementType::TxPower, + contents: &[0x01] + }) + .unwrap(), + ); + assert_eq!( + PlainDataElement::Actions(ActionsDataElement::from( + ActionBits::try_from(0x00400000).unwrap() + )), + PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement { + de_type: DataElementType::Actions, + contents: &[0x00, 0x40] + }) + .unwrap(), ); } #[test] -fn plain_data_elements_rejects_identity_de_error() { +fn raw_de_to_plain_de_rejects_identity_de_error() { for idet in IdentityDataElementType::iter() { assert_eq!( - AdvDeserializeError::InvalidDataElementHierarchy, - plain_data_elements(&[ - RawDataElement { de_type: DataElementType::TxPower, contents: &[0x01] }, - RawDataElement { de_type: id_de_type_as_generic_de_type(idet), contents: &[0x02] } - ]) + DataElementDeserializeError::DuplicateIdentityDataElement, + PlainDeIterator::<Plaintext>::raw_de_to_plain_de(RawDataElement { + de_type: id_de_type_as_generic_de_type(idet), + contents: &[0x02], + }) .unwrap_err() ); } @@ -333,7 +357,10 @@ fn plain_data_elements_rejects_identity_de_error() { #[test] fn parse_encrypted_identity_contents_too_short_error() { // 2 byte salt + 15 byte ciphertext: 1 too short - let input = <[u8; 17]>::init_with_indices(|i| i as u8); + let mut input = [0u8; 17]; + for (pos, e) in input.iter_mut().enumerate() { + *e = pos as u8 + } assert_eq!( nom::Err::Error(error::Error { input: &input[2..], code: error::ErrorKind::TakeWhileMN }), parse_encrypted_identity_de_contents(&input).unwrap_err() @@ -343,7 +370,10 @@ fn parse_encrypted_identity_contents_too_short_error() { #[test] fn parse_encrypted_identity_contents_ok() { // 2 byte salt + minimum 16 byte ciphertext - let input = <[u8; 18]>::init_with_indices(|i| i as u8); + let mut input = [0u8; 18]; + for (pos, e) in input.iter_mut().enumerate() { + *e = pos as u8 + } assert_eq!( ([].as_slice(), (ldt_np_adv::LegacySalt::from([0, 1]), &input[2..])), parse_encrypted_identity_de_contents(&input).unwrap() @@ -351,6 +381,17 @@ fn parse_encrypted_identity_contents_ok() { } #[test] +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_public() { plaintext_random_adv_contents_round_trip(PublicIdentity::default, PlaintextIdentityMode::Public) } @@ -372,15 +413,17 @@ fn ciphertext_random_adv_contents_round_trip() { 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 { @@ -418,10 +461,11 @@ fn ciphertext_random_adv_contents_round_trip() { eac ); - assert_eq!( - DecryptedAdvContents { identity_type, metadata_key, salt, data_elements: des }, - eac.try_decrypt(&cipher).unwrap() - ) + let contents = eac.try_decrypt(&cipher).unwrap(); + assert_eq!(identity_type, contents.identity_type); + assert_eq!(metadata_key, contents.metadata_key); + assert_eq!(salt, contents.salt); + assert_eq!(des, contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap()); } else { panic!("Unexpected variant: {:?}", parsed_adv); } @@ -435,15 +479,18 @@ fn decrypt_and_deserialize_ciphertext_adv_canned() { 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(); @@ -479,17 +526,16 @@ fn decrypt_and_deserialize_ciphertext_adv_canned() { eac ); + let decrypted = eac.try_decrypt(&cipher).unwrap(); + assert_eq!(EncryptedIdentityDataElementType::Private, decrypted.identity_type); + assert_eq!(metadata_key, decrypted.metadata_key); + assert_eq!(salt, decrypted.salt); assert_eq!( - DecryptedAdvContents { - identity_type: EncryptedIdentityDataElementType::Private, - metadata_key, - salt, - data_elements: vec![PlainDataElement::TxPower(TxPowerDataElement::from( - TxPower::try_from(3).unwrap() - ))], - }, - eac.try_decrypt(&cipher).unwrap() - ) + vec![PlainDataElement::TxPower(TxPowerDataElement::from( + TxPower::try_from(3).unwrap() + ))], + decrypted.data_elements().collect::<Result<Vec<_>, _>>().unwrap() + ); } else { panic!("Unexpected variant: {:?}", parsed_adv); } @@ -497,7 +543,7 @@ fn decrypt_and_deserialize_ciphertext_adv_canned() { #[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(); @@ -517,16 +563,14 @@ fn decrypt_and_deserialize_plaintext_adv_canned() { assert_eq!(AdvHeader::V0, header); let parsed_adv = deserialize_adv_contents::<CryptoProviderImpl>(remaining).unwrap(); - if let IntermediateAdvContents::Plaintext(parc) = parsed_adv { + if let IntermediateAdvContents::Plaintext(adv_contents) = parsed_adv { + assert_eq!(PlaintextIdentityMode::Public, adv_contents.identity()); assert_eq!( - PlaintextAdvContents { - identity_type: PlaintextIdentityMode::Public, - data_elements: vec![PlainDataElement::Actions(ActionsDataElement::from( - ActionBits::default() - ))], - }, - parc - ) + vec![PlainDataElement::<Plaintext>::Actions(ActionsDataElement::from( + ActionBits::default() + ))], + adv_contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap() + ); } else { panic!("Unexpected variant: {:?}", parsed_adv); } @@ -653,8 +697,12 @@ fn decrypt_and_deserialize_ciphertext_with_public_adv_inside_error() { let parsed_adv = deserialize_adv_contents::<CryptoProviderImpl>(&adv[1..]).unwrap(); if let IntermediateAdvContents::Ciphertext(eac) = parsed_adv { assert_eq!( - DecryptError::DeserializeError(AdvDeserializeError::InvalidDataElementHierarchy), - eac.try_decrypt(&cipher).unwrap_err() + DataElementDeserializeError::DuplicateIdentityDataElement, + eac.try_decrypt(&cipher) + .unwrap() + .data_elements() + .collect::<Result<Vec<_>, _>>() + .unwrap_err() ) } else { panic!("Unexpected variant: {:?}", parsed_adv); @@ -667,18 +715,19 @@ fn build_ciphertext_adv_contents<C: CryptoProvider>( 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) @@ -716,11 +765,11 @@ where ); buf.extend_from_slice(contents); - let raw_de = combinator::all_consuming(parse_de)(&buf).map(|(_remaining, de)| de).unwrap(); - - let plain_des = plain_data_elements(&[raw_de]).unwrap(); + let mut plain_des = PlainDeIterator { data: &buf, _marker: PhantomData } + .collect::<Result<Vec<_>, _>>() + .unwrap(); assert_eq!(1, plain_des.len()); - plain_des.first().unwrap().try_deserialize().unwrap() + plain_des.swap_remove(0) } fn plaintext_random_adv_contents_round_trip<I: Identity<Flavor = Plaintext>, F: Fn() -> I>( @@ -756,31 +805,12 @@ fn plaintext_random_adv_contents_round_trip<I: Identity<Flavor = Plaintext>, F: parse_raw_adv_contents::<CryptoProviderImpl>(&serialized.as_slice()[1..]).unwrap(); assert_eq!(AdvHeader::V0, header); - if let RawAdvertisement::Plaintext(parc) = parsed_adv { + if let RawAdvertisement::Plaintext(adv_contents) = parsed_adv { + assert_eq!(identity_type, adv_contents.identity_type); assert_eq!( - PlaintextAdvRawContents { - identity_type, - data_elements: de_tuples - .iter() - .map(|(_de, de_type, bundle)| RawPlainDataElement { - de_type: *de_type, - contents: bundle.contents_as_slice(), - }) - .collect() - }, - parc + de_tuples.into_iter().map(|(de, _de_type, _bundle)| de).collect::<Vec<_>>(), + adv_contents.data_elements().collect::<Result<Vec<_>, _>>().unwrap(), ); - - assert_eq!( - PlaintextAdvContents { - identity_type, - data_elements: de_tuples - .into_iter() - .map(|(de, _de_type, _bundle)| de) - .collect(), - }, - parc.try_deserialize().unwrap() - ) } else { panic!("Unexpected variant: {:?}", parsed_adv); } 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 @@ pub const BLE_ADV_SVC_CONTENT_LEN: usize = 24; /// 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/random_data_elements.rs b/nearby/presence/np_adv/src/legacy/random_data_elements.rs index f416ffe..aca906b 100644 --- a/nearby/presence/np_adv/src/legacy/random_data_elements.rs +++ b/nearby/presence/np_adv/src/legacy/random_data_elements.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; use crate::{ @@ -87,9 +89,8 @@ impl distributions::Distribution<ActionsDataElement<Ciphertext>> for distributio ActionType::PresenceManager => bits.set_action(PresenceManager::from(true)), ActionType::InstantTethering => bits.set_action(InstantTethering::from(true)), ActionType::PhoneHub => bits.set_action(PhoneHub::from(true)), - ActionType::Finder | ActionType::FastPairSass => { - unreachable!("not ciphertext actions") - } + ActionType::Finder => bits.set_action(Finder::from(true)), + ActionType::FastPairSass => bits.set_action(FastPairSass::from(true)), } } diff --git a/nearby/presence/np_adv/src/legacy/serialize/mod.rs b/nearby/presence/np_adv/src/legacy/serialize/mod.rs index 68fc8a6..3746796 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, 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; @@ -105,7 +111,7 @@ pub trait Identity: fmt::Debug { lazy_static::lazy_static! { // Avoid either a panic-able code path or an error case that never happens by precalculating. static ref PUBLIC_IDENTITY_DE_HEADER: u8 = - encode_de_header_actual_len(DataElementType::PublicIdentity, DeActualLength::ZERO).unwrap(); + encode_de_header_actual_len(DataElementType::PublicIdentity, DeActualLength::ZERO).expect("de length is in range"); } impl Identity for PublicIdentity { @@ -126,10 +132,8 @@ impl Identity for PublicIdentity { 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 @@ -140,14 +144,20 @@ impl<C: CryptoProvider> fmt::Debug for LdtIdentity<C> { } 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 } } } @@ -169,7 +179,7 @@ impl<C: CryptoProvider> Identity for LdtIdentity<C> { 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 8972313..7be30ba 100644 --- a/nearby/presence/np_adv/src/legacy/serialize/tests.rs +++ b/nearby/presence/np_adv/src/legacy/serialize/tests.rs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + extern crate std; 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 +30,7 @@ use std::vec; #[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(); @@ -49,7 +52,7 @@ fn public_identity_packet_serialization() { #[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(); @@ -69,22 +72,24 @@ fn packet_limits_capacity() { #[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(); @@ -105,16 +110,16 @@ fn ldt_packet_serialization() { #[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 @@ -123,7 +128,7 @@ fn ldt_packet_cant_encrypt_without_des() { #[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 51a5046..0b6d7ae 100644 --- a/nearby/presence/np_adv/src/lib.rs +++ b/nearby/presence/np_adv/src/lib.rs @@ -11,6 +11,7 @@ // 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. + //! Serialization and deserialization for v0 (legacy) and v1 (extended) Nearby Presence //! advertisements. //! @@ -18,48 +19,58 @@ //! deserialization scenarios. #![no_std] -#![forbid(unsafe_code)] -#![deny(missing_docs)] +#![allow(clippy::expect_used, clippy::indexing_slicing, clippy::panic)] -extern crate alloc; -extern crate core; use crate::{ credential::{ - source::{BothCredentialSource, CredentialSource}, - v0::V0CryptoMaterial, - v1::{BorrowableIdentityResolutionMaterial, V1CryptoMaterial}, - MatchedCredFromCred, MatchedCredential, V0Credential, V1Credential, + book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v0::V0, v1::V1DiscoveryCryptoMaterial, + v1::V1, DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion, + ReferencedMatchedCredential, }, + deserialization_arena::ArenaOutOfSpace, extended::deserialize::{ - encrypted_section::*, 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, }, }; + +#[cfg(any(test, feature = "alloc"))] +extern crate alloc; +#[cfg(any(test, feature = "alloc"))] use alloc::vec::Vec; + +use array_vec::ArrayVecOption; #[cfg(feature = "devtools")] use array_view::ArrayView; -use core::{fmt::Debug, marker}; +use core::fmt::Debug; use crypto_provider::CryptoProvider; +use deserialization_arena::{DeserializationArena, DeserializationArenaAllocator}; #[cfg(feature = "devtools")] use extended::NP_ADV_MAX_SECTION_LEN; +use extended::NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT; use legacy::{data_elements::DataElementDeserializeError, deserialize::AdvDeserializeError}; use nom::{combinator, number}; pub use strum; +mod array_vec; 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 @@ -68,73 +79,26 @@ pub const NP_SVC_UUID: [u8; 2] = [0xFC, 0xF1]; /// 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: &'s [u8], - cred_source: &'s S, -) -> Result<DeserializedAdvertisement<'s, M>, AdvDeserializationError> +pub fn deserialize_advertisement<'adv, 'cred, B, P>( + arena: 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, { 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), - } -} - -/// 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: &'s [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), + AdvHeader::V1(header) => deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header) + .map(DeserializedAdvertisement::V1), } } -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 { @@ -157,17 +121,17 @@ pub enum AdvDecryptionError { } /// 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: 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 }; @@ -178,16 +142,22 @@ where IntermediateSection::Ciphertext(section) => section, }; - for cred in cred_source.iter() { - let crypto_material = cred.crypto_material(); - if let Some(plaintext) = - cipher_section.try_resolve_identity_and_decrypt::<_, P>(crypto_material) + let mut allocator = arena.into_allocator(); + for (crypto_material, _) in cred_book.v1_iter() { + if let Some(plaintext) = cipher_section + .try_resolve_identity_and_decrypt::<_, P>(&mut allocator, &crypto_material) { + let pt = plaintext.expect(concat!( + "Should not run out of space because DeserializationArenaAllocator is big ", + "enough to hold a single advertisement, and we exit immediately upon ", + "successful decryption", + )); + let encryption_scheme = match cipher_section { CiphertextSection::SignatureEncryptedIdentity(_) => V1EncryptionScheme::Signature, CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic, }; - return Ok((plaintext, encryption_scheme)); + return Ok((pt, encryption_scheme)); } } Err(AdvDecryptionError::NoMatchingCredentials) @@ -208,26 +178,32 @@ struct ResolvableCiphertextSection<'a> { /// 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<'a, M: MatchedCredential<'a>> { - deserialized_sections: Vec<(usize, V1DeserializedSection<'a, M>)>, - encrypted_sections: Vec<(usize, ResolvableCiphertextSection<'a>)>, +struct SectionsInProcessing<'adv, M: MatchedCredential> { + deserialized_sections: ArrayVecOption< + (usize, V1DeserializedSection<'adv, M>), + { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT }, + >, + encrypted_sections: ArrayVecOption< + (usize, ResolvableCiphertextSection<'adv>), + { NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT }, + >, malformed_sections_count: usize, } -impl<'a, M: MatchedCredential<'a>> SectionsInProcessing<'a, M> { +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: &'a [u8], + 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(); + let mut deserialized_sections = ArrayVecOption::default(); + let mut encrypted_sections = ArrayVecOption::default(); // keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`. for (idx, s) in int_sections.into_iter().enumerate() { match s { @@ -257,11 +233,12 @@ impl<'a, M: MatchedCredential<'a>> SectionsInProcessing<'a, M> { /// 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, P: CryptoProvider>(&mut self, cred: &'a C) - where - C: V1Credential<Matched<'a> = M> + 'a, - { - let crypto_material = cred.crypto_material(); + fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>( + &mut self, + arena: &mut DeserializationArenaAllocator<'adv>, + crypto_material: C, + match_data: M, + ) -> Result<(), ArenaOutOfSpace> { let mut i = 0; while i < self.encrypted_sections.len() { let (section_idx, section): &(usize, ResolvableCiphertextSection) = @@ -269,37 +246,34 @@ impl<'a, M: MatchedCredential<'a>> SectionsInProcessing<'a, M> { // 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(_) => { - BorrowableIdentityResolutionMaterial::unsigned_from_crypto_material::<_, P>( - crypto_material, - ) - } - CiphertextSection::SignatureEncryptedIdentity(_) => { - BorrowableIdentityResolutionMaterial::signed_from_crypto_material::<_, P>( - crypto_material, - ) - } + 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.as_raw_resolution_material()) - { + match identity_resolution_contents.try_match::<P>(&identity_resolution_material) { None => { - // Try again with another credential + // 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, identity_match, &crypto_material.signed_verification_material::<P>(), ) .map_err(SectionDeserializeError::from), CiphertextSection::MicEncryptedIdentity(c) => c .try_deserialize( + arena, identity_match, &crypto_material.unsigned_verification_material::<P>(), ) @@ -310,7 +284,8 @@ impl<'a, M: MatchedCredential<'a>> SectionsInProcessing<'a, M> { self.deserialized_sections.push(( *section_idx, V1DeserializedSection::Decrypted(WithMatchedCredential::new( - cred.matched(), + match_data.clone(), + metadata_nonce, s, )), )); @@ -325,56 +300,71 @@ impl<'a, M: MatchedCredential<'a>> SectionsInProcessing<'a, M> { // 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); + let _ = self.encrypted_sections.swap_remove(i); // don't advance i -- it now points to a new element } } } + Ok(()) } /// Packages the current state of the deserialization process into a - /// `V1AdvContents` representing a fully-deserialized V1 advertisement. + /// `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) -> V1AdvContents<'a, M> { + 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 `V1AdvContents` - self.deserialized_sections.sort_by_key(|(idx, _section)| *idx); + // 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(); - V1AdvContents::new(ordered_sections, invalid_sections_count) + V1AdvertisementContents::new(ordered_sections, invalid_sections_count) } } /// 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: &'s [u8], +fn deser_decrypt_v1<'adv, 'cred, B, P>( + arena: DeserializationArena<'adv>, + cred_book: &'cred B, + remaining: &'adv [u8], header: V1Header, -) -> Result<V1AdvertisementContents<'s, C>, AdvDeserializationError> +) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError> where - C: V1Credential, - S: CredentialSource<C>, + B: CredentialBook<'cred>, P: CryptoProvider, { let mut sections_in_processing = - SectionsInProcessing::from_advertisement_contents::<P>(header, remaining)?; + SectionsInProcessing::<'_, B::Matched>::from_advertisement_contents::<P>( + header, remaining, + )?; + + let mut allocator = arena.into_allocator(); // Hot loop // We assume that iterating credentials is more expensive than iterating sections - for cred in cred_source.iter() { - sections_in_processing.try_decrypt_with_credential::<C, P>(cred); + for (crypto_material, match_data) in cred_book.v1_iter() { + sections_in_processing + .try_decrypt_with_credential::<_, P>(&mut allocator, crypto_material, match_data) + .expect(concat!( + "Should not run out of space because DeserializationArenaAllocator is big ", + "enough to hold a single advertisement, and we exit immediately upon ", + "successful decryption", + )); if sections_in_processing.resolved_all_identities() { // No need to consider the other credentials break; @@ -383,31 +373,29 @@ where 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, - remaining: &[u8], -) -> Result<V0AdvertisementContents<'s, C>, AdvDeserializationError> +fn deser_decrypt_v0<'adv, 'cred, B, P>( + cred_book: &'cred B, + remaining: &'adv [u8], +) -> Result<V0AdvertisementContents<'adv, B::Matched>, AdvDeserializationError> where - C: V0Credential, - S: CredentialSource<C>, + B: CredentialBook<'cred>, 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, @@ -417,10 +405,11 @@ where }, } } - 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 @@ -444,88 +433,204 @@ fn parse_adv_header(adv: &[u8]) -> nom::IResult<&[u8], AdvHeader> { _ => unreachable!(), } } + #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum AdvHeader { V0, V1(V1Header), } + /// An NP advertisement with its header parsed. +#[allow(clippy::large_enum_variant)] #[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<'adv, 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<'adv, 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: ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT>, 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: ArrayVecOption< + V1DeserializedSection<'adv, M>, + NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, + >, + 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, + ) -> ArrayVecOption<V1DeserializedSection<'adv, M>, NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT> { 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<'adv, M: MatchedCredential> { /// Contents of an originally plaintext advertisement - Plaintext(PlaintextAdvContents), + Plaintext(PlaintextAdvContents<'adv>), /// 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(), } } } + +/// 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 + } +} + +/// 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; +} + +impl HasIdentityMatch for legacy::ShortMetadataKey { + type Version = V0; + fn metadata_key(&self) -> Self { + *self + } +} + +impl HasIdentityMatch for MetadataKey { + type Version = V1; + fn metadata_key(&self) -> Self { + *self + } +} + +#[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. +/// it, along with any other information which is relevant to the identity-match. #[derive(Debug, PartialEq, Eq)] -pub struct WithMatchedCredential<'m, M: MatchedCredential<'m>, T> { +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, - // the compiler sees 'm as unused - marker: marker::PhantomData<&'m ()>, } -impl<'m, M: MatchedCredential<'m>, T> WithMatchedCredential<'m, M, T> { - fn new(matched: M, contents: T) -> Self { - Self { matched, contents, marker: marker::PhantomData } +impl<'a, M: MatchedCredential + Clone, T: HasIdentityMatch> + WithMatchedCredential<ReferencedMatchedCredential<'a, M>, T> +{ + /// Clones the referenced match-data to update this container + /// so that the match-data is owned, rather than borrowed. + pub fn clone_match_data(self) -> WithMatchedCredential<M, T> { + let matched = self.matched.as_ref().clone(); + let metadata_nonce = self.metadata_nonce; + let contents = self.contents; + + WithMatchedCredential { matched, metadata_nonce, contents } + } +} +impl<M: MatchedCredential, T: HasIdentityMatch> WithMatchedCredential<M, T> { + fn new(matched: M, metadata_nonce: [u8; 12], contents: T) -> Self { + Self { matched, metadata_nonce, contents } + } + /// Applies the given function to the wrapped contents, yielding + /// a new instance with the same matched-credential. + pub fn map<R: HasIdentityMatch>( + self, + mapping: impl FnOnce(T) -> R, + ) -> WithMatchedCredential<M, R> { + let contents = mapping(self.contents); + let matched = self.matched; + let metadata_nonce = self.metadata_nonce; + WithMatchedCredential { matched, metadata_nonce, contents } } /// Credential data for the credential that decrypted the content. pub fn matched_credential(&self) -> &M { @@ -535,12 +640,41 @@ impl<'m, M: MatchedCredential<'m>, T> WithMatchedCredential<'m, M, T> { 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, Copy)] pub(crate) struct V1Header { header_byte: u8, } + const PROTOCOL_VERSION_LEGACY: u8 = 0; const PROTOCOL_VERSION_EXTENDED: u8 = 1; @@ -582,6 +716,8 @@ pub enum AdvDeserializationErrorDetailsHazmat { InvalidDataElementHierarchy, /// Must have an identity DE MissingIdentity, + /// Non-identity DE contents must not be empty + NoPublicDataElements, } impl From<AdvDeserializeError> for AdvDeserializationError { @@ -593,27 +729,18 @@ impl From<AdvDeserializeError> for AdvDeserializationError { AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError, } } - AdvDeserializeError::DataElementDeserializeError(e) => { - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::V0DataElementDeserializeError(e), - } - } AdvDeserializeError::TooManyTopLevelDataElements => { AdvDeserializationError::ParseError { details_hazmat: AdvDeserializationErrorDetailsHazmat::TooManyTopLevelDataElements, } } - AdvDeserializeError::InvalidDataElementHierarchy => { - AdvDeserializationError::ParseError { - details_hazmat: - AdvDeserializationErrorDetailsHazmat::InvalidDataElementHierarchy, - } - } AdvDeserializeError::MissingIdentity => AdvDeserializationError::ParseError { details_hazmat: AdvDeserializationErrorDetailsHazmat::MissingIdentity, }, + AdvDeserializeError::NoPublicDataElements => AdvDeserializationError::ParseError { + details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements, + }, } } } @@ -634,4 +761,4 @@ pub enum PlaintextIdentityMode { /// /// Used when serializing V0 advertisements or V1 sections. #[derive(Default, Debug)] -pub struct PublicIdentity {} +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..f7b7d5e 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 @@ -12,57 +11,103 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)] + use crypto_provider_default::CryptoProviderImpl; use ldt_np_adv::*; 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 arena = deserialization_arena!(); + let adv = deserialize_advertisement::<_, CryptoProviderImpl>( + 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( + vec![PlainDataElement::TxPower(TxPowerDataElement::from( TxPower::try_from(3).unwrap() - )),], - p.data_elements().collect::<Vec<_>>() + ))], + p.data_elements().collect::<Result<Vec<_>, _>>().unwrap() ); } _ => panic!("this example is plaintext"), } } +/// 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,28 +119,45 @@ fn v0_deser_ciphertext() { 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 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_v0_advertisement::<_, _, CryptoProviderImpl>(adv, &cred_source) - .unwrap() + 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())),], - decrypted.data_elements().collect::<Vec<_>>() + vec![PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),], + decrypted.data_elements().collect::<Result<Vec<_>, _>>().unwrap(), ); } diff --git a/nearby/presence/np_adv/tests/examples_v1.rs b/nearby/presence/np_adv/tests/examples_v1.rs index ddb7acf..6e93a3d 100644 --- a/nearby/presence/np_adv/tests/examples_v1.rs +++ b/nearby/presence/np_adv/tests/examples_v1.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)] + use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; use np_adv::extended::data_elements::TxPowerDataElement; @@ -20,11 +22,11 @@ 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, SignedEncryptedSectionEncoder}, @@ -32,6 +34,7 @@ use np_adv::{ PlaintextIdentityMode, *, }; use np_hkdf::v1_salt; +use serde::{Deserialize, Serialize}; #[test] fn v1_deser_plaintext() { @@ -43,10 +46,18 @@ fn v1_deser_plaintext() { 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 +70,7 @@ fn v1_deser_plaintext() { _ => 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 +79,61 @@ fn v1_deser_plaintext() { 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 json byte-string. + fn to_bytes(&self) -> Vec<u8> { + serde_json::to_vec(self).expect("Identity metadata serialization is infallible") + } + /// 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 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(AdvertisementType::Encrypted); + let mut section_builder = adv_builder - .section_builder(SignedEncryptedSectionEncoder::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 +142,50 @@ fn v1_deser_ciphertext() { 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().to_bytes(), + ); + + 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]; @@ -137,10 +198,14 @@ fn v1_deser_ciphertext() { fn v1_deser_no_section() { 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, @@ -170,10 +235,14 @@ fn v1_deser_plaintext_over_max_sections() { ] .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_adv_dynamic/Cargo.toml b/nearby/presence/np_adv_dynamic/Cargo.toml new file mode 100644 index 0000000..74d6e3a --- /dev/null +++ b/nearby/presence/np_adv_dynamic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "np_adv_dynamic" +version.workspace = true +edition.workspace = true +publish.workspace = true + +[lints] +workspace = true + +[dependencies] +array_view.workspace = true +np_adv = { workspace = true, features = ["alloc"] } +crypto_provider.workspace = true +thiserror.workspace = true +sink.workspace = true diff --git a/nearby/presence/np_adv_dynamic/src/extended.rs b/nearby/presence/np_adv_dynamic/src/extended.rs new file mode 100644 index 0000000..5e1328c --- /dev/null +++ b/nearby/presence/np_adv_dynamic/src/extended.rs @@ -0,0 +1,350 @@ +// 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::CryptoProvider; +use np_adv::{extended::data_elements::*, extended::serialize::*, shared_data::*}; +use sink::Sink; +use thiserror::Error; + +/// An advertisement builder for V1 advertisements where the +/// presence/absence of salt is determined at run-time instead of compile-time. +pub struct BoxedAdvBuilder { + adv_builder: Box<AdvBuilder>, +} + +impl From<AdvBuilder> for BoxedAdvBuilder { + fn from(adv_builder: AdvBuilder) -> Self { + let adv_builder = Box::new(adv_builder); + BoxedAdvBuilder { adv_builder } + } +} + +/// Error possibly generated when attempting to add a section to +/// a BoxedAdvBuilder. +#[derive(Debug, Error)] +pub enum BoxedAddSectionError { + /// An error which was generated by the underlying AdvBuilder wrapped by the BoxedAdvBuilder + #[error("{0}")] + Underlying(AddSectionError), + /// An error generated when the boxed advertisement builder is unsalted, but the section + /// identity requires salt. + #[error("Error generated when the BoxedAdvBuilder is unsalted, but the section identity requires salt.")] + IdentityRequiresSaltError, +} + +impl From<AddSectionError> for BoxedAddSectionError { + fn from(wrapped: AddSectionError) -> Self { + BoxedAddSectionError::Underlying(wrapped) + } +} + +fn wrap_owning_section_builder<C: CryptoProvider, S: Into<BoxedSectionBuilder<AdvBuilder, C>>>( + maybe_section_builder: Result<S, (AdvBuilder, AddSectionError)>, +) -> Result<BoxedSectionBuilder<AdvBuilder, C>, (BoxedAdvBuilder, BoxedAddSectionError)> { + match maybe_section_builder { + Ok(section_builder) => Ok(section_builder.into()), + Err((adv_builder, err)) => Err((adv_builder.into(), err.into())), + } +} + +fn wrap_mut_ref_section_builder< + 'a, + C: CryptoProvider, + S: Into<BoxedSectionBuilder<&'a mut AdvBuilder, C>>, +>( + maybe_section_builder: Result<S, AddSectionError>, +) -> Result<BoxedSectionBuilder<&'a mut AdvBuilder, C>, BoxedAddSectionError> { + let section_builder = maybe_section_builder?; + Ok(section_builder.into()) +} + +impl BoxedAdvBuilder { + /// Gets the current count of sections which have been added + /// to this advertisement builder. Does not count currently- + /// outstanding section builders. + pub fn section_count(&self) -> usize { + self.adv_builder.section_count() + } + + /// Create a section builder using the given identity, + /// taking ownership of this advertisement builder. + /// + /// Unlike `BoxedAdvBuilder#section_builder`, the returned + /// section builder will take ownership of this advertisement + /// builder, if the operation was successful. Otherwise, + /// this advertisement builder will be returned back to the + /// caller unaltered as part of the `Err` arm. + pub fn into_section_builder<C: CryptoProvider>( + self, + identity: BoxedIdentity<C>, + ) -> Result<BoxedSectionBuilder<AdvBuilder, C>, (Self, BoxedAddSectionError)> { + match identity { + BoxedIdentity::PublicIdentity => wrap_owning_section_builder( + self.adv_builder.into_section_builder(PublicSectionEncoder::default()), + ), + BoxedIdentity::MicEncrypted(ident) => { + wrap_owning_section_builder(self.adv_builder.into_section_builder(ident)) + } + BoxedIdentity::SignedEncrypted(ident) => { + wrap_owning_section_builder(self.adv_builder.into_section_builder(ident)) + } + } + } + + /// Create a section builder using the given identity. + /// + /// Returns `Err` if the underlying advertisement builder + /// yields an error when attempting to add a new section + /// (typically because there's no more available adv space), + /// or if the requested identity requires salt, and the + /// advertisement builder is salt-less. + pub fn section_builder<C: CryptoProvider>( + &mut self, + identity: BoxedIdentity<C>, + ) -> Result<BoxedSectionBuilder<&mut AdvBuilder, C>, BoxedAddSectionError> { + match identity { + BoxedIdentity::PublicIdentity => wrap_mut_ref_section_builder( + self.adv_builder.section_builder(PublicSectionEncoder::default()), + ), + BoxedIdentity::MicEncrypted(ident) => { + wrap_mut_ref_section_builder(self.adv_builder.section_builder(ident)) + } + BoxedIdentity::SignedEncrypted(ident) => { + wrap_mut_ref_section_builder(self.adv_builder.section_builder(ident)) + } + } + } + + /// Convert the builder into an encoded advertisement. + pub fn into_advertisement(self) -> EncodedAdvertisement { + self.adv_builder.into_advertisement() + } +} + +/// A wrapped v1 identity whose type is given at run-time. +pub enum BoxedIdentity<C: CryptoProvider> { + /// Public identity. + PublicIdentity, + /// An encrypted identity leveraging MIC for verification. + MicEncrypted(MicEncryptedSectionEncoder<C>), + /// An encrypted identity leveraging signatures for verification. + SignedEncrypted(SignedEncryptedSectionEncoder<C>), +} + +/// A `SectionBuilder` whose corresponding Identity +/// and salted-ness is given at run-time instead of +/// at compile-time. +pub enum BoxedSectionBuilder<R: AsMut<AdvBuilder>, C: CryptoProvider> { + /// A builder for a public section. + Public(Box<SectionBuilder<R, PublicSectionEncoder>>), + /// A builder for a MIC-verified section. + MicEncrypted(Box<SectionBuilder<R, MicEncryptedSectionEncoder<C>>>), + /// A builder for a signature-verified section. + SignedEncrypted(Box<SectionBuilder<R, SignedEncryptedSectionEncoder<C>>>), +} + +impl<C: CryptoProvider> BoxedSectionBuilder<AdvBuilder, C> { + /// Gets the 0-based index of the section currently under construction + /// in the context of the containing advertisement. + pub fn section_index(&self) -> usize { + match self { + BoxedSectionBuilder::Public(x) => x.section_index(), + BoxedSectionBuilder::MicEncrypted(x) => x.section_index(), + BoxedSectionBuilder::SignedEncrypted(x) => x.section_index(), + } + } + /// Add this builder to the advertisement that created it, + /// returning the containing advertisement builder. + pub fn add_to_advertisement(self) -> BoxedAdvBuilder { + let adv_builder = match self { + BoxedSectionBuilder::Public(x) => x.add_to_advertisement(), + BoxedSectionBuilder::MicEncrypted(x) => x.add_to_advertisement(), + BoxedSectionBuilder::SignedEncrypted(x) => x.add_to_advertisement(), + }; + BoxedAdvBuilder::from(adv_builder) + } +} + +impl<'a, C: CryptoProvider> BoxedSectionBuilder<&'a mut AdvBuilder, C> { + /// Add this builder to the advertisement that created it. + pub fn add_to_advertisement(self) { + match self { + BoxedSectionBuilder::Public(x) => x.add_to_advertisement(), + BoxedSectionBuilder::MicEncrypted(x) => x.add_to_advertisement(), + BoxedSectionBuilder::SignedEncrypted(x) => x.add_to_advertisement(), + } + } +} + +impl<R: AsMut<AdvBuilder>, C: CryptoProvider> BoxedSectionBuilder<R, C> { + /// Returns true if this wraps a section builder which + /// leverages some encrypted identity. + pub fn is_encrypted(&self) -> bool { + match self { + BoxedSectionBuilder::Public(_) => false, + BoxedSectionBuilder::MicEncrypted(_) => true, + BoxedSectionBuilder::SignedEncrypted(_) => true, + } + } + /// Gets the derived salt of the next DE to be added to the section, + /// if this section-builder corresponds to an encrypted section. + /// Otherwise, returns nothing. + /// + /// Suitable for scenarios (like FFI) where a closure would be inappropriate + /// for DE construction, and interaction with the client is preferred. + pub fn next_de_salt(&self) -> Option<DeSalt<C>> { + match self { + BoxedSectionBuilder::Public(_) => None, + BoxedSectionBuilder::MicEncrypted(x) => Some(x.next_de_salt()), + BoxedSectionBuilder::SignedEncrypted(x) => Some(x.next_de_salt()), + } + } + + /// 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, + /// if any salt has been specified for the surrounding advertisement. + pub fn add_de_res<E>( + &mut self, + build_de: impl FnOnce(Option<DeSalt<C>>) -> Result<BoxedWriteDataElement, E>, + ) -> Result<(), AddDataElementError<E>> { + match self { + BoxedSectionBuilder::Public(x) => { + let build_de_modified = |()| build_de(None); + x.add_de_res(build_de_modified) + } + BoxedSectionBuilder::MicEncrypted(x) => { + let build_de_modified = |de_salt: DeSalt<C>| build_de(Some(de_salt)); + x.add_de_res(build_de_modified) + } + BoxedSectionBuilder::SignedEncrypted(x) => { + let build_de_modified = |de_salt: DeSalt<C>| build_de(Some(de_salt)); + x.add_de_res(build_de_modified) + } + } + } + /// Like add_de_res, but for infalliable closures + pub fn add_de( + &mut self, + build_de: impl FnOnce(Option<DeSalt<C>>) -> BoxedWriteDataElement, + ) -> Result<(), AddDataElementError<()>> { + self.add_de_res(|derived_salt| Ok::<_, ()>(build_de(derived_salt))) + } +} + +impl<R: AsMut<AdvBuilder>, C: CryptoProvider> From<SectionBuilder<R, PublicSectionEncoder>> + for BoxedSectionBuilder<R, C> +{ + fn from(section_builder: SectionBuilder<R, PublicSectionEncoder>) -> Self { + BoxedSectionBuilder::Public(Box::new(section_builder)) + } +} + +impl<R: AsMut<AdvBuilder>, C: CryptoProvider> From<SectionBuilder<R, MicEncryptedSectionEncoder<C>>> + for BoxedSectionBuilder<R, C> +{ + fn from(section_builder: SectionBuilder<R, MicEncryptedSectionEncoder<C>>) -> Self { + BoxedSectionBuilder::MicEncrypted(Box::new(section_builder)) + } +} + +impl<R: AsMut<AdvBuilder>, C: CryptoProvider> + From<SectionBuilder<R, SignedEncryptedSectionEncoder<C>>> for BoxedSectionBuilder<R, C> +{ + fn from(section_builder: SectionBuilder<R, SignedEncryptedSectionEncoder<C>>) -> Self { + BoxedSectionBuilder::SignedEncrypted(Box::new(section_builder)) + } +} + +/// Mutable trait object reference to a `Sink<u8>` +pub struct DynSink<'a> { + wrapped: &'a mut dyn Sink<u8>, +} + +impl<'a> Sink<u8> for DynSink<'a> { + fn try_extend_from_slice(&mut self, items: &[u8]) -> Option<()> { + self.wrapped.try_extend_from_slice(items) + } + fn try_push(&mut self, item: u8) -> Option<()> { + self.wrapped.try_push(item) + } +} + +impl<'a> From<&'a mut dyn Sink<u8>> for DynSink<'a> { + fn from(wrapped: &'a mut dyn Sink<u8>) -> Self { + DynSink { wrapped } + } +} + +/// A version of the WriteDataElement trait which is object-safe +pub trait DynWriteDataElement { + /// Gets the data-element header for the data element + fn de_header(&self) -> DeHeader; + /// Writes the contents of the DE payload to the given DynSink. + /// Returns Some(()) if the write operation was successful, + /// and None if it was unsuccessful + fn write_de_contents(&self, sink: DynSink) -> Option<()>; +} + +impl<T: WriteDataElement> DynWriteDataElement for T { + fn de_header(&self) -> DeHeader { + WriteDataElement::de_header(self) + } + fn write_de_contents(&self, mut sink: DynSink) -> Option<()> { + WriteDataElement::write_de_contents(self, &mut sink) + } +} + +/// Trait object wrapper for DynWriteDataElement instances +pub struct BoxedWriteDataElement { + wrapped: Box<dyn DynWriteDataElement>, +} + +impl BoxedWriteDataElement { + /// Constructs a new `BoxedWriteDataElement` from a `WriteDataElement` + /// whose trait impl is valid for a `'static` lifetime. + pub fn new<D: WriteDataElement + 'static>(wrapped: D) -> Self { + let wrapped = Box::new(wrapped); + Self { wrapped } + } +} + +impl WriteDataElement for BoxedWriteDataElement { + fn de_header(&self) -> DeHeader { + self.wrapped.de_header() + } + fn write_de_contents<S: Sink<u8>>(&self, sink: &mut S) -> Option<()> { + let sink: &mut dyn Sink<u8> = sink; + let dyn_sink = DynSink::from(sink); + self.wrapped.write_de_contents(dyn_sink) + } +} + +impl From<TxPower> for BoxedWriteDataElement { + fn from(tx_power: TxPower) -> Self { + BoxedWriteDataElement::new::<TxPowerDataElement>(tx_power.into()) + } +} + +impl From<ContextSyncSeqNum> for BoxedWriteDataElement { + fn from(context_sync_sequence_num: ContextSyncSeqNum) -> Self { + BoxedWriteDataElement::new::<ContextSyncSeqNumDataElement>(context_sync_sequence_num.into()) + } +} + +impl From<ActionsDataElement> for BoxedWriteDataElement { + fn from(data: ActionsDataElement) -> Self { + BoxedWriteDataElement::new(data) + } +} diff --git a/nearby/presence/np_adv_dynamic/src/legacy.rs b/nearby/presence/np_adv_dynamic/src/legacy.rs new file mode 100644 index 0000000..191ef75 --- /dev/null +++ b/nearby/presence/np_adv_dynamic/src/legacy.rs @@ -0,0 +1,349 @@ +// 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 array_view::ArrayView; +use crypto_provider::CryptoProvider; +use np_adv::legacy::actions::*; +use np_adv::legacy::data_elements::*; +use np_adv::legacy::serialize::*; +use np_adv::legacy::*; +use np_adv::shared_data::*; +use np_adv::PublicIdentity; +use thiserror::Error; + +/// Wrapper around a V0 advertisement builder which +/// is agnostic to the kind of identity used in the advertisement. +/// Instead of compile-time errors for non-matching packet flavors, +/// this builder instead defers any generated errors to run-time. +/// Generic over the Aes algorithm used for any encrypted identities, +/// since that is generally specified at compile-time. +pub enum BoxedAdvBuilder<C: CryptoProvider> { + /// Builder for public advertisements. + Public(AdvBuilder<PublicIdentity>), + /// Builder for LDT-encryptedadvertisements. + Ldt(AdvBuilder<LdtIdentity<C>>), +} + +/// Wrapper around possible errors which occur only during +/// advertisement construction from a builder. +#[derive(Debug)] +pub enum BoxedAdvConstructionError { + /// An error originating from a problem with LDT + /// encryption of the advertisement contents. + Ldt(LdtPostprocessError), +} + +impl<C: CryptoProvider> BoxedAdvBuilder<C> { + /// Returns true if this wraps an adv builder which + /// leverages some encrypted identity. + pub fn is_encrypted(&self) -> bool { + match self { + BoxedAdvBuilder::Public(_) => false, + BoxedAdvBuilder::Ldt(_) => true, + } + } + /// Constructs a new BoxedAdvBuilder from the given BoxedIdentity + pub fn new(identity: BoxedIdentity<C>) -> Self { + match identity { + BoxedIdentity::Public(identity) => BoxedAdvBuilder::Public(AdvBuilder::new(identity)), + BoxedIdentity::LdtIdentity(identity) => BoxedAdvBuilder::Ldt(AdvBuilder::new(identity)), + } + } + /// Attempts to add a data element to the advertisement + /// being built by this BoxedAdvBuilder. Returns + /// nothing if successful, and a BoxedAddDataElementError + /// if something went wrong in the attempt to add the DE. + pub fn add_data_element( + &mut self, + data_element: ToBoxedDataElementBundle, + ) -> Result<(), BoxedAddDataElementError> { + match self { + BoxedAdvBuilder::Public(public_builder) => { + //Verify that we can get the data element as plaintext + let maybe_plaintext_data_element = data_element.to_plaintext(); + match maybe_plaintext_data_element { + Some(plaintext_data_element) => public_builder + .add_data_element(plaintext_data_element) + .map_err(|e| e.into()), + None => Err(BoxedAddDataElementError::FlavorMismatchError), + } + } + BoxedAdvBuilder::Ldt(private_builder) => { + //Verify that we can get the data element as ciphertext + let maybe_ciphertext_data_element = data_element.to_ciphertext(); + match maybe_ciphertext_data_element { + Some(ciphertext_data_element) => private_builder + .add_data_element(ciphertext_data_element) + .map_err(|e| e.into()), + None => Err(BoxedAddDataElementError::FlavorMismatchError), + } + } + } + } + /// Consume this BoxedAdvBuilder and attempt to create + /// a serialized advertisement including the added DEs. + pub fn into_advertisement( + self, + ) -> Result<ArrayView<u8, BLE_ADV_SVC_CONTENT_LEN>, BoxedAdvConstructionError> { + match self { + BoxedAdvBuilder::Public(x) => { + match x.into_advertisement() { + Ok(x) => Ok(x), + Err(x) => match x {}, //Infallible + } + } + BoxedAdvBuilder::Ldt(x) => { + x.into_advertisement().map_err(BoxedAdvConstructionError::Ldt) + } + } + } +} + +/// Possible errors generated when trying to add a DE to a +/// BoxedAdvBuilder. +#[derive(Debug, Error)] +pub enum BoxedAddDataElementError { + /// Some kind of error in adding the data element which + /// is not an issue with trying to add a DE of the incorrect + /// packet flavoring. + #[error("{0:?}")] + UnderlyingError(AddDataElementError), + #[error( + "Expected packet flavoring for added DEs does not match the actual packet flavor of the DE" + )] + /// Error when attempting to add a DE which requires one + /// of an (encrypted/plaintext) advertisement, but the + /// advertisement builder doesn't match this requirement. + FlavorMismatchError, +} + +impl From<AddDataElementError> for BoxedAddDataElementError { + fn from(add_data_element_error: AddDataElementError) -> Self { + BoxedAddDataElementError::UnderlyingError(add_data_element_error) + } +} + +/// Trait object reference to a `ToDataElementBundle<I>` with lifetime `'a`. +/// Implements `ToDataElementBundle<I>` by deferring to the wrapped trait object. +pub struct DynamicToDataElementBundle<'a, I: PacketFlavor> { + wrapped: &'a dyn ToDataElementBundle<I>, +} + +impl<'a, I: PacketFlavor> From<&'a dyn ToDataElementBundle<I>> + for DynamicToDataElementBundle<'a, I> +{ + fn from(wrapped: &'a dyn ToDataElementBundle<I>) -> Self { + DynamicToDataElementBundle { wrapped } + } +} + +impl<'a, I: PacketFlavor> ToDataElementBundle<I> for DynamicToDataElementBundle<'a, I> { + fn to_de_bundle(&self) -> DataElementBundle<I> { + self.wrapped.to_de_bundle() + } +} + +/// Trait for types which can provide trait object +/// references to either plaintext or ciphertext [ToDataElementBundle] +pub trait ToMultiFlavorElementBundle { + /// Gets the associated trait object reference to a `ToDataElementBundle<Plaintext>` + /// with the same lifetime as a reference to the implementor. + fn to_plaintext(&self) -> DynamicToDataElementBundle<Plaintext>; + + /// Gets the associated trait object reference to a `ToDataElementBundle<Ciphertext>` + /// with the same lifetime as a reference to the implementor. + fn to_ciphertext(&self) -> DynamicToDataElementBundle<Ciphertext>; +} + +/// Blanket impl of [ToMultiFlavorElementBundle] for implementors of [ToDataElementBundle] +/// for both [Plaintext] and [Ciphertext] packet flavors. +impl<T: ToDataElementBundle<Plaintext> + ToDataElementBundle<Ciphertext>> ToMultiFlavorElementBundle + for T +{ + fn to_plaintext(&self) -> DynamicToDataElementBundle<Plaintext> { + let reference: &dyn ToDataElementBundle<Plaintext> = self; + reference.into() + } + fn to_ciphertext(&self) -> DynamicToDataElementBundle<Ciphertext> { + let reference: &dyn ToDataElementBundle<Ciphertext> = self; + reference.into() + } +} + +/// Boxed trait object version of [ToDataElementBundle] which incorporates +/// all possible variants on generatable packet flavoring +/// (`Plaintext`, `Ciphertext`, or both, as a [ToMultiFlavorElementBundle]) +pub enum ToBoxedDataElementBundle { + /// The underlying DE is plaintext-only. + Plaintext(Box<dyn ToDataElementBundle<Plaintext>>), + /// The underlying DE is ciphertext-only. + Ciphertext(Box<dyn ToDataElementBundle<Ciphertext>>), + /// The underlying DE may exist in plaintext or + /// in ciphertext advertisements. + Both(Box<dyn ToMultiFlavorElementBundle>), +} + +impl ToBoxedDataElementBundle { + /// If this [ToBoxedDataElementBundle] can generate plaintext, returns + /// a trait object reference to a `ToDataElementBundle<Plaintext>` + pub fn to_plaintext(&self) -> Option<DynamicToDataElementBundle<Plaintext>> { + match &self { + ToBoxedDataElementBundle::Plaintext(x) => Some(x.as_ref().into()), + ToBoxedDataElementBundle::Ciphertext(_) => None, + ToBoxedDataElementBundle::Both(x) => Some(x.as_ref().to_plaintext()), + } + } + /// If this [ToBoxedDataElementBundle] can generate ciphertext, returns + /// a trait object reference to a `ToDataElementBundle<Ciphertext>` + pub fn to_ciphertext(&self) -> Option<DynamicToDataElementBundle<Ciphertext>> { + match &self { + ToBoxedDataElementBundle::Plaintext(_) => None, + ToBoxedDataElementBundle::Ciphertext(x) => Some(x.as_ref().into()), + ToBoxedDataElementBundle::Both(x) => Some(x.as_ref().to_ciphertext()), + } + } +} + +/// Boxed version of implementors of the Identity trait. +/// A is the underlying Aes algorithm leveraged by ciphertext-based identities. +pub enum BoxedIdentity<C: CryptoProvider> { + /// Public Identity. + Public(PublicIdentity), + /// An encrypted identity, using LDT encryption. + LdtIdentity(LdtIdentity<C>), +} + +impl<C: CryptoProvider> From<PublicIdentity> for BoxedIdentity<C> { + fn from(public_identity: PublicIdentity) -> BoxedIdentity<C> { + BoxedIdentity::Public(public_identity) + } +} + +impl<C: CryptoProvider> From<LdtIdentity<C>> for BoxedIdentity<C> { + fn from(ldt_identity: LdtIdentity<C>) -> BoxedIdentity<C> { + BoxedIdentity::LdtIdentity(ldt_identity) + } +} + +impl From<TxPower> for ToBoxedDataElementBundle { + fn from(data: TxPower) -> Self { + ToBoxedDataElementBundle::Both(Box::new(TxPowerDataElement::from(data))) + } +} + +impl From<BoxedActionBits> for ToBoxedDataElementBundle { + fn from(action_bits: BoxedActionBits) -> Self { + match action_bits { + BoxedActionBits::Plaintext(action_bits) => { + ToBoxedDataElementBundle::Plaintext(Box::new(ActionsDataElement::from(action_bits))) + } + BoxedActionBits::Ciphertext(action_bits) => ToBoxedDataElementBundle::Ciphertext( + Box::new(ActionsDataElement::from(action_bits)), + ), + } + } +} + +/// Boxed version of `ToActionElement` which allows abstracting over +/// what packet flavors are supported by a given action. +pub enum ToBoxedActionElement { + /// A context-sync sequence number. + ContextSyncSeqNum(ContextSyncSeqNum), + /// Action bit for active unlock. + ActiveUnlock(bool), + /// Action bit for nearby share. + NearbyShare(bool), + /// Action bit for instant tethering. + InstantTethering(bool), + /// Action bit for PhoneHub. + PhoneHub(bool), + /// Action bit for Finder. + Finder(bool), + /// Action bit for Fast Pair/SASS + FastPairSass(bool), + /// Action bit for Presence Manager. + PresenceManager(bool), +} + +/// [`ActionBits`] with runtime-determined packet flavoring +pub enum BoxedActionBits { + /// Action-bits for a plaintext advertisement. + Plaintext(ActionBits<Plaintext>), + /// Action-bits for a ciphertext advertisement. + Ciphertext(ActionBits<Ciphertext>), +} + +/// Error which is raised when the flavor of a [`BoxedActionBits`] +/// does not match the supported flavors of a [`ToBoxedActionElement`] +/// upon attempting to add the action to the bit-field. +#[derive(Debug)] +pub struct BoxedSetActionFlavorError; + +impl BoxedActionBits { + /// Constructs the [`BoxedActionBits`] variant with the specified packet + /// flavor variant, and no bits set. + pub fn new(packet_flavor: PacketFlavorEnum) -> Self { + match packet_flavor { + PacketFlavorEnum::Plaintext => BoxedActionBits::Plaintext(ActionBits::default()), + PacketFlavorEnum::Ciphertext => BoxedActionBits::Ciphertext(ActionBits::default()), + } + } + + fn set<F: PacketFlavor, E: ToActionElement<F>>( + action_bits: &mut ActionBits<F>, + to_element: E, + ) -> Result<(), BoxedSetActionFlavorError> { + action_bits.set_action(to_element); + Ok(()) + } + + /// Attempts to set the specified [`ToBoxedActionElement`], yielding + /// a [`BoxedSetActionFlavorError`] if the flavor of this + /// [`BoxedActionBits`] isn't supported by the passed [`ToBoxedActionElement`]. + pub fn set_action( + &mut self, + to_element: ToBoxedActionElement, + ) -> Result<(), BoxedSetActionFlavorError> { + match self { + BoxedActionBits::Plaintext(action_bits) => match to_element { + ToBoxedActionElement::ContextSyncSeqNum(x) => Self::set(action_bits, x), + ToBoxedActionElement::NearbyShare(b) => { + Self::set(action_bits, NearbyShare::from(b)) + } + ToBoxedActionElement::Finder(b) => Self::set(action_bits, Finder::from(b)), + ToBoxedActionElement::FastPairSass(b) => { + Self::set(action_bits, FastPairSass::from(b)) + } + _ => Err(BoxedSetActionFlavorError), + }, + BoxedActionBits::Ciphertext(action_bits) => match to_element { + ToBoxedActionElement::ContextSyncSeqNum(x) => Self::set(action_bits, x), + ToBoxedActionElement::ActiveUnlock(b) => { + Self::set(action_bits, ActiveUnlock::from(b)) + } + ToBoxedActionElement::NearbyShare(b) => { + Self::set(action_bits, NearbyShare::from(b)) + } + ToBoxedActionElement::InstantTethering(b) => { + Self::set(action_bits, InstantTethering::from(b)) + } + ToBoxedActionElement::PhoneHub(b) => Self::set(action_bits, PhoneHub::from(b)), + ToBoxedActionElement::PresenceManager(b) => { + Self::set(action_bits, PresenceManager::from(b)) + } + _ => Err(BoxedSetActionFlavorError), + }, + } + } +} diff --git a/nearby/presence/np_adv_dynamic/src/lib.rs b/nearby/presence/np_adv_dynamic/src/lib.rs new file mode 100644 index 0000000..4ba8f8b --- /dev/null +++ b/nearby/presence/np_adv_dynamic/src/lib.rs @@ -0,0 +1,21 @@ +// 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 no_std-friendly wrapper around `np_adv` to allow dynamic-style +//! advertisement serialization, with correctness checked at run-time. + +/// Dynamic wrappers around extended adv serialization. +pub mod extended; +/// Dynamic wrappers around legacy adv serialization. +pub mod legacy; diff --git a/nearby/presence/np_c_ffi/Cargo.lock b/nearby/presence/np_c_ffi/Cargo.lock index 6f79c82..bce44c6 100644 --- a/nearby/presence/np_c_ffi/Cargo.lock +++ b/nearby/presence/np_c_ffi/Cargo.lock @@ -25,38 +25,35 @@ dependencies = [ ] [[package]] -name = "aes-gcm-siv" -version = "0.11.1" +name = "aes-gcm" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", "cipher", "ctr", - "polyval", + "ghash", "subtle", - "zeroize", ] [[package]] -name = "ahash" -version = "0.8.3" +name = "aes-gcm-siv" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" dependencies = [ - "cfg-if", - "once_cell", - "version_check", + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", ] [[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 @@ version = "0.2.14" 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 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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 @@ dependencies = [ ] [[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 @@ dependencies = [ ] [[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 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_lex", "indexmap", "strsim", @@ -193,24 +207,24 @@ dependencies = [ [[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 @@ dependencies = [ [[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 @@ version = "0.1.0" dependencies = [ "cfg-if", "crypto_provider", + "crypto_provider_boringssl", "crypto_provider_rustcrypto", ] @@ -248,6 +274,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -277,9 +304,9 @@ dependencies = [ [[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 @@ dependencies = [ "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,34 @@ dependencies = [ [[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.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", + "serde", "sha2", + "subtle", + "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 +397,19 @@ dependencies = [ [[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 +423,9 @@ dependencies = [ [[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 +450,16 @@ dependencies = [ ] [[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" @@ -446,11 +474,7 @@ dependencies = [ name = "handle_map" 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 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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 @@ dependencies = [ ] [[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 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -529,30 +537,10 @@ dependencies = [ ] [[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 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] @@ -592,43 +580,31 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libc_alloc" -version = "1.0.4" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a090348b66d90d8507e30f0d2bd88e5a5c454bd1733fc6d617cbc3471bf69ea" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] +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" @@ -658,7 +634,6 @@ dependencies = [ "nom", "np_ed25519", "np_hkdf", - "rand", "sink", "strum", "strum_macros", @@ -667,15 +642,23 @@ dependencies = [ ] [[package]] +name = "np_adv_dynamic" +version = "0.1.0" +dependencies = [ + "array_view", + "crypto_provider", + "np_adv", + "sink", + "thiserror", +] + +[[package]] name = "np_c_ffi" version = "0.1.0" dependencies = [ "cbindgen", - "crypto_provider_default", - "libc_alloc", + "lock_adapter", "np_ffi_core", - "panic-abort", - "spin 0.9.8", ] [[package]] @@ -696,8 +679,12 @@ dependencies = [ "crypto_provider", "crypto_provider_default", "handle_map", + "lazy_static", + "ldt_np_adv", + "lock_adapter", "np_adv", - "spin 0.9.8", + "np_adv_dynamic", + "np_hkdf", ] [[package]] @@ -710,12 +697,6 @@ dependencies = [ ] [[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" @@ -723,9 +704,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[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" @@ -738,16 +719,20 @@ dependencies = [ ] [[package]] -name = "panic-abort" -version = "0.3.2" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e20e6499bbbc412f280b04a42346b356c6fa0753d5fd22b7bd752ff34c778ee" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] [[package]] name = "platforms" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" [[package]] name = "polyval" @@ -762,12 +747,6 @@ dependencies = [ ] [[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" @@ -784,18 +763,18 @@ dependencies = [ [[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", ] @@ -806,6 +785,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -830,11 +811,11 @@ dependencies = [ [[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]] @@ -848,13 +829,12 @@ dependencies = [ [[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", @@ -862,27 +842,21 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" - -[[package]] -name = "ryu" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] -name = "scopeguard" -version = "1.1.0" +name = "ryu" +version = "1.0.15" 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", @@ -893,35 +867,35 @@ dependencies = [ [[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", @@ -930,9 +904,9 @@ dependencies = [ [[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", @@ -959,12 +933,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" 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]] @@ -975,21 +950,21 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[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]] @@ -1011,9 +986,9 @@ dependencies = [ [[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", @@ -1022,11 +997,10 @@ dependencies = [ [[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", @@ -1036,9 +1010,9 @@ dependencies = [ [[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", ] @@ -1050,6 +1024,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] +name = "thiserror" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1066,15 +1060,15 @@ dependencies = [ [[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" @@ -1116,9 +1110,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[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", ] @@ -1140,9 +1134,9 @@ dependencies = [ [[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", @@ -1155,51 +1149,51 @@ dependencies = [ [[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 2dede26..86bbd53 100644 --- a/nearby/presence/np_c_ffi/Cargo.toml +++ b/nearby/presence/np_c_ffi/Cargo.toml @@ -5,17 +5,17 @@ edition = "2021" 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" -libc_alloc = "1.0.4" -panic-abort = "0.3.2" +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"] 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/include/c/np_c_ffi.h b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h index 03e5821..aab2a30 100644 --- a/nearby/presence/np_c_ffi/include/c/np_c_ffi.h +++ b/nearby/presence/np_c_ffi/include/c/np_c_ffi.h @@ -33,6 +33,21 @@ #include <stdlib.h> /** + * Result type for trying to add a credential to a credential-slab. + */ +enum np_ffi_AddCredentialToSlabResult { + /** + * We succeeded in adding the credential to the slab. + */ + NP_FFI_ADD_CREDENTIAL_TO_SLAB_RESULT_SUCCESS = 0, + /** + * The handle to the slab was actually invalid. + */ + NP_FFI_ADD_CREDENTIAL_TO_SLAB_RESULT_INVALID_HANDLE = 1, +}; +typedef uint8_t np_ffi_AddCredentialToSlabResult; + +/** * The possible boolean action types which can be present in an Actions data element */ enum np_ffi_BooleanActionType { @@ -51,19 +66,41 @@ typedef uint8_t np_ffi_BooleanActionType; */ enum np_ffi_CreateCredentialBookResultKind { /** - * There was no space left to create a new credential book - */ - NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_NO_SPACE_LEFT = 0, - /** * We created a new credential book behind the given handle. * The associated payload may be obtained via * `CreateCredentialBookResult#into_success()`. */ - NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_SUCCESS = 1, + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_SUCCESS = 0, + /** + * There was no space left to create a new credential book + */ + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_NO_SPACE_LEFT = 1, + /** + * The slab that we tried to create a credential-book from + * actually was an invalid handle. + */ + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_KIND_INVALID_SLAB_HANDLE = 2, }; typedef uint8_t np_ffi_CreateCredentialBookResultKind; /** + * Discriminant for `CreateCredentialSlabResult` + */ +enum np_ffi_CreateCredentialSlabResultKind { + /** + * There was no space left to create a new credential slab + */ + NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_KIND_NO_SPACE_LEFT = 0, + /** + * We created a new credential slab behind the given handle. + * The associated payload may be obtained via + * `CreateCredentialSlabResult#into_success()`. + */ + NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_KIND_SUCCESS = 1, +}; +typedef uint8_t np_ffi_CreateCredentialSlabResultKind; + +/** * A result-type enum which tells the caller whether/not a deallocation * succeeded or failed due to the requested handle not being present. */ @@ -79,6 +116,24 @@ typedef enum { } np_ffi_DeallocateResult; /** + * Discriminant for `DecryptMetadataResult`. + */ +enum np_ffi_DecryptMetadataResultKind { + /** + * The attempt to decrypt the metadata of the associated credential succeeded + * The associated payload may be obtained via + * `DecryptMetadataResult#into_success`. + */ + NP_FFI_DECRYPT_METADATA_RESULT_KIND_SUCCESS, + /** + * The attempt to decrypt the metadata failed, either the payload had no matching identity + * ie it was a public advertisement OR the decrypt attempt itself was unsuccessful + */ + NP_FFI_DECRYPT_METADATA_RESULT_KIND_ERROR, +}; +typedef uint8_t np_ffi_DecryptMetadataResultKind; + +/** * Discriminant for `DeserializeAdvertisementResult`. */ enum np_ffi_DeserializeAdvertisementResultKind { @@ -122,16 +177,8 @@ enum np_ffi_DeserializedV0AdvertisementKind { typedef uint8_t np_ffi_DeserializedV0AdvertisementKind; /** - * Represents deserialized information about the V0 identity utilized - * by a deserialized V0 advertisement - */ -typedef enum { - NP_FFI_DESERIALIZED_V0_IDENTITY_PLAINTEXT, - NP_FFI_DESERIALIZED_V0_IDENTITY_DECRYPTED, -} np_ffi_DeserializedV0Identity; - -/** - * Discriminant for `DeserializedV0Identity`. + * Discriminant for deserialized information about the V0 + * identity utilized by a deserialized V0 advertisement. */ enum np_ffi_DeserializedV0IdentityKind { /** @@ -162,6 +209,34 @@ enum np_ffi_DeserializedV1IdentityKind { typedef uint8_t np_ffi_DeserializedV1IdentityKind; /** + * The DE type for an encrypted identity + */ +enum np_ffi_EncryptedIdentityType { + /** + * Identity for broadcasts to nearby devices with the same + * logged-in-account (for some account). + */ + NP_FFI_ENCRYPTED_IDENTITY_TYPE_PRIVATE = 1, + /** + * Identity for broadcasts to nearby devices which this + * device has declared to trust. + */ + NP_FFI_ENCRYPTED_IDENTITY_TYPE_TRUSTED = 2, + /** + * Identity for broadcasts to devices which have been provisioned + * offline with this device. + */ + NP_FFI_ENCRYPTED_IDENTITY_TYPE_PROVISIONED = 4, +}; +typedef uint8_t np_ffi_EncryptedIdentityType; + +enum np_ffi_GetMetadataBufferPartsResultKind { + NP_FFI_GET_METADATA_BUFFER_PARTS_RESULT_KIND_SUCCESS = 0, + NP_FFI_GET_METADATA_BUFFER_PARTS_RESULT_KIND_ERROR = 1, +}; +typedef uint8_t np_ffi_GetMetadataBufferPartsResultKind; + +/** * Discriminant of `GetV0DEResult`. */ enum np_ffi_GetV0DEResultKind { @@ -182,6 +257,47 @@ enum np_ffi_GetV0DEResultKind { typedef uint8_t np_ffi_GetV0DEResultKind; /** + * Discriminant for `GetV0IdentityDetailsResult` + */ +enum np_ffi_GetV0IdentityDetailsResultKind { + /** + * The attempt to get the identity details + * for the advertisement failed, possibly + * due to the advertisement being a public + * advertisement, or the underlying + * advertisement has already been deallocated. + */ + NP_FFI_GET_V0_IDENTITY_DETAILS_RESULT_KIND_ERROR = 0, + /** + * The attempt to get the identity details succeeded. + * The wrapped identity details may be obtained via + * `GetV0IdentityDetailsResult#into_success`. + */ + NP_FFI_GET_V0_IDENTITY_DETAILS_RESULT_KIND_SUCCESS = 1, +}; +typedef uint8_t np_ffi_GetV0IdentityDetailsResultKind; + +/** + * Discriminant for `GetV1DE16ByteSaltResult`. + */ +enum np_ffi_GetV1DE16ByteSaltResultKind { + /** + * The attempt to get the derived salt failed, possibly + * because the passed DE offset was invalid (==255), + * or because there was no salt included for the + * referenced advertisement section (i.e: it was + * a public advertisement section, or it was deallocated.) + */ + NP_FFI_GET_V1DE16_BYTE_SALT_RESULT_KIND_ERROR = 0, + /** + * A 16-byte salt for the given DE offset was successfully + * derived. + */ + NP_FFI_GET_V1DE16_BYTE_SALT_RESULT_KIND_SUCCESS = 1, +}; +typedef uint8_t np_ffi_GetV1DE16ByteSaltResultKind; + +/** * Discriminant for the `GetV1DEResult` enum. */ enum np_ffi_GetV1DEResultKind { @@ -200,6 +316,27 @@ enum np_ffi_GetV1DEResultKind { typedef uint8_t np_ffi_GetV1DEResultKind; /** + * Discriminant for `GetV1IdentityDetailsResult` + */ +enum np_ffi_GetV1IdentityDetailsResultKind { + /** + * The attempt to get the identity details + * for the section failed, possibly + * due to the section being a public + * section, or the underlying + * advertisement has already been deallocated. + */ + NP_FFI_GET_V1_IDENTITY_DETAILS_RESULT_KIND_ERROR = 0, + /** + * The attempt to get the identity details succeeded. + * The wrapped identity details may be obtained via + * `GetV1IdentityDetailsResult#into_success`. + */ + NP_FFI_GET_V1_IDENTITY_DETAILS_RESULT_KIND_SUCCESS = 1, +}; +typedef uint8_t np_ffi_GetV1IdentityDetailsResultKind; + +/** * Discriminant for `GetV1SectionResult` */ enum np_ffi_GetV1SectionResultKind { @@ -267,6 +404,23 @@ enum np_ffi_V0DataElementKind { typedef uint8_t np_ffi_V0DataElementKind; /** + * Information about the verification scheme used + * for verifying the integrity of the contents + * of a decrypted section. + */ +enum np_ffi_V1VerificationMode { + /** + * Message integrity code verification. + */ + NP_FFI_V1_VERIFICATION_MODE_MIC = 0, + /** + * Signature verification. + */ + NP_FFI_V1_VERIFICATION_MODE_SIGNATURE = 1, +}; +typedef uint8_t np_ffi_V1VerificationMode; + +/** *A `#[repr(C)]` handle to a value of type `super::CredentialBookInternals`. */ typedef struct { @@ -277,8 +431,9 @@ typedef struct { * Result type for `create_credential_book` */ enum np_ffi_CreateCredentialBookResult_Tag { - NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_NO_SPACE_LEFT = 0, - NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_SUCCESS = 1, + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_SUCCESS = 0, + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_NO_SPACE_LEFT = 1, + NP_FFI_CREATE_CREDENTIAL_BOOK_RESULT_INVALID_SLAB_HANDLE = 2, }; typedef uint8_t np_ffi_CreateCredentialBookResult_Tag; @@ -291,6 +446,125 @@ typedef union { } np_ffi_CreateCredentialBookResult; /** + *A `#[repr(C)]` handle to a value of type `super::CredentialSlabInternals`. + */ +typedef struct { + uint64_t handle_id; +} np_ffi_CredentialSlab; + +/** + * Result type for `create_credential_slab` + */ +typedef enum { + NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_NO_SPACE_LEFT, + NP_FFI_CREATE_CREDENTIAL_SLAB_RESULT_SUCCESS, +} np_ffi_CreateCredentialSlabResult_Tag; + +typedef struct { + np_ffi_CreateCredentialSlabResult_Tag tag; + union { + struct { + np_ffi_CredentialSlab success; + }; + }; +} np_ffi_CreateCredentialSlabResult; + +/** + * Cryptographic information about a particular V0 discovery credential + * necessary to match and decrypt encrypted V0 advertisements. + */ +typedef struct { + uint8_t key_seed[32]; + uint8_t legacy_metadata_key_hmac[32]; +} np_ffi_V0DiscoveryCredential; + +/** + * A representation of a MatchedCredential which is passable across the FFI boundary + */ +typedef struct { + uint32_t cred_id; + const uint8_t *encrypted_metadata_bytes_buffer; + uintptr_t encrypted_metadata_bytes_len; +} np_ffi_FfiMatchedCredential; + +/** + * Representation of a V0 credential that contains additional data to provide back to caller once it + * is matched. The credential_id can be used by the caller to correlate it back to the full + * credentials details. + */ +typedef struct { + np_ffi_V0DiscoveryCredential discovery_cred; + np_ffi_FfiMatchedCredential matched_cred; +} np_ffi_V0MatchableCredential; + +/** + * Cryptographic information about a particular V1 discovery credential + * necessary to match and decrypt encrypted V1 advertisement sections. + */ +typedef struct { + uint8_t key_seed[32]; + uint8_t expected_unsigned_metadata_key_hmac[32]; + uint8_t expected_signed_metadata_key_hmac[32]; + uint8_t pub_key[32]; +} np_ffi_V1DiscoveryCredential; + +/** + * Representation of a V1 credential that contains additional data to provide back to caller once it + * is matched. The credential_id can be used by the caller to correlate it back to the full + * credentials details. + */ +typedef struct { + np_ffi_V1DiscoveryCredential discovery_cred; + np_ffi_FfiMatchedCredential matched_cred; +} np_ffi_V1MatchableCredential; + +/** + *A `#[repr(C)]` handle to a value of type `super::DecryptedMetadataInternals`. + */ +typedef struct { + uint64_t handle_id; +} np_ffi_DecryptedMetadata; + +/** + * The result of decrypting metadata from either a V0Payload or DeserializedV1Section + */ +typedef enum { + NP_FFI_DECRYPT_METADATA_RESULT_SUCCESS, + NP_FFI_DECRYPT_METADATA_RESULT_ERROR, +} np_ffi_DecryptMetadataResult_Tag; + +typedef struct { + np_ffi_DecryptMetadataResult_Tag tag; + union { + struct { + np_ffi_DecryptedMetadata success; + }; + }; +} np_ffi_DecryptMetadataResult; + +/** + * The pointer and length of the decrypted metadata byte buffer + */ +typedef struct { + const uint8_t *ptr; + uintptr_t len; +} np_ffi_MetadataBufferParts; + +typedef enum { + NP_FFI_GET_METADATA_BUFFER_PARTS_RESULT_SUCCESS, + NP_FFI_GET_METADATA_BUFFER_PARTS_RESULT_ERROR, +} np_ffi_GetMetadataBufferPartsResult_Tag; + +typedef struct { + np_ffi_GetMetadataBufferPartsResult_Tag tag; + union { + struct { + np_ffi_MetadataBufferParts success; + }; + }; +} np_ffi_GetMetadataBufferPartsResult; + +/** *A `#[repr(C)]` handle to a value of type `super::V0PayloadInternals`. */ typedef struct { @@ -303,7 +577,7 @@ typedef struct { typedef struct { uint8_t num_des; np_ffi_V0Payload payload; - np_ffi_DeserializedV0Identity identity; + np_ffi_DeserializedV0IdentityKind identity_kind; } np_ffi_LegibleDeserializedV0Advertisement; /** @@ -473,6 +747,49 @@ typedef struct { } np_ffi_GetV0DEResult; /** + * Information about the identity which matched a + * decrypted V0 advertisement. + */ +typedef struct { + /** + * The identity type (private/provisioned/trusted) + */ + np_ffi_EncryptedIdentityType identity_type; + /** + * The ID of the credential which + * matched the deserialized adv + */ + uint32_t cred_id; + /** + * The 14-byte legacy metadata key + */ + uint8_t metadata_key[14]; + /** + * The 2-byte advertisement salt + */ + uint8_t salt[2]; +} np_ffi_DeserializedV0IdentityDetails; + +/** + * The result of attempting to get the identity details + * for a V0 advertisement via + * `DeserializedV0Advertisement#get_identity_details`. + */ +typedef enum { + NP_FFI_GET_V0_IDENTITY_DETAILS_RESULT_ERROR, + NP_FFI_GET_V0_IDENTITY_DETAILS_RESULT_SUCCESS, +} np_ffi_GetV0IdentityDetailsResult_Tag; + +typedef struct { + np_ffi_GetV0IdentityDetailsResult_Tag tag; + union { + struct { + np_ffi_DeserializedV0IdentityDetails success; + }; + }; +} np_ffi_GetV0IdentityDetailsResult; + +/** * Handle to a deserialized V1 section */ typedef struct { @@ -527,6 +844,10 @@ typedef struct { */ typedef struct { /** + * The offset of this generic data-element. + */ + uint8_t offset; + /** * The DE type code of this generic data-element. */ np_ffi_V1DEType de_type; @@ -576,6 +897,75 @@ typedef struct { } np_ffi_GetV1DEResult; /** + * Information about the identity which matched + * a decrypted V1 section. + */ +typedef struct { + /** + * The identity type (private/provisioned/trusted) + */ + np_ffi_EncryptedIdentityType identity_type; + /** + * The verification mode (MIC/Signature) which + * was used to verify the decrypted adv contents. + */ + np_ffi_V1VerificationMode verification_mode; + /** + * The ID of the credential which + * matched the deserialized section. + */ + uint32_t cred_id; + /** + * The 16-byte metadata key. + */ + uint8_t metadata_key[16]; +} np_ffi_DeserializedV1IdentityDetails; + +/** + * The result of attempting to get the identity details + * for a V1 advertisement section via + * `DeserializedV1Advertisement#get_identity_details`. + */ +typedef enum { + NP_FFI_GET_V1_IDENTITY_DETAILS_RESULT_ERROR, + NP_FFI_GET_V1_IDENTITY_DETAILS_RESULT_SUCCESS, +} np_ffi_GetV1IdentityDetailsResult_Tag; + +typedef struct { + np_ffi_GetV1IdentityDetailsResult_Tag tag; + union { + struct { + np_ffi_DeserializedV1IdentityDetails success; + }; + }; +} np_ffi_GetV1IdentityDetailsResult; + +/** + * A FFI safe wrapper of a fixed size array + */ +typedef struct { + uint8_t _0[16]; +} np_ffi_FixedSizeArray_16; + +/** + * The result of attempting to get a derived 16-byte salt + * for a given DE within a section. + */ +typedef enum { + NP_FFI_GET_V1DE16_BYTE_SALT_RESULT_ERROR, + NP_FFI_GET_V1DE16_BYTE_SALT_RESULT_SUCCESS, +} np_ffi_GetV1DE16ByteSaltResult_Tag; + +typedef struct { + np_ffi_GetV1DE16ByteSaltResult_Tag tag; + union { + struct { + np_ffi_FixedSizeArray_16 success; + }; + }; +} np_ffi_GetV1DE16ByteSaltResult; + +/** * Overrides the global panic handler to be used when NP C FFI calls panic. * This method will only have an effect on the global panic-handler * the first time it's called, and this method will return `true` @@ -616,6 +1006,22 @@ bool np_ffi_global_config_panic_handler(void (*handler)(np_ffi_PanicReason)); void np_ffi_global_config_set_num_shards(uint8_t num_shards); /** + * Sets the maximum number of active handles to credential slabs + * which may be active at any one time. + * Default value: Max value. + * Max value: `u32::MAX - 1`. + * + * Useful for bounding the maximum memory used by the client application + * on credential slabs in constrained-memory environments. + * + * Setting this value will have no effect if the handle-maps for the + * API have already begun being used by the client code, and any + * values set will take effect upon the first usage of any API + * call utilizing credential slabs. + */ +void np_ffi_global_config_set_max_num_credential_slabs(uint32_t max_num_credential_slabs); + +/** * Sets the maximum number of active handles to credential books * which may be active at any one time. * Default value: Max value. @@ -666,9 +1072,10 @@ void np_ffi_global_config_set_max_num_deserialized_v0_advertisements(uint32_t ma void np_ffi_global_config_set_max_num_deserialized_v1_advertisements(uint32_t max_num_deserialized_v1_advertisements); /** - * Allocates a new credential-book, returning a handle to the created object + * Allocates a new credential-book from the given slab, returning a handle + * to the created object. The slab will be deallocated by this call. */ -np_ffi_CreateCredentialBookResult np_ffi_create_credential_book(void); +np_ffi_CreateCredentialBookResult np_ffi_create_credential_book_from_slab(np_ffi_CredentialSlab slab); /** * Gets the tag of a `CreateCredentialBookResult` tagged enum. @@ -682,11 +1089,90 @@ np_ffi_CreateCredentialBookResultKind np_ffi_CreateCredentialBookResult_kind(np_ np_ffi_CredentialBook np_ffi_CreateCredentialBookResult_into_SUCCESS(np_ffi_CreateCredentialBookResult result); /** + * Deallocates a credential-slab by its handle. + */ +np_ffi_DeallocateResult np_ffi_deallocate_credential_slab(np_ffi_CredentialSlab credential_slab); + +/** * Deallocates a credential-book by its handle */ np_ffi_DeallocateResult np_ffi_deallocate_credential_book(np_ffi_CredentialBook credential_book); /** + * Allocates a new credential-slab, returning a handle to the created object + */ +np_ffi_CreateCredentialSlabResult np_ffi_create_credential_slab(void); + +/** + * Gets the tag of a `CreateCredentialSlabResult` tagged enum. + */ +np_ffi_CreateCredentialSlabResultKind np_ffi_CreateCredentialSlabResult_kind(np_ffi_CreateCredentialSlabResult result); + +/** + * Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the + * case where the passed value is of a different enum variant. + */ +np_ffi_CredentialSlab np_ffi_CreateCredentialSlabResult_into_SUCCESS(np_ffi_CreateCredentialSlabResult result); + +/** + * Adds the given V0 discovery credential with some associated + * match-data to this credential slab. + * + * Safety: this is safe if the provided pointer points to a valid memory address + * which contains the correct len amount of bytes. The copy from the memory address isn't atomic, + * so concurrent modification of the array from another thread would cause undefined behavior. + */ +np_ffi_AddCredentialToSlabResult np_ffi_CredentialSlab_add_v0_credential(np_ffi_CredentialSlab credential_slab, + np_ffi_V0MatchableCredential v0_cred); + +/** + * Adds the given V1 discovery credential with some associated + * match-data to this credential slab. + * + * Safety: this is safe if the provided pointer points to a valid memory address + * which contains the correct len amount of bytes. The copy from the memory address isn't atomic, + * so concurrent modification of the array from another thread would cause undefined behavior. + */ +np_ffi_AddCredentialToSlabResult np_ffi_CredentialSlab_add_v1_credential(np_ffi_CredentialSlab credential_slab, + np_ffi_V1MatchableCredential v1_cred); + +/** + * Frees the underlying resources of the decrypted metadata buffer + */ +np_ffi_DeallocateResult np_ffi_deallocate_DecryptedMetadata(np_ffi_DecryptedMetadata metadata); + +/** + * Gets the tag of a `DecryptMetadataResult` tagged-union. On success the wrapped identity + * details may be obtained via `DecryptMetadataResult#into_success`. + */ +np_ffi_DecryptMetadataResultKind np_ffi_DecryptMetadataResult_kind(np_ffi_DecryptMetadataResult result); + +/** + * Casts a `DecryptMetadataResult` to the `Success` variant, panicking in the + * case where the passed value is of a different enum variant. + */ +np_ffi_DecryptedMetadata np_ffi_DecryptMetadataResult_into_SUCCESS(np_ffi_DecryptMetadataResult result); + +/** + * Gets the pointer and length of the heap allocated byte buffer of decrypted metadata + */ +np_ffi_GetMetadataBufferPartsResult np_ffi_DecryptedMetadata_get_metadata_buffer_parts(np_ffi_DecryptedMetadata metadata); + +/** + * Gets the tag of a `GetMetadataBufferPartsResult` tagged-union. On success the wrapped identity + * details may be obtained via `GetMetadataBufferPartsResult#into_success`. + */ +np_ffi_GetMetadataBufferPartsResultKind np_ffi_GetMetadataBufferPartsResult_kind(np_ffi_GetMetadataBufferPartsResult result); + +/** + * Casts a `GetMetadataBufferPartsResult` to the `Success` variant, panicking in the + * case where the passed value is of a different enum variant. This returns the pointer and length + * of the byte buffer containing the decrypted metadata. There can be a data-race between attempts + * to access the contents of the buffer and attempts to free the handle from different threads. + */ +np_ffi_MetadataBufferParts np_ffi_GetMetadataBufferPartsResult_into_SUCCESS(np_ffi_GetMetadataBufferPartsResult result); + +/** * Attempts to deserialize an advertisement with the given service-data * payload (presumed to be under the NP service UUID) using credentials * pulled from the given credential-book. @@ -751,9 +1237,9 @@ uint8_t np_ffi_LegibleDeserializedV0Advertisement_get_num_des(np_ffi_LegibleDese np_ffi_V0Payload np_ffi_LegibleDeserializedV0Advertisement_into_payload(np_ffi_LegibleDeserializedV0Advertisement adv); /** - * Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`. + * Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`. */ -np_ffi_DeserializedV0Identity np_ffi_LegibleDeserializedV0Advertisement_into_identity(np_ffi_LegibleDeserializedV0Advertisement adv); +np_ffi_DeserializedV0IdentityKind np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(np_ffi_LegibleDeserializedV0Advertisement adv); /** * Deallocates any internal data of a `LegibleDeserializedV0Advertisement` @@ -761,14 +1247,32 @@ np_ffi_DeserializedV0Identity np_ffi_LegibleDeserializedV0Advertisement_into_ide np_ffi_DeallocateResult np_ffi_deallocate_legible_v0_advertisement(np_ffi_LegibleDeserializedV0Advertisement adv); /** - * Gets the tag of the `DeserializedV0Identity` tagged-union. + * Attempts to get the data-element with the given index in the passed v0 adv payload */ -np_ffi_DeserializedV0IdentityKind np_ffi_DeserializedV0Identity_kind(np_ffi_DeserializedV0Identity identity); +np_ffi_GetV0DEResult np_ffi_V0Payload_get_de(np_ffi_V0Payload payload, uint8_t index); /** - * Attempts to get the data-element with the given index in the passed v0 adv payload + * Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) */ -np_ffi_GetV0DEResult np_ffi_V0Payload_get_de(np_ffi_V0Payload payload, uint8_t index); +np_ffi_DecryptMetadataResult np_ffi_V0Payload_decrypt_metadata(np_ffi_V0Payload payload); + +/** + * Gets the identity details for this V0 payload, or returns an error if this payload does not have + * any associated identity (public advertisement) + */ +np_ffi_GetV0IdentityDetailsResult np_ffi_V0Payload_get_identity_details(np_ffi_V0Payload payload); + +/** + * Gets the tag of a `GetV0IdentityDetailsResult` tagged-union. On success the wrapped identity + * details may be obtained via `GetV0IdentityDetailsResult#into_success`. + */ +np_ffi_GetV0IdentityDetailsResultKind np_ffi_GetV0IdentityDetailsResult_kind(np_ffi_GetV0IdentityDetailsResult result); + +/** + * Casts a `GetV0IdentityDetailsResult` to the `Success` variant, panicking in the + * case where the passed value is of a different enum variant. + */ +np_ffi_DeserializedV0IdentityDetails np_ffi_GetV0IdentityDetailsResult_into_SUCCESS(np_ffi_GetV0IdentityDetailsResult result); /** * Deallocates any internal data of a `V0Payload` @@ -867,12 +1371,55 @@ np_ffi_GetV1DEResult np_ffi_DeserializedV1Section_get_de(np_ffi_DeserializedV1Se uint8_t de_index); /** + * Gets the identity details used to decrypt this V1 section, or returns an error if this payload + * does not have any associated identity (public advertisement) + */ +np_ffi_GetV1IdentityDetailsResult np_ffi_DeserializedV1Section_get_identity_details(np_ffi_DeserializedV1Section section); + +/** + * Gets the tag of a `GetV1IdentityDetailsResult` tagged-union. On success the wrapped identity + * details may be obtained via `GetV0IdentityDetailsResult#into_success`. + */ +np_ffi_GetV1IdentityDetailsResultKind np_ffi_GetV1IdentityDetailsResult_kind(np_ffi_GetV1IdentityDetailsResult result); + +/** + * Casts a `GetV1IdentityDetailsResult` to the `Success` variant, panicking in the + * case where the passed value is of a different enum variant. + */ +np_ffi_DeserializedV1IdentityDetails np_ffi_GetV1IdentityDetailsResult_into_SUCCESS(np_ffi_GetV1IdentityDetailsResult result); + +/** + * Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) + */ +np_ffi_DecryptMetadataResult np_ffi_DeserializedV1Section_decrypt_metadata(np_ffi_DeserializedV1Section section); + +/** + * Attempts to derive a 16-byte DE salt for a DE in this section with the given DE offset. This + * operation may fail if the passed offset is 255 (causes overflow) or if the section + * is leveraging a public identity, and hence, doesn't have an associated salt. + */ +np_ffi_GetV1DE16ByteSaltResult np_ffi_DeserializedV1Section_derive_16_byte_salt_for_offset(np_ffi_DeserializedV1Section section, + uint8_t offset); + +/** + * Gets the tag of a `GetV1DE16ByteSaltResult` tagged-union. On success the wrapped identity + * details may be obtained via `GetV1DE16ByteSaltResult#into_success`. + */ +np_ffi_GetV1DE16ByteSaltResultKind np_ffi_GetV1DE16ByteSaltResult_kind(np_ffi_GetV1DE16ByteSaltResult result); + +/** + * Casts a `GetV1DE16ByteSaltResult` to the `Success` variant, panicking in the + * case where the passed value is of a different enum variant. + */ +np_ffi_FixedSizeArray_16 np_ffi_GetV1DE16ByteSaltResult_into_SUCCESS(np_ffi_GetV1DE16ByteSaltResult result); + +/** * Gets the tag of the `GetV1DEResult` tagged-union. */ np_ffi_GetV1DEResultKind np_ffi_GetV1DEResult_kind(np_ffi_GetV1DEResult result); /** - * Casts a `GetV1DEResult` to the `Success` vartiant, panicking in the + * Casts a `GetV1DEResult` to the `Success` variant, panicking in the * case where the passed value is of a different enum variant. */ np_ffi_V1DataElement np_ffi_GetV1DEResult_into_SUCCESS(np_ffi_GetV1DEResult result); diff --git a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h index 3361c5d..ffd63b0 100644 --- a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h +++ b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_functions.h @@ -75,6 +75,20 @@ bool np_ffi_global_config_panic_handler(void (*handler)(PanicReason)); /// API call. void np_ffi_global_config_set_num_shards(uint8_t num_shards); +/// Sets the maximum number of active handles to credential slabs +/// which may be active at any one time. +/// Default value: Max value. +/// Max value: `u32::MAX - 1`. +/// +/// Useful for bounding the maximum memory used by the client application +/// on credential slabs in constrained-memory environments. +/// +/// Setting this value will have no effect if the handle-maps for the +/// API have already begun being used by the client code, and any +/// values set will take effect upon the first usage of any API +/// call utilizing credential slabs. +void np_ffi_global_config_set_max_num_credential_slabs(uint32_t max_num_credential_slabs); + /// Sets the maximum number of active handles to credential books /// which may be active at any one time. /// Default value: Max value. @@ -119,8 +133,9 @@ void np_ffi_global_config_set_max_num_deserialized_v0_advertisements(uint32_t ma /// call which references or returns a deserialized V1 advertisement. void np_ffi_global_config_set_max_num_deserialized_v1_advertisements(uint32_t max_num_deserialized_v1_advertisements); -/// Allocates a new credential-book, returning a handle to the created object -CreateCredentialBookResult np_ffi_create_credential_book(); +/// Allocates a new credential-book from the given slab, returning a handle +/// to the created object. The slab will be deallocated by this call. +CreateCredentialBookResult np_ffi_create_credential_book_from_slab(CredentialSlab slab); /// Gets the tag of a `CreateCredentialBookResult` tagged enum. CreateCredentialBookResultKind np_ffi_CreateCredentialBookResult_kind(CreateCredentialBookResult result); @@ -129,9 +144,64 @@ CreateCredentialBookResultKind np_ffi_CreateCredentialBookResult_kind(CreateCred /// case where the passed value is of a different enum variant. CredentialBook np_ffi_CreateCredentialBookResult_into_SUCCESS(CreateCredentialBookResult result); +/// Deallocates a credential-slab by its handle. +DeallocateResult np_ffi_deallocate_credential_slab(CredentialSlab credential_slab); + /// Deallocates a credential-book by its handle DeallocateResult np_ffi_deallocate_credential_book(CredentialBook credential_book); +/// Allocates a new credential-slab, returning a handle to the created object +CreateCredentialSlabResult np_ffi_create_credential_slab(); + +/// Gets the tag of a `CreateCredentialSlabResult` tagged enum. +CreateCredentialSlabResultKind np_ffi_CreateCredentialSlabResult_kind(CreateCredentialSlabResult result); + +/// Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the +/// case where the passed value is of a different enum variant. +CredentialSlab np_ffi_CreateCredentialSlabResult_into_SUCCESS(CreateCredentialSlabResult result); + +/// Adds the given V0 discovery credential with some associated +/// match-data to this credential slab. +/// +/// Safety: this is safe if the provided pointer points to a valid memory address +/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic, +/// so concurrent modification of the array from another thread would cause undefined behavior. +AddCredentialToSlabResult np_ffi_CredentialSlab_add_v0_credential(CredentialSlab credential_slab, + V0MatchableCredential v0_cred); + +/// Adds the given V1 discovery credential with some associated +/// match-data to this credential slab. +/// +/// Safety: this is safe if the provided pointer points to a valid memory address +/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic, +/// so concurrent modification of the array from another thread would cause undefined behavior. +AddCredentialToSlabResult np_ffi_CredentialSlab_add_v1_credential(CredentialSlab credential_slab, + V1MatchableCredential v1_cred); + +/// Frees the underlying resources of the decrypted metadata buffer +DeallocateResult np_ffi_deallocate_DecryptedMetadata(DecryptedMetadata metadata); + +/// Gets the tag of a `DecryptMetadataResult` tagged-union. On success the wrapped identity +/// details may be obtained via `DecryptMetadataResult#into_success`. +DecryptMetadataResultKind np_ffi_DecryptMetadataResult_kind(DecryptMetadataResult result); + +/// Casts a `DecryptMetadataResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +DecryptedMetadata np_ffi_DecryptMetadataResult_into_SUCCESS(DecryptMetadataResult result); + +/// Gets the pointer and length of the heap allocated byte buffer of decrypted metadata +GetMetadataBufferPartsResult np_ffi_DecryptedMetadata_get_metadata_buffer_parts(DecryptedMetadata metadata); + +/// Gets the tag of a `GetMetadataBufferPartsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetMetadataBufferPartsResult#into_success`. +GetMetadataBufferPartsResultKind np_ffi_GetMetadataBufferPartsResult_kind(GetMetadataBufferPartsResult result); + +/// Casts a `GetMetadataBufferPartsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. This returns the pointer and length +/// of the byte buffer containing the decrypted metadata. There can be a data-race between attempts +/// to access the contents of the buffer and attempts to free the handle from different threads. +MetadataBufferParts np_ffi_GetMetadataBufferPartsResult_into_SUCCESS(GetMetadataBufferPartsResult result); + /// Attempts to deserialize an advertisement with the given service-data /// payload (presumed to be under the NP service UUID) using credentials /// pulled from the given credential-book. @@ -174,18 +244,30 @@ uint8_t np_ffi_LegibleDeserializedV0Advertisement_get_num_des(LegibleDeserialize /// Gets just the data-element payload of a `LegibleDeserializedV0Advertisement`. V0Payload np_ffi_LegibleDeserializedV0Advertisement_into_payload(LegibleDeserializedV0Advertisement adv); -/// Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`. -DeserializedV0Identity np_ffi_LegibleDeserializedV0Advertisement_into_identity(LegibleDeserializedV0Advertisement adv); +/// Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`. +DeserializedV0IdentityKind np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind(LegibleDeserializedV0Advertisement adv); /// Deallocates any internal data of a `LegibleDeserializedV0Advertisement` DeallocateResult np_ffi_deallocate_legible_v0_advertisement(LegibleDeserializedV0Advertisement adv); -/// Gets the tag of the `DeserializedV0Identity` tagged-union. -DeserializedV0IdentityKind np_ffi_DeserializedV0Identity_kind(DeserializedV0Identity identity); - /// Attempts to get the data-element with the given index in the passed v0 adv payload GetV0DEResult np_ffi_V0Payload_get_de(V0Payload payload, uint8_t index); +/// Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) +DecryptMetadataResult np_ffi_V0Payload_decrypt_metadata(V0Payload payload); + +/// Gets the identity details for this V0 payload, or returns an error if this payload does not have +/// any associated identity (public advertisement) +GetV0IdentityDetailsResult np_ffi_V0Payload_get_identity_details(V0Payload payload); + +/// Gets the tag of a `GetV0IdentityDetailsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV0IdentityDetailsResult#into_success`. +GetV0IdentityDetailsResultKind np_ffi_GetV0IdentityDetailsResult_kind(GetV0IdentityDetailsResult result); + +/// Casts a `GetV0IdentityDetailsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +DeserializedV0IdentityDetails np_ffi_GetV0IdentityDetailsResult_into_SUCCESS(GetV0IdentityDetailsResult result); + /// Deallocates any internal data of a `V0Payload` DeallocateResult np_ffi_deallocate_v0_payload(V0Payload payload); @@ -247,10 +329,39 @@ DeserializedV1IdentityKind np_ffi_DeserializedV1Section_get_identity_kind(Deseri /// Gets the data-element with the given index in the passed section. GetV1DEResult np_ffi_DeserializedV1Section_get_de(DeserializedV1Section section, uint8_t de_index); +/// Gets the identity details used to decrypt this V1 section, or returns an error if this payload +/// does not have any associated identity (public advertisement) +GetV1IdentityDetailsResult np_ffi_DeserializedV1Section_get_identity_details(DeserializedV1Section section); + +/// Gets the tag of a `GetV1IdentityDetailsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV0IdentityDetailsResult#into_success`. +GetV1IdentityDetailsResultKind np_ffi_GetV1IdentityDetailsResult_kind(GetV1IdentityDetailsResult result); + +/// Casts a `GetV1IdentityDetailsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +DeserializedV1IdentityDetails np_ffi_GetV1IdentityDetailsResult_into_SUCCESS(GetV1IdentityDetailsResult result); + +/// Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) +DecryptMetadataResult np_ffi_DeserializedV1Section_decrypt_metadata(DeserializedV1Section section); + +/// Attempts to derive a 16-byte DE salt for a DE in this section with the given DE offset. This +/// operation may fail if the passed offset is 255 (causes overflow) or if the section +/// is leveraging a public identity, and hence, doesn't have an associated salt. +GetV1DE16ByteSaltResult np_ffi_DeserializedV1Section_derive_16_byte_salt_for_offset(DeserializedV1Section section, + uint8_t offset); + +/// Gets the tag of a `GetV1DE16ByteSaltResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV1DE16ByteSaltResult#into_success`. +GetV1DE16ByteSaltResultKind np_ffi_GetV1DE16ByteSaltResult_kind(GetV1DE16ByteSaltResult result); + +/// Casts a `GetV1DE16ByteSaltResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +FixedSizeArray<16> np_ffi_GetV1DE16ByteSaltResult_into_SUCCESS(GetV1DE16ByteSaltResult result); + /// Gets the tag of the `GetV1DEResult` tagged-union. GetV1DEResultKind np_ffi_GetV1DEResult_kind(GetV1DEResult result); -/// Casts a `GetV1DEResult` to the `Success` vartiant, panicking in the +/// Casts a `GetV1DEResult` to the `Success` variant, panicking in the /// case where the passed value is of a different enum variant. V1DataElement np_ffi_GetV1DEResult_into_SUCCESS(GetV1DEResult result); diff --git a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h index 4d66308..b5908ef 100644 --- a/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h +++ b/nearby/presence/np_c_ffi/include/cpp/np_cpp_ffi_types.h @@ -36,6 +36,14 @@ namespace np_ffi { namespace internal { +/// Result type for trying to add a credential to a credential-slab. +enum class AddCredentialToSlabResult : uint8_t { + /// We succeeded in adding the credential to the slab. + Success = 0, + /// The handle to the slab was actually invalid. + InvalidHandle = 1, +}; + /// The possible boolean action types which can be present in an Actions data element enum class BooleanActionType : uint8_t { ActiveUnlock = 8, @@ -49,11 +57,24 @@ enum class BooleanActionType : uint8_t { /// Discriminant for `CreateCredentialBookResult` enum class CreateCredentialBookResultKind : uint8_t { - /// There was no space left to create a new credential book - NoSpaceLeft = 0, /// We created a new credential book behind the given handle. /// The associated payload may be obtained via /// `CreateCredentialBookResult#into_success()`. + Success = 0, + /// There was no space left to create a new credential book + NoSpaceLeft = 1, + /// The slab that we tried to create a credential-book from + /// actually was an invalid handle. + InvalidSlabHandle = 2, +}; + +/// Discriminant for `CreateCredentialSlabResult` +enum class CreateCredentialSlabResultKind : uint8_t { + /// There was no space left to create a new credential slab + NoSpaceLeft = 0, + /// We created a new credential slab behind the given handle. + /// The associated payload may be obtained via + /// `CreateCredentialSlabResult#into_success()`. Success = 1, }; @@ -66,6 +87,17 @@ enum class DeallocateResult { Success = 1, }; +/// Discriminant for `DecryptMetadataResult`. +enum class DecryptMetadataResultKind : uint8_t { + /// The attempt to decrypt the metadata of the associated credential succeeded + /// The associated payload may be obtained via + /// `DecryptMetadataResult#into_success`. + Success, + /// The attempt to decrypt the metadata failed, either the payload had no matching identity + /// ie it was a public advertisement OR the decrypt attempt itself was unsuccessful + Error, +}; + /// Discriminant for `DeserializeAdvertisementResult`. enum class DeserializeAdvertisementResultKind : uint8_t { /// Deserializing the advertisement failed, for some reason or another. @@ -93,14 +125,8 @@ enum class DeserializedV0AdvertisementKind : uint8_t { NoMatchingCredentials = 1, }; -/// Represents deserialized information about the V0 identity utilized -/// by a deserialized V0 advertisement -enum class DeserializedV0Identity { - Plaintext, - Decrypted, -}; - -/// Discriminant for `DeserializedV0Identity`. +/// Discriminant for deserialized information about the V0 +/// identity utilized by a deserialized V0 advertisement. enum class DeserializedV0IdentityKind : uint8_t { /// The deserialized identity was a plaintext identity. Plaintext = 0, @@ -117,6 +143,24 @@ enum class DeserializedV1IdentityKind : uint8_t { Decrypted = 1, }; +/// The DE type for an encrypted identity +enum class EncryptedIdentityType : uint8_t { + /// Identity for broadcasts to nearby devices with the same + /// logged-in-account (for some account). + Private = 1, + /// Identity for broadcasts to nearby devices which this + /// device has declared to trust. + Trusted = 2, + /// Identity for broadcasts to devices which have been provisioned + /// offline with this device. + Provisioned = 4, +}; + +enum class GetMetadataBufferPartsResultKind : uint8_t { + Success = 0, + Error = 1, +}; + /// Discriminant of `GetV0DEResult`. enum class GetV0DEResultKind : uint8_t { /// The attempt to get the DE succeeded. @@ -130,6 +174,33 @@ enum class GetV0DEResultKind : uint8_t { Error = 1, }; +/// Discriminant for `GetV0IdentityDetailsResult` +enum class GetV0IdentityDetailsResultKind : uint8_t { + /// The attempt to get the identity details + /// for the advertisement failed, possibly + /// due to the advertisement being a public + /// advertisement, or the underlying + /// advertisement has already been deallocated. + Error = 0, + /// The attempt to get the identity details succeeded. + /// The wrapped identity details may be obtained via + /// `GetV0IdentityDetailsResult#into_success`. + Success = 1, +}; + +/// Discriminant for `GetV1DE16ByteSaltResult`. +enum class GetV1DE16ByteSaltResultKind : uint8_t { + /// The attempt to get the derived salt failed, possibly + /// because the passed DE offset was invalid (==255), + /// or because there was no salt included for the + /// referenced advertisement section (i.e: it was + /// a public advertisement section, or it was deallocated.) + Error = 0, + /// A 16-byte salt for the given DE offset was successfully + /// derived. + Success = 1, +}; + /// Discriminant for the `GetV1DEResult` enum. enum class GetV1DEResultKind : uint8_t { /// Attempting to get the DE at the given position failed, @@ -141,6 +212,20 @@ enum class GetV1DEResultKind : uint8_t { Success = 1, }; +/// Discriminant for `GetV1IdentityDetailsResult` +enum class GetV1IdentityDetailsResultKind : uint8_t { + /// The attempt to get the identity details + /// for the section failed, possibly + /// due to the section being a public + /// section, or the underlying + /// advertisement has already been deallocated. + Error = 0, + /// The attempt to get the identity details succeeded. + /// The wrapped identity details may be obtained via + /// `GetV1IdentityDetailsResult#into_success`. + Success = 1, +}; + /// Discriminant for `GetV1SectionResult` enum class GetV1SectionResultKind : uint8_t { /// The attempt to get the section failed, @@ -185,6 +270,16 @@ enum class V0DataElementKind : uint8_t { Actions = 1, }; +/// Information about the verification scheme used +/// for verifying the integrity of the contents +/// of a decrypted section. +enum class V1VerificationMode : uint8_t { + /// Message integrity code verification. + Mic = 0, + /// Signature verification. + Signature = 1, +}; + ///A `#[repr(C)]` handle to a value of type `super::CredentialBookInternals`. struct CredentialBook { uint64_t handle_id; @@ -193,8 +288,9 @@ struct CredentialBook { /// Result type for `create_credential_book` union CreateCredentialBookResult { enum class Tag : uint8_t { - NoSpaceLeft = 0, - Success = 1, + Success = 0, + NoSpaceLeft = 1, + InvalidSlabHandle = 2, }; struct Success_Body { @@ -208,6 +304,111 @@ union CreateCredentialBookResult { Success_Body success; }; +///A `#[repr(C)]` handle to a value of type `super::CredentialSlabInternals`. +struct CredentialSlab { + uint64_t handle_id; +}; + +/// Result type for `create_credential_slab` +struct CreateCredentialSlabResult { + enum class Tag { + NoSpaceLeft, + Success, + }; + + struct Success_Body { + CredentialSlab _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + +/// Cryptographic information about a particular V0 discovery credential +/// necessary to match and decrypt encrypted V0 advertisements. +struct V0DiscoveryCredential { + uint8_t key_seed[32]; + uint8_t legacy_metadata_key_hmac[32]; +}; + +/// A representation of a MatchedCredential which is passable across the FFI boundary +struct FfiMatchedCredential { + uint32_t cred_id; + const uint8_t *encrypted_metadata_bytes_buffer; + uintptr_t encrypted_metadata_bytes_len; +}; + +/// Representation of a V0 credential that contains additional data to provide back to caller once it +/// is matched. The credential_id can be used by the caller to correlate it back to the full +/// credentials details. +struct V0MatchableCredential { + V0DiscoveryCredential discovery_cred; + FfiMatchedCredential matched_cred; +}; + +/// Cryptographic information about a particular V1 discovery credential +/// necessary to match and decrypt encrypted V1 advertisement sections. +struct V1DiscoveryCredential { + uint8_t key_seed[32]; + uint8_t expected_unsigned_metadata_key_hmac[32]; + uint8_t expected_signed_metadata_key_hmac[32]; + uint8_t pub_key[32]; +}; + +/// Representation of a V1 credential that contains additional data to provide back to caller once it +/// is matched. The credential_id can be used by the caller to correlate it back to the full +/// credentials details. +struct V1MatchableCredential { + V1DiscoveryCredential discovery_cred; + FfiMatchedCredential matched_cred; +}; + +///A `#[repr(C)]` handle to a value of type `super::DecryptedMetadataInternals`. +struct DecryptedMetadata { + uint64_t handle_id; +}; + +/// The result of decrypting metadata from either a V0Payload or DeserializedV1Section +struct DecryptMetadataResult { + enum class Tag { + Success, + Error, + }; + + struct Success_Body { + DecryptedMetadata _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + +/// The pointer and length of the decrypted metadata byte buffer +struct MetadataBufferParts { + const uint8_t *ptr; + uintptr_t len; +}; + +struct GetMetadataBufferPartsResult { + enum class Tag { + Success, + Error, + }; + + struct Success_Body { + MetadataBufferParts _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + ///A `#[repr(C)]` handle to a value of type `super::V0PayloadInternals`. struct V0Payload { uint64_t handle_id; @@ -217,7 +418,7 @@ struct V0Payload { struct LegibleDeserializedV0Advertisement { uint8_t num_des; V0Payload payload; - DeserializedV0Identity identity; + DeserializedV0IdentityKind identity_kind; }; /// Represents a deserialized V0 advertisement @@ -372,6 +573,39 @@ struct GetV0DEResult { }; }; +/// Information about the identity which matched a +/// decrypted V0 advertisement. +struct DeserializedV0IdentityDetails { + /// The identity type (private/provisioned/trusted) + EncryptedIdentityType identity_type; + /// The ID of the credential which + /// matched the deserialized adv + uint32_t cred_id; + /// The 14-byte legacy metadata key + uint8_t metadata_key[14]; + /// The 2-byte advertisement salt + uint8_t salt[2]; +}; + +/// The result of attempting to get the identity details +/// for a V0 advertisement via +/// `DeserializedV0Advertisement#get_identity_details`. +struct GetV0IdentityDetailsResult { + enum class Tag { + Error, + Success, + }; + + struct Success_Body { + DeserializedV0IdentityDetails _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + /// Handle to a deserialized V1 section struct DeserializedV1Section { LegibleV1Sections legible_sections_handle; @@ -409,6 +643,8 @@ struct V1DEType { /// This representation is stable, and so you may directly /// reference this struct's fields if you wish. struct GenericV1DataElement { + /// The offset of this generic data-element. + uint8_t offset; /// The DE type code of this generic data-element. V1DEType de_type; /// The raw data-element byte payload, up to @@ -452,5 +688,63 @@ struct GetV1DEResult { }; }; +/// Information about the identity which matched +/// a decrypted V1 section. +struct DeserializedV1IdentityDetails { + /// The identity type (private/provisioned/trusted) + EncryptedIdentityType identity_type; + /// The verification mode (MIC/Signature) which + /// was used to verify the decrypted adv contents. + V1VerificationMode verification_mode; + /// The ID of the credential which + /// matched the deserialized section. + uint32_t cred_id; + /// The 16-byte metadata key. + uint8_t metadata_key[16]; +}; + +/// The result of attempting to get the identity details +/// for a V1 advertisement section via +/// `DeserializedV1Advertisement#get_identity_details`. +struct GetV1IdentityDetailsResult { + enum class Tag { + Error, + Success, + }; + + struct Success_Body { + DeserializedV1IdentityDetails _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + +/// A FFI safe wrapper of a fixed size array +template<uintptr_t N> +struct FixedSizeArray { + uint8_t _0[N]; +}; + +/// The result of attempting to get a derived 16-byte salt +/// for a given DE within a section. +struct GetV1DE16ByteSaltResult { + enum class Tag { + Error, + Success, + }; + + struct Success_Body { + FixedSizeArray<16> _0; + }; + + Tag tag; + union { + Success_Body success; + }; +}; + } // namespace internal } // namespace np_ffi diff --git a/nearby/presence/np_c_ffi/src/credentials.rs b/nearby/presence/np_c_ffi/src/credentials.rs index 6b60545..3cbd76f 100644 --- a/nearby/presence/np_c_ffi/src/credentials.rs +++ b/nearby/presence/np_c_ffi/src/credentials.rs @@ -14,15 +14,25 @@ //! Credential-related data-types and functions use crate::{unwrap, PanicReason}; +use core::slice; use np_ffi_core::common::*; use np_ffi_core::credentials::credential_book::CredentialBook; +use np_ffi_core::credentials::credential_slab::CredentialSlab; use np_ffi_core::credentials::*; +use np_ffi_core::deserialize::decrypted_metadata::DecryptedMetadata; +use np_ffi_core::deserialize::{ + DecryptMetadataResult, DecryptMetadataResultKind, GetMetadataBufferPartsResult, + GetMetadataBufferPartsResultKind, MetadataBufferParts, +}; use np_ffi_core::utils::FfiEnum; -/// Allocates a new credential-book, returning a handle to the created object +/// Allocates a new credential-book from the given slab, returning a handle +/// to the created object. The slab will be deallocated by this call. #[no_mangle] -pub extern "C" fn np_ffi_create_credential_book() -> CreateCredentialBookResult { - create_credential_book() +pub extern "C" fn np_ffi_create_credential_book_from_slab( + slab: CredentialSlab, +) -> CreateCredentialBookResult { + create_credential_book_from_slab(slab) } /// Gets the tag of a `CreateCredentialBookResult` tagged enum. @@ -42,6 +52,14 @@ pub extern "C" fn np_ffi_CreateCredentialBookResult_into_SUCCESS( unwrap(result.into_success(), PanicReason::EnumCastFailed) } +/// Deallocates a credential-slab by its handle. +#[no_mangle] +pub extern "C" fn np_ffi_deallocate_credential_slab( + credential_slab: CredentialSlab, +) -> DeallocateResult { + deallocate_credential_slab(credential_slab) +} + /// Deallocates a credential-book by its handle #[no_mangle] pub extern "C" fn np_ffi_deallocate_credential_book( @@ -49,3 +67,152 @@ pub extern "C" fn np_ffi_deallocate_credential_book( ) -> DeallocateResult { deallocate_credential_book(credential_book) } + +/// Allocates a new credential-slab, returning a handle to the created object +#[no_mangle] +pub extern "C" fn np_ffi_create_credential_slab() -> CreateCredentialSlabResult { + create_credential_slab() +} + +/// Gets the tag of a `CreateCredentialSlabResult` tagged enum. +#[no_mangle] +pub extern "C" fn np_ffi_CreateCredentialSlabResult_kind( + result: CreateCredentialSlabResult, +) -> CreateCredentialSlabResultKind { + result.kind() +} + +/// Casts a `CreateCredentialSlabResult` to the `SUCCESS` variant, panicking in the +/// case where the passed value is of a different enum variant. +#[no_mangle] +pub extern "C" fn np_ffi_CreateCredentialSlabResult_into_SUCCESS( + result: CreateCredentialSlabResult, +) -> CredentialSlab { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} + +/// Representation of a V0 credential that contains additional data to provide back to caller once it +/// is matched. The credential_id can be used by the caller to correlate it back to the full +/// credentials details. +#[repr(C)] +pub struct V0MatchableCredential { + discovery_cred: V0DiscoveryCredential, + matched_cred: FfiMatchedCredential, +} + +/// Representation of a V1 credential that contains additional data to provide back to caller once it +/// is matched. The credential_id can be used by the caller to correlate it back to the full +/// credentials details. +#[repr(C)] +pub struct V1MatchableCredential { + discovery_cred: V1DiscoveryCredential, + matched_cred: FfiMatchedCredential, +} + +/// A representation of a MatchedCredential which is passable across the FFI boundary +#[repr(C)] +pub struct FfiMatchedCredential { + cred_id: u32, + encrypted_metadata_bytes_buffer: *const u8, + encrypted_metadata_bytes_len: usize, +} + +/// Adds the given V0 discovery credential with some associated +/// match-data to this credential slab. +/// +/// Safety: this is safe if the provided pointer points to a valid memory address +/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic, +/// so concurrent modification of the array from another thread would cause undefined behavior. +#[no_mangle] +pub extern "C" fn np_ffi_CredentialSlab_add_v0_credential( + credential_slab: CredentialSlab, + v0_cred: V0MatchableCredential, +) -> AddCredentialToSlabResult { + #[allow(unsafe_code)] + let metadata_slice = unsafe { + slice::from_raw_parts( + v0_cred.matched_cred.encrypted_metadata_bytes_buffer, + v0_cred.matched_cred.encrypted_metadata_bytes_len, + ) + }; + + let matched_credential = MatchedCredential::new(v0_cred.matched_cred.cred_id, metadata_slice); + credential_slab.add_v0(v0_cred.discovery_cred, matched_credential) +} + +/// Adds the given V1 discovery credential with some associated +/// match-data to this credential slab. +/// +/// Safety: this is safe if the provided pointer points to a valid memory address +/// which contains the correct len amount of bytes. The copy from the memory address isn't atomic, +/// so concurrent modification of the array from another thread would cause undefined behavior. +#[no_mangle] +pub extern "C" fn np_ffi_CredentialSlab_add_v1_credential( + credential_slab: CredentialSlab, + v1_cred: V1MatchableCredential, +) -> AddCredentialToSlabResult { + #[allow(unsafe_code)] + let metadata_slice = unsafe { + slice::from_raw_parts( + v1_cred.matched_cred.encrypted_metadata_bytes_buffer, + v1_cred.matched_cred.encrypted_metadata_bytes_len, + ) + }; + + let matched_credential = MatchedCredential::new(v1_cred.matched_cred.cred_id, metadata_slice); + credential_slab.add_v1(v1_cred.discovery_cred, matched_credential) +} + +/// Frees the underlying resources of the decrypted metadata buffer +#[no_mangle] +pub extern "C" fn np_ffi_deallocate_DecryptedMetadata( + metadata: DecryptedMetadata, +) -> DeallocateResult { + metadata.deallocate_metadata() +} + +/// Gets the tag of a `DecryptMetadataResult` tagged-union. On success the wrapped identity +/// details may be obtained via `DecryptMetadataResult#into_success`. +#[no_mangle] +pub extern "C" fn np_ffi_DecryptMetadataResult_kind( + result: DecryptMetadataResult, +) -> DecryptMetadataResultKind { + result.kind() +} + +/// Casts a `DecryptMetadataResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +#[no_mangle] +pub extern "C" fn np_ffi_DecryptMetadataResult_into_SUCCESS( + result: DecryptMetadataResult, +) -> DecryptedMetadata { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} + +/// Gets the pointer and length of the heap allocated byte buffer of decrypted metadata +#[no_mangle] +pub extern "C" fn np_ffi_DecryptedMetadata_get_metadata_buffer_parts( + metadata: DecryptedMetadata, +) -> GetMetadataBufferPartsResult { + metadata.get_metadata_buffer_parts() +} + +/// Gets the tag of a `GetMetadataBufferPartsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetMetadataBufferPartsResult#into_success`. +#[no_mangle] +pub extern "C" fn np_ffi_GetMetadataBufferPartsResult_kind( + result: GetMetadataBufferPartsResult, +) -> GetMetadataBufferPartsResultKind { + result.kind() +} + +/// Casts a `GetMetadataBufferPartsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. This returns the pointer and length +/// of the byte buffer containing the decrypted metadata. There can be a data-race between attempts +/// to access the contents of the buffer and attempts to free the handle from different threads. +#[no_mangle] +pub extern "C" fn np_ffi_GetMetadataBufferPartsResult_into_SUCCESS( + result: GetMetadataBufferPartsResult, +) -> MetadataBufferParts { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} diff --git a/nearby/presence/np_c_ffi/src/deserialize/v0.rs b/nearby/presence/np_c_ffi/src/deserialize/v0.rs index 877cee5..7a460a2 100644 --- a/nearby/presence/np_c_ffi/src/deserialize/v0.rs +++ b/nearby/presence/np_c_ffi/src/deserialize/v0.rs @@ -16,6 +16,7 @@ use crate::{panic, unwrap, PanicReason}; use np_ffi_core::common::DeallocateResult; use np_ffi_core::deserialize::v0::v0_payload::V0Payload; use np_ffi_core::deserialize::v0::*; +use np_ffi_core::deserialize::DecryptMetadataResult; use np_ffi_core::utils::FfiEnum; /// Gets the tag of a `DeserializedV0Advertisement` tagged-union. @@ -49,15 +50,15 @@ pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_get_num_des( 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`. +/// Gets just the identity kind associated with a `LegibleDeserializedV0Advertisement`. #[no_mangle] -pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_identity( +pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind( adv: LegibleDeserializedV0Advertisement, -) -> DeserializedV0Identity { - adv.into_identity() +) -> DeserializedV0IdentityKind { + adv.identity_kind() } /// Deallocates any internal data of a `LegibleDeserializedV0Advertisement` @@ -68,20 +69,45 @@ pub extern "C" fn np_ffi_deallocate_legible_v0_advertisement( adv.deallocate() } -/// Gets the tag of the `DeserializedV0Identity` tagged-union. -#[no_mangle] -pub extern "C" fn np_ffi_DeserializedV0Identity_kind( - identity: DeserializedV0Identity, -) -> DeserializedV0IdentityKind { - identity.kind() -} - /// Attempts to get the data-element with the given index in the passed v0 adv payload #[no_mangle] pub extern "C" fn np_ffi_V0Payload_get_de(payload: V0Payload, index: u8) -> GetV0DEResult { payload.get_de(index) } +/// Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) +#[no_mangle] +pub extern "C" fn np_ffi_V0Payload_decrypt_metadata(payload: V0Payload) -> DecryptMetadataResult { + payload.decrypt_metadata() +} + +/// Gets the identity details for this V0 payload, or returns an error if this payload does not have +/// any associated identity (public advertisement) +#[no_mangle] +pub extern "C" fn np_ffi_V0Payload_get_identity_details( + payload: V0Payload, +) -> GetV0IdentityDetailsResult { + payload.get_identity_details() +} + +/// Gets the tag of a `GetV0IdentityDetailsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV0IdentityDetailsResult#into_success`. +#[no_mangle] +pub extern "C" fn np_ffi_GetV0IdentityDetailsResult_kind( + result: GetV0IdentityDetailsResult, +) -> GetV0IdentityDetailsResultKind { + result.kind() +} + +/// Casts a `GetV0IdentityDetailsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +#[no_mangle] +pub extern "C" fn np_ffi_GetV0IdentityDetailsResult_into_SUCCESS( + result: GetV0IdentityDetailsResult, +) -> DeserializedV0IdentityDetails { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} + /// Deallocates any internal data of a `V0Payload` #[no_mangle] pub extern "C" fn np_ffi_deallocate_v0_payload(payload: V0Payload) -> DeallocateResult { diff --git a/nearby/presence/np_c_ffi/src/deserialize/v1.rs b/nearby/presence/np_c_ffi/src/deserialize/v1.rs index dc682a3..598ce9c 100644 --- a/nearby/presence/np_c_ffi/src/deserialize/v1.rs +++ b/nearby/presence/np_c_ffi/src/deserialize/v1.rs @@ -13,7 +13,9 @@ // limitations under the License. use crate::{unwrap, PanicReason}; +use np_ffi_core::common::FixedSizeArray; use np_ffi_core::deserialize::v1::*; +use np_ffi_core::deserialize::DecryptMetadataResult; use np_ffi_core::utils::FfiEnum; /// Gets the number of legible sections on a deserialized V1 advertisement. @@ -86,13 +88,77 @@ pub extern "C" fn np_ffi_DeserializedV1Section_get_de( section.get_de(de_index) } +/// Gets the identity details used to decrypt this V1 section, or returns an error if this payload +/// does not have any associated identity (public advertisement) +#[no_mangle] +pub extern "C" fn np_ffi_DeserializedV1Section_get_identity_details( + section: DeserializedV1Section, +) -> GetV1IdentityDetailsResult { + section.get_identity_details() +} + +/// Gets the tag of a `GetV1IdentityDetailsResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV0IdentityDetailsResult#into_success`. +#[no_mangle] +pub extern "C" fn np_ffi_GetV1IdentityDetailsResult_kind( + result: GetV1IdentityDetailsResult, +) -> GetV1IdentityDetailsResultKind { + result.kind() +} + +/// Casts a `GetV1IdentityDetailsResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +#[no_mangle] +pub extern "C" fn np_ffi_GetV1IdentityDetailsResult_into_SUCCESS( + result: GetV1IdentityDetailsResult, +) -> DeserializedV1IdentityDetails { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} + +/// Attempts to decrypt the metadata for the matched credential for this V0 payload (if any) +#[no_mangle] +pub extern "C" fn np_ffi_DeserializedV1Section_decrypt_metadata( + section: DeserializedV1Section, +) -> DecryptMetadataResult { + section.decrypt_metadata() +} + +/// Attempts to derive a 16-byte DE salt for a DE in this section with the given DE offset. This +/// operation may fail if the passed offset is 255 (causes overflow) or if the section +/// is leveraging a public identity, and hence, doesn't have an associated salt. +#[no_mangle] +pub extern "C" fn np_ffi_DeserializedV1Section_derive_16_byte_salt_for_offset( + section: DeserializedV1Section, + offset: u8, +) -> GetV1DE16ByteSaltResult { + section.derive_16_byte_salt_for_offset(offset) +} + +/// Gets the tag of a `GetV1DE16ByteSaltResult` tagged-union. On success the wrapped identity +/// details may be obtained via `GetV1DE16ByteSaltResult#into_success`. +#[no_mangle] +pub extern "C" fn np_ffi_GetV1DE16ByteSaltResult_kind( + result: GetV1DE16ByteSaltResult, +) -> GetV1DE16ByteSaltResultKind { + result.kind() +} + +/// Casts a `GetV1DE16ByteSaltResult` to the `Success` variant, panicking in the +/// case where the passed value is of a different enum variant. +#[no_mangle] +pub extern "C" fn np_ffi_GetV1DE16ByteSaltResult_into_SUCCESS( + result: GetV1DE16ByteSaltResult, +) -> FixedSizeArray<16> { + unwrap(result.into_success(), PanicReason::EnumCastFailed) +} + /// Gets the tag of the `GetV1DEResult` tagged-union. #[no_mangle] pub extern "C" fn np_ffi_GetV1DEResult_kind(result: GetV1DEResult) -> GetV1DEResultKind { result.kind() } -/// Casts a `GetV1DEResult` to the `Success` vartiant, panicking in the +/// Casts a `GetV1DEResult` to the `Success` variant, panicking in the /// case where the passed value is of a different enum variant. #[no_mangle] pub extern "C" fn np_ffi_GetV1DEResult_into_SUCCESS(result: GetV1DEResult) -> V1DataElement { diff --git a/nearby/presence/np_c_ffi/src/lib.rs b/nearby/presence/np_c_ffi/src/lib.rs index 836b467..50e74b6 100644 --- a/nearby/presence/np_c_ffi/src/lib.rs +++ b/nearby/presence/np_c_ffi/src/lib.rs @@ -13,28 +13,16 @@ // limitations under the License. //! NP Rust C FFI -// TODO: Remove usage of `lang_items` when ffi is no longer alloc -#![allow(internal_features)] -#![feature(lang_items)] -#![cfg_attr(not(test), no_std)] -#![allow(dead_code)] -extern crate alloc; -extern crate core; pub mod credentials; pub mod deserialize; -#[global_allocator] -static ALLOCATOR: libc_alloc::LibcAlloc = libc_alloc::LibcAlloc; - -extern crate panic_abort; - -#[lang = "eh_personality"] -extern "C" fn eh_personality() {} +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 @@ -90,27 +78,16 @@ impl PanicHandler { } 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()); +static PANIC_HANDLER: RwLock<PanicHandler> = RwLock::new(PanicHandler::new()); pub(crate) fn panic(reason: PanicReason) -> ! { PANIC_HANDLER.read().panic(reason) @@ -168,6 +145,23 @@ pub extern "C" fn np_ffi_global_config_set_num_shards(num_shards: u8) { np_ffi_core::common::global_config_set_num_shards(num_shards) } +/// Sets the maximum number of active handles to credential slabs +/// which may be active at any one time. +/// Default value: Max value. +/// Max value: `u32::MAX - 1`. +/// +/// Useful for bounding the maximum memory used by the client application +/// on credential slabs in constrained-memory environments. +/// +/// Setting this value will have no effect if the handle-maps for the +/// API have already begun being used by the client code, and any +/// values set will take effect upon the first usage of any API +/// call utilizing credential slabs. +#[no_mangle] +pub extern "C" fn np_ffi_global_config_set_max_num_credential_slabs(max_num_credential_slabs: u32) { + np_ffi_core::common::global_config_set_max_num_credential_slabs(max_num_credential_slabs) +} + /// Sets the maximum number of active handles to credential books /// which may be active at any one time. /// Default value: Max value. diff --git a/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc b/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc index 72a3f91..164bab3 100644 --- a/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc +++ b/nearby/presence/np_cpp_ffi/benchmarks/np_ffi_bench.cc @@ -40,7 +40,9 @@ class NpCppBenchmark : public benchmark::Fixture { BENCHMARK_DEFINE_F(NpCppBenchmark, V0PlaintextAdvertisement) (benchmark::State &state) { - auto cred_book = nearby_protocol::CredentialBook::TryCreate(); + auto cred_slab = nearby_protocol::CredentialSlab::TryCreate(); + assert(cred_slab.ok()); + auto cred_book = nearby_protocol::CredentialBook::TryCreateFromSlab(cred_slab.value()); assert(cred_book.ok()); auto num_ciphers = state.range(0); @@ -68,7 +70,14 @@ class NpCBenchmark : public benchmark::Fixture { BENCHMARK_DEFINE_F(NpCBenchmark, V0PlaintextAdvertisement) (benchmark::State &state) { auto num_ciphers = state.range(0); - auto book_result = np_ffi::internal::np_ffi_create_credential_book(); + auto slab_result = np_ffi::internal::np_ffi_create_credential_slab(); + assert( + np_ffi::internal::np_ffi_CreateCredentialSlabResult_kind(slab_result) == + np_ffi::internal::CreateCredentialSlabResultKind::Success); + auto slab = np_ffi::internal::np_ffi_CreateCredentialSlabResult_into_SUCCESS( + slab_result); + + auto book_result = np_ffi::internal::np_ffi_create_credential_book_from_slab(slab); assert( np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(book_result) == np_ffi::internal::CreateCredentialBookResultKind::Success); @@ -99,4 +108,4 @@ BENCHMARK_REGISTER_F(NpCBenchmark, V0PlaintextAdvertisement) ->Range(1, 1000) ->Unit(benchmark::kMicrosecond); -BENCHMARK_MAIN();
\ No newline at end of file +BENCHMARK_MAIN(); diff --git a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc index 84d7247..3c409e7 100644 --- a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc +++ b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_deserialize.cc @@ -34,7 +34,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { nearby_protocol::RawAdvertisementPayload payload( (nearby_protocol::ByteBuffer<255>(raw_bytes))); - auto credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + if (!credential_slab.ok()) { + printf("Error: create Credential slab failed\n"); + __builtin_trap(); + } + + auto credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab.value()); if (!credential_book.ok()) { printf("Error: create Credential book failed\n"); __builtin_trap(); @@ -45,4 +51,4 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { payload, credential_book.value()); return 0; -}
\ No newline at end of file +} diff --git a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc index c08b6cd..2475b5e 100644 --- a/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc +++ b/nearby/presence/np_cpp_ffi/fuzz/fuzzer_np_cpp_valid_header.cc @@ -32,7 +32,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { nearby_protocol::RawAdvertisementPayload payload( (nearby_protocol::ByteBuffer<255>(raw_bytes))); - auto credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + if (!credential_slab.ok()) { + printf("Error: create Credential slab failed\n"); + __builtin_trap(); + } + + auto credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab.value()); if (!credential_book.ok()) { printf("Error: create Credential book failed\n"); __builtin_trap(); @@ -51,4 +57,4 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { payload, credential_book.value()); return 0; -}
\ No newline at end of file +} diff --git a/nearby/presence/np_cpp_ffi/include/nearby_protocol.h b/nearby/presence/np_cpp_ffi/include/nearby_protocol.h index e7e46e7..cc02007 100644 --- a/nearby/presence/np_cpp_ffi/include/nearby_protocol.h +++ b/nearby/presence/np_cpp_ffi/include/nearby_protocol.h @@ -19,6 +19,8 @@ #include "absl/strings/str_format.h" #include "np_cpp_ffi_types.h" +#include <span> + // This namespace provides a C++ API surface to the Rust nearby protocol // implementation. This is a wrapper over the np_ffi::internal namespace defined // in the headers np_cpp_ffi_functions.h and np_cpp_ffi_types.h which are @@ -50,28 +52,37 @@ namespace nearby_protocol { // Re-exporting cbindgen generated types which are used in the public API +using np_ffi::internal::AddCredentialToSlabResult; using np_ffi::internal::BooleanActionType; using np_ffi::internal::CreateCredentialBookResultKind; +using np_ffi::internal::CreateCredentialSlabResultKind; using np_ffi::internal::DeserializeAdvertisementResultKind; using np_ffi::internal::DeserializedV0AdvertisementKind; +using np_ffi::internal::DeserializedV0IdentityDetails; using np_ffi::internal::DeserializedV0IdentityKind; +using np_ffi::internal::DeserializedV1IdentityDetails; using np_ffi::internal::DeserializedV1IdentityKind; +using np_ffi::internal::EncryptedIdentityType; using np_ffi::internal::GetV0DEResultKind; using np_ffi::internal::PanicReason; using np_ffi::internal::TxPower; using np_ffi::internal::V0DataElementKind; +using np_ffi::internal::V1VerificationMode; template <uintptr_t N> using FfiByteBuffer = np_ffi::internal::ByteBuffer<N>; // All of the types defined in this header class RawAdvertisementPayload; class CredentialBook; +class CredentialSlab; class Deserializer; class DeserializeAdvertisementResult; +class MatchedCredentialData; +class V0MatchableCredential; +class V1MatchableCredential; // V0 Classes class DeserializedV0Advertisement; -class DeserializedV0Identity; class LegibleDeserializedV0Advertisement; class V0DataElement; class V0Payload; @@ -109,6 +120,12 @@ public: // np_ffi_global_config_set_num_shards in np_cpp_ffi_functions.h for more info static void SetNumShards(uint8_t num_shards); + // Sets the maximum number of active handles to credential slabs which may be + // active at any one time. See + // np_ffi_global_config_set_max_num_credential_slabs in np_cpp_ffi_functions.h + // for more info + static void SetMaxNumCredentialSlabs(uint32_t max_num_credential_slabs); + // Sets the maximum number of active handles to credential books which may be // active at any one time. See // np_ffi_global_config_set_max_num_credential_books in np_cpp_ffi_functions.h @@ -128,6 +145,44 @@ public: uint32_t max_num_deserialized_v1_advertisements); }; +// Holds the credentials used in the construction of a credential book +// using CredentialBook::TryCreateFromSlab() +class CredentialSlab { +public: + // Don't allow copy constructor or copy assignment, since that would result in + // the underlying handle being freed multiple times + CredentialSlab(const CredentialSlab &other) = delete; + CredentialSlab &operator=(const CredentialSlab &other) = delete; + + // Move constructor and move assignment are needed in order to wrap this class + // in absl::StatusOr + CredentialSlab(CredentialSlab &&other) noexcept; + CredentialSlab &operator=(CredentialSlab &&other) noexcept; + + // The destructor for a CredentialSlab, this will be called when a + // CredentialSlab instance goes out of scope and will free the underlying + // resources + ~CredentialSlab(); + + // Creates a new instance of a CredentialSlab, returns the CredentialSlab on + // success or a Status code on failure + [[nodiscard]] static absl::StatusOr<CredentialSlab> TryCreate(); + + // Adds a V0 credential to the slab + [[nodiscard]] absl::Status AddV0Credential(V0MatchableCredential v0_cred); + + // Adds a V1 credential to the slab + [[nodiscard]] absl::Status AddV1Credential(V1MatchableCredential v1_cred); + +private: + friend class CredentialBook; + explicit CredentialSlab(np_ffi::internal::CredentialSlab credential_slab) + : credential_slab_(credential_slab), moved_(false) {} + + np_ffi::internal::CredentialSlab credential_slab_; + bool moved_; +}; + // Holds the credentials used when decrypting data of an advertisement. // This needs to be passed to Deserializer::DeserializeAdvertisement() when // attempting to deserialize a payload @@ -148,9 +203,12 @@ public: // resources ~CredentialBook(); - // Creates a new instance of a CredentialBook, returns the CredentialBook on - // success or a Status code on failure - [[nodiscard]] static absl::StatusOr<CredentialBook> TryCreate(); + // Creates a new instance of a CredentialBook from a CredentialSlab, + // returning the CredentialBook on success or a Status code on failure. + // The passed credential-slab will be deallocated if this operation + // is successful. + [[nodiscard]] static absl::StatusOr<CredentialBook> + TryCreateFromSlab(CredentialSlab &slab); private: friend class Deserializer; @@ -161,6 +219,64 @@ private: bool moved_; }; +// Holds data associated with a specific credential which will be returned to +// the caller when it is successfully matched with an advertisement. +class MatchedCredentialData { +public: + // Creates matched credential data from a provided credential_id used to + // correlate the data back to its full credential data, and the metadata byte + // buffer as copied from the given span over bytes. After calling + // this the bytes are copied into the rust code, so the + // encrypted_metadata_bytes_buffer can be freed. + // + // Safety: this is safe if the span is over a valid buffer of bytes. The copy + // from the memory address isn't atomic, so concurrent modification of the + // array from another thread would cause undefined behavior. + [[nodiscard]] MatchedCredentialData(uint32_t cred_id, + std::span<uint8_t> metadata_bytes); + +private: + np_ffi::internal::FfiMatchedCredential data_; + friend class V0MatchableCredential; + friend class V1MatchableCredential; +}; + +// Holds the v0 credential data needed by the deserializer to decrypt +// advertisements, along with some provided matched data that will be returned +// back to the caller upon a successful credential match. +class V0MatchableCredential { +public: + // Creates a new V0MatchableCredential from a key seed, its calculated hmac + // value and some match data. + [[nodiscard]] V0MatchableCredential( + std::array<uint8_t, 32> key_seed, + std::array<uint8_t, 32> legacy_metadata_key_hmac, + MatchedCredentialData matched_credential_data); + +private: + friend class CredentialSlab; + np_ffi::internal::V0MatchableCredential internal_{}; +}; + +// Holds the v1 credential data needed by the deserializer to decrypt +// advertisements, along with some provided matched data that will be returned +// back to the caller upon a successful credential match. +class V1MatchableCredential { +public: + // Creates a new V1MatchableCredential from key material, its calculated hmac + // value and some match data. + [[nodiscard]] V1MatchableCredential( + std::array<uint8_t, 32> key_seed, + std::array<uint8_t, 32> expected_unsigned_metadata_key_hmac, + std::array<uint8_t, 32> expected_signed_metadata_key_hmac, + std::array<uint8_t, 32> pub_key, + MatchedCredentialData matched_credential_data); + +private: + friend class CredentialSlab; + np_ffi::internal::V1MatchableCredential internal_; +}; + // Representation of a buffer of bytes returned from deserialization APIs template <size_t N> class ByteBuffer { public: @@ -339,8 +455,9 @@ public: // and will free the underlying parent handle. ~LegibleDeserializedV0Advertisement(); - // Returns just the identity information associated with the advertisement - [[nodiscard]] DeserializedV0Identity GetIdentity(); + // Returns just the kind of identity (public/encrypted) + // associated with the advertisement + [[nodiscard]] DeserializedV0IdentityKind GetIdentityKind(); // Returns the number of data elements in the advertisement [[nodiscard]] uint8_t GetNumberOfDataElements(); // Returns just the data-element payload of the advertisement @@ -358,20 +475,6 @@ private: bool moved_; }; -// A V0 identity of an advertisement -class DeserializedV0Identity { -public: - // Returns the DeserializedV0IdentityKind of the advertisement - [[nodiscard]] DeserializedV0IdentityKind GetKind(); - -private: - friend class LegibleDeserializedV0Advertisement; - explicit DeserializedV0Identity( - np_ffi::internal::DeserializedV0Identity v0_identity) - : v0_identity_(v0_identity) {} - np_ffi::internal::DeserializedV0Identity v0_identity_; -}; - // A data element payload of a Deserialized V0 Advertisement. class V0Payload { public: @@ -391,6 +494,16 @@ public: // element if it exists otherwise returns an Error status code [[nodiscard]] absl::StatusOr<V0DataElement> TryGetDataElement(uint8_t index); + // Decrypts the metadata of the credential which matched with this + // advertisement, or returns an error if the metadata key is invalid and unable + // to successfully decrypt the metadata. + [[nodiscard]] absl::StatusOr<std::vector<uint8_t>> DecryptMetadata(); + + // Gets the details of the identity data element of this payload or returns an + // error if the payload does not have an identity (public advertisement) + [[nodiscard]] absl::StatusOr<DeserializedV0IdentityDetails> + GetIdentityDetails(); + private: friend class LegibleDeserializedV0Advertisement; explicit V0Payload(np_ffi::internal::V0Payload v0_payload) @@ -480,11 +593,29 @@ class DeserializedV1Section { public: // Returns the number of data elements present in the section [[nodiscard]] uint8_t NumberOfDataElements(); + // Returns the DeserializedV1IdentityKind of the identity [[nodiscard]] DeserializedV1IdentityKind GetIdentityKind(); + // Tries to get the data element in the section at the given index [[nodiscard]] absl::StatusOr<V1DataElement> TryGetDataElement(uint8_t index); + // Decrypts the metadata of the credential which matched with this section + [[nodiscard]] absl::StatusOr<std::vector<uint8_t>> DecryptMetadata(); + + // Gets the details of the identity data element of this section or returns an + // error if the section does not conatin an identity (public section) + [[nodiscard]] absl::StatusOr<DeserializedV1IdentityDetails> + GetIdentityDetails(); + + // Attempts to derive a 16-byte DE salt for a DE in this section with the + // given DE offset. This operation may fail if the passed offset is 255 + // (causes overflow) or if the section is leveraging a public identity, and + // hence, doesn't have an associated salt. The offset should come from a + // particular deserialized v1 de via `V1DataElement::GetOffset()` + [[nodiscard]] absl::StatusOr<std::array<uint8_t, 16>> + DeriveSaltForOffset(uint8_t offset); + private: friend class DeserializedV1Advertisement; explicit DeserializedV1Section( @@ -505,6 +636,8 @@ public: [[nodiscard]] uint32_t GetDataElementTypeCode() const; // Yields the payload bytes of the data element [[nodiscard]] ByteBuffer<127> GetPayload() const; + /// Gets the offset for this V1 data element. + [[nodiscard]] uint8_t GetOffset() const; private: friend class DeserializedV1Section; diff --git a/nearby/presence/np_cpp_ffi/nearby_protocol.cc b/nearby/presence/np_cpp_ffi/nearby_protocol.cc index 3ee5c63..c2f8566 100644 --- a/nearby/presence/np_cpp_ffi/nearby_protocol.cc +++ b/nearby/presence/np_cpp_ffi/nearby_protocol.cc @@ -27,7 +27,7 @@ namespace nearby_protocol { static void panic_handler(PanicReason reason); struct PanicHandler { - void (*handler)(PanicReason); + void (* handler)(PanicReason); bool set_by_client; }; @@ -43,7 +43,7 @@ static void panic_handler(PanicReason reason) { std::abort(); } -static void _assert_panic(bool condition, const char *func, const char *file, +static void _assert_panic(bool condition, const char* func, const char* file, int line) { if (!condition) { std::cout << "Assert failed: \n function: " << func << "\n file: " << file @@ -54,7 +54,7 @@ static void _assert_panic(bool condition, const char *func, const char *file, #define assert_panic(e) _assert_panic(e, __func__, __ASSERT_FILE_NAME, __LINE__) -bool GlobalConfig::SetPanicHandler(void (*handler)(PanicReason)) { +bool GlobalConfig::SetPanicHandler(void (* handler)(PanicReason)) { if (!gPanicHandler.set_by_client) { gPanicHandler.handler = handler; gPanicHandler.set_by_client = true; @@ -67,6 +67,11 @@ void GlobalConfig::SetNumShards(uint8_t num_shards) { np_ffi::internal::np_ffi_global_config_set_num_shards(num_shards); } +void GlobalConfig::SetMaxNumCredentialSlabs(uint32_t max_num_credential_slabs) { + np_ffi::internal::np_ffi_global_config_set_max_num_credential_slabs( + max_num_credential_slabs); +} + void GlobalConfig::SetMaxNumCredentialBooks(uint32_t max_num_credential_books) { np_ffi::internal::np_ffi_global_config_set_max_num_credential_books( max_num_credential_books); @@ -75,32 +80,118 @@ void GlobalConfig::SetMaxNumCredentialBooks(uint32_t max_num_credential_books) { void GlobalConfig::SetMaxNumDeserializedV0Advertisements( uint32_t max_num_deserialized_v0_advertisements) { np_ffi::internal:: - np_ffi_global_config_set_max_num_deserialized_v0_advertisements( - max_num_deserialized_v0_advertisements); + np_ffi_global_config_set_max_num_deserialized_v0_advertisements( + max_num_deserialized_v0_advertisements); } void GlobalConfig::SetMaxNumDeserializedV1Advertisements( uint32_t max_num_deserialized_v1_advertisements) { np_ffi::internal:: - np_ffi_global_config_set_max_num_deserialized_v1_advertisements( - max_num_deserialized_v1_advertisements); + np_ffi_global_config_set_max_num_deserialized_v1_advertisements( + max_num_deserialized_v1_advertisements); } -absl::StatusOr<CredentialBook> CredentialBook::TryCreate() { - auto result = np_ffi::internal::np_ffi_create_credential_book(); - auto kind = np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(result); +absl::StatusOr<CredentialSlab> CredentialSlab::TryCreate() { + auto result = np_ffi::internal::np_ffi_create_credential_slab(); + auto kind = np_ffi::internal::np_ffi_CreateCredentialSlabResult_kind(result); switch (kind) { - case CreateCredentialBookResultKind::Success: { - auto book = CredentialBook( - np_ffi::internal::np_ffi_CreateCredentialBookResult_into_SUCCESS( - result)); - return book; + case CreateCredentialSlabResultKind::Success: { + auto slab = CredentialSlab( + np_ffi::internal::np_ffi_CreateCredentialSlabResult_into_SUCCESS( + result)); + return slab; + } + case CreateCredentialSlabResultKind::NoSpaceLeft: { + return absl::ResourceExhaustedError( + "No space left to create credential slab"); + } } - case CreateCredentialBookResultKind::NoSpaceLeft: { - return absl::ResourceExhaustedError( - "No space left to create credential book"); +} + +CredentialSlab::~CredentialSlab() { + if (!this->moved_) { + auto result = + np_ffi::internal::np_ffi_deallocate_credential_slab(credential_slab_); + assert_panic(result == np_ffi::internal::DeallocateResult::Success); } +} + +CredentialSlab::CredentialSlab(CredentialSlab&& other) noexcept + : credential_slab_(other.credential_slab_), moved_(other.moved_) { + other.credential_slab_ = {}; + other.moved_ = true; +} + +CredentialSlab& CredentialSlab::operator=(CredentialSlab&& other) noexcept { + if (this != &other) { + if (!this->moved_) { + auto result = np_ffi::internal::np_ffi_deallocate_credential_slab( + this->credential_slab_); + assert_panic(result == np_ffi::internal::DeallocateResult::Success); + } + + this->credential_slab_ = other.credential_slab_; + this->moved_ = other.moved_; + + other.credential_slab_ = {}; + other.moved_ = true; + } + return *this; +} + +absl::Status CredentialSlab::AddV0Credential(V0MatchableCredential v0_cred) { + assert_panic(!this->moved_); + auto result = np_ffi::internal::np_ffi_CredentialSlab_add_v0_credential( + this->credential_slab_, v0_cred.internal_); + switch (result) { + case AddCredentialToSlabResult::Success: { + return absl::OkStatus(); + } + case AddCredentialToSlabResult::InvalidHandle: { + return absl::InvalidArgumentError( + "invalid credential slab handle provided"); + } + } +} + +absl::Status CredentialSlab::AddV1Credential(V1MatchableCredential v1_cred) { + assert_panic(!this->moved_); + auto result = np_ffi::internal::np_ffi_CredentialSlab_add_v1_credential( + this->credential_slab_, v1_cred.internal_); + switch (result) { + case AddCredentialToSlabResult::Success: { + return absl::OkStatus(); + } + case AddCredentialToSlabResult::InvalidHandle: { + return absl::InvalidArgumentError( + "invalid credential slab handle provided"); + } + } +} + +absl::StatusOr<CredentialBook> +CredentialBook::TryCreateFromSlab(CredentialSlab& slab) { + assert_panic(!slab.moved_); + auto result = np_ffi::internal::np_ffi_create_credential_book_from_slab( + slab.credential_slab_); + auto kind = np_ffi::internal::np_ffi_CreateCredentialBookResult_kind(result); + switch (kind) { + case CreateCredentialBookResultKind::Success: { + auto book = + np_ffi::internal::np_ffi_CreateCredentialBookResult_into_SUCCESS( + result); + slab.moved_ = true; + return CredentialBook(book); + } + case CreateCredentialBookResultKind::NoSpaceLeft: { + return absl::ResourceExhaustedError( + "No space left to create credential book"); + } + case CreateCredentialBookResultKind::InvalidSlabHandle: { + return absl::NotFoundError( + "The slab referenced by the given handle was not found."); + } } } @@ -112,13 +203,13 @@ CredentialBook::~CredentialBook() { } } -CredentialBook::CredentialBook(CredentialBook &&other) noexcept +CredentialBook::CredentialBook(CredentialBook&& other) noexcept : credential_book_(other.credential_book_), moved_(other.moved_) { other.credential_book_ = {}; other.moved_ = true; } -CredentialBook &CredentialBook::operator=(CredentialBook &&other) noexcept { +CredentialBook& CredentialBook::operator=(CredentialBook&& other) noexcept { if (this != &other) { if (!this->moved_) { auto result = np_ffi::internal::np_ffi_deallocate_credential_book( @@ -136,8 +227,8 @@ CredentialBook &CredentialBook::operator=(CredentialBook &&other) noexcept { } DeserializeAdvertisementResult -Deserializer::DeserializeAdvertisement(RawAdvertisementPayload &payload, - const CredentialBook &credential_book) { +Deserializer::DeserializeAdvertisement(RawAdvertisementPayload& payload, + const CredentialBook& credential_book) { assert_panic(!credential_book.moved_); auto result = np_ffi::internal::np_ffi_deserialize_advertisement( {payload.buffer_.internal_}, credential_book.credential_book_); @@ -175,14 +266,14 @@ DeserializeAdvertisementResult::~DeserializeAdvertisementResult() { } DeserializeAdvertisementResult::DeserializeAdvertisementResult( - DeserializeAdvertisementResult &&other) noexcept + DeserializeAdvertisementResult&& other) noexcept : result_(other.result_), moved_(other.moved_) { other.result_ = {}; other.moved_ = true; } -DeserializeAdvertisementResult &DeserializeAdvertisementResult::operator=( - DeserializeAdvertisementResult &&other) noexcept { +DeserializeAdvertisementResult& DeserializeAdvertisementResult::operator=( + DeserializeAdvertisementResult&& other) noexcept { if (this != &other) { if (!this->moved_) { auto result = @@ -201,14 +292,14 @@ DeserializeAdvertisementResult &DeserializeAdvertisementResult::operator=( // V0 Stuff DeserializedV0Advertisement::DeserializedV0Advertisement( - DeserializedV0Advertisement &&other) noexcept + DeserializedV0Advertisement&& other) noexcept : v0_advertisement_(other.v0_advertisement_), moved_(other.moved_) { other.v0_advertisement_ = {}; other.moved_ = true; } -DeserializedV0Advertisement &DeserializedV0Advertisement::operator=( - DeserializedV0Advertisement &&other) noexcept { +DeserializedV0Advertisement& DeserializedV0Advertisement::operator=( + DeserializedV0Advertisement&& other) noexcept { if (this != &other) { if (!this->moved_) { auto result = @@ -252,16 +343,16 @@ LegibleDeserializedV0Advertisement DeserializedV0Advertisement::IntoLegible() { } LegibleDeserializedV0Advertisement::LegibleDeserializedV0Advertisement( - LegibleDeserializedV0Advertisement &&other) noexcept + LegibleDeserializedV0Advertisement&& other) noexcept : legible_v0_advertisement_(other.legible_v0_advertisement_), moved_(other.moved_) { other.moved_ = true; other.legible_v0_advertisement_ = {}; } -LegibleDeserializedV0Advertisement & +LegibleDeserializedV0Advertisement& LegibleDeserializedV0Advertisement::operator=( - LegibleDeserializedV0Advertisement &&other) noexcept { + LegibleDeserializedV0Advertisement&& other) noexcept { if (this != &other) { if (!this->moved_) { auto result = @@ -286,19 +377,20 @@ LegibleDeserializedV0Advertisement::~LegibleDeserializedV0Advertisement() { } } -DeserializedV0Identity LegibleDeserializedV0Advertisement::GetIdentity() { +DeserializedV0IdentityKind +LegibleDeserializedV0Advertisement::GetIdentityKind() { assert_panic(!this->moved_); - auto result = - np_ffi::internal::np_ffi_LegibleDeserializedV0Advertisement_into_identity( - legible_v0_advertisement_); - return DeserializedV0Identity(result); + auto result = np_ffi::internal:: + np_ffi_LegibleDeserializedV0Advertisement_get_identity_kind( + legible_v0_advertisement_); + return result; } uint8_t LegibleDeserializedV0Advertisement::GetNumberOfDataElements() { assert_panic(!this->moved_); return np_ffi::internal:: - np_ffi_LegibleDeserializedV0Advertisement_get_num_des( - legible_v0_advertisement_); + np_ffi_LegibleDeserializedV0Advertisement_get_num_des( + legible_v0_advertisement_); } V0Payload LegibleDeserializedV0Advertisement::IntoPayload() { @@ -309,17 +401,13 @@ V0Payload LegibleDeserializedV0Advertisement::IntoPayload() { return V0Payload(result); } -np_ffi::internal::DeserializedV0IdentityKind DeserializedV0Identity::GetKind() { - return np_ffi::internal::np_ffi_DeserializedV0Identity_kind(v0_identity_); -} - -V0Payload::V0Payload(V0Payload &&other) noexcept +V0Payload::V0Payload(V0Payload&& other) noexcept : v0_payload_(other.v0_payload_), moved_(other.moved_) { other.v0_payload_ = {}; other.moved_ = true; } -V0Payload &V0Payload::operator=(V0Payload &&other) noexcept { +V0Payload& V0Payload::operator=(V0Payload&& other) noexcept { if (this != &other) { if (!this->moved_) { auto result = @@ -348,16 +436,75 @@ absl::StatusOr<V0DataElement> V0Payload::TryGetDataElement(uint8_t index) { auto result = np_ffi::internal::np_ffi_V0Payload_get_de(v0_payload_, index); auto kind = np_ffi::internal::np_ffi_GetV0DEResult_kind(result); switch (kind) { - case GetV0DEResultKind::Success: { - auto de = np_ffi_GetV0DEResult_into_SUCCESS(result); - return V0DataElement(de); + case GetV0DEResultKind::Success: { + auto de = np_ffi_GetV0DEResult_into_SUCCESS(result); + return V0DataElement(de); + } + case GetV0DEResultKind::Error: { + return absl::OutOfRangeError("Invalid Data Element index"); + } } - case GetV0DEResultKind::Error: { - return absl::OutOfRangeError("Invalid Data Element index"); +} + +static absl::StatusOr<std::vector<uint8_t>> +MetadataResultToVec(np_ffi::internal::DecryptMetadataResult decrypt_result) { + auto kind = + np_ffi::internal::np_ffi_DecryptMetadataResult_kind(decrypt_result); + switch (kind) { + case np_ffi::internal::DecryptMetadataResultKind::Success: { + auto metadata = + np_ffi::internal::np_ffi_DecryptMetadataResult_into_SUCCESS( + decrypt_result); + auto parts_result = + np_ffi::internal::np_ffi_DecryptedMetadata_get_metadata_buffer_parts( + metadata); + // The handle is guaranteed to be valid by the C++ wrapper so this should + // never fail + assert_panic(np_ffi::internal::np_ffi_GetMetadataBufferPartsResult_kind( + parts_result) == + np_ffi::internal::GetMetadataBufferPartsResultKind::Success); + auto parts = + np_ffi::internal::np_ffi_GetMetadataBufferPartsResult_into_SUCCESS( + parts_result); + std::vector<uint8_t> result(parts.ptr, parts.ptr + parts.len); + + // Now that the contents have been copied into the vec, the underlying + // handle can be de-allocated + auto deallocate_result = + np_ffi::internal::np_ffi_deallocate_DecryptedMetadata(metadata); + assert_panic(deallocate_result == + np_ffi::internal::DeallocateResult::Success); + return result; + } + case np_ffi::internal::DecryptMetadataResultKind::Error: { + return absl::InvalidArgumentError("Invalid V0 payload handle"); + } } +} + +absl::StatusOr<DeserializedV0IdentityDetails> V0Payload::GetIdentityDetails() { + assert_panic(!this->moved_); + auto result = np_ffi::internal::np_ffi_V0Payload_get_identity_details( + this->v0_payload_); + auto kind = np_ffi::internal::np_ffi_GetV0IdentityDetailsResult_kind(result); + switch (kind) { + case np_ffi::internal::GetV0IdentityDetailsResultKind::Error: { + return absl::InvalidArgumentError("Invalid handle"); + } + case np_ffi::internal::GetV0IdentityDetailsResultKind::Success: { + return np_ffi::internal::np_ffi_GetV0IdentityDetailsResult_into_SUCCESS( + result); + } } } +absl::StatusOr<std::vector<uint8_t>> V0Payload::DecryptMetadata() { + assert_panic(!this->moved_); + auto decrypt_result = + np_ffi::internal::np_ffi_V0Payload_decrypt_metadata(this->v0_payload_); + return MetadataResultToVec(decrypt_result); +} + V0DataElementKind V0DataElement::GetKind() { return np_ffi::internal::np_ffi_V0DataElement_kind(v0_data_element_); } @@ -387,7 +534,7 @@ uint8_t V0Actions::GetContextSyncSequenceNumber() { // This is called after all references to the shared_ptr have gone out of scope auto DeallocateV1Adv( - np_ffi::internal::DeserializedV1Advertisement *v1_advertisement) { + np_ffi::internal::DeserializedV1Advertisement* v1_advertisement) { auto result = np_ffi::internal::np_ffi_deallocate_deserialized_V1_advertisement( *v1_advertisement); @@ -404,11 +551,11 @@ DeserializedV1Advertisement::DeserializedV1Advertisement( } DeserializedV1Advertisement::DeserializedV1Advertisement( - DeserializedV1Advertisement &&other) noexcept + DeserializedV1Advertisement&& other) noexcept : v1_advertisement_(std::move(other.v1_advertisement_)) {} -DeserializedV1Advertisement &DeserializedV1Advertisement::operator=( - DeserializedV1Advertisement &&other) noexcept { +DeserializedV1Advertisement& DeserializedV1Advertisement::operator=( + DeserializedV1Advertisement&& other) noexcept { if (this != &other) { this->v1_advertisement_ = std::move(other.v1_advertisement_); } @@ -419,15 +566,15 @@ DeserializedV1Advertisement &DeserializedV1Advertisement::operator=( uint8_t DeserializedV1Advertisement::GetNumLegibleSections() { assert_panic(this->v1_advertisement_ != nullptr); return np_ffi::internal:: - np_ffi_DeserializedV1Advertisement_get_num_legible_sections( - *v1_advertisement_); + np_ffi_DeserializedV1Advertisement_get_num_legible_sections( + *v1_advertisement_); } uint8_t DeserializedV1Advertisement::GetNumUndecryptableSections() { assert_panic(this->v1_advertisement_ != nullptr); return np_ffi::internal:: - np_ffi_DeserializedV1Advertisement_get_num_undecryptable_sections( - *v1_advertisement_); + np_ffi_DeserializedV1Advertisement_get_num_undecryptable_sections( + *v1_advertisement_); } absl::StatusOr<DeserializedV1Section> @@ -438,14 +585,14 @@ DeserializedV1Advertisement::TryGetSection(uint8_t section_index) { *v1_advertisement_, section_index); auto kind = np_ffi::internal::np_ffi_GetV1SectionResult_kind(result); switch (kind) { - case np_ffi::internal::GetV1SectionResultKind::Error: { - return absl::OutOfRangeError("Invalid section index"); - } - case np_ffi::internal::GetV1SectionResultKind::Success: { - auto section = - np_ffi::internal::np_ffi_GetV1SectionResult_into_SUCCESS(result); - return DeserializedV1Section(section, v1_advertisement_); - } + case np_ffi::internal::GetV1SectionResultKind::Error: { + return absl::OutOfRangeError("Invalid section index"); + } + case np_ffi::internal::GetV1SectionResultKind::Success: { + auto section = + np_ffi::internal::np_ffi_GetV1SectionResult_into_SUCCESS(result); + return DeserializedV1Section(section, v1_advertisement_); + } } } @@ -464,23 +611,112 @@ DeserializedV1Section::TryGetDataElement(uint8_t index) { np_ffi::internal::np_ffi_DeserializedV1Section_get_de(section_, index); auto kind = np_ffi::internal::np_ffi_GetV1DEResult_kind(result); switch (kind) { - case np_ffi::internal::GetV1DEResultKind::Error: { - return absl::OutOfRangeError("Invalid data element index for this section"); + case np_ffi::internal::GetV1DEResultKind::Error: { + return absl::OutOfRangeError("Invalid data element index for this section"); + } + case np_ffi::internal::GetV1DEResultKind::Success: { + return V1DataElement( + np_ffi::internal::np_ffi_GetV1DEResult_into_SUCCESS(result)); + } } - case np_ffi::internal::GetV1DEResultKind::Success: { - return V1DataElement( - np_ffi::internal::np_ffi_GetV1DEResult_into_SUCCESS(result)); +} + +absl::StatusOr<std::vector<uint8_t>> DeserializedV1Section::DecryptMetadata() { + assert_panic(this->owning_v1_advertisement_ != nullptr); + auto decrypt_result = + np_ffi::internal::np_ffi_DeserializedV1Section_decrypt_metadata( + this->section_); + return MetadataResultToVec(decrypt_result); +} + +absl::StatusOr<DeserializedV1IdentityDetails> +DeserializedV1Section::GetIdentityDetails() { + assert_panic(this->owning_v1_advertisement_ != nullptr); + auto result = + np_ffi::internal::np_ffi_DeserializedV1Section_get_identity_details( + this->section_); + auto kind = np_ffi::internal::np_ffi_GetV1IdentityDetailsResult_kind(result); + switch (kind) { + case np_ffi::internal::GetV1IdentityDetailsResultKind::Error: { + return absl::InvalidArgumentError("Invalid handle"); + } + case np_ffi::internal::GetV1IdentityDetailsResultKind::Success: { + return np_ffi::internal::np_ffi_GetV1IdentityDetailsResult_into_SUCCESS( + result); + } } +} + +absl::StatusOr<std::array<uint8_t, 16>> DeserializedV1Section::DeriveSaltForOffset( + uint8_t offset) { + auto result = + np_ffi::internal::np_ffi_DeserializedV1Section_derive_16_byte_salt_for_offset( + this->section_, + offset); + auto kind = np_ffi::internal::np_ffi_GetV1DE16ByteSaltResult_kind(result); + switch (kind) { + case np_ffi::internal::GetV1DE16ByteSaltResultKind::Error: { + return absl::InvalidArgumentError("Failed to derive salt for offset"); + } + case np_ffi::internal::GetV1DE16ByteSaltResultKind::Success: { + auto buffer = np_ffi::internal::np_ffi_GetV1DE16ByteSaltResult_into_SUCCESS(result); + return std::to_array(buffer._0); + } } } uint32_t V1DataElement::GetDataElementTypeCode() const { return np_ffi::internal::np_ffi_V1DEType_to_uint32_t( - v1_data_element_.generic._0.de_type); + np_ffi::internal::np_ffi_V1DataElement_to_generic(this->v1_data_element_) + .de_type); } ByteBuffer<127> V1DataElement::GetPayload() const { - return ByteBuffer(v1_data_element_.generic._0.payload); + return ByteBuffer( + np_ffi::internal::np_ffi_V1DataElement_to_generic(this->v1_data_element_) + .payload); +} + +uint8_t V1DataElement::GetOffset() const { + return np_ffi::internal::np_ffi_V1DataElement_to_generic( + this->v1_data_element_) + .offset; +} + +MatchedCredentialData::MatchedCredentialData( + uint32_t cred_id, std::span<uint8_t> metadata_bytes) { + this->data_ = {cred_id, metadata_bytes.data(), metadata_bytes.size()}; } +template<typename T, size_t N> +static void CopyToRawArray(T (& dest)[N], const std::array<T, N>& src) { + memcpy(dest, src.data(), sizeof(T) * N); +} + +V0MatchableCredential::V0MatchableCredential( + std::array<uint8_t, 32> key_seed, + std::array<uint8_t, 32> legacy_metadata_key_hmac, + MatchedCredentialData matched_credential_data) { + np_ffi::internal::V0DiscoveryCredential discovery_cred{}; + CopyToRawArray(discovery_cred.key_seed, key_seed); + CopyToRawArray(discovery_cred.legacy_metadata_key_hmac, + legacy_metadata_key_hmac); + this->internal_ = {discovery_cred, matched_credential_data.data_}; +} + +V1MatchableCredential::V1MatchableCredential( + std::array<uint8_t, 32> key_seed, + std::array<uint8_t, 32> expected_unsigned_metadata_key_hmac, + std::array<uint8_t, 32> expected_signed_metadata_key_hmac, + std::array<uint8_t, 32> pub_key, + MatchedCredentialData matched_credential_data) { + np_ffi::internal::V1DiscoveryCredential discovery_cred{}; + CopyToRawArray(discovery_cred.key_seed, key_seed); + CopyToRawArray(discovery_cred.expected_unsigned_metadata_key_hmac, + expected_unsigned_metadata_key_hmac); + CopyToRawArray(discovery_cred.expected_signed_metadata_key_hmac, + expected_signed_metadata_key_hmac); + CopyToRawArray(discovery_cred.pub_key, pub_key); + this->internal_ = {discovery_cred, matched_credential_data.data_}; +} } // namespace nearby_protocol 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 index 114ed64..1d273da 100644 --- a/nearby/presence/np_cpp_ffi/sample/main.cpp +++ b/nearby/presence/np_cpp_ffi/sample/main.cc @@ -25,7 +25,7 @@ void HandleAdvertisementResult(nearby_protocol::DeserializeAdvertisementResult); void HandleV0Adv(nearby_protocol::DeserializedV0Advertisement); void HandleLegibleV0Adv(nearby_protocol::LegibleDeserializedV0Advertisement); -void HandleV0Identity(nearby_protocol::DeserializedV0Identity); +void HandleV0IdentityKind(nearby_protocol::DeserializedV0IdentityKind); void HandleDataElement(nearby_protocol::V0DataElement); void HandleV1Adv(nearby_protocol::DeserializedV1Advertisement); @@ -46,8 +46,15 @@ int main() { std::cout << "\n========= Example V0 Adv ==========\n"; std::cout << "Hex bytes: 00031503260046\n\n"; - // Create an empty credential book and verify that is is successful - auto cred_book_result = nearby_protocol::CredentialBook::TryCreate(); + // Create an empty credential slab and verify that it is successful + auto cred_slab_result = nearby_protocol::CredentialSlab::TryCreate(); + if (!cred_slab_result.ok()) { + std::cout << cred_slab_result.status().ToString(); + return -1; + } + + // Create an empty credential book from the empty slab, and verify success. + auto cred_book_result = nearby_protocol::CredentialBook::TryCreateFromSlab(cred_slab_result.value()); if (!cred_book_result.ok()) { std::cout << cred_book_result.status().ToString(); return -1; @@ -74,10 +81,8 @@ int main() { auto v1_byte_string = "20" // V1 Advertisement header "04" // Section Header "03" // Public Identity DE header - "260046" // Length 2 Actions DE - "03" // Section Header - "03" // Public Identity DE header - "1505"; // Length 1 Tx Power DE with value 5 + "260046";// Length 2 Actions DE + auto v1_bytes = absl::HexStringToBytes(v1_byte_string); auto v1_buffer = nearby_protocol::ByteBuffer<255>::CopyFrom(v1_bytes); nearby_protocol::RawAdvertisementPayload v1_payload(v1_buffer.value()); @@ -132,7 +137,7 @@ static void SamplePanicHandler(nearby_protocol::PanicReason reason) { std::cout << "AssertFailed \n"; break; } - case np_ffi::internal::PanicReason::InvalidActionBits: { + case nearby_protocol::PanicReason::InvalidActionBits: { std::cout << "InvalidActionBits \n"; break; } @@ -171,7 +176,7 @@ void HandleV0Adv(nearby_protocol::DeserializedV0Advertisement result) { void HandleLegibleV0Adv( nearby_protocol::LegibleDeserializedV0Advertisement legible_adv) { - HandleV0Identity(legible_adv.GetIdentity()); + HandleV0IdentityKind(legible_adv.GetIdentityKind()); auto num_des = legible_adv.GetNumberOfDataElements(); std::cout << "\t\tAdv contains " << unsigned(num_des) << " data elements \n"; @@ -187,8 +192,8 @@ void HandleLegibleV0Adv( } } -void HandleV0Identity(nearby_protocol::DeserializedV0Identity identity) { - switch (identity.GetKind()) { +void HandleV0IdentityKind(nearby_protocol::DeserializedV0IdentityKind identity) { + switch (identity) { case np_ffi::internal::DeserializedV0IdentityKind::Plaintext: { std::cout << "\t\tIdentity is Plaintext\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 @@ std::string PanicReasonToString(nearby_protocol::PanicReason reason) { 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/tests/CMakeLists.txt b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt index bddda64..549a072 100644 --- a/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt +++ b/nearby/presence/np_cpp_ffi/tests/CMakeLists.txt @@ -14,12 +14,16 @@ add_executable( np_ffi_tests - deserialize_result_tests.cc - deserialize_v0_tests.cc - deserialize_v1_tests.cc - global_config_tests.cc - credential_book_tests.cc byte_buffer_tests.cc + credential_book_tests.cc + credential_slab_tests.cc + deserialize_result_tests.cc + np_cpp_test.h + np_cpp_test.cc + v0_private_identity_tests.cc + v0_public_identity_tests.cc + v1_private_identity_tests.cc + v1_public_identity_tests.cc ) target_link_libraries( diff --git a/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc b/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc index 4bca8ec..7e1a048 100644 --- a/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/byte_buffer_tests.cc @@ -13,12 +13,13 @@ // limitations under the License. #include "nearby_protocol.h" +#include "np_cpp_test.h" #include "shared_test_util.h" #include "absl/strings/escaping.h" #include "gtest/gtest.h" -TEST(ByteBufferTests, ByteBufferMaxLength) { +TEST_F(NpCppTest, ByteBufferMaxLength) { // Each hex byte takes up 2 characters so length 510 string = 255 bytes of hex auto str_bytes = generate_hex_string(510); auto bytes = absl::HexStringToBytes(str_bytes); @@ -28,7 +29,7 @@ TEST(ByteBufferTests, ByteBufferMaxLength) { ASSERT_EQ(bytes, string); } -TEST(ByteBufferTooLarge, ByteBufferInvalidLength) { +TEST_F(NpCppTest, ByteBufferInvalidLength) { // 256 bytes should fail auto str_bytes = generate_hex_string(512); auto bytes = absl::HexStringToBytes(str_bytes); @@ -36,26 +37,26 @@ TEST(ByteBufferTooLarge, ByteBufferInvalidLength) { ASSERT_FALSE(buffer.ok()); } -TEST(ByteBufferTests, ByteBufferRoundTrip) { +TEST_F(NpCppTest, ByteBufferRoundTrip) { auto bytes = absl::HexStringToBytes("2003031503"); auto buffer = nearby_protocol::ByteBuffer<255>::CopyFrom(bytes); auto string = buffer.value().ToString(); ASSERT_EQ(bytes, string); } -TEST(ByteBufferTests, ByteBufferPayloadWrongSize) { +TEST_F(NpCppTest, ByteBufferPayloadWrongSize) { auto bytes = absl::HexStringToBytes("1111111111111111111111"); auto buffer = nearby_protocol::ByteBuffer<10>::CopyFrom(bytes); ASSERT_FALSE(buffer.ok()); } -TEST(ByteBufferTests, ByteBufferEmptyString) { +TEST_F(NpCppTest, ByteBufferEmptyString) { auto bytes = absl::HexStringToBytes(""); auto buffer = nearby_protocol::ByteBuffer<10>::CopyFrom(bytes); ASSERT_TRUE(buffer.ok()); } -TEST(ByteBufferTests, ByteBufferToVector) { +TEST_F(NpCppTest, ByteBufferToVector) { auto bytes = absl::HexStringToBytes("1234567890"); auto buffer = nearby_protocol::ByteBuffer<100>::CopyFrom(bytes); auto vec = buffer.value().ToVector(); @@ -63,16 +64,19 @@ TEST(ByteBufferTests, ByteBufferToVector) { ASSERT_EQ(vec, expected); } -TEST(ByteBufferTests, ByteBufferEndToEndPayloadAsString) { +TEST_F(NpCppTest, ByteBufferEndToEndPayloadAsString) { std::string bytes = absl::HexStringToBytes("2003031503"); auto buffer = nearby_protocol::ByteBuffer<255>::CopyFrom(bytes); ASSERT_TRUE(buffer.ok()); nearby_protocol::RawAdvertisementPayload adv(buffer.value()); - auto credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto credential_slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto credential_book = + nearby_protocol::CredentialBook::TryCreateFromSlab(credential_slab) + .value(); auto str = nearby_protocol::Deserializer::DeserializeAdvertisement( - adv, credential_book.value()) + adv, credential_book) .IntoV1() .TryGetSection(0) .value() 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..af8fc0d 100644 --- a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc @@ -14,13 +14,35 @@ #include "nearby_protocol.h" #include "shared_test_util.h" +#include "np_cpp_test.h" #include "gtest/gtest.h" -TEST(NpFfiCredentialBookTests, TestMoveConstructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, TestSetMaxCredBooks) { + auto slab1_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab1_result.ok()); + auto book1_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab1_result.value()); + ASSERT_TRUE(book1_result.ok()); + + auto slab2_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab2_result.ok()); + auto book2_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab2_result.value()); + ASSERT_TRUE(book2_result.ok()); + + auto slab3_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab3_result.ok()); + auto book3_result = nearby_protocol::CredentialBook::TryCreateFromSlab(slab3_result.value()); + + ASSERT_FALSE(book3_result.ok()); + ASSERT_TRUE(absl::IsResourceExhausted(book3_result.status())); +} + +TEST_F(NpCppTest, TestBookMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).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 +50,7 @@ TEST(NpFfiCredentialBookTests, TestMoveConstructor) { // 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,31 +58,34 @@ TEST(NpFfiCredentialBookTests, TestMoveConstructor) { // 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(); +TEST_F(NpCppTest, TestBookMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).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); // create a second empty credential book - auto other_book = nearby_protocol::CredentialBook::TryCreate().value(); + auto other_slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto other_book = nearby_protocol::CredentialBook::TryCreateFromSlab(other_slab).value(); other_book = std::move(book); // 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 +93,13 @@ TEST(NpFfiCredentialBookTests, TestMoveAssignment) { // 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/credential_slab_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_slab_tests.cc new file mode 100644 index 0000000..c8ddb3c --- /dev/null +++ b/nearby/presence/np_cpp_ffi/tests/credential_slab_tests.cc @@ -0,0 +1,226 @@ +// 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. + +#include "nearby_protocol.h" +#include "np_cpp_test.h" +#include "shared_test_util.h" + +#include "gtest/gtest.h" + +TEST_F(NpCppTest, TestSetMaxCredSlabs) { + auto slab1_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab1_result.ok()); + + auto slab2_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab2_result.ok()); + + auto slab3_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab3_result.ok()); + + auto slab4_result = nearby_protocol::CredentialSlab::TryCreate(); + + ASSERT_FALSE(slab4_result.ok()); + ASSERT_TRUE(absl::IsResourceExhausted(slab4_result.status())); +} + +TEST_F(NpCppTest, TestSlabMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + // It should be possible to move the slab into a new object + // and use the moved version to successfully construct a + // credential-book. + nearby_protocol::CredentialSlab next_slab(std::move(slab)); + + auto maybe_book = + nearby_protocol::CredentialBook::TryCreateFromSlab(next_slab); + ASSERT_TRUE(maybe_book.ok()); + + // Now, both slabs should be moved-out-of, since `TryCreateFromSlab` takes + // ownership. Verify that this is the case, and attempts to re-use the slabs + // result in an assert failure. + ASSERT_DEATH([[maybe_unused]] auto failure = + nearby_protocol::CredentialBook::TryCreateFromSlab( + slab), // NOLINT(bugprone-use-after-move) + ""); + ASSERT_DEATH( + [[maybe_unused]] auto failure = + nearby_protocol::CredentialBook::TryCreateFromSlab(next_slab), + ""); +} + +TEST_F(NpCppTest, TestSlabDestructor) { + { + auto slab1_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab1_result.ok()); + + auto slab2_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab2_result.ok()); + + auto slab3_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab3_result.ok()); + + auto slab4_result = nearby_protocol::CredentialSlab::TryCreate(); + + ASSERT_FALSE(slab4_result.ok()); + ASSERT_TRUE(absl::IsResourceExhausted(slab4_result.status())); + } + + // Now that the above variables have gone out of scope we should verify that + // the destructor succeeded in cleaning up those resources + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); +} + +TEST_F(NpCppTest, TestSlabMoveAssignment) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + // create a second slab + auto other_slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(other_slab_result.ok()); + + // move assignment should override currently assigned slab with new one, + // freeing the existing one. + auto other_slab = std::move(slab_result.value()); + auto maybe_book = + nearby_protocol::CredentialBook::TryCreateFromSlab(other_slab); + ASSERT_TRUE(maybe_book.ok()); + + // The old object should now lead to use after moved assert failure + ASSERT_DEATH([[maybe_unused]] auto failure = + nearby_protocol::CredentialBook::TryCreateFromSlab( + slab_result.value()), // NOLINT(bugprone-use-after-move) + ""); + + // moving again should still lead to a use after moved assert failure + auto another_moved_book = std::move(slab_result.value()); + ASSERT_DEATH([[maybe_unused]] auto failure = + nearby_protocol::CredentialBook::TryCreateFromSlab( + another_moved_book), // NOLINT(bugprone-use-after-move) + ""); +} + +TEST_F(NpCppTest, TestAddV0Credential) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + uint8_t metadata[] = {1, 2, 3}; + std::span<uint8_t> metadata_span(metadata); + + nearby_protocol::MatchedCredentialData match_data(111, metadata_span); + std::array<uint8_t, 32> key_seed{1, 2, 3}; + std::array<uint8_t, 32> legacy_metadata_key_hmac{1, 2, 3}; + + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + auto add_result = slab_result.value().AddV0Credential(v0_cred); + ASSERT_EQ(add_result, absl::OkStatus()); +} + +TEST_F(NpCppTest, TestAddV0CredentialAfterMoved) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + // creating a book will move the slab + auto maybe_book = + nearby_protocol::CredentialBook::TryCreateFromSlab(slab_result.value()); + ASSERT_TRUE(maybe_book.ok()); + + uint8_t metadata[] = {1, 2, 3}; + std::span<uint8_t> metadata_span(metadata); + nearby_protocol::MatchedCredentialData match_data(111, metadata_span); + std::array<uint8_t, 32> key_seed{1, 2, 3}; + std::array<uint8_t, 32> legacy_metadata_key_hmac{1, 2, 3}; + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + + ASSERT_DEATH([[maybe_unused]] auto add_result = + slab_result.value().AddV0Credential(v0_cred); + , ""); +} + +TEST_F(NpCppTest, TestAddV1Credential) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + uint8_t metadata[] = {1, 2, 3}; + std::span<uint8_t> metadata_span(metadata); + nearby_protocol::MatchedCredentialData match_data(111, metadata_span); + std::array<uint8_t, 32> key_seed{1, 2, 3}; + std::array<uint8_t, 32> expected_unsigned_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> expected_signed_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> pub_key{1, 2, 3}; + nearby_protocol::V1MatchableCredential v1_cred( + key_seed, expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, pub_key, match_data); + + auto add_result = slab_result.value().AddV1Credential(v1_cred); + ASSERT_EQ(add_result, absl::OkStatus()); +} + +TEST_F(NpCppTest, TestAddV1CredentialAfterMoved) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + // creating a book will move the slab + auto maybe_book = + nearby_protocol::CredentialBook::TryCreateFromSlab(slab_result.value()); + ASSERT_TRUE(maybe_book.ok()); + + uint8_t metadata[] = {1, 2, 3}; + std::span<uint8_t> metadata_span(metadata); + nearby_protocol::MatchedCredentialData match_data(111, metadata_span); + std::array<uint8_t, 32> key_seed{1, 2, 3}; + std::array<uint8_t, 32> expected_unsigned_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> expected_signed_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> pub_key{1, 2, 3}; + nearby_protocol::V1MatchableCredential v1_cred( + key_seed, expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, pub_key, match_data); + + ASSERT_DEATH([[maybe_unused]] auto add_result = + slab_result.value().AddV1Credential(v1_cred); + , ""); +} + +// make sure the book can be populated with many credentials +TEST_F(NpCppTest, TestAddManyCredentials) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + // Should be able to load the slab up with many credentials + for (int i = 0; i < 500; i++) { + uint8_t metadata[] = {1, 2, 3}; + std::span<uint8_t> metadata_span(metadata); + nearby_protocol::MatchedCredentialData match_data(111, metadata_span); + std::array<uint8_t, 32> key_seed{1, 2, 3}; + std::array<uint8_t, 32> legacy_metadata_key_hmac{1, 2, 3}; + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + auto add_result = slab_result->AddV0Credential(v0_cred); + ASSERT_EQ(add_result, absl::OkStatus()); + + std::array<uint8_t, 32> v1_key_seed{1, 2, 3}; + std::array<uint8_t, 32> v1_expected_unsigned_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> v1_expected_signed_metadata_key_hmac{1, 2, 3}; + std::array<uint8_t, 32> v1_pub_key{1, 2, 3}; + nearby_protocol::V1MatchableCredential v1_cred( + v1_key_seed, v1_expected_unsigned_metadata_key_hmac, + v1_expected_signed_metadata_key_hmac, v1_pub_key, match_data); + + auto add_v1_result = slab_result->AddV1Credential(v1_cred); + ASSERT_EQ(add_v1_result, absl::OkStatus()); + } + ASSERT_TRUE( + nearby_protocol::CredentialBook::TryCreateFromSlab(*slab_result).ok()); +} 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..0469933 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc @@ -14,14 +14,16 @@ #include "nearby_protocol.h" #include "shared_test_util.h" +#include "np_cpp_test.h" #include "absl/strings/escaping.h" #include "gtest/gtest.h" -TEST(NpFfiDeserializeResultTests, TestResultMoveConstructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); +TEST_F(NpCppTest, TestResultMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -49,15 +51,19 @@ TEST(NpFfiDeserializeResultTests, TestResultMoveConstructor) { ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), ""); } -TEST(NpFfiDeserializeResultTests, DeserializeFromStringView) { +TEST_F(NpCppTest, DeserializeFromStringView) { auto bytes = absl::HexStringToBytes("00031503"); auto buffer = nearby_protocol::ByteBuffer<255>::CopyFrom(bytes); ASSERT_TRUE(buffer.ok()); nearby_protocol::RawAdvertisementPayload adv(buffer.value()); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); + auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( adv, maybe_credential_book.value()); @@ -69,8 +75,8 @@ TEST(NpFfiDeserializeResultTests, DeserializeFromStringView) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -86,16 +92,17 @@ TEST(NpFfiDeserializeResultTests, DeserializeFromStringView) { ASSERT_EQ(tx_power.tx_power, 3); } -TEST(NpFfiDeserializeResultTests, TestResultMoveAssignment) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); +TEST_F(NpCppTest, TestResultMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = 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); + auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -119,15 +126,17 @@ TEST(NpFfiDeserializeResultTests, TestResultMoveAssignment) { ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), ""); } -TEST(NpFfiDeserializeResultTests, TestInvalidPayloadHeader) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); - +TEST_F(NpCppTest, TestInvalidPayloadHeader) { // An invalid header result should result in error nearby_protocol::RawAdvertisementPayload InvalidHeaderPayload( nearby_protocol::ByteBuffer<255>({1, {0xFF}})); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); + auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( InvalidHeaderPayload, maybe_credential_book.value()); @@ -141,11 +150,13 @@ TEST(NpFfiDeserializeResultTests, TestInvalidPayloadHeader) { ""); } -TEST(NpFfiDeserializeResultTests, TestInvalidV0Cast) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); +TEST_F(NpCppTest, TestInvalidV0Cast) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( V1AdvSimple, maybe_credential_book.value()); @@ -156,16 +167,18 @@ TEST(NpFfiDeserializeResultTests, TestInvalidV0Cast) { ""); } -TEST(NpFfiDeserializeResultTests, TestInvalidV1Cast) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); - +TEST_F(NpCppTest, TestInvalidV1Cast) { // Create an empty credential book and verify that is is successful - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); 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); @@ -173,13 +186,16 @@ TEST(NpFfiDeserializeResultTests, TestInvalidV1Cast) { ""); } -TEST(NpFfiDeserializeResultTests, V0UseResultTwice) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, V0UseResultTwice) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -191,13 +207,16 @@ TEST(NpFfiDeserializeResultTests, V0UseResultTwice) { ""); } -TEST(NpFfiDeserializeResultTests, V1UseResultTwice) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, V1UseResultTwice) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); + V1AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V1); @@ -209,13 +228,16 @@ TEST(NpFfiDeserializeResultTests, V1UseResultTwice) { ""); } -TEST(NpFfiDeserializeResultTests, IntoV0AfterOutOfScope) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, IntoV0AfterOutOfScope) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -228,13 +250,16 @@ TEST(NpFfiDeserializeResultTests, IntoV0AfterOutOfScope) { ""); } -TEST(NpFfiDeserializeResultTests, IntoV1AfterOutOfScope) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, IntoV1AfterOutOfScope) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); + V1AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V1); @@ -247,13 +272,16 @@ TEST(NpFfiDeserializeResultTests, IntoV1AfterOutOfScope) { ""); } -TEST(NpFfiDeserializeV0Tests, V0ResultKindAfterOutOfScope) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, V0ResultKindAfterOutOfScope) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + V0AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); @@ -266,13 +294,16 @@ TEST(NpFfiDeserializeV0Tests, V0ResultKindAfterOutOfScope) { { [[maybe_unused]] auto failure = deserialize_result.GetKind(); }, ""); } -TEST(NpFfiDeserializeResultTests, V1ResultKindAfterOutOfScope) { - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); +TEST_F(NpCppTest, V1ResultKindAfterOutOfScope) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); + ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); + V1AdvSimple, maybe_credential_book.value()); ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V1); @@ -283,4 +314,4 @@ TEST(NpFfiDeserializeResultTests, V1ResultKindAfterOutOfScope) { // in a crash. ASSERT_DEATH( { [[maybe_unused]] auto failure = deserialize_result.GetKind(); }, ""); -}
\ 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 deleted file mode 100644 index e120aee..0000000 --- a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc +++ /dev/null @@ -1,205 +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. - -#include "nearby_protocol.h" -#include "shared_test_util.h" - -#include <iostream> - -#include "gtest/gtest.h" - -TEST(NpFfiGlobalConfigTests, TestPanicHandler) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); - ASSERT_EQ(deserialize_result.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); - - // Now try to cast the result into the wrong type and verify the process - // aborts - ASSERT_DEATH({ [[maybe_unused]] auto failure = deserialize_result.IntoV1(); }, - ""); -} - -TEST(NpFfiGlobalConfigTests, TestPanicHandlerTwice) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); - - // Second time trying to set should fail - ASSERT_FALSE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); -} - -// There is not much we can actually test here since this will affect memory -// consumption. This is more of just a simple check that things still work after -// configuring this -TEST(NpFfiGlobalConfigTests, TestSetMaxShardsDefault) { - // 0 should still work as default behavior - nearby_protocol::GlobalConfig::SetNumShards(0); - - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto book2 = nearby_protocol::CredentialBook::TryCreate().value(); - auto book3 = nearby_protocol::CredentialBook::TryCreate().value(); - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); - - // Should still work - ASSERT_EQ(deserialize_result.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); - - // Call again with a lower number, should have no effect. books 2 and 3 should - // still work. - nearby_protocol::GlobalConfig::SetNumShards(1); - auto deserialize_result_2 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, - book2); - ASSERT_EQ(deserialize_result_2.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); - auto deserialize_result_3 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, - book3); - ASSERT_EQ(deserialize_result_3.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); -} - -TEST(NpFfiGlobalConfigTests, TestSetMaxShardsSmall) { - nearby_protocol::GlobalConfig::SetNumShards(1); - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - - // should still be able to parse 2 payloads with only one shard - auto deserialize_result1 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); - ASSERT_EQ(deserialize_result1.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); - auto deserialize_result2 = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); - ASSERT_EQ(deserialize_result2.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); -} - -TEST(NpFfiGlobalConfigTests, TestSetMaxCredBooks) { - nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(1); - auto book1_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book1_result.ok()); - - auto book2_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_FALSE(book2_result.ok()); - ASSERT_TRUE(absl::IsResourceExhausted(book2_result.status())); -} - -TEST(NpFfiGlobalConfigTests, TestSetMaxCredBooksAfterFirstCall) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto book2 = nearby_protocol::CredentialBook::TryCreate().value(); - auto book3 = nearby_protocol::CredentialBook::TryCreate().value(); - - // setting this after books have already been created should have no affect - nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(1); - auto book4_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book4_result.ok()); -} - -TEST(NpFfiGlobalConfigTests, TestSetMaxV0Advs) { - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1); - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); - - { - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, 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()); - ASSERT_EQ(deserialize_result2.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::Error); - } - - // Now that the first v0 adv is out of scope, it will be de-allocated which - // will create room for one more to be created. - auto deserialize_result3 = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); - ASSERT_EQ(deserialize_result3.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V0); -} - -TEST(NpFfiGlobalConfigTests, TestSetMaxV1Advs) { - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1); - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); - - { - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V1); - - // Going over max amount should result in error - auto deserialize_result2 = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result2.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::Error); - } - - // Now that the first v1 adv is out of scope, it will be de-allocated which - // will create room for one more to be created. - auto deserialize_result3 = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result3.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V1); -} - -// Same test case as above, but verifies that the de-allocation still succeeds -// after calling IntoV1() and that no double frees occur. -TEST(NpFfiGlobalConfigTests, TestSetMaxV1AdvsFreeAfterInto) { - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1); - auto book_result = nearby_protocol::CredentialBook::TryCreate(); - ASSERT_TRUE(book_result.ok()); - - { - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V1); - - // Going over max amount should result in error - auto deserialize_result2 = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result2.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::Error); - - // Calling IntoV1() should move the underlying resources into the v0 object - // when both go out of scope only one should be freed - auto v0_adv = deserialize_result.IntoV1(); - } - - // Now that the first v1 adv is out of scope, it will be de-allocated which - // will create room for one more to be created. - auto deserialize_result3 = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V1AdvSimple, book_result.value()); - ASSERT_EQ(deserialize_result3.GetKind(), - np_ffi::internal::DeserializeAdvertisementResultKind::V1); -}
\ No newline at end of file diff --git a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.cc index 3be7db3..52049e7 100644 --- a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs +++ b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.cc @@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for AES-GCM-SIV. +#include "np_cpp_test.h" -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]> {} +bool NpCppTest::panic_handler_set = false;
\ No newline at end of file diff --git a/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h new file mode 100644 index 0000000..1787c35 --- /dev/null +++ b/nearby/presence/np_cpp_ffi/tests/np_cpp_test.h @@ -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. + +#ifndef NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_ +#define NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_ + +#include "nearby_protocol.h" +#include "shared_test_util.h" + +#include "gtest/gtest.h" + +class NpCppTest : public testing::Test { +protected: + static void SetUpTestSuite() { + if (!panic_handler_set) { + ASSERT_TRUE( + nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); + panic_handler_set = true; + nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(2); + nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(2); + nearby_protocol::GlobalConfig::SetMaxNumCredentialSlabs(3); + nearby_protocol::GlobalConfig::SetMaxNumCredentialBooks(2); + } else { + ASSERT_FALSE( + nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); + } + } + static bool panic_handler_set; +}; + +#endif // NEARBY_PRESENCE_NP_CPP_FFI_TESTS_NP_CPP_TEST_H_ diff --git a/nearby/presence/np_cpp_ffi/tests/v0_private_identity_tests.cc b/nearby/presence/np_cpp_ffi/tests/v0_private_identity_tests.cc new file mode 100644 index 0000000..e2a60f2 --- /dev/null +++ b/nearby/presence/np_cpp_ffi/tests/v0_private_identity_tests.cc @@ -0,0 +1,211 @@ +// 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. + +#include "nearby_protocol.h" +#include "np_cpp_test.h" +#include "gtest/gtest.h" + +#include <algorithm> + +static nearby_protocol::RawAdvertisementPayload + V0AdvPrivateIdentity(nearby_protocol::ByteBuffer<255>( + {20, + { + 0x00, // Adv Header + 0x21, // private DE w/ a 2 byte payload + 0x22, 0x22, // salt + 0x85, 0xBF, 0xA8, // encrypted de contents, Tx Power with value 3 + 0x83, 0x58, 0x7C, 0x50, 0xCF, 0x98, 0x38, + 0xA7, 0x8A, 0xC0, 0x1C, 0x96, 0xF9, + }})); + +static uint8_t encrypted_metadata[] = { + 0x26, 0xC5, 0xEA, 0xD4, 0xED, 0x58, 0xF8, 0xFC, 0xE8, 0xF4, 0xAB, 0x0C, + 0x93, 0x2B, 0x75, 0xAA, 0x74, 0x39, 0x67, 0xDB, 0x1E, 0xF2, 0x33, 0xB5, + 0x43, 0xCC, 0x94, 0xAA, 0xA3, 0xBB, 0xB9, 0x4C, 0xBF, 0x57, 0x77, 0xD0, + 0x43, 0x0C, 0x7F, 0xF7, 0x36, 0x03, 0x29, 0xE0, 0x57, 0xBA, 0x97, 0x7F, + 0xF2, 0xD1, 0x51, 0xDB, 0xC9, 0x01, 0x47, 0xE7, 0x48, 0x36, +}; + +static std::array<uint8_t, 32> legacy_metadata_key_hmac = { + 0x88, 0x33, 0xDE, 0xD5, 0x4D, 0x00, 0x92, 0xE8, 0x80, 0x70, 0xD5, + 0x1F, 0x18, 0xEC, 0x22, 0x45, 0x75, 0x7C, 0x24, 0xDF, 0xE3, 0x8C, + 0xB2, 0xDE, 0x77, 0xB6, 0x78, 0x85, 0xFC, 0xA5, 0x67, 0x4D, +}; + +// The canned data in this test was taken from np_adv/tests/examples_v0.rs +TEST_F(NpCppTest, V0PrivateIdentitySimpleCase) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + std::span<uint8_t> metadata_span(encrypted_metadata); + nearby_protocol::MatchedCredentialData match_data(123, metadata_span); + + std::array<uint8_t, 32> key_seed = {}; + std::fill_n(key_seed.begin(), 32, 0x11); + + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + + auto add_result = slab_result->AddV0Credential(v0_cred); + ASSERT_EQ(add_result, absl::OkStatus()); + + auto book_result = + nearby_protocol::CredentialBook::TryCreateFromSlab(*slab_result); + ASSERT_TRUE(book_result.ok()); + + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvPrivateIdentity, *book_result); + ASSERT_EQ(deserialize_result.GetKind(), + nearby_protocol::DeserializeAdvertisementResultKind::V0); + + auto v0_adv = deserialize_result.IntoV0(); + auto kind = v0_adv.GetKind(); + ASSERT_EQ(kind, nearby_protocol::DeserializedV0AdvertisementKind::Legible); + + auto legible_adv = v0_adv.IntoLegible(); + auto identity_kind = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity_kind, + nearby_protocol::DeserializedV0IdentityKind::Decrypted); + ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1); + + auto payload = legible_adv.IntoPayload(); + auto de = payload.TryGetDataElement(0); + ASSERT_TRUE(de.ok()); + + auto metadata = payload.DecryptMetadata(); + ASSERT_TRUE(metadata.ok()); + ASSERT_EQ(std::string("{\"name\":\"Alice\",\"email\":\"alice@gmail.com\"}"), + std::string(metadata->begin(), metadata->end())); + + auto identity_details = payload.GetIdentityDetails(); + ASSERT_TRUE(identity_details.ok()); + ASSERT_EQ(identity_details->cred_id, 123); + ASSERT_EQ(identity_details->identity_type, + nearby_protocol::EncryptedIdentityType::Private); + + auto de_type = de->GetKind(); + ASSERT_EQ(de_type, nearby_protocol::V0DataElementKind::TxPower); + + auto tx_power_de = de->AsTxPower(); + ASSERT_EQ(tx_power_de.tx_power, 3); +} + +static nearby_protocol::CredentialBook CreateEmptyCredBook() { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + return book; +} + +TEST_F(NpCppTest, V0PrivateIdentityEmptyBook) { + auto book = CreateEmptyCredBook(); + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvPrivateIdentity, book); + ASSERT_EQ(deserialize_result.GetKind(), + nearby_protocol::DeserializeAdvertisementResultKind::V0); + + auto v0_adv = deserialize_result.IntoV0(); + ASSERT_EQ( + v0_adv.GetKind(), + nearby_protocol::DeserializedV0AdvertisementKind::NoMatchingCredentials); + + // Should not be able to actually access contents + ASSERT_DEATH([[maybe_unused]] auto failure = v0_adv.IntoLegible(), ""); +} + +TEST_F(NpCppTest, V0PrivateIdentityNoMatchingCreds) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + uint8_t metadata[] = {0}; + std::span<uint8_t> metadata_span(metadata); + nearby_protocol::MatchedCredentialData match_data(123, metadata_span); + + // A randomly picked key seed, does NOT match what was used for the canned adv + std::array<uint8_t, 32> key_seed = {}; + std::fill_n(key_seed.begin(), 31, 0x11); + + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + + auto add_result = slab_result->AddV0Credential(v0_cred); + ASSERT_EQ(add_result, absl::OkStatus()); + + auto book_result = + nearby_protocol::CredentialBook::TryCreateFromSlab(*slab_result); + ASSERT_TRUE(book_result.ok()); + + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvPrivateIdentity, *book_result); + ASSERT_EQ(deserialize_result.GetKind(), + nearby_protocol::DeserializeAdvertisementResultKind::V0); + + auto v0_adv = deserialize_result.IntoV0(); + ASSERT_EQ( + v0_adv.GetKind(), + nearby_protocol::DeserializedV0AdvertisementKind::NoMatchingCredentials); + + // Should not be able to actually access contents + ASSERT_DEATH([[maybe_unused]] auto failure = v0_adv.IntoLegible(), ""); +} + +// Make sure the correct credential is matched out of multiple provided +TEST_F(NpCppTest, V0PrivateIdentityMultipleCredentials) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + std::span<uint8_t> metadata_span(encrypted_metadata); + std::array<uint8_t, 32> key_seed = {}; + + // Non matching credential + nearby_protocol::MatchedCredentialData match_data(123, metadata_span); + std::fill_n(key_seed.begin(), 32, 0x12); + nearby_protocol::V0MatchableCredential v0_cred( + key_seed, legacy_metadata_key_hmac, match_data); + ASSERT_TRUE(slab.AddV0Credential(v0_cred).ok()); + + // Matching credential + nearby_protocol::MatchedCredentialData match_data2(456, metadata_span); + std::fill_n(key_seed.begin(), 32, 0x11); + nearby_protocol::V0MatchableCredential v0_cred2( + key_seed, legacy_metadata_key_hmac, match_data2); + ASSERT_TRUE(slab.AddV0Credential(v0_cred2).ok()); + + // Non matching credential + nearby_protocol::MatchedCredentialData match_data3(789, metadata_span); + std::fill_n(key_seed.begin(), 32, 0x13); + nearby_protocol::V0MatchableCredential v0_cred3( + key_seed, legacy_metadata_key_hmac, match_data3); + ASSERT_TRUE(slab.AddV0Credential(v0_cred3).ok()); + + auto book = + nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto legible_adv = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvPrivateIdentity, book).IntoV0().IntoLegible(); + ASSERT_EQ(legible_adv.GetIdentityKind(), + nearby_protocol::DeserializedV0IdentityKind::Decrypted); + ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1); + + auto payload = legible_adv.IntoPayload(); + ASSERT_TRUE(payload.TryGetDataElement(0).ok()); + + // Make sure the correct credential matches + auto identity_details = payload.GetIdentityDetails(); + ASSERT_TRUE(identity_details.ok()); + ASSERT_EQ(identity_details->cred_id, 456); + ASSERT_EQ(identity_details->identity_type, + nearby_protocol::EncryptedIdentityType::Private); +} diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc b/nearby/presence/np_cpp_ffi/tests/v0_public_identity_tests.cc index 9781720..3ed2d79 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/v0_public_identity_tests.cc @@ -14,10 +14,24 @@ #include "nearby_protocol.h" #include "shared_test_util.h" - +#include "np_cpp_test.h" #include "gtest/gtest.h" -TEST(NpFfiDeserializeV0Tests, V0SingleDataElementTxPower) { +TEST_F(NpCppTest, InvalidCast) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book); + ASSERT_EQ(deserialize_result.GetKind(), + np_ffi::internal::DeserializeAdvertisementResultKind::V0); + + // Now try to cast the result into the wrong type and verify the process + // aborts + ASSERT_DEATH({ [[maybe_unused]] auto failure = deserialize_result.IntoV1(); }, + ""); +} + +TEST_F(NpCppTest, V0SingleDataElementTxPower) { nearby_protocol::RawAdvertisementPayload adv( nearby_protocol::ByteBuffer<255>({ 4, @@ -26,7 +40,8 @@ TEST(NpFfiDeserializeV0Tests, V0SingleDataElementTxPower) { 0x15, 0x03} // Length 1 Tx Power DE with value 3 })); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -39,8 +54,8 @@ TEST(NpFfiDeserializeV0Tests, V0SingleDataElementTxPower) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -56,7 +71,7 @@ TEST(NpFfiDeserializeV0Tests, V0SingleDataElementTxPower) { ASSERT_EQ(tx_power.tx_power, 3); } -TEST(NpFfiDeserializeV0Tests, V0LengthOneActionsDataElement) { +TEST_F(NpCppTest, V0LengthOneActionsDataElement) { nearby_protocol::RawAdvertisementPayload adv( nearby_protocol::ByteBuffer<255>({ 4, @@ -65,7 +80,9 @@ TEST(NpFfiDeserializeV0Tests, V0LengthOneActionsDataElement) { 0x16, 0x00} // Length 1 Actions DE })); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -78,8 +95,8 @@ TEST(NpFfiDeserializeV0Tests, V0LengthOneActionsDataElement) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -92,10 +109,10 @@ TEST(NpFfiDeserializeV0Tests, V0LengthOneActionsDataElement) { ASSERT_EQ(de.GetKind(), nearby_protocol::V0DataElementKind::Actions); auto actions = de.AsActions(); - ASSERT_EQ(actions.GetAsU32(), 0); + ASSERT_EQ(actions.GetAsU32(), (uint32_t)0); } -TEST(NpFfiDeserializeV0Tests, V0LengthTwoActionsDataElement) { +TEST_F(NpCppTest, V0LengthTwoActionsDataElement) { nearby_protocol::RawAdvertisementPayload adv( nearby_protocol::ByteBuffer<255>({ 5, @@ -104,7 +121,9 @@ TEST(NpFfiDeserializeV0Tests, V0LengthTwoActionsDataElement) { 0x26, 0xD0, 0x46} // Length 2 Actions DE })); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -117,8 +136,8 @@ TEST(NpFfiDeserializeV0Tests, V0LengthTwoActionsDataElement) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -150,7 +169,7 @@ TEST(NpFfiDeserializeV0Tests, V0LengthTwoActionsDataElement) { ASSERT_EQ(actions.GetContextSyncSequenceNumber(), 0xD); } -TEST(NpFfiDeserializeV0Tests, V0MultipleDataElements) { +TEST_F(NpCppTest, V0MultipleDataElements) { nearby_protocol::RawAdvertisementPayload adv(nearby_protocol::ByteBuffer<255>( {7, { @@ -160,7 +179,9 @@ TEST(NpFfiDeserializeV0Tests, V0MultipleDataElements) { 0x26, 0x00, 0x46, // Length 2 Actions }})); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -173,8 +194,8 @@ TEST(NpFfiDeserializeV0Tests, V0MultipleDataElements) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -195,41 +216,28 @@ TEST(NpFfiDeserializeV0Tests, V0MultipleDataElements) { ASSERT_EQ(second_de.GetKind(), nearby_protocol::V0DataElementKind::Actions); auto actions = second_de.AsActions(); - ASSERT_EQ(actions.GetAsU32(), 0x00460000); - ASSERT_EQ(actions.GetContextSyncSequenceNumber(), 0); + ASSERT_EQ(actions.GetAsU32(), (uint32_t)0x00460000); + ASSERT_EQ(actions.GetContextSyncSequenceNumber(), (uint8_t)0); } -TEST(NpFfiDeserializeV0Tests, V0EmptyPayload) { - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, V0EmptyPayload) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( 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); +TEST_F(NpCppTest, TestV0AdvMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); ASSERT_EQ(result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto adv = result.IntoV0(); @@ -253,17 +261,18 @@ TEST(NpFfiDeserializeV0Tests, TestV0AdvMoveConstructor) { ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), ""); } -TEST(NpFfiDeserializeResultTests, TestV0AdvMoveAssignment) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); +TEST_F(NpCppTest, TestV0AdvMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = 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); + auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto adv2 = another_result.IntoV0(); @@ -286,40 +295,53 @@ TEST(NpFfiDeserializeResultTests, TestV0AdvMoveAssignment) { ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetKind(), ""); } -TEST(NpFfiDeserializeV0Tests, V0AdvDestructor) { - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1); - auto book_result = nearby_protocol::CredentialBook::TryCreate(); +static nearby_protocol::DeserializeAdvertisementResult +CreateAdv(nearby_protocol::CredentialBook &book) { + auto adv = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); + return adv; +} + +TEST_F(NpCppTest, V0AdvDestructor) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto book_result = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(book_result.ok()); { - auto deserialize_result = - nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); + auto deserialize_result = CreateAdv(book_result.value()); + auto deserialize_result2 = CreateAdv(book_result.value()); + // Deserialize 2 advertisements, which will take up 2 slots in the handle + // map ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); + ASSERT_EQ(deserialize_result2.GetKind(), + np_ffi::internal::DeserializeAdvertisementResultKind::V0); // Going over max amount should result in error - auto deserialize_result2 = + auto deserialize_result3 = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); - ASSERT_EQ(deserialize_result2.GetKind(), + V0AdvSimple, book_result.value()); + ASSERT_EQ(deserialize_result3.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::Error); - // Calling IntoV0() should move the underlying resources into the v0 object - // when both go out of scope only one should be freed + // Calling IntoV0() should move the underlying resources into the v0 + // object when both go out of scope only one should be freed auto v0_adv = deserialize_result.IntoV0(); } // Now that the first v0 adv is out of scope, it should be de-allocated which // will create room for one more to be created. - auto deserialize_result3 = + auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( - V0AdvEmpty, book_result.value()); - ASSERT_EQ(deserialize_result3.GetKind(), + V0AdvSimple, book_result.value()); + ASSERT_EQ(deserialize_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); } -TEST(NpFfiDeserializeV0Tests, V0AdvUseAfterMove) { - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, V0AdvUseAfterMove) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -338,24 +360,25 @@ TEST(NpFfiDeserializeV0Tests, V0AdvUseAfterMove) { ASSERT_DEATH([[maybe_unused]] auto failure = v0_adv.IntoLegible(), ""); } -TEST(NpFfiDeserializeV0Tests, TestLegibleAdvMoveConstructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); +TEST_F(NpCppTest, TestLegibleAdvMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = 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.GetIdentity().GetKind(), + ASSERT_EQ(moved.GetNumberOfDataElements(), 1); + ASSERT_EQ(moved.GetIdentityKind(), np_ffi::internal::DeserializedV0IdentityKind::Plaintext); // trying to use the moved object should result in a use after free which // triggers an abort ASSERT_DEATH([[maybe_unused]] auto failure = - legible.GetIdentity(), // NOLINT(bugprone-use-after-move + legible.GetIdentityKind(), // NOLINT(bugprone-use-after-move ""); ASSERT_DEATH( [[maybe_unused]] auto failure = legible.GetNumberOfDataElements(), ""); @@ -365,37 +388,38 @@ TEST(NpFfiDeserializeV0Tests, TestLegibleAdvMoveConstructor) { // abort nearby_protocol::LegibleDeserializedV0Advertisement moved_again( std::move(legible)); - ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentity(), ""); + ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentityKind(), ""); ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetNumberOfDataElements(), ""); ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.IntoPayload(), ""); } -TEST(NpFfiDeserializeResultTests, TestLegibleAdvMoveAssignment) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - auto result = - nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book); +TEST_F(NpCppTest, TestLegibleAdvMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); + auto result = 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); + auto another_result = nearby_protocol::Deserializer::DeserializeAdvertisement( + V0AdvSimple, book); ASSERT_EQ(another_result.GetKind(), np_ffi::internal::DeserializeAdvertisementResultKind::V0); auto legible2 = another_result.IntoV0().IntoLegible(); // move adv2 into adv, the original should be deallocated by assignment legible2 = std::move(legible); - ASSERT_EQ(legible2.GetIdentity().GetKind(), + ASSERT_EQ(legible2.GetIdentityKind(), np_ffi::internal::DeserializedV0IdentityKind::Plaintext); // original result should now be invalid, using it will trigger a use after // free abort. ASSERT_DEATH([[maybe_unused]] auto failure = - legible.GetIdentity(), // NOLINT(bugprone-use-after-move) + legible.GetIdentityKind(), // NOLINT(bugprone-use-after-move) ""); ASSERT_DEATH( [[maybe_unused]] auto failure = legible.GetNumberOfDataElements(), ""); @@ -404,7 +428,7 @@ TEST(NpFfiDeserializeResultTests, TestLegibleAdvMoveAssignment) { // moving again should still lead to an error auto moved_again = std::move(legible); ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.IntoPayload(), ""); - ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentity(), ""); + ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetIdentityKind(), ""); ASSERT_DEATH([[maybe_unused]] auto failure = moved_again.GetNumberOfDataElements(), ""); @@ -418,36 +442,41 @@ CreateLegibleAdv(nearby_protocol::CredentialBook &book) { return v0_adv.IntoLegible(); } -TEST(NpFfiDeserializeV0Tests, V0LegibleAdvUseAfterMove) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, V0LegibleAdvUseAfterMove) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); auto legible_adv = CreateLegibleAdv(book); // Should be able to use the valid legible adv even though its original parent // is now out of scope. - ASSERT_EQ(legible_adv.GetIdentity().GetKind(), + ASSERT_EQ(legible_adv.GetIdentityKind(), nearby_protocol::DeserializedV0IdentityKind::Plaintext); ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1); [[maybe_unused]] auto payload = legible_adv.IntoPayload(); // now that the legible adv has moved into the payload it should no longer be // valid - ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.GetIdentity(), ""); + ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.GetIdentityKind(), ""); ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.GetNumberOfDataElements(), ""); ASSERT_DEATH([[maybe_unused]] auto failure = legible_adv.IntoPayload(), ""); } -TEST(NpFfiDeserializeV0Tests, LegibleAdvDestructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1); +TEST_F(NpCppTest, LegibleAdvDestructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); { auto legible_adv = CreateLegibleAdv(book); + auto legible_adv2 = CreateLegibleAdv(book); - // check that legible adv is valid. - ASSERT_EQ(legible_adv.GetIdentity().GetKind(), + // check that legible advs are valid. + ASSERT_EQ(legible_adv.GetIdentityKind(), nearby_protocol::DeserializedV0IdentityKind::Plaintext); ASSERT_EQ(legible_adv.GetNumberOfDataElements(), 1); + ASSERT_EQ(legible_adv2.GetIdentityKind(), + nearby_protocol::DeserializedV0IdentityKind::Plaintext); + ASSERT_EQ(legible_adv2.GetNumberOfDataElements(), 1); // allocation slots should be full ASSERT_EQ(nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -469,14 +498,16 @@ CreatePayload(nearby_protocol::CredentialBook &book) { return legible_adv.IntoPayload(); } -TEST(NpFfiDeserializeV0Tests, V0PayloadDestructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV0Advertisements(1); +TEST_F(NpCppTest, V0PayloadDestructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); { auto payload = CreatePayload(book); + auto payload2 = CreatePayload(book); // check that payload adv is valid even though its parent is out of scope ASSERT_TRUE(payload.TryGetDataElement(0).ok()); + ASSERT_TRUE(payload2.TryGetDataElement(0).ok()); // allocation slots should be full ASSERT_EQ(nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -493,8 +524,9 @@ TEST(NpFfiDeserializeV0Tests, V0PayloadDestructor) { nearby_protocol::DeserializeAdvertisementResultKind::V0); } -TEST(NpFfiDeserializeV0Tests, TestV0PayloadMoveConstructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, TestV0PayloadMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( V0AdvSimple, book); ASSERT_EQ(result.GetKind(), @@ -519,8 +551,9 @@ TEST(NpFfiDeserializeV0Tests, TestV0PayloadMoveConstructor) { ""); } -TEST(NpFfiDeserializeResultTests, TestV0PayloadMoveAssignment) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, TestV0PayloadMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( V0AdvSimple, book); ASSERT_EQ(result.GetKind(), @@ -550,11 +583,10 @@ TEST(NpFfiDeserializeResultTests, TestV0PayloadMoveAssignment) { ""); } -TEST(NpFfiDeserializeV0Tests, InvalidDataElementCast) { - ASSERT_TRUE( - nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler)); - - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, InvalidDataElementCast) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -566,8 +598,8 @@ TEST(NpFfiDeserializeV0Tests, InvalidDataElementCast) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); @@ -582,8 +614,10 @@ TEST(NpFfiDeserializeV0Tests, InvalidDataElementCast) { ASSERT_DEATH([[maybe_unused]] auto failure = de.AsActions();, ""); } -TEST(NpFfiDeserializeV0Tests, InvalidDataElementIndex) { - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, InvalidDataElementIndex) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = nearby_protocol::Deserializer::DeserializeAdvertisement( @@ -596,8 +630,8 @@ TEST(NpFfiDeserializeV0Tests, InvalidDataElementIndex) { ASSERT_EQ(v0_adv.GetKind(), nearby_protocol::DeserializedV0AdvertisementKind::Legible); auto legible_adv = v0_adv.IntoLegible(); - auto identity = legible_adv.GetIdentity(); - ASSERT_EQ(identity.GetKind(), + auto identity = legible_adv.GetIdentityKind(); + ASSERT_EQ(identity, nearby_protocol::DeserializedV0IdentityKind::Plaintext); auto num_des = legible_adv.GetNumberOfDataElements(); diff --git a/nearby/presence/np_cpp_ffi/tests/v1_private_identity_tests.cc b/nearby/presence/np_cpp_ffi/tests/v1_private_identity_tests.cc new file mode 100644 index 0000000..8cd0bd0 --- /dev/null +++ b/nearby/presence/np_cpp_ffi/tests/v1_private_identity_tests.cc @@ -0,0 +1,130 @@ +// 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. + +#include "nearby_protocol.h" +#include "np_cpp_test.h" +#include "gtest/gtest.h" +#include <algorithm> + +// All canned data output from np_adv/tests/examples_v1.rs +static nearby_protocol::RawAdvertisementPayload + V1AdvPrivateIdentity(nearby_protocol::ByteBuffer<255>( + {105, + { + 0x20, 0x67, 0x91, 0x10, 0x08, 0xAD, 0x69, 0x46, 0x04, 0x5D, 0xAE, + 0x6D, 0xB7, 0xF7, 0x5C, 0xD3, 0xB8, 0xAC, 0xF0, 0xBF, 0x75, 0x90, + 0x01, 0xBE, 0x73, 0x33, 0xA4, 0x76, 0x84, 0x4A, 0x09, 0x0F, 0x2B, + 0x99, 0x47, 0xDF, 0x8B, 0x46, 0xCA, 0x16, 0xCE, 0x13, 0xB5, 0x6E, + 0x53, 0xAE, 0x28, 0x56, 0x44, 0x0E, 0xA6, 0x8D, 0xEB, 0xA1, 0x11, + 0xAF, 0x4E, 0x1B, 0xE0, 0x8E, 0xF5, 0xBA, 0x90, 0x4F, 0x2E, 0x94, + 0xFC, 0xDE, 0xA6, 0x7F, 0x5D, 0xC8, 0x37, 0xB7, 0xEF, 0xCA, 0xAC, + 0x8B, 0x9F, 0x1B, 0xD4, 0xC6, 0x11, 0x85, 0xD3, 0x67, 0x39, 0x32, + 0xD1, 0x82, 0xCA, 0x4E, 0xB9, 0x46, 0x03, 0x83, 0x68, 0x56, 0x0B, + 0xCC, 0xFD, 0x7A, 0x2A, 0xBE, 0x07, + }})); + +static std::array<uint8_t, 32> key_seed = { + 0x31, 0x43, 0x63, 0x1E, 0xCA, 0xE8, 0x97, 0x4B, 0x96, 0x50, 0xCC, + 0x1C, 0x48, 0x25, 0x0E, 0x81, 0x58, 0x06, 0x81, 0x51, 0xF9, 0xEB, + 0x25, 0x23, 0x03, 0xD4, 0x97, 0x6D, 0x95, 0x19, 0x91, 0x39, +}; + +static std::array<uint8_t, 32> expected_unsigned_metadata_key_hmac = {0}; + +static std::array<uint8_t, 32> expected_signed_metadata_key_hmac = { + 0x1C, 0xBC, 0xEB, 0xDC, 0x17, 0xB5, 0x91, 0xE5, 0x07, 0x9D, 0x70, + 0xC1, 0xE8, 0x4B, 0xCC, 0xDB, 0x4B, 0x0F, 0x76, 0x83, 0x59, 0x62, + 0x0A, 0x2D, 0x55, 0x0B, 0x3B, 0x36, 0xA4, 0x92, 0x8B, 0x13, +}; + +static std::array<uint8_t, 32> public_key = { + 0x6D, 0x0D, 0xB6, 0x09, 0x10, 0xB1, 0x4E, 0xC4, 0x7E, 0x10, 0x16, + 0x14, 0x9C, 0x9F, 0xF2, 0x14, 0x0F, 0xEC, 0x53, 0x76, 0xE3, 0x07, + 0xD9, 0xD3, 0x9E, 0xAE, 0xE7, 0x45, 0x2C, 0x03, 0xEC, 0x6D, +}; + +static uint8_t encrypted_metadata[] = { + 0x09, 0xB8, 0xC6, 0x6B, 0x71, 0x43, 0x55, 0x4C, 0xB9, 0x9D, 0xBF, + 0xE4, 0xAF, 0x3E, 0xA2, 0x56, 0x0E, 0x6C, 0xBC, 0xDC, 0x3F, 0x3F, + 0x0D, 0x28, 0xD4, 0x50, 0xA9, 0xEA, 0xC3, 0x60, 0xB0, 0x81, 0x31, + 0xE2, 0x67, 0xB5, 0xC8, 0x15, 0x0C, 0xCA, 0x0B, 0x9B, 0x2C, 0x80, + 0xC1, 0xB1, 0xF6, 0x5F, 0xE1, 0x51, 0xF9, 0xE2, 0x23, 0x56, 0xD4, + 0x0B, 0x89, 0xA7, 0xF3, 0x4D, 0xE8, 0x79, 0x26, 0x44, 0x7E, 0x62, + 0xDE, 0x53, 0x13, 0x15, 0x3D, 0xFC, 0x04, 0x2E, 0x2D, 0x08, 0x43, + 0x2E, 0xE1, 0x96, 0xE8, 0x0F, 0xD0, 0xFC, 0xDE, 0x03, 0x86, 0x23, + 0xB6, 0x98, 0x85, 0x27, 0x67, 0xD8, 0x1D, 0xC3, 0xE2, 0xE0, 0xA4, + 0x32, 0x1A, 0x5F, 0x51, 0x0B, 0xA8, 0xD8, 0xA7, 0x23, 0xA4, 0x57, +}; + +TEST_F(NpCppTest, V1PrivateIdentitySimpleCase) { + auto slab_result = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(slab_result.ok()); + + std::span<uint8_t> metadata_span(encrypted_metadata); + nearby_protocol::MatchedCredentialData match_data(123, metadata_span); + + nearby_protocol::V1MatchableCredential v1_cred( + key_seed, expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, public_key, match_data); + + auto add_result = slab_result->AddV1Credential(v1_cred); + ASSERT_EQ(add_result, absl::OkStatus()); + + auto book_result = + nearby_protocol::CredentialBook::TryCreateFromSlab(*slab_result); + ASSERT_TRUE(book_result.ok()); + + auto deserialize_result = + nearby_protocol::Deserializer::DeserializeAdvertisement( + V1AdvPrivateIdentity, *book_result); + ASSERT_EQ(deserialize_result.GetKind(), + nearby_protocol::DeserializeAdvertisementResultKind::V1); + + auto v1_adv = deserialize_result.IntoV1(); + ASSERT_EQ(v1_adv.GetNumUndecryptableSections(), 0); + ASSERT_EQ(v1_adv.GetNumLegibleSections(), 1); + + auto section = v1_adv.TryGetSection(0); + ASSERT_TRUE(section.ok()); + ASSERT_EQ(section->GetIdentityKind(), + nearby_protocol::DeserializedV1IdentityKind::Decrypted); + ASSERT_EQ(section->NumberOfDataElements(), 1); + + auto metadata = section->DecryptMetadata(); + ASSERT_TRUE(metadata.ok()); + ASSERT_EQ( + std::string("{\"uuid\":\"378845e1-2616-420d-86f5-674177a7504d\"," + "\"display_name\":\"Alice\",\"location\":\"Wonderland\"}"), + std::string(metadata->begin(), metadata->end())); + + auto identity_details = section->GetIdentityDetails(); + ASSERT_TRUE(identity_details.ok()); + ASSERT_EQ(identity_details->cred_id, 123); + ASSERT_EQ(identity_details->verification_mode, + nearby_protocol::V1VerificationMode::Signature); + ASSERT_EQ(identity_details->identity_type, + nearby_protocol::EncryptedIdentityType::Private); + + auto de = section->TryGetDataElement(0); + ASSERT_TRUE(de.ok()); + ASSERT_EQ(de->GetDataElementTypeCode(), 5); + ASSERT_EQ(de->GetPayload().ToVector(), std::vector<uint8_t>{7}); + + auto offset = de->GetOffset(); + auto derived_salt = section->DeriveSaltForOffset(offset); + ASSERT_TRUE(derived_salt.ok()); + std::array<uint8_t, 16> expected = + {94, 154, 245, 152, 164, 22, 131, 157, 8, 79, 28, 77, 236, 57, 17, 97}; + ASSERT_EQ(*derived_salt, expected); +}
\ No newline at end of file diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc b/nearby/presence/np_cpp_ffi/tests/v1_public_identity_tests.cc index 115ad14..aa32f7b 100644 --- a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc +++ b/nearby/presence/np_cpp_ffi/tests/v1_public_identity_tests.cc @@ -14,11 +14,14 @@ #include "nearby_protocol.h" #include "shared_test_util.h" +#include "np_cpp_test.h" #include "gtest/gtest.h" -TEST(NpFfiDeserializeV1Tests, V1SimpleTestCase) { - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, V1SimpleTestCase) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); auto deserialize_result = @@ -47,7 +50,7 @@ TEST(NpFfiDeserializeV1Tests, V1SimpleTestCase) { auto de = section.value().TryGetDataElement(0); ASSERT_TRUE(de.ok()); - ASSERT_EQ(de.value().GetDataElementTypeCode(), 5); + ASSERT_EQ(de.value().GetDataElementTypeCode(), (uint8_t)5); auto payload = de.value().GetPayload(); auto vec = payload.ToVector(); @@ -55,8 +58,9 @@ TEST(NpFfiDeserializeV1Tests, V1SimpleTestCase) { ASSERT_EQ(vec, expected); } -TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveConstructor) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, TestV1AdvMoveConstructor) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( V1AdvSimple, book); ASSERT_EQ(result.GetKind(), @@ -91,8 +95,9 @@ TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveConstructor) { ""); } -TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveAssignment) { - auto book = nearby_protocol::CredentialBook::TryCreate().value(); +TEST_F(NpCppTest, TestV1AdvMoveAssignment) { + auto slab = nearby_protocol::CredentialSlab::TryCreate().value(); + auto book = nearby_protocol::CredentialBook::TryCreateFromSlab(slab).value(); auto result = nearby_protocol::Deserializer::DeserializeAdvertisement( V1AdvSimple, book); ASSERT_EQ(result.GetKind(), @@ -151,10 +156,10 @@ bool TryDeserializeNewV1Adv(nearby_protocol::CredentialBook &book) { np_ffi::internal::DeserializeAdvertisementResultKind::V1; } -TEST(NpFfiDeserializeV1Tests, TestSectionOwnership) { - // Capping the max number so we can verify that de-allocations are happening - nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1); - auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); +TEST_F(NpCppTest, TestSectionOwnership) { + auto maybe_credential_slab = nearby_protocol::CredentialSlab::TryCreate(); + ASSERT_TRUE(maybe_credential_slab.ok()); + auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreateFromSlab(maybe_credential_slab.value()); ASSERT_TRUE(maybe_credential_book.ok()); { @@ -164,6 +169,12 @@ TEST(NpFfiDeserializeV1Tests, TestSectionOwnership) { ASSERT_EQ(section.NumberOfDataElements(), 1); ASSERT_TRUE(section.TryGetDataElement(0).ok()); + auto section2 = GetSection(maybe_credential_book.value()); + ASSERT_EQ(section2.GetIdentityKind(), + nearby_protocol::DeserializedV1IdentityKind::Plaintext); + ASSERT_EQ(section2.NumberOfDataElements(), 1); + ASSERT_TRUE(section2.TryGetDataElement(0).ok()); + ASSERT_FALSE(TryDeserializeNewV1Adv(maybe_credential_book.value())); } @@ -175,7 +186,7 @@ TEST(NpFfiDeserializeV1Tests, TestSectionOwnership) { /* * Multiple sections are not supported in plaintext advertisements * TODO Update the below test to use encrypted sections -TEST(NpFfiDeserializeV1Tests, V1MultipleSections) { +TEST(NpCppTest, V1MultipleSections) { auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate(); ASSERT_TRUE(maybe_credential_book.ok()); @@ -218,4 +229,4 @@ TEST(NpFfiDeserializeV1Tests, V1MultipleSections) { nearby_protocol::DeserializedV1IdentityKind::Plaintext); ASSERT_EQ(section2.value().NumberOfDataElements(), 1); } -*/
\ No newline at end of file +*/ diff --git a/nearby/presence/np_ed25519/Cargo.toml b/nearby/presence/np_ed25519/Cargo.toml index b4bca66..bd42c65 100644 --- a/nearby/presence/np_ed25519/Cargo.toml +++ b/nearby/presence/np_ed25519/Cargo.toml @@ -4,9 +4,12 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [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..fe2ab5a 100644 --- a/nearby/presence/np_ed25519/src/lib.rs +++ b/nearby/presence/np_ed25519/src/lib.rs @@ -19,15 +19,11 @@ //! or verified. These "context" bytes allow for usage of the //! same base key-pair for different purposes in the protocol. #![no_std] -#![forbid(unsafe_code)] -#![deny(missing_docs, clippy::indexing_slicing)] - -extern crate core; 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 +51,30 @@ pub const MAX_SIGNATURE_BUFFER_LEN: usize = 512; 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 +138,11 @@ pub struct PublicKey<C: CryptoProvider> { } 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 @@ -171,7 +187,8 @@ impl<C: CryptoProvider> PublicKey<C> { impl<C: CryptoProvider> Clone for PublicKey<C> { fn clone(&self) -> Self { - Self::from_bytes(&self.to_bytes()).unwrap() + #[allow(clippy::expect_used)] + Self::from_bytes(&self.to_bytes()).expect("This should always succeed since self will always contain valid public key bytes, which is verified on creation") } } @@ -230,6 +247,7 @@ impl SignatureContext { /// [`SignatureContext#write_length_prefixed`]. fn create_signature_buffer(&self) -> impl Sink<u8> + AsRef<[u8]> { let mut buffer = ArrayVec::<[u8; MAX_SIGNATURE_BUFFER_LEN]>::new(); + #[allow(clippy::expect_used)] self.write_length_prefixed(&mut buffer).expect("Context should always fit into sig buffer"); buffer } diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml index 5be03a6..6c9a6f4 100644 --- a/nearby/presence/np_ffi_core/Cargo.toml +++ b/nearby/presence/np_ffi_core/Cargo.toml @@ -4,14 +4,23 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] array_view.workspace = true -np_adv.workspace = true +ldt_np_adv.workspace = true +np_adv = { workspace = true, features = ["alloc"] } +np_adv_dynamic.workspace = true +np_hkdf.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..5996e80 100644 --- a/nearby/presence/np_ffi_core/src/common.rs +++ b/nearby/presence/np_ffi_core/src/common.rs @@ -14,11 +14,15 @@ //! 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 crypto_provider::{CryptoProvider, CryptoRng}; +use crypto_provider_default::CryptoProviderImpl; use handle_map::HandleNotPresentError; +use lock_adapter::std::{RwLock, RwLockWriteGuard}; +use lock_adapter::RwLock as _; +use std::string::String; -const DEFAULT_MAX_HANDLES: u32 = u32::MAX - 1; +pub(crate) const DEFAULT_MAX_HANDLES: u32 = u32::MAX - 1; /// Configuration for top-level constants to be used /// by the rest of the FFI which are independent of @@ -38,6 +42,11 @@ pub struct CommonConfig { /// - In all other cases, 16 shards will be used by default. num_shards: u8, + /// The maximum number of credential slabs which may be active + /// at any one time. By default, this value will be set to + /// `u32::MAX - 1`, which is the upper-bound on this value. + max_num_credential_slabs: u32, + /// The maximum number of credential books which may be active /// at any one time. By default, this value will be set to /// `u32::MAX - 1`, which is the upper-bound on this value. @@ -54,6 +63,12 @@ pub struct CommonConfig { /// value will be set to `u32::MAX - 1`, which is the upper-bound /// on this value. max_num_deserialized_v1_advertisements: u32, + + /// The maximum number of v1 advertisement builders + /// which may be active at any one time. By default, this + /// value will be set to `u32::MAX - 1`, which is the upper-bound + /// on this value. + max_num_v1_advertisement_builders: u32, } impl Default for CommonConfig { @@ -66,9 +81,11 @@ impl CommonConfig { pub(crate) const fn new() -> Self { Self { num_shards: 0, + max_num_credential_slabs: DEFAULT_MAX_HANDLES, max_num_credential_books: DEFAULT_MAX_HANDLES, max_num_deserialized_v0_advertisements: DEFAULT_MAX_HANDLES, max_num_deserialized_v1_advertisements: DEFAULT_MAX_HANDLES, + max_num_v1_advertisement_builders: DEFAULT_MAX_HANDLES, } } #[cfg(feature = "std")] @@ -90,6 +107,9 @@ impl CommonConfig { self.num_shards } } + pub(crate) fn max_num_credential_slabs(&self) -> u32 { + self.max_num_credential_slabs + } pub(crate) fn max_num_credential_books(&self) -> u32 { self.max_num_credential_books } @@ -99,6 +119,9 @@ impl CommonConfig { pub(crate) fn max_num_deserialized_v1_advertisements(&self) -> u32 { self.max_num_deserialized_v1_advertisements } + pub(crate) fn max_num_v1_advertisement_builders(&self) -> u32 { + self.max_num_v1_advertisement_builders + } pub(crate) fn set_num_shards(&mut self, num_shards: u8) { self.num_shards = num_shards } @@ -110,6 +133,13 @@ impl CommonConfig { self.max_num_credential_books = DEFAULT_MAX_HANDLES.min(max_num_credential_books) } + /// Sets the maximum number of active handles to credential-slabs + /// which may be active at any one time. + /// Max value: `u32::MAX - 1`. + pub fn set_max_num_credential_slabs(&mut self, max_num_credential_slabs: u32) { + self.max_num_credential_slabs = DEFAULT_MAX_HANDLES.min(max_num_credential_slabs) + } + /// Sets the maximum number of active handles to deserialized v0 /// advertisements which may be active at any one time. /// Max value: `u32::MAX - 1`. @@ -121,7 +151,7 @@ impl CommonConfig { DEFAULT_MAX_HANDLES.min(max_num_deserialized_v0_advertisements) } - /// Sets the maximum number of active handles to deserialized v0 + /// Sets the maximum number of active handles to deserialized v1 /// advertisements which may be active at any one time. /// Max value: `u32::MAX - 1`. pub fn set_max_num_deserialized_v1_advertisements( @@ -131,13 +161,26 @@ impl CommonConfig { self.max_num_deserialized_v1_advertisements = DEFAULT_MAX_HANDLES.min(max_num_deserialized_v1_advertisements) } + /// Sets the maximum number of active handles to v1 advertisement + /// builders which may be active at any one time. + /// Max value: `u32::MAX - 1`. + pub fn set_max_num_v1_advertisement_builders( + &mut self, + max_num_v1_advertisement_builders: u32, + ) { + self.max_num_v1_advertisement_builders = + DEFAULT_MAX_HANDLES.min(max_num_v1_advertisement_builders) + } } -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() } +pub(crate) fn global_max_num_credential_slabs() -> u32 { + COMMON_CONFIG.read().max_num_credential_slabs() +} pub(crate) fn global_max_num_credential_books() -> u32 { COMMON_CONFIG.read().max_num_credential_books() } @@ -147,6 +190,9 @@ pub(crate) fn global_max_num_deserialized_v0_advertisements() -> u32 { pub(crate) fn global_max_num_deserialized_v1_advertisements() -> u32 { COMMON_CONFIG.read().max_num_deserialized_v1_advertisements() } +pub(crate) fn global_max_num_v1_advertisement_builders() -> u32 { + COMMON_CONFIG.read().max_num_v1_advertisement_builders() +} /// Sets an override to the number of shards to employ in the NP FFI's /// internal handle-maps, which places an upper bound on the number @@ -167,6 +213,17 @@ pub fn global_config_set_num_shards(num_shards: u8) { config.set_num_shards(num_shards); } +/// Sets the maximum number of active handles to credential slabs +/// which may be active at any one time. Max value: `u32::MAX - 1`. +/// +/// Setting this value will have no effect if the handle-maps for the +/// API have already begun being used by the client code, and any +/// values set will take effect upon the first usage of any API +/// call utilizing credential slabs. +pub fn global_config_set_max_num_credential_slabs(max_num_credential_slabs: u32) { + let mut config = COMMON_CONFIG.write(); + config.set_max_num_credential_slabs(max_num_credential_slabs); +} /// Sets the maximum number of active handles to credential books /// which may be active at any one time. Max value: `u32::MAX - 1`. /// @@ -239,8 +296,12 @@ pub struct RawAdvertisementPayload { impl RawAdvertisementPayload { /// Yields a slice of the bytes in this raw advertisement payload. + #[allow(clippy::unwrap_used)] pub fn as_slice(&self) -> &[u8] { - self.bytes.as_slice() + // The unwrapping here will never trigger a panic, + // because the byte-buffer is 255 bytes, the byte-length + // of which is the maximum value storable in a u8. + self.bytes.as_slice().unwrap() } } @@ -257,6 +318,21 @@ pub struct ByteBuffer<const N: usize> { bytes: [u8; N], } +/// A FFI safe wrapper of a fixed size array +#[repr(C)] +pub struct FixedSizeArray<const N: usize>([u8; N]); + +impl<const N: usize> FixedSizeArray<N> { + /// Constructs a byte-buffer from a Rust-side-derived owned array + pub(crate) fn from_array(bytes: [u8; N]) -> Self { + Self(bytes) + } + /// Yields a slice of the bytes + pub fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } +} + impl<const N: usize> ByteBuffer<N> { /// Constructs a byte-buffer from a Rust-side-derived /// ArrayView, which is assumed to be trusted to be @@ -268,8 +344,73 @@ impl<const N: usize> ByteBuffer<N> { Self { len, bytes } } /// Yields a slice of the first `self.len` bytes of `self.bytes`. - pub fn as_slice(&self) -> &[u8] { - &self.bytes[..(self.len as usize)] + pub fn as_slice(&self) -> Option<&[u8]> { + if self.len as usize <= N { + Some(&self.bytes[..(self.len as usize)]) + } else { + None + } + } +} + +pub(crate) type CryptoRngImpl = <CryptoProviderImpl as CryptoProvider>::CryptoRng; + +pub(crate) struct LazyInitCryptoRng { + maybe_rng: Option<CryptoRngImpl>, +} + +impl LazyInitCryptoRng { + const fn new() -> Self { + Self { maybe_rng: None } + } + pub(crate) fn get_rng(&mut self) -> &mut CryptoRngImpl { + self.maybe_rng.get_or_insert_with(CryptoRngImpl::new) + } +} + +/// Shared, lazily-initialized cryptographically-secure +/// RNG for all operations in the FFI core. +static CRYPTO_RNG: RwLock<LazyInitCryptoRng> = RwLock::new(LazyInitCryptoRng::new()); + +/// Gets a write guard to the (lazily-init) library-global crypto rng. +pub(crate) fn get_global_crypto_rng() -> RwLockWriteGuard<'static, LazyInitCryptoRng> { + CRYPTO_RNG.write() +} + +/// The DE type for an encrypted identity +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum EncryptedIdentityType { + /// Identity for broadcasts to nearby devices with the same + /// logged-in-account (for some account). + Private = 1, + /// Identity for broadcasts to nearby devices which this + /// device has declared to trust. + Trusted = 2, + /// Identity for broadcasts to devices which have been provisioned + /// offline with this device. + Provisioned = 4, +} + +impl From<EncryptedIdentityType> for np_adv::de_type::EncryptedIdentityDataElementType { + fn from(val: EncryptedIdentityType) -> np_adv::de_type::EncryptedIdentityDataElementType { + use np_adv::de_type::EncryptedIdentityDataElementType; + match val { + EncryptedIdentityType::Private => EncryptedIdentityDataElementType::Private, + EncryptedIdentityType::Trusted => EncryptedIdentityDataElementType::Trusted, + EncryptedIdentityType::Provisioned => EncryptedIdentityDataElementType::Provisioned, + } + } +} + +impl From<np_adv::de_type::EncryptedIdentityDataElementType> for EncryptedIdentityType { + fn from(value: np_adv::de_type::EncryptedIdentityDataElementType) -> Self { + use np_adv::de_type::EncryptedIdentityDataElementType; + match value { + EncryptedIdentityDataElementType::Private => Self::Private, + EncryptedIdentityDataElementType::Trusted => Self::Trusted, + EncryptedIdentityDataElementType::Provisioned => Self::Provisioned, + } } } diff --git a/nearby/presence/np_ffi_core/src/credentials.rs b/nearby/presence/np_ffi_core/src/credentials.rs index 5e945dd..c1fc808 100644 --- a/nearby/presence/np_ffi_core/src/credentials.rs +++ b/nearby/presence/np_ffi_core/src/credentials.rs @@ -14,13 +14,282 @@ //! Credential-related data-types and functions use crate::common::*; -use crate::utils::FfiEnum; -use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use crate::utils::{FfiEnum, LocksLongerThan}; +use crypto_provider_default::CryptoProviderImpl; +use handle_map::{ + declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError, + HandleMapTryAllocateError, +}; +use std::sync::Arc; + +/// Cryptographic information about a particular V0 discovery credential +/// necessary to match and decrypt encrypted V0 advertisements. +#[repr(C)] +pub struct V0DiscoveryCredential { + key_seed: [u8; 32], + legacy_metadata_key_hmac: [u8; 32], +} + +impl V0DiscoveryCredential { + /// Constructs a new V0 discovery credential with the given 32-byte key-seed + /// and the given 32-byte HMAC for the (14-byte) legacy metadata key. + pub fn new(key_seed: [u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self { + Self { key_seed, legacy_metadata_key_hmac } + } + fn into_internal(self) -> np_adv::credential::v0::V0DiscoveryCredential { + np_adv::credential::v0::V0DiscoveryCredential::new( + self.key_seed, + self.legacy_metadata_key_hmac, + ) + } +} + +/// Cryptographic information about a particular V1 discovery credential +/// necessary to match and decrypt encrypted V1 advertisement sections. +#[repr(C)] +pub struct V1DiscoveryCredential { + key_seed: [u8; 32], + expected_unsigned_metadata_key_hmac: [u8; 32], + expected_signed_metadata_key_hmac: [u8; 32], + pub_key: [u8; 32], +} + +impl V1DiscoveryCredential { + /// Constructs a new V1 discovery credential with the given 32-byte key-seed, + /// unsigned-variant HMAC of the metadata key, the signed-variant HMAC of + /// the metadata key, and the given public key for signature verification. + pub fn new( + key_seed: [u8; 32], + expected_unsigned_metadata_key_hmac: [u8; 32], + expected_signed_metadata_key_hmac: [u8; 32], + pub_key: [u8; 32], + ) -> Self { + Self { + key_seed, + expected_unsigned_metadata_key_hmac, + expected_signed_metadata_key_hmac, + pub_key, + } + } + fn into_internal(self) -> np_adv::credential::v1::V1DiscoveryCredential { + np_adv::credential::v1::V1DiscoveryCredential::new( + self.key_seed, + self.expected_unsigned_metadata_key_hmac, + self.expected_signed_metadata_key_hmac, + self.pub_key, + ) + } +} + +/// A [`MatchedCredential`] implementation for the purpose of +/// capturing match-data details across the FFI boundary. +/// Since we can't know what plaintext match-data the client +/// wants to keep around, we just expose an ID for them to do +/// their own look-up. +/// +/// For the encrypted metadata, we need a slightly richer +/// representation, since we need to be able to decrypt +/// the metadata as part of an API call. Internally, we +/// keep this as an atomic-reference-counted pointer to +/// a byte array, and never expose this raw pointer across +/// the FFI boundary. +#[derive(Debug, Clone)] +pub struct MatchedCredential { + cred_id: u32, + encrypted_metadata_bytes: Arc<[u8]>, +} + +impl MatchedCredential { + /// Constructs a new matched credential from the given match-id + /// (some arbitrary `u32` identifier) and encrypted metadata bytes, + /// copied from the given slice. + pub fn new(cred_id: u32, encrypted_metadata_bytes: &[u8]) -> Self { + let encrypted_metadata_bytes = encrypted_metadata_bytes.to_vec().into_boxed_slice(); + let encrypted_metadata_bytes = Arc::from(encrypted_metadata_bytes); + Self { cred_id, encrypted_metadata_bytes } + } + /// Gets the pre-specified numerical identifier for this matched-credential. + pub(crate) fn id(&self) -> u32 { + self.cred_id + } +} + +impl PartialEq<MatchedCredential> for MatchedCredential { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + +impl Eq for MatchedCredential {} + +impl np_adv::credential::MatchedCredential for MatchedCredential { + type EncryptedMetadata = Arc<[u8]>; + type EncryptedMetadataFetchError = core::convert::Infallible; + fn fetch_encrypted_metadata(&self) -> Result<Arc<[u8]>, core::convert::Infallible> { + Ok(self.encrypted_metadata_bytes.clone()) + } +} + +/// Internals of a credential slab, +/// an intermediate used in the construction +/// of a credential-book. +pub struct CredentialSlabInternals { + v0_creds: + Vec<np_adv::credential::MatchableCredential<np_adv::credential::v0::V0, MatchedCredential>>, + v1_creds: + Vec<np_adv::credential::MatchableCredential<np_adv::credential::v1::V1, MatchedCredential>>, +} + +impl CredentialSlabInternals { + pub(crate) fn new() -> Self { + Self { v0_creds: Vec::new(), v1_creds: Vec::new() } + } + /// Adds the given V0 discovery credential with the given + /// identity match-data onto the end of the V0 credentials + /// currently stored in this slab. + pub(crate) fn add_v0( + &mut self, + discovery_credential: V0DiscoveryCredential, + match_data: MatchedCredential, + ) { + let discovery_credential = discovery_credential.into_internal(); + let matchable_credential = + np_adv::credential::MatchableCredential { discovery_credential, match_data }; + self.v0_creds.push(matchable_credential); + } + /// Adds the given V1 discovery credential with the given + /// identity match-data onto the end of the V1 credentials + /// currently stored in this slab. + pub(crate) fn add_v1( + &mut self, + discovery_credential: V1DiscoveryCredential, + match_data: MatchedCredential, + ) { + let discovery_credential = discovery_credential.into_internal(); + let matchable_credential = + np_adv::credential::MatchableCredential { discovery_credential, match_data }; + self.v1_creds.push(matchable_credential); + } +} + +/// Discriminant for `CreateCredentialSlabResult` +#[repr(u8)] +pub enum CreateCredentialSlabResultKind { + /// There was no space left to create a new credential slab + NoSpaceLeft = 0, + /// We created a new credential slab behind the given handle. + /// The associated payload may be obtained via + /// `CreateCredentialSlabResult#into_success()`. + Success = 1, +} + +/// Result type for `create_credential_slab` +#[repr(C)] +#[allow(missing_docs)] +pub enum CreateCredentialSlabResult { + NoSpaceLeft, + Success(CredentialSlab), +} + +impl From<Result<CredentialSlab, HandleMapFullError>> for CreateCredentialSlabResult { + fn from(result: Result<CredentialSlab, HandleMapFullError>) -> Self { + match result { + Ok(slab) => CreateCredentialSlabResult::Success(slab), + Err(_) => CreateCredentialSlabResult::NoSpaceLeft, + } + } +} + +/// Result type for trying to add a credential to a credential-slab. +#[repr(u8)] +pub enum AddCredentialToSlabResult { + /// We succeeded in adding the credential to the slab. + Success = 0, + /// The handle to the slab was actually invalid. + InvalidHandle = 1, +} + +declare_handle_map! { + mod credential_slab { + #[dimensions = super::get_credential_slab_handle_map_dimensions()] + type CredentialSlab: HandleLike<Object = super::CredentialSlabInternals>; + } +} +use credential_slab::CredentialSlab; + +fn get_credential_slab_handle_map_dimensions() -> HandleMapDimensions { + HandleMapDimensions { + num_shards: global_num_shards(), + max_active_handles: global_max_num_credential_slabs(), + } +} + +impl CredentialSlab { + /// Adds the given V0 discovery credential with some associated + /// match-data to this credential slab. + pub fn add_v0( + &self, + discovery_credential: V0DiscoveryCredential, + match_data: MatchedCredential, + ) -> AddCredentialToSlabResult { + match self.get_mut() { + Ok(mut write_guard) => { + write_guard.add_v0(discovery_credential, match_data); + AddCredentialToSlabResult::Success + } + Err(_) => AddCredentialToSlabResult::InvalidHandle, + } + } + /// Adds the given V1 discovery credential with some associated + /// match-data to this credential slab. + pub fn add_v1( + &self, + discovery_credential: V1DiscoveryCredential, + match_data: MatchedCredential, + ) -> AddCredentialToSlabResult { + match self.get_mut() { + Ok(mut write_guard) => { + write_guard.add_v1(discovery_credential, match_data); + AddCredentialToSlabResult::Success + } + Err(_) => AddCredentialToSlabResult::InvalidHandle, + } + } +} + +/// Allocates a new credential-slab, returning a handle to the created object +pub fn create_credential_slab() -> CreateCredentialSlabResult { + CredentialSlab::allocate(CredentialSlabInternals::new).into() +} + +impl FfiEnum for CreateCredentialSlabResult { + type Kind = CreateCredentialSlabResultKind; + fn kind(&self) -> Self::Kind { + match self { + CreateCredentialSlabResult::NoSpaceLeft => CreateCredentialSlabResultKind::NoSpaceLeft, + CreateCredentialSlabResult::Success(_) => CreateCredentialSlabResultKind::Success, + } + } +} + +impl CreateCredentialSlabResult { + declare_enum_cast! {into_success, Success, CredentialSlab } +} /// Internal, Rust-side implementation of a credential-book. /// See [`CredentialBook`] for the FFI-side handles. -// TODO: Give this a real definition! -pub struct CredentialBookInternals; +pub struct CredentialBookInternals { + pub(crate) book: np_adv::credential::book::PrecalculatedOwnedCredentialBook<MatchedCredential>, +} + +impl CredentialBookInternals { + fn create_from_slab(credential_slab: CredentialSlabInternals) -> Self { + let book = np_adv::credential::book::CredentialBookBuilder::build_precalculated_owned_book::< + CryptoProviderImpl, + >(credential_slab.v0_creds, credential_slab.v1_creds); + Self { book } + } +} fn get_credential_book_handle_map_dimensions() -> HandleMapDimensions { HandleMapDimensions { @@ -29,48 +298,71 @@ fn get_credential_book_handle_map_dimensions() -> HandleMapDimensions { } } -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)] pub enum CreateCredentialBookResultKind { - /// There was no space left to create a new credential book - NoSpaceLeft = 0, /// We created a new credential book behind the given handle. /// The associated payload may be obtained via /// `CreateCredentialBookResult#into_success()`. - Success = 1, + Success = 0, + /// There was no space left to create a new credential book + NoSpaceLeft = 1, + /// The slab that we tried to create a credential-book from + /// actually was an invalid handle. + InvalidSlabHandle = 2, } /// Result type for `create_credential_book` #[repr(u8)] #[allow(missing_docs)] pub enum CreateCredentialBookResult { - NoSpaceLeft = 0, - Success(CredentialBook) = 1, + Success(CredentialBook) = 0, + NoSpaceLeft = 1, + InvalidSlabHandle = 2, } -impl From<Result<CredentialBook, HandleMapFullError>> for CreateCredentialBookResult { - fn from(result: Result<CredentialBook, HandleMapFullError>) -> Self { - match result { - Ok(book) => CreateCredentialBookResult::Success(book), - Err(_) => CreateCredentialBookResult::NoSpaceLeft, - } - } -} +impl LocksLongerThan<CredentialSlab> for CredentialBook {} /// Allocates a new credential-book, returning a handle to the created object -pub fn create_credential_book() -> CreateCredentialBookResult { - CredentialBook::allocate(|| CredentialBookInternals).into() +pub fn create_credential_book_from_slab( + credential_slab: CredentialSlab, +) -> CreateCredentialBookResult { + // The credential-book allocation is on the outside, since we should ensure + // that we have a slot available for construction before we try to deallocate + // the credential-slab which was passed in. + let op_result = CredentialBook::try_allocate(|| { + credential_slab.deallocate().map(CredentialBookInternals::create_from_slab) + }); + match op_result { + Ok(book) => CreateCredentialBookResult::Success(book), + Err(HandleMapTryAllocateError::ValueProviderFailed(_)) => { + // Unable to deallocate the referenced credential-slab + CreateCredentialBookResult::InvalidSlabHandle + } + Err(HandleMapTryAllocateError::HandleMapFull) => { + // Unable to allocate space for a new credential-book + CreateCredentialBookResult::NoSpaceLeft + } + } } impl FfiEnum for CreateCredentialBookResult { type Kind = CreateCredentialBookResultKind; fn kind(&self) -> Self::Kind { match self { - CreateCredentialBookResult::NoSpaceLeft => CreateCredentialBookResultKind::NoSpaceLeft, CreateCredentialBookResult::Success(_) => CreateCredentialBookResultKind::Success, + CreateCredentialBookResult::NoSpaceLeft => CreateCredentialBookResultKind::NoSpaceLeft, + CreateCredentialBookResult::InvalidSlabHandle => { + CreateCredentialBookResultKind::InvalidSlabHandle + } } } } @@ -84,5 +376,41 @@ pub fn deallocate_credential_book(credential_book: CredentialBook) -> Deallocate credential_book.deallocate().map(|_| ()).into() } -/// A handle on a particular v0 shared credential stored within a credential book -pub struct V0SharedCredential; +/// Deallocates a credential-slab by its handle +pub fn deallocate_credential_slab(credential_slab: CredentialSlab) -> DeallocateResult { + credential_slab.deallocate().map(|_| ()).into() +} + +/// Cryptographic information about a particular V1 broadcast credential +/// necessary to encrypt V1 MIC-verified and signature-verified sections. +#[repr(C)] +pub struct V1BroadcastCredential { + key_seed: [u8; 32], + metadata_key: [u8; 16], + private_key: [u8; 32], +} + +impl V1BroadcastCredential { + /// Constructs a new `V1BroadcastCredential` from the given + /// key-seed, 16-byte metadata key, and the raw bytes + /// of the ed25519 private key. + /// + /// Safety: Since this representation requires transmission + /// of the raw bytes of an ed25519 private key (and other + /// sensitive cryptographic info) over FFI, foreign-lang + /// code around how this information is maintained + /// deserves close scrutiny. + pub fn new(key_seed: [u8; 32], metadata_key: [u8; 16], private_key: [u8; 32]) -> Self { + Self { key_seed, metadata_key, private_key } + } + pub(crate) fn into_internal( + self, + ) -> np_adv::credential::v1::SimpleSignedBroadcastCryptoMaterial { + let permit = crypto_provider::ed25519::RawPrivateKeyPermit::default(); + np_adv::credential::v1::SimpleSignedBroadcastCryptoMaterial::new( + self.key_seed, + np_adv::MetadataKey(self.metadata_key), + crypto_provider::ed25519::PrivateKey::from_raw_private_key(self.private_key, &permit), + ) + } +} diff --git a/nearby/presence/np_ffi_core/src/deserialize/mod.rs b/nearby/presence/np_ffi_core/src/deserialize/mod.rs index 99ba846..5c1a891 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/mod.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/mod.rs @@ -19,7 +19,10 @@ use crate::deserialize::v0::*; use crate::deserialize::v1::*; use crate::utils::FfiEnum; use crypto_provider_default::CryptoProviderImpl; -use handle_map::{HandleLike, HandleMapFullError, HandleNotPresentError}; +use handle_map::{ + declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError, HandleNotPresentError, +}; +use np_adv::deserialization_arena; pub mod v0; pub mod v1; @@ -86,7 +89,7 @@ enum DeserializeAdvertisementSuccess { V1(DeserializedV1Advertisement), } -struct DeserializeAdvertisementError; +pub(crate) struct DeserializeAdvertisementError; impl From<HandleMapFullError> for DeserializeAdvertisementError { fn from(_: HandleMapFullError) -> Self { @@ -106,32 +109,18 @@ impl From<np_adv::AdvDeserializationError> for DeserializeAdvertisementError { } } -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, ) -> Result<DeserializeAdvertisementSuccess, DeserializeAdvertisementError> { // Deadlock Safety: Credential-book locks always live longer than deserialized advs. - let _credential_book_read_guard = credential_book.get()?; + let credential_book_read_guard = credential_book.get()?; - //TODO: Use an actual credential source - let cred_source: DefaultBothCredentialSource = DefaultBothCredentialSource::new_empty(); + let cred_book = &credential_book_read_guard.book; + 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)?; @@ -166,3 +155,132 @@ pub fn deserialize_advertisement( ) -> DeserializeAdvertisementResult { deserialize_advertisement_from_slice(adv_payload.as_slice(), credential_book) } + +/// Errors returned from attempting to decrypt metadata +pub(crate) enum DecryptMetadataError { + /// The advertisement payload handle was either deallocated + /// or corresponds to a public advertisement, and so we + /// don't have any metadata to decrypt. + EncryptedMetadataNotAvailable, + /// Decryption of the raw metadata bytes failed. + DecryptionFailed, +} + +/// The result of decrypting metadata from either a V0Payload or DeserializedV1Section +#[repr(C)] +#[allow(missing_docs)] +pub enum DecryptMetadataResult { + Success(DecryptedMetadata), + Error, +} + +/// Discriminant for `DecryptMetadataResult`. +#[repr(u8)] +pub enum DecryptMetadataResultKind { + /// The attempt to decrypt the metadata of the associated credential succeeded + /// The associated payload may be obtained via + /// `DecryptMetadataResult#into_success`. + Success, + /// The attempt to decrypt the metadata failed, either the payload had no matching identity + /// ie it was a public advertisement OR the decrypt attempt itself was unsuccessful + Error, +} + +impl FfiEnum for DecryptMetadataResult { + type Kind = DecryptMetadataResultKind; + + fn kind(&self) -> Self::Kind { + match self { + DecryptMetadataResult::Success(_) => DecryptMetadataResultKind::Success, + DecryptMetadataResult::Error => DecryptMetadataResultKind::Error, + } + } +} + +impl DecryptMetadataResult { + declare_enum_cast! {into_success, Success, DecryptedMetadata} +} + +/// Internals of decrypted metadata +pub struct DecryptedMetadataInternals { + decrypted_bytes: Box<[u8]>, +} + +declare_handle_map! { + mod decrypted_metadata { + #[dimensions = super::get_decrypted_metadata_handle_map_dimensions()] + type DecryptedMetadata: HandleLike<Object = super::DecryptedMetadataInternals>; + } +} +use decrypted_metadata::DecryptedMetadata; + +fn get_decrypted_metadata_handle_map_dimensions() -> HandleMapDimensions { + HandleMapDimensions { num_shards: global_num_shards(), max_active_handles: DEFAULT_MAX_HANDLES } +} + +/// The pointer and length of the decrypted metadata byte buffer +#[repr(C)] +pub struct MetadataBufferParts { + ptr: *const u8, + len: usize, +} + +#[repr(C)] +#[allow(missing_docs)] +pub enum GetMetadataBufferPartsResult { + Success(MetadataBufferParts), + Error, +} + +impl GetMetadataBufferPartsResult { + declare_enum_cast! {into_success, Success, MetadataBufferParts} +} + +#[repr(u8)] +#[allow(missing_docs)] +pub enum GetMetadataBufferPartsResultKind { + Success = 0, + Error = 1, +} + +impl FfiEnum for GetMetadataBufferPartsResult { + type Kind = GetMetadataBufferPartsResultKind; + + fn kind(&self) -> Self::Kind { + match self { + GetMetadataBufferPartsResult::Success(_) => GetMetadataBufferPartsResultKind::Success, + GetMetadataBufferPartsResult::Error => GetMetadataBufferPartsResultKind::Error, + } + } +} + +fn allocate_decrypted_metadata_handle(metadata: Vec<u8>) -> DecryptMetadataResult { + let allocate_result = DecryptedMetadata::allocate(move || DecryptedMetadataInternals { + decrypted_bytes: metadata.into_boxed_slice(), + }); + match allocate_result { + Ok(decrypted) => DecryptMetadataResult::Success(decrypted), + Err(_) => DecryptMetadataResult::Error, + } +} + +impl DecryptedMetadata { + /// Gets the raw parts, pointer + length representation of the metadata byte buffer + pub fn get_metadata_buffer_parts(&self) -> GetMetadataBufferPartsResult { + match self.get() { + Ok(metadata_internals) => { + let result = MetadataBufferParts { + ptr: metadata_internals.decrypted_bytes.as_ptr(), + len: metadata_internals.decrypted_bytes.len(), + }; + GetMetadataBufferPartsResult::Success(result) + } + Err(_) => GetMetadataBufferPartsResult::Error, + } + } + + /// Frees the underlying decrypted metadata buffer + pub fn deallocate_metadata(&self) -> DeallocateResult { + self.deallocate().map(|_| ()).into() + } +} diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs index 0f4b24b..14214c4 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs @@ -15,11 +15,17 @@ use crate::common::*; use crate::credentials::credential_book::CredentialBook; +use crate::credentials::MatchedCredential; +use crate::deserialize::{ + allocate_decrypted_metadata_handle, DecryptMetadataError, DecryptMetadataResult, +}; use crate::utils::{FfiEnum, LocksLongerThan}; -use alloc::vec::Vec; +use crypto_provider_default::CryptoProviderImpl; 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 np_adv::HasIdentityMatch; +use std::vec::Vec; /// Discriminant for possible results of V0 advertisement deserialization #[derive(Clone, Copy)] @@ -66,20 +72,28 @@ impl DeserializedV0Advertisement { } } - pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>( - contents: np_adv::V0AdvContents<'m, M>, - ) -> Result<Self, HandleMapFullError> { + pub(crate) fn allocate_with_contents( + contents: np_adv::V0AdvertisementContents< + np_adv::credential::ReferencedMatchedCredential<MatchedCredential>, + >, + ) -> Result<Self, DeserializeAdvertisementError> { 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(_) => { - unimplemented!(); + np_adv::V0AdvertisementContents::Decrypted(decrypted_contents) => { + let decrypted_contents = decrypted_contents.clone_match_data(); + let adv = LegibleDeserializedV0Advertisement::allocate_with_decrypted_contents( + decrypted_contents, + )?; + Ok(Self::Legible(adv)) + } + np_adv::V0AdvertisementContents::NoMatchingCredentials => { + Ok(Self::NoMatchingCredentials) } - np_adv::V0AdvContents::NoMatchingCredentials => Ok(Self::NoMatchingCredentials), } } @@ -91,17 +105,50 @@ impl DeserializedV0Advertisement { pub struct LegibleDeserializedV0Advertisement { num_des: u8, payload: V0Payload, - identity: DeserializedV0Identity, + identity_kind: DeserializedV0IdentityKind, } impl LegibleDeserializedV0Advertisement { pub(crate) fn allocate_with_plaintext_contents( contents: np_adv::legacy::deserialize::PlaintextAdvContents, - ) -> Result<Self, HandleMapFullError> { - let data_elements = contents.to_data_elements(); + ) -> Result<Self, DeserializeAdvertisementError> { + let data_elements = contents + .data_elements() + .collect::<Result<Vec<_>, _>>() + .map_err(|_| DeserializeAdvertisementError)?; let num_des = data_elements.len() as u8; - let payload = V0Payload::allocate_with_data_elements(data_elements)?; - Ok(Self { num_des, payload, identity: DeserializedV0Identity::Plaintext }) + let payload = V0Payload::allocate_with_plaintext_data_elements(data_elements)?; + Ok(Self { num_des, payload, identity_kind: DeserializedV0IdentityKind::Plaintext }) + } + pub(crate) fn allocate_with_decrypted_contents( + contents: np_adv::WithMatchedCredential< + MatchedCredential, + np_adv::legacy::deserialize::DecryptedAdvContents, + >, + ) -> Result<Self, DeserializeAdvertisementError> { + let data_elements = contents + .contents() + .data_elements() + .collect::<Result<Vec<_>, _>>() + .map_err(|_| DeserializeAdvertisementError)?; + let num_des = data_elements.len() as u8; + + let salt = contents.contents().salt(); + let identity_type = contents.contents().identity_type(); + + // Reduce the information contained in the contents to just + // the metadata key, since we're done copying over the DEs + // and other data into an FFI-friendly form. + let match_data = contents.map(|x| x.metadata_key()); + + let payload = V0Payload::allocate_with_decrypted_contents( + identity_type, + salt, + match_data, + data_elements, + )?; + + Ok(Self { num_des, payload, identity_kind: DeserializedV0IdentityKind::Decrypted }) } /// Gets the number of data-elements in this adv's payload /// Suitable as an iteration bound for `Self.into_payload().get_de(...)`. @@ -109,12 +156,13 @@ impl LegibleDeserializedV0Advertisement { 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 { - self.identity + /// Destructures this legible advertisement into just the discriminant + /// for the kind of identity (plaintext/encrypted) used for its contents. + pub fn identity_kind(&self) -> DeserializedV0IdentityKind { + self.identity_kind } /// Deallocates the underlying handle of the payload pub fn deallocate(self) -> DeallocateResult { @@ -122,7 +170,8 @@ impl LegibleDeserializedV0Advertisement { } } -/// Discriminant for `DeserializedV0Identity`. +/// Discriminant for deserialized information about the V0 +/// identity utilized by a deserialized V0 advertisement. #[derive(Clone, Copy)] #[repr(u8)] pub enum DeserializedV0IdentityKind { @@ -132,29 +181,136 @@ pub enum DeserializedV0IdentityKind { Decrypted = 1, } -/// Represents deserialized information about the V0 identity utilized -/// by a deserialized V0 advertisement +/// Information about the identity which matched a +/// decrypted V0 advertisement. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DeserializedV0IdentityDetails { + /// The identity type (private/provisioned/trusted) + identity_type: EncryptedIdentityType, + /// The ID of the credential which + /// matched the deserialized adv + cred_id: u32, + /// The 14-byte legacy metadata key + metadata_key: [u8; 14], + /// The 2-byte advertisement salt + salt: [u8; 2], +} + +impl DeserializedV0IdentityDetails { + pub(crate) fn new( + cred_id: u32, + identity_type: np_adv::de_type::EncryptedIdentityDataElementType, + salt: ldt_np_adv::LegacySalt, + metadata_key: np_adv::legacy::ShortMetadataKey, + ) -> Self { + let metadata_key = metadata_key.0; + let salt = *salt.bytes(); + let identity_type = identity_type.into(); + Self { identity_type, cred_id, salt, metadata_key } + } + /// Returns the ID of the credential which + /// matched the deserialized adv + pub fn cred_id(&self) -> u32 { + self.cred_id + } + /// Returns the identity type (private/provisioned/trusted) + pub fn identity_type(&self) -> EncryptedIdentityType { + self.identity_type + } + /// Returns the 14-byte legacy metadata key + pub fn metadata_key(&self) -> [u8; 14] { + self.metadata_key + } + /// Returns the 2-byte advertisement salt + pub fn salt(&self) -> [u8; 2] { + self.salt + } +} + +/// Discriminant for `GetV0IdentityDetailsResult` +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum GetV0IdentityDetailsResultKind { + /// The attempt to get the identity details + /// for the advertisement failed, possibly + /// due to the advertisement being a public + /// advertisement, or the underlying + /// advertisement has already been deallocated. + Error = 0, + /// The attempt to get the identity details succeeded. + /// The wrapped identity details may be obtained via + /// `GetV0IdentityDetailsResult#into_success`. + Success = 1, +} + +/// The result of attempting to get the identity details +/// for a V0 advertisement via +/// `DeserializedV0Advertisement#get_identity_details`. #[repr(C)] #[allow(missing_docs)] -pub enum DeserializedV0Identity { - Plaintext, - // TODO: This gets a payload once we support creds - Decrypted, +pub enum GetV0IdentityDetailsResult { + Error, + Success(DeserializedV0IdentityDetails), } -impl FfiEnum for DeserializedV0Identity { - type Kind = DeserializedV0IdentityKind; +impl FfiEnum for GetV0IdentityDetailsResult { + type Kind = GetV0IdentityDetailsResultKind; fn kind(&self) -> Self::Kind { match self { - DeserializedV0Identity::Plaintext => DeserializedV0IdentityKind::Plaintext, - DeserializedV0Identity::Decrypted => DeserializedV0IdentityKind::Decrypted, + GetV0IdentityDetailsResult::Error => GetV0IdentityDetailsResultKind::Error, + GetV0IdentityDetailsResult::Success(_) => GetV0IdentityDetailsResultKind::Success, } } } +impl GetV0IdentityDetailsResult { + declare_enum_cast! {into_success, Success, DeserializedV0IdentityDetails} +} + +/// Internal implementation of a deserialized V0 identity. +pub(crate) struct DeserializedV0IdentityInternals { + /// The details about the identity, suitable + /// for direct communication over FFI + details: DeserializedV0IdentityDetails, + /// The metadata key, together with the matched + /// credential and enough information to decrypt + /// the credential metadata, if desired. + match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::legacy::ShortMetadataKey>, +} + +impl DeserializedV0IdentityInternals { + pub(crate) fn new( + identity_type: np_adv::de_type::EncryptedIdentityDataElementType, + salt: ldt_np_adv::LegacySalt, + match_data: np_adv::WithMatchedCredential< + MatchedCredential, + np_adv::legacy::ShortMetadataKey, + >, + ) -> Self { + let cred_id = match_data.matched_credential().id(); + let metadata_key = match_data.contents(); + let details = + DeserializedV0IdentityDetails::new(cred_id, identity_type, salt, *metadata_key); + Self { details, match_data } + } + /// Gets the directly-transmissible details about + /// this deserialized V0 identity. Does not include + /// decrypted metadata bytes. + pub(crate) fn details(&self) -> DeserializedV0IdentityDetails { + self.details + } + /// Attempts to decrypt the metadata associated + /// with this identity. + pub(crate) fn decrypt_metadata(&self) -> Option<Vec<u8>> { + self.match_data.decrypt_metadata::<CryptoProviderImpl>().ok() + } +} + /// The internal data-structure used for storing /// the payload of a deserialized V0 advertisement. pub struct V0PayloadInternals { + identity: Option<DeserializedV0IdentityInternals>, des: Vec<V0DataElement>, } @@ -167,6 +323,24 @@ impl V0PayloadInternals { None => GetV0DEResult::Error, } } + /// Gets the identity details for this V0 payload, + /// if this payload was associated with an identity. + fn get_identity_details(&self) -> GetV0IdentityDetailsResult { + match &self.identity { + Some(x) => GetV0IdentityDetailsResult::Success(x.details()), + None => GetV0IdentityDetailsResult::Error, + } + } + /// Attempts to decrypt the metadata for the matched + /// credential for this V0 payload (if any) + fn decrypt_metadata(&self) -> Result<Vec<u8>, DecryptMetadataError> { + match &self.identity { + None => Err(DecryptMetadataError::EncryptedMetadataNotAvailable), + Some(identity) => { + identity.decrypt_metadata().ok_or(DecryptMetadataError::DecryptionFailed) + } + } + } } fn get_v0_payload_handle_map_dimensions() -> HandleMapDimensions { @@ -176,18 +350,46 @@ fn get_v0_payload_handle_map_dimensions() -> HandleMapDimensions { } } -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; +use super::DeserializeAdvertisementError; + impl LocksLongerThan<V0Payload> for CredentialBook {} impl V0Payload { - pub(crate) fn allocate_with_data_elements<F: np_adv::legacy::PacketFlavor>( - data_elements: Vec<np_adv::legacy::deserialize::PlainDataElement<F>>, + pub(crate) fn allocate_with_plaintext_data_elements( + data_elements: Vec< + np_adv::legacy::deserialize::PlainDataElement<np_adv::legacy::Plaintext>, + >, ) -> Result<Self, HandleMapFullError> { Self::allocate(move || { let des = data_elements.into_iter().map(V0DataElement::from).collect(); - V0PayloadInternals { des } + let identity = None; + V0PayloadInternals { des, identity } + }) + } + pub(crate) fn allocate_with_decrypted_contents( + identity_type: np_adv::de_type::EncryptedIdentityDataElementType, + salt: ldt_np_adv::LegacySalt, + match_data: np_adv::WithMatchedCredential< + MatchedCredential, + np_adv::legacy::ShortMetadataKey, + >, + data_elements: Vec< + np_adv::legacy::deserialize::PlainDataElement<np_adv::legacy::Ciphertext>, + >, + ) -> Result<Self, HandleMapFullError> { + Self::allocate(move || { + let des = data_elements.into_iter().map(V0DataElement::from).collect(); + let identity = + Some(DeserializedV0IdentityInternals::new(identity_type, salt, match_data)); + V0PayloadInternals { des, identity } }) } /// Gets the data-element with the given index in this v0 adv payload @@ -198,6 +400,28 @@ impl V0Payload { } } + /// Gets the identity details for this V0 payload, + /// if this payload was associated with an identity + /// (i.e: non-public advertisements). + pub fn get_identity_details(&self) -> GetV0IdentityDetailsResult { + match self.get() { + Ok(read_guard) => read_guard.get_identity_details(), + Err(_) => GetV0IdentityDetailsResult::Error, + } + } + + /// Attempts to decrypt the metadata for the matched + /// credential for this V0 payload (if any) + pub fn decrypt_metadata(&self) -> DecryptMetadataResult { + match self.get() { + Ok(read_guard) => match read_guard.decrypt_metadata() { + Ok(decrypted_metadata) => allocate_decrypted_metadata_handle(decrypted_metadata), + Err(_) => DecryptMetadataResult::Error, + }, + Err(_) => DecryptMetadataResult::Error, + } + } + /// Deallocates any underlying data held by a V0Payload pub fn deallocate_payload(&self) -> DeallocateResult { self.deallocate().map(|_| ()).into() @@ -327,10 +551,10 @@ impl<F: np_adv::legacy::PacketFlavor> From<np_adv::legacy::actions::ActionsDataE 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 +626,7 @@ impl V0Actions { 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 +638,7 @@ impl V0Actions { 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 +656,7 @@ impl V0Actions { .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 +664,7 @@ impl V0Actions { ) .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..cb56b33 100644 --- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs +++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs @@ -13,12 +13,20 @@ // limitations under the License. //! Core NP Rust FFI structures and methods for v1 advertisement deserialization. +use super::DeserializeAdvertisementError; use crate::common::*; use crate::credentials::credential_book::CredentialBook; +use crate::credentials::MatchedCredential; +use crate::deserialize::{allocate_decrypted_metadata_handle, DecryptMetadataResult}; use crate::utils::*; -use alloc::vec::Vec; +use crate::v1::V1VerificationMode; use array_view::ArrayView; -use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use crypto_provider_default::CryptoProviderImpl; +use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions}; +use legible_v1_sections::LegibleV1Sections; +use np_adv::extended::deserialize::DataElementParseError; +use np_adv::HasIdentityMatch; +use std::vec::Vec; /// Representation of a deserialized V1 advertisement #[repr(C)] @@ -56,14 +64,17 @@ impl DeserializedV1Advertisement { 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>, - ) -> Result<Self, HandleMapFullError> { + pub(crate) fn allocate_with_contents( + contents: np_adv::V1AdvertisementContents< + np_adv::credential::ReferencedMatchedCredential<MatchedCredential>, + >, + ) -> Result<Self, DeserializeAdvertisementError> { // 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)?; + let legible_sections = + LegibleV1Sections::allocate_with_contents(legible_sections.into_vec())?; Ok(Self { num_undecryptable_sections, num_legible_sections, legible_sections }) } } @@ -105,12 +116,31 @@ impl LegibleV1SectionsInternals { } } -impl<'m, M: np_adv::credential::MatchedCredential<'m>> - From<Vec<np_adv::V1DeserializedSection<'m, M>>> for LegibleV1SectionsInternals +impl<'adv> + TryFrom< + Vec< + np_adv::V1DeserializedSection< + 'adv, + np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>, + >, + >, + > for LegibleV1SectionsInternals { - fn from(contents: Vec<np_adv::V1DeserializedSection<'m, M>>) -> Self { - let sections = contents.into_iter().map(DeserializedV1SectionInternals::from).collect(); - Self { sections } + type Error = DataElementParseError; + + fn try_from( + contents: Vec< + np_adv::V1DeserializedSection< + 'adv, + np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>, + >, + >, + ) -> Result<Self, Self::Error> { + let sections = contents + .into_iter() + .map(DeserializedV1SectionInternals::try_from) + .collect::<Result<Vec<_>, _>>()?; + Ok(Self { sections }) } } @@ -121,16 +151,26 @@ fn get_legible_v1_sections_handle_map_dimensions() -> HandleMapDimensions { } } -declare_handle_map! {legible_v1_sections, LegibleV1Sections, super::LegibleV1SectionsInternals, super::get_legible_v1_sections_handle_map_dimensions() } -use legible_v1_sections::LegibleV1Sections; +declare_handle_map! { + mod legible_v1_sections { + #[dimensions = super::get_legible_v1_sections_handle_map_dimensions()] + type LegibleV1Sections: HandleLike<Object = super::LegibleV1SectionsInternals>; + } +} 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>>, - ) -> Result<Self, HandleMapFullError> { - Self::allocate(move || LegibleV1SectionsInternals::from(contents)) + pub(crate) fn allocate_with_contents( + contents: Vec< + np_adv::V1DeserializedSection< + np_adv::credential::ReferencedMatchedCredential<MatchedCredential>, + >, + >, + ) -> Result<Self, DeserializeAdvertisementError> { + let section = LegibleV1SectionsInternals::try_from(contents) + .map_err(|_| DeserializeAdvertisementError)?; + Self::allocate(move || section).map_err(|e| e.into()) } } @@ -173,10 +213,48 @@ impl GetV1SectionResult { declare_enum_cast! {into_success, Success, DeserializedV1Section} } +/// Discriminant for `GetV1DE16ByteSaltResult`. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum GetV1DE16ByteSaltResultKind { + /// The attempt to get the derived salt failed, possibly + /// because the passed DE offset was invalid (==255), + /// or because there was no salt included for the + /// referenced advertisement section (i.e: it was + /// a public advertisement section, or it was deallocated.) + Error = 0, + /// A 16-byte salt for the given DE offset was successfully + /// derived. + Success = 1, +} + +/// The result of attempting to get a derived 16-byte salt +/// for a given DE within a section. +#[repr(C)] +#[allow(missing_docs)] +pub enum GetV1DE16ByteSaltResult { + Error, + Success(FixedSizeArray<16>), +} + +impl GetV1DE16ByteSaltResult { + declare_enum_cast! {into_success, Success, FixedSizeArray<16>} +} + +impl FfiEnum for GetV1DE16ByteSaltResult { + type Kind = GetV1DE16ByteSaltResultKind; + fn kind(&self) -> Self::Kind { + match self { + GetV1DE16ByteSaltResult::Error => GetV1DE16ByteSaltResultKind::Error, + GetV1DE16ByteSaltResult::Success(_) => GetV1DE16ByteSaltResultKind::Success, + } + } +} + /// The internal FFI-friendly representation of a deserialized v1 section pub struct DeserializedV1SectionInternals { des: Vec<V1DataElement>, - identity: DeserializedV1Identity, + identity: Option<DeserializedV1IdentityInternals>, } impl DeserializedV1SectionInternals { @@ -184,10 +262,16 @@ impl DeserializedV1SectionInternals { fn num_des(&self) -> u8 { self.des.len() as u8 } + /// Gets the enum tag of the identity used for this section. fn identity_kind(&self) -> DeserializedV1IdentityKind { - self.identity.kind() + if self.identity.is_some() { + DeserializedV1IdentityKind::Decrypted + } else { + DeserializedV1IdentityKind::Plaintext + } } + /// Attempts to get the DE with the given index in this section. fn get_de(&self, index: u8) -> GetV1DEResult { match self.des.get(index as usize) { @@ -195,22 +279,89 @@ impl DeserializedV1SectionInternals { None => GetV1DEResult::Error, } } + + /// Attempts to get the directly-transmissible details about + /// the deserialized V1 identity for this section. Does + /// not include decrypted metadata bytes nor the section salt. + pub(crate) fn get_identity_details(&self) -> GetV1IdentityDetailsResult { + match &self.identity { + Some(identity) => GetV1IdentityDetailsResult::Success(identity.details()), + None => GetV1IdentityDetailsResult::Error, + } + } + + /// Attempts to decrypt the metadata for the matched + /// credential for this V1 section (if any). + pub(crate) fn decrypt_metadata(&self) -> DecryptMetadataResult { + match &self.identity { + None => DecryptMetadataResult::Error, + Some(identity) => match identity.decrypt_metadata() { + None => DecryptMetadataResult::Error, + Some(metadata) => allocate_decrypted_metadata_handle(metadata), + }, + } + } + + /// Attempts to derive a 16-byte DE salt for a DE in this section + /// with the given DE offset. This operation may fail if the + /// passed offset is 255 (causes overflow) or if the section + /// is leveraging a public identity, and hence, doesn't have + /// an associated salt. + pub(crate) fn derive_16_byte_salt_for_offset(&self, de_offset: u8) -> GetV1DE16ByteSaltResult { + self.identity + .as_ref() + .and_then(|x| x.derive_16_byte_salt_for_offset(de_offset)) + .map_or(GetV1DE16ByteSaltResult::Error, GetV1DE16ByteSaltResult::Success) + } } -impl<'m, M: np_adv::credential::MatchedCredential<'m>> From<np_adv::V1DeserializedSection<'m, M>> - for DeserializedV1SectionInternals +impl<'adv> + TryFrom< + np_adv::V1DeserializedSection< + 'adv, + np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>, + >, + > for DeserializedV1SectionInternals { - fn from(section: np_adv::V1DeserializedSection<'m, M>) -> Self { + type Error = DataElementParseError; + + fn try_from( + section: np_adv::V1DeserializedSection< + np_adv::credential::ReferencedMatchedCredential<'adv, MatchedCredential>, + >, + ) -> 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 identity = DeserializedV1Identity::Plaintext; - Self { des, identity } + let des = section + .iter_data_elements() + .map(|r| r.map(|de| V1DataElement::from(&de))) + .collect::<Result<Vec<_>, _>>()?; + let identity = None; + Ok(Self { des, identity }) } - V1DeserializedSection::Decrypted(_) => { - unimplemented!(); + V1DeserializedSection::Decrypted(with_matched) => { + let section = with_matched.contents(); + let des = section + .iter_data_elements() + .map(|r| r.map(|de| V1DataElement::from(&de))) + .collect::<Result<Vec<_>, _>>()?; + + let identity_type = section.identity_type(); + let verification_mode = section.verification_mode(); + let salt = section.salt(); + + let match_data = with_matched.clone_match_data(); + let match_data = match_data.map(|x| x.metadata_key()); + + let identity = Some(DeserializedV1IdentityInternals::new( + identity_type, + verification_mode, + salt, + match_data, + )); + Ok(Self { des, identity }) } } } @@ -226,27 +377,149 @@ pub enum DeserializedV1IdentityKind { Decrypted = 1, } -/// Deserialized information about the identity -/// employed in a V1 adveritsement section. +/// Internals for the representation of a decrypted +/// V1 section identity. +pub(crate) struct DeserializedV1IdentityInternals { + /// The details about the identity, suitable + /// for direct communication over FFI + details: DeserializedV1IdentityDetails, + /// The metadata key, together with the matched + /// credential and enough information to decrypt + /// the credential metadata, if desired. + match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::MetadataKey>, + /// The 16-byte section salt + salt: np_adv::extended::deserialize::RawV1Salt, +} + +impl DeserializedV1IdentityInternals { + pub(crate) fn new( + identity_type: np_adv::de_type::EncryptedIdentityDataElementType, + verification_mode: np_adv::extended::deserialize::VerificationMode, + salt: np_adv::extended::deserialize::RawV1Salt, + match_data: np_adv::WithMatchedCredential<MatchedCredential, np_adv::MetadataKey>, + ) -> Self { + let cred_id = match_data.matched_credential().id(); + let metadata_key = match_data.contents(); + let details = DeserializedV1IdentityDetails::new( + cred_id, + identity_type, + verification_mode, + *metadata_key, + ); + Self { details, match_data, salt } + } + /// Gets the directly-transmissible details about + /// this deserialized V1 identity. Does not include + /// decrypted metadata bytes nor the section salt. + pub(crate) fn details(&self) -> DeserializedV1IdentityDetails { + self.details + } + /// Attempts to decrypt the metadata associated + /// with this identity. + pub(crate) fn decrypt_metadata(&self) -> Option<Vec<u8>> { + self.match_data.decrypt_metadata::<CryptoProviderImpl>().ok() + } + /// For a given data-element offset, derives a 16-byte DE salt + /// for a DE in that position within this section. + pub(crate) fn derive_16_byte_salt_for_offset( + &self, + de_offset: u8, + ) -> Option<FixedSizeArray<16>> { + let section_salt = np_hkdf::v1_salt::V1Salt::<CryptoProviderImpl>::from(self.salt); + let de_offset = np_hkdf::v1_salt::DataElementOffset::from(de_offset); + section_salt.derive::<16>(Some(de_offset)).map(FixedSizeArray::from_array) + } +} + +/// Discriminant for `GetV1IdentityDetailsResult` #[derive(Clone, Copy)] +#[repr(u8)] +pub enum GetV1IdentityDetailsResultKind { + /// The attempt to get the identity details + /// for the section failed, possibly + /// due to the section being a public + /// section, or the underlying + /// advertisement has already been deallocated. + Error = 0, + /// The attempt to get the identity details succeeded. + /// The wrapped identity details may be obtained via + /// `GetV1IdentityDetailsResult#into_success`. + Success = 1, +} + +/// The result of attempting to get the identity details +/// for a V1 advertisement section via +/// `DeserializedV1Advertisement#get_identity_details`. #[repr(C)] #[allow(missing_docs)] -pub enum DeserializedV1Identity { - Plaintext, - // TODO: This gets a payload once we support creds - Decrypted, +pub enum GetV1IdentityDetailsResult { + Error, + Success(DeserializedV1IdentityDetails), } -impl FfiEnum for DeserializedV1Identity { - type Kind = DeserializedV1IdentityKind; +impl FfiEnum for GetV1IdentityDetailsResult { + type Kind = GetV1IdentityDetailsResultKind; fn kind(&self) -> Self::Kind { match self { - DeserializedV1Identity::Plaintext => DeserializedV1IdentityKind::Plaintext, - DeserializedV1Identity::Decrypted => DeserializedV1IdentityKind::Decrypted, + GetV1IdentityDetailsResult::Error => GetV1IdentityDetailsResultKind::Error, + GetV1IdentityDetailsResult::Success(_) => GetV1IdentityDetailsResultKind::Success, } } } +impl GetV1IdentityDetailsResult { + declare_enum_cast! {into_success, Success, DeserializedV1IdentityDetails} +} + +/// Information about the identity which matched +/// a decrypted V1 section. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DeserializedV1IdentityDetails { + /// The identity type (private/provisioned/trusted) + identity_type: EncryptedIdentityType, + /// The verification mode (MIC/Signature) which + /// was used to verify the decrypted adv contents. + verification_mode: V1VerificationMode, + /// The ID of the credential which + /// matched the deserialized section. + cred_id: u32, + /// The 16-byte metadata key. + metadata_key: [u8; 16], +} + +impl DeserializedV1IdentityDetails { + pub(crate) fn new( + cred_id: u32, + identity_type: np_adv::de_type::EncryptedIdentityDataElementType, + verification_mode: np_adv::extended::deserialize::VerificationMode, + metadata_key: np_adv::MetadataKey, + ) -> Self { + let metadata_key = metadata_key.0; + let identity_type = identity_type.into(); + let verification_mode = verification_mode.into(); + Self { cred_id, identity_type, verification_mode, metadata_key } + } + /// Returns the ID of the credential which + /// matched the deserialized section. + pub fn cred_id(&self) -> u32 { + self.cred_id + } + /// Returns the identity type (private/provisioned/trusted) + pub fn identity_type(&self) -> EncryptedIdentityType { + self.identity_type + } + /// Returns the verification mode (MIC/Signature) + /// employed for the decrypted section. + pub fn verification_mode(&self) -> V1VerificationMode { + self.verification_mode + } + /// Returns the 16-byte section metadata key. + pub fn metadata_key(&self) -> [u8; 16] { + self.metadata_key + } +} + /// Handle to a deserialized V1 section #[repr(C)] pub struct DeserializedV1Section { @@ -270,16 +543,57 @@ impl DeserializedV1Section { /// Gets the DE with the given index in this section. pub fn get_de(&self, de_index: u8) -> GetV1DEResult { + self.apply_to_section_internals( + move |section_ref| section_ref.get_de(de_index), + GetV1DEResult::Error, + ) + } + /// Attempts to get the details of the identity employed + /// for the section referenced by this handle. May fail + /// if the handle is invalid, or if the advertisement + /// section leverages a public identity. + pub fn get_identity_details(&self) -> GetV1IdentityDetailsResult { + self.apply_to_section_internals( + DeserializedV1SectionInternals::get_identity_details, + GetV1IdentityDetailsResult::Error, + ) + } + /// Attempts to decrypt the metadata for the matched + /// credential for the V1 section referenced by + /// this handle (if any). + pub fn decrypt_metadata(&self) -> DecryptMetadataResult { + self.apply_to_section_internals( + DeserializedV1SectionInternals::decrypt_metadata, + DecryptMetadataResult::Error, + ) + } + /// Attempts to derive a 16-byte DE salt for a DE in this section + /// with the given DE offset. This operation may fail if the + /// passed offset is 255 (causes overflow) or if the section + /// is leveraging a public identity, and hence, doesn't have + /// an associated salt. + pub fn derive_16_byte_salt_for_offset(&self, de_offset: u8) -> GetV1DE16ByteSaltResult { + self.apply_to_section_internals( + move |section_ref| section_ref.derive_16_byte_salt_for_offset(de_offset), + GetV1DE16ByteSaltResult::Error, + ) + } + + fn apply_to_section_internals<R>( + &self, + func: impl FnOnce(&DeserializedV1SectionInternals) -> R, + lookup_failure_result: R, + ) -> R { // TODO: Once the `FromResidual` trait is stabilized, this can be simplified. match self.legible_sections_handle.get() { Ok(legible_sections_read_guard) => { match legible_sections_read_guard.get_section_internals(self.legible_section_index) { - Some(section_ref) => section_ref.get_de(de_index), - None => GetV1DEResult::Error, + Some(section_ref) => func(section_ref), + None => lookup_failure_result, } } - Err(_) => GetV1DEResult::Error, + Err(_) => lookup_failure_result, } } } @@ -341,15 +655,16 @@ impl V1DataElement { } } -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 offset = de.offset().as_u8(); let de_type = V1DEType::from(de.de_type()); let contents_as_slice = de.contents(); //Guaranteed not to panic due DE size limit. #[allow(clippy::unwrap_used)] let array_view: ArrayView<u8, 127> = ArrayView::try_from_slice(contents_as_slice).unwrap(); let payload = ByteBuffer::from_array_view(array_view); - Self::Generic(GenericV1DataElement { de_type, payload }) + Self::Generic(GenericV1DataElement { de_type, offset, payload }) } } @@ -359,6 +674,8 @@ impl<'a> From<np_adv::extended::deserialize::DataElement<'a>> for V1DataElement #[derive(Clone)] #[repr(C)] pub struct GenericV1DataElement { + /// The offset of this generic data-element. + pub offset: u8, /// The DE type code of this generic data-element. pub de_type: V1DEType, /// The raw data-element byte payload, up to @@ -367,6 +684,10 @@ pub struct GenericV1DataElement { } impl GenericV1DataElement { + /// Gets the offset for this generic V1 data element. + pub fn offset(&self) -> u8 { + self.offset + } /// Gets the DE-type of this generic V1 data element. pub fn de_type(&self) -> V1DEType { self.de_type diff --git a/nearby/presence/np_ffi_core/src/lib.rs b/nearby/presence/np_ffi_core/src/lib.rs index dd9e49e..f3bb7a3 100644 --- a/nearby/presence/np_ffi_core/src/lib.rs +++ b/nearby/presence/np_ffi_core/src/lib.rs @@ -11,22 +11,16 @@ // 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. + //! Core functionality common to all NP Rust FFI layers -#![cfg_attr(not(feature = "std"), no_std)] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] -extern crate alloc; -extern crate core; +#[macro_use] +extern crate lazy_static; #[macro_use] pub mod utils; pub mod common; pub mod credentials; pub mod deserialize; +pub mod serialize; +pub mod v1; diff --git a/nearby/presence/np_ffi_core/src/serialize/mod.rs b/nearby/presence/np_ffi_core/src/serialize/mod.rs new file mode 100644 index 0000000..aeb68a0 --- /dev/null +++ b/nearby/presence/np_ffi_core/src/serialize/mod.rs @@ -0,0 +1,38 @@ +// 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. +//! Core NP Rust FFI structures and methods for advertisement serialization. + +pub mod v1; + +/// Enum common to V0 and V1 serialization expressing +/// what kind of advertisement builder (public/encrypted) +/// is in use. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum AdvertisementBuilderKind { + /// The builder is for a public advertisement. + Public = 0, + /// The builder is for an encrypted advertisement. + Encrypted = 1, +} + +impl AdvertisementBuilderKind { + pub(crate) fn as_internal_v1(&self) -> np_adv::extended::serialize::AdvertisementType { + use np_adv::extended::serialize::AdvertisementType; + match self { + Self::Public => AdvertisementType::Plaintext, + Self::Encrypted => AdvertisementType::Encrypted, + } + } +} diff --git a/nearby/presence/np_ffi_core/src/serialize/v1.rs b/nearby/presence/np_ffi_core/src/serialize/v1.rs new file mode 100644 index 0000000..ccc6ef7 --- /dev/null +++ b/nearby/presence/np_ffi_core/src/serialize/v1.rs @@ -0,0 +1,589 @@ +// 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. + +//! NP Rust FFI structures and methods for V1 advertisement serialization. + +use crate::common::*; +use crate::credentials::V1BroadcastCredential; +use crate::serialize::AdvertisementBuilderKind; +use crate::utils::FfiEnum; +use crate::v1::V1VerificationMode; +use crypto_provider_default::CryptoProviderImpl; +use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError}; +use np_adv; +use np_adv_dynamic; + +/// A handle to a builder for V1 advertisements. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct V1AdvertisementBuilder { + kind: AdvertisementBuilderKind, + handle: V1AdvertisementBuilderHandle, +} + +impl V1AdvertisementBuilder { + /// Attempts to create a builder for a new public section within + /// this advertisement, returning a handle to the newly-created + /// section builder if successful. + /// + /// This method may fail if there is another currently-active + /// section builder for the same advertisement builder, if the + /// kind of section being added does not match the advertisement + /// type (public/encrypted), or if the section would not manage + /// to fit within the enclosing advertisement. + pub fn public_section_builder(&self) -> CreateV1SectionBuilderResult { + self.section_builder_internals(|internals| internals.public_section_builder()) + } + /// Attempts to create a builder for a new encrypted section within + /// this advertisement, returning a handle to the newly-created + /// section builder if successful. + /// + /// The identity details for the new section builder may be specified + /// via providing the broadcast credential data, the kind of encrypted + /// identity being broadcast (private/trusted/provisioned), and the + /// verification mode (MIC/Signature) to be used for the encrypted section. + /// + /// This method may fail if there is another currently-active + /// section builder for the same advertisement builder, if the + /// kind of section being added does not match the advertisement + /// type (public/encrypted), or if the section would not manage + /// to fit within the enclosing advertisement. + pub fn encrypted_section_builder( + &self, + broadcast_cred: V1BroadcastCredential, + identity_type: EncryptedIdentityType, + verification_mode: V1VerificationMode, + ) -> CreateV1SectionBuilderResult { + self.section_builder_internals(move |internals| { + internals.encrypted_section_builder(broadcast_cred, identity_type, verification_mode) + }) + } + + fn section_builder_internals( + &self, + builder_supplier: impl FnOnce( + &mut V1AdvertisementBuilderInternals, + ) -> Result<usize, SectionBuilderError>, + ) -> CreateV1SectionBuilderResult { + match self.handle.get_mut() { + Ok(mut adv_builder_write_guard) => { + match builder_supplier(&mut adv_builder_write_guard) { + Ok(section_index) => CreateV1SectionBuilderResult::Success(V1SectionBuilder { + adv_builder: *self, + section_index: section_index as u8, + }), + Err(e) => e.into(), + } + } + Err(_) => CreateV1SectionBuilderResult::InvalidAdvBuilderHandle, + } + } +} + +/// Discriminant for `CreateV1AdvertisementBuilderResult` + +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum CreateV1AdvertisementBuilderResultKind { + /// The attempt to create a new advertisement builder + /// failed since there are no more available + /// slots for V1 advertisement builders in their handle-map. + NoSpaceLeft = 0, + /// The attempt succeeded. The wrapped advertisement builder + /// may be obtained via + /// `CreateV1AdvertisementBuilderResult#into_success`. + Success = 1, +} + +/// The result of attempting to create a new V1 advertisement builder. +#[repr(C)] +#[allow(missing_docs)] +pub enum CreateV1AdvertisementBuilderResult { + NoSpaceLeft, + Success(V1AdvertisementBuilder), +} + +impl From<Result<V1AdvertisementBuilder, HandleMapFullError>> + for CreateV1AdvertisementBuilderResult +{ + fn from(result: Result<V1AdvertisementBuilder, HandleMapFullError>) -> Self { + match result { + Ok(builder) => CreateV1AdvertisementBuilderResult::Success(builder), + Err(_) => CreateV1AdvertisementBuilderResult::NoSpaceLeft, + } + } +} + +impl FfiEnum for CreateV1AdvertisementBuilderResult { + type Kind = CreateV1AdvertisementBuilderResultKind; + fn kind(&self) -> Self::Kind { + match self { + CreateV1AdvertisementBuilderResult::NoSpaceLeft => { + CreateV1AdvertisementBuilderResultKind::NoSpaceLeft + } + CreateV1AdvertisementBuilderResult::Success(_) => { + CreateV1AdvertisementBuilderResultKind::Success + } + } + } +} + +impl CreateV1AdvertisementBuilderResult { + declare_enum_cast! {into_success, Success, V1AdvertisementBuilder } +} + +/// Creates a new V1 advertisement builder for the given advertisement +/// kind (public/encrypted). +pub fn create_v1_advertisement_builder( + kind: AdvertisementBuilderKind, +) -> CreateV1AdvertisementBuilderResult { + V1AdvertisementBuilderHandle::allocate(move || V1AdvertisementBuilderInternals::new(kind)) + .map(|handle| V1AdvertisementBuilder { kind, handle }) + .into() +} + +impl V1AdvertisementBuilder { + /// Gets the kind of advertisement builder (public/encrypted) + pub fn kind(&self) -> AdvertisementBuilderKind { + self.kind + } +} + +pub(crate) enum V1AdvertisementBuilderState { + /// Internal state for when we have an active advertisement + /// builder, but no currently-active section builder. + Advertisement(np_adv_dynamic::extended::BoxedAdvBuilder), + /// Internal state for when we have both an active advertisement + /// builder and an active section builder. + Section( + np_adv_dynamic::extended::BoxedSectionBuilder< + np_adv::extended::serialize::AdvBuilder, + CryptoProviderImpl, + >, + ), +} + +/// Internal version of errors which may be raised when +/// attempting to derive a new section builder from an +/// advertisement builder. +pub(crate) enum SectionBuilderError { + /// We're currently in the middle of building a section. + UnclosedActiveSection, + /// We're attempting to build a section with an identity + /// kind (public/encrypted) which doesn't match the kind + /// for the entire advertisement. + IdentityKindMismatch, + /// There isn't enough space for a new section, either + /// because the maximum section count has been exceeded + /// or because the advertisement is almost full, and + /// the minimum size of a section wouldn't fit. + NoSpaceLeft, +} + +impl From<np_adv_dynamic::extended::BoxedAddSectionError> for SectionBuilderError { + fn from(err: np_adv_dynamic::extended::BoxedAddSectionError) -> Self { + use np_adv::extended::serialize::AddSectionError; + use np_adv_dynamic::extended::BoxedAddSectionError; + match err { + BoxedAddSectionError::IdentityRequiresSaltError + | BoxedAddSectionError::Underlying(AddSectionError::IncompatibleSectionType) => { + SectionBuilderError::IdentityKindMismatch + } + BoxedAddSectionError::Underlying(AddSectionError::InsufficientAdvSpace) + | BoxedAddSectionError::Underlying(AddSectionError::MaxSectionCountExceeded) => { + SectionBuilderError::NoSpaceLeft + } + } + } +} + +/// Internal, Rust-side implementation of a V1 advertisement builder. +pub struct V1AdvertisementBuilderInternals { + // Note: This is actually always populated from an external + // perspective in the absence of panics. We only need + // the `Option` in order to be able to take ownership + // and perform a state transition when needed. + state: Option<V1AdvertisementBuilderState>, +} + +impl V1AdvertisementBuilderInternals { + pub(crate) fn new(kind: AdvertisementBuilderKind) -> Self { + let adv_type = kind.as_internal_v1(); + let builder = np_adv::extended::serialize::AdvBuilder::new(adv_type); + let builder = builder.into(); + let state = Some(V1AdvertisementBuilderState::Advertisement(builder)); + Self { state } + } + /// Internals of section_builder-type routines. Upon success, yields the index + /// of the newly-added section builder. + pub(crate) fn section_builder_internal( + &mut self, + identity: np_adv_dynamic::extended::BoxedIdentity<CryptoProviderImpl>, + ) -> Result<usize, SectionBuilderError> { + let state = self.state.take(); + match state { + Some(V1AdvertisementBuilderState::Advertisement(adv_builder)) => { + match adv_builder.into_section_builder::<CryptoProviderImpl>(identity) { + Ok(section_builder) => { + let section_index = section_builder.section_index(); + self.state = Some(V1AdvertisementBuilderState::Section(section_builder)); + Ok(section_index) + } + Err((adv_builder, err)) => { + self.state = Some(V1AdvertisementBuilderState::Advertisement(adv_builder)); + Err(err.into()) + } + } + } + x => { + // Note: Technically, this case also would leave the `None` state + // if we ever entered into it, but we never transition to that + // state during normal operation. + self.state = x; + Err(SectionBuilderError::UnclosedActiveSection) + } + } + } + + pub(crate) fn public_section_builder(&mut self) -> Result<usize, SectionBuilderError> { + let identity = np_adv_dynamic::extended::BoxedIdentity::PublicIdentity; + self.section_builder_internal(identity) + } + pub(crate) fn encrypted_section_builder( + &mut self, + broadcast_cred: V1BroadcastCredential, + identity_type: EncryptedIdentityType, + verification_mode: V1VerificationMode, + ) -> Result<usize, SectionBuilderError> { + let mut rng = get_global_crypto_rng(); + let rng = rng.get_rng(); + let identity_type = identity_type.into(); + let internal_broadcast_cred = broadcast_cred.into_internal(); + let identity = match verification_mode { + V1VerificationMode::Mic => { + let encoder = np_adv::extended::serialize::MicEncryptedSectionEncoder::< + CryptoProviderImpl, + >::new_random_salt( + rng, identity_type, &internal_broadcast_cred + ); + np_adv_dynamic::extended::BoxedIdentity::MicEncrypted(encoder) + } + V1VerificationMode::Signature => { + let encoder = np_adv::extended::serialize::SignedEncryptedSectionEncoder::< + CryptoProviderImpl, + >::new_random_salt( + rng, identity_type, &internal_broadcast_cred + ); + np_adv_dynamic::extended::BoxedIdentity::SignedEncrypted(encoder) + } + }; + self.section_builder_internal(identity) + } +} + +fn get_v1_advertisement_builder_handle_map_dimensions() -> HandleMapDimensions { + HandleMapDimensions { + num_shards: global_num_shards(), + max_active_handles: global_max_num_v1_advertisement_builders(), + } +} + +declare_handle_map! { + mod advertisement_builder { + #[dimensions = super::get_v1_advertisement_builder_handle_map_dimensions()] + type V1AdvertisementBuilderHandle: HandleLike<Object = super::V1AdvertisementBuilderInternals>; + } +} +use crate::serialize::v1::advertisement_builder::V1AdvertisementBuilderHandle; + +/// Discriminant for `CreateV1SectionBuilderResult` +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum CreateV1SectionBuilderResultKind { + /// The attempt to create a new section builder succeeded. + Success = 0, + /// We're currently in the middle of building a section. + UnclosedActiveSection = 1, + /// The advertisement builder handle was invalid. + InvalidAdvBuilderHandle = 2, + /// We're attempting to build a section with an identity + /// kind (public/encrypted) which doesn't match the kind + /// for the entire advertisement. + IdentityKindMismatch = 3, + /// There isn't enough space for a new section, either + /// because the maximum section count has been exceeded + /// or because the advertisement is almost full, and + /// the minimum size of a section wouldn't fit. + NoSpaceLeft = 4, +} + +/// The result of attempting to create a new V1 section builder. +#[repr(C)] +#[allow(missing_docs)] +pub enum CreateV1SectionBuilderResult { + Success(V1SectionBuilder), + UnclosedActiveSection, + InvalidAdvBuilderHandle, + IdentityKindMismatch, + NoSpaceLeft, +} + +impl FfiEnum for CreateV1SectionBuilderResult { + type Kind = CreateV1SectionBuilderResultKind; + fn kind(&self) -> Self::Kind { + match self { + Self::Success(_) => CreateV1SectionBuilderResultKind::Success, + Self::UnclosedActiveSection => CreateV1SectionBuilderResultKind::UnclosedActiveSection, + Self::InvalidAdvBuilderHandle => { + CreateV1SectionBuilderResultKind::InvalidAdvBuilderHandle + } + Self::IdentityKindMismatch => CreateV1SectionBuilderResultKind::IdentityKindMismatch, + Self::NoSpaceLeft => CreateV1SectionBuilderResultKind::NoSpaceLeft, + } + } +} + +impl CreateV1SectionBuilderResult { + declare_enum_cast! {into_success, Success, V1SectionBuilder} +} + +impl From<SectionBuilderError> for CreateV1SectionBuilderResult { + fn from(err: SectionBuilderError) -> Self { + match err { + SectionBuilderError::UnclosedActiveSection => Self::UnclosedActiveSection, + SectionBuilderError::IdentityKindMismatch => Self::IdentityKindMismatch, + SectionBuilderError::NoSpaceLeft => Self::NoSpaceLeft, + } + } +} + +/// Result code for [`V1SectionBuilder#add_to_advertisement`]. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum AddV1SectionToAdvertisementResult { + /// The section referenced by the given handle + /// couldn't be added to the containing advertisement, + /// possibly because the handle is invalid or the section + /// has already been added to the containing section. + Error = 0, + /// The section referenced by the given handle + /// was successfully added to the containing advertisement. + /// After obtaining this result code, the section + /// handle will no longer be valid. + Success = 1, +} + +/// Result code for operations adding DEs to a section builder. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum AddV1DEResult { + /// The DE was successfully added to the section builder + /// behind the given handle. + Success = 0, + /// The handle for the section builder was invalid. + InvalidSectionHandle = 1, + /// There was no more space left in the advertisement + /// to fit the DE in the containing section. + InsufficientSectionSpace = 2, + /// The data element itself had invalid characteristics, + /// most likely a length above 127. + InvalidDataElement = 3, +} + +/// Discriminant for `NextV1DE16ByteSaltResult`. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum NextV1DE16ByteSaltResultKind { + /// We couldn't return a 16-byte DE salt, possibly + /// because the handle to the section builder + /// was invalid, or possibly because the section + /// builder was for a public section. + Error = 0, + /// A 16-byte DE salt was returned successfully. + Success = 1, +} + +/// The result of attempting to get the derived V1 DE +/// 16-byte salt for the next-added DE to the section +/// builder behind the given handle. +#[derive(Clone, Copy)] +#[repr(C)] +#[allow(missing_docs)] +pub enum NextV1DE16ByteSaltResult { + Error, + Success([u8; 16]), +} + +impl FfiEnum for NextV1DE16ByteSaltResult { + type Kind = NextV1DE16ByteSaltResultKind; + fn kind(&self) -> Self::Kind { + match self { + Self::Error => NextV1DE16ByteSaltResultKind::Error, + Self::Success(_) => NextV1DE16ByteSaltResultKind::Success, + } + } +} + +impl NextV1DE16ByteSaltResult { + declare_enum_cast! {into_success, Success, [u8; 16] } +} + +impl From<Option<np_adv::extended::serialize::DeSalt<CryptoProviderImpl>>> + for NextV1DE16ByteSaltResult +{ + fn from(maybe_salt: Option<np_adv::extended::serialize::DeSalt<CryptoProviderImpl>>) -> Self { + match maybe_salt.and_then(|salt| salt.derive::<16>()) { + Some(salt) => NextV1DE16ByteSaltResult::Success(salt), + None => NextV1DE16ByteSaltResult::Error, + } + } +} + +/// A handle to a builder for V1 sections. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct V1SectionBuilder { + adv_builder: V1AdvertisementBuilder, + section_index: u8, +} + +impl V1SectionBuilder { + /// Attempts to add the section constructed behind this handle + /// to a section builder to the containing advertisement it + /// originated from. + pub fn add_to_advertisement(self) -> AddV1SectionToAdvertisementResult { + match self.adv_builder.handle.get_mut() { + Ok(mut adv_builder) => { + let state = adv_builder.state.take(); + match state { + Some(V1AdvertisementBuilderState::Section(section_builder)) => { + // Make sure the index of the section we're trying to close + // matches the index of the section currently under construction. + let actual_section_index = section_builder.section_index() as u8; + if self.section_index == actual_section_index { + let updated_adv_builder = section_builder.add_to_advertisement(); + adv_builder.state = Some(V1AdvertisementBuilderState::Advertisement( + updated_adv_builder, + )); + AddV1SectionToAdvertisementResult::Success + } else { + adv_builder.state = + Some(V1AdvertisementBuilderState::Section(section_builder)); + AddV1SectionToAdvertisementResult::Error + } + } + x => { + adv_builder.state = x; + AddV1SectionToAdvertisementResult::Error + } + } + } + Err(_) => AddV1SectionToAdvertisementResult::Error, + } + } + + /// Attempts to get the derived 16-byte V1 DE salt for the next + /// DE to be added to this section builder. May fail if this + /// section builder handle is invalid, or if the section + /// is a public section. + pub fn next_de_salt(&self) -> NextV1DE16ByteSaltResult { + self.try_apply_to_internals( + |section_builder| section_builder.next_de_salt().into(), + NextV1DE16ByteSaltResult::Error, + ) + } + + /// Attempts to add the given DE to the section builder behind + /// this handle. The passed DE may have a payload of up to 127 + /// bytes, the maximum for a V1 DE. + pub fn add_127_byte_buffer_de(&self, de: V1DE127ByteBuffer) -> AddV1DEResult { + match de.into_internal() { + Some(generic_de) => self + .add_de_internals(np_adv_dynamic::extended::BoxedWriteDataElement::new(generic_de)), + None => AddV1DEResult::InvalidDataElement, + } + } + + fn add_de_internals( + &self, + de: np_adv_dynamic::extended::BoxedWriteDataElement, + ) -> AddV1DEResult { + self.try_apply_to_internals( + move |section_builder| match section_builder.add_de(move |_| de) { + Ok(_) => AddV1DEResult::Success, + Err(_) => AddV1DEResult::InsufficientSectionSpace, + }, + AddV1DEResult::InvalidSectionHandle, + ) + } + + fn try_apply_to_internals<R>( + &self, + func: impl FnOnce( + &mut np_adv_dynamic::extended::BoxedSectionBuilder< + np_adv::extended::serialize::AdvBuilder, + CryptoProviderImpl, + >, + ) -> R, + invalid_handle_error: R, + ) -> R { + match self.adv_builder.handle.get_mut() { + Ok(mut adv_builder) => { + match adv_builder.state.as_mut() { + Some(V1AdvertisementBuilderState::Section(ref mut section_builder)) => { + // Check to make sure that the section index matches, otherwise + // we have an invalid handle. + let current_section_index = section_builder.section_index() as u8; + if current_section_index == self.section_index { + func(section_builder) + } else { + invalid_handle_error + } + } + Some(V1AdvertisementBuilderState::Advertisement(_)) => invalid_handle_error, + None => invalid_handle_error, + } + } + Err(_) => invalid_handle_error, + } + } +} + +/// Represents the contents of a V1 DE whose payload +/// is stored in a buffer which may contain up to 127 bytes, +/// which is the maximum for any V1 DE. +/// +/// This representation is stable, and so you may directly +/// reference this struct's fields if you wish. +#[repr(C)] +//TODO: Partial unification with `deserialize::v1::GenericV1DataElement`? +pub struct V1DE127ByteBuffer { + /// The DE type code of this generic data-element. + pub de_type: u32, + /// The raw data-element byte payload, up to + /// 127 bytes in length. + pub payload: ByteBuffer<127>, +} + +impl V1DE127ByteBuffer { + /// Attempts to convert this FFI-friendly DE with a byte-buffer size of 127 + /// to the internal representation of a generic DE. May fail in the case + /// where the underlying payload byte-buffer has an invalid length above 127. + fn into_internal(self) -> Option<np_adv::extended::data_elements::GenericDataElement> { + let de_type = np_adv::extended::de_type::DeType::from(self.de_type); + self.payload.as_slice().and_then(move |payload_slice| { + np_adv::extended::data_elements::GenericDataElement::try_from(de_type, payload_slice) + .ok() + }) + } +} diff --git a/nearby/presence/np_ffi_core/src/utils.rs b/nearby/presence/np_ffi_core/src/utils.rs index e547a89..b1495b9 100644 --- a/nearby/presence/np_ffi_core/src/utils.rs +++ b/nearby/presence/np_ffi_core/src/utils.rs @@ -37,6 +37,7 @@ pub trait FfiEnum { /// /// If the enclosing enum turns out to not be the requested /// variant, the generated method will return `None`. +#[macro_export] macro_rules! declare_enum_cast { ($projection_method_name:ident, $variant_enum_name:ident, $variant_type_name:ty) => { #[doc = concat!("Attempts to cast `self` to the `", stringify!($variant_enum_name), diff --git a/nearby/presence/np_ffi_core/src/v1.rs b/nearby/presence/np_ffi_core/src/v1.rs new file mode 100644 index 0000000..c7b564d --- /dev/null +++ b/nearby/presence/np_ffi_core/src/v1.rs @@ -0,0 +1,38 @@ +// 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. + +//! Common externally-acessible V1 constructs for both of the +//! serialization+deserialization flows. + +/// Information about the verification scheme used +/// for verifying the integrity of the contents +/// of a decrypted section. +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum V1VerificationMode { + /// Message integrity code verification. + Mic = 0, + /// Signature verification. + Signature = 1, +} + +impl From<np_adv::extended::deserialize::VerificationMode> for V1VerificationMode { + fn from(verification_mode: np_adv::extended::deserialize::VerificationMode) -> Self { + use np_adv::extended::deserialize::VerificationMode; + match verification_mode { + VerificationMode::Mic => Self::Mic, + VerificationMode::Signature => Self::Signature, + } + } +} diff --git a/nearby/presence/np_hkdf/Cargo.toml b/nearby/presence/np_hkdf/Cargo.toml index 885913f..45e40da 100644 --- a/nearby/presence/np_hkdf/Cargo.toml +++ b/nearby/presence/np_hkdf/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] diff --git a/nearby/presence/np_hkdf/benches/np_hkdf.rs b/nearby/presence/np_hkdf/benches/np_hkdf.rs index e3ce506..bec3b0e 100644 --- a/nearby/presence/np_hkdf/benches/np_hkdf.rs +++ b/nearby/presence/np_hkdf/benches/np_hkdf.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs, unused_results)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use crypto_provider::{CryptoProvider, CryptoRng}; use crypto_provider_default::CryptoProviderImpl; diff --git a/nearby/presence/np_hkdf/src/lib.rs b/nearby/presence/np_hkdf/src/lib.rs index f4f98eb..8201f38 100644 --- a/nearby/presence/np_hkdf/src/lib.rs +++ b/nearby/presence/np_hkdf/src/lib.rs @@ -16,17 +16,9 @@ //! //! All HKDF calls should happen in this module and expose the correct result type for //! each derived key use case. + #![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] - -extern crate core; + #[cfg(feature = "std")] extern crate std; diff --git a/nearby/presence/np_hkdf/src/v1_salt.rs b/nearby/presence/np_hkdf/src/v1_salt.rs index f7f4f28..94e6855 100644 --- a/nearby/presence/np_hkdf/src/v1_salt.rs +++ b/nearby/presence/np_hkdf/src/v1_salt.rs @@ -45,7 +45,7 @@ impl<C: CryptoProvider> V1Salt<C> { &[ b"V1 derived salt", &de.and_then(|d| d.offset.checked_add(1)) - .and_then(|o| o.try_into().ok()) + .map(|o| o.into()) .unwrap_or(0_u32) .to_be_bytes(), ], @@ -96,7 +96,7 @@ impl<C: CryptoProvider> fmt::Debug for V1Salt<C> { #[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 { @@ -104,7 +104,7 @@ impl DataElementOffset { 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 } @@ -116,8 +116,8 @@ impl DataElementOffset { } } -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/np_hkdf/tests/test_vectors.rs b/nearby/presence/np_hkdf/tests/test_vectors.rs index 380c107..175e73e 100644 --- a/nearby/presence/np_hkdf/tests/test_vectors.rs +++ b/nearby/presence/np_hkdf/tests/test_vectors.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::indexing_slicing, clippy::unwrap_used)] + use anyhow::anyhow; use crypto_provider::aes::AesKey; use crypto_provider_default::CryptoProviderImpl; @@ -28,7 +30,7 @@ fn hkdf_test_vectors() -> Result<(), anyhow::Error> { test_helper::get_data_file("presence/np_hkdf/resources/test/hkdf-test-vectors.json"); let mut file = fs::File::open(full_path)?; let mut data = String::new(); - file.read_to_string(&mut data)?; + let _ = file.read_to_string(&mut data)?; let test_cases = match serde_json::de::from_str(&data)? { serde_json::Value::Array(a) => a, _ => return Err(anyhow!("bad json")), diff --git a/nearby/presence/rand_ext/Cargo.toml b/nearby/presence/rand_ext/Cargo.toml index bbdb5af..79a8ea8 100644 --- a/nearby/presence/rand_ext/Cargo.toml +++ b/nearby/presence/rand_ext/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] crypto_provider.workspace = true log.workspace = true diff --git a/nearby/presence/rand_ext/src/lib.rs b/nearby/presence/rand_ext/src/lib.rs index 9819c6b..d767143 100644 --- a/nearby/presence/rand_ext/src/lib.rs +++ b/nearby/presence/rand_ext/src/lib.rs @@ -14,8 +14,6 @@ //! Helper functions around `rand`'s offerings for convenient test usage. #![no_std] -#![forbid(unsafe_code)] -#![deny(missing_docs)] extern crate alloc; diff --git a/nearby/presence/sink/Cargo.toml b/nearby/presence/sink/Cargo.toml index a0322b6..a479205 100644 --- a/nearby/presence/sink/Cargo.toml +++ b/nearby/presence/sink/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] tinyvec.workspace = true diff --git a/nearby/presence/sink/src/lib.rs b/nearby/presence/sink/src/lib.rs index 75c7c1d..fe6e5b6 100644 --- a/nearby/presence/sink/src/lib.rs +++ b/nearby/presence/sink/src/lib.rs @@ -14,15 +14,8 @@ //! A no_std-friendly data-writing "sink" trait which allows for convenient expression //! of "write me into a limited-size buffer"-type methods on traits. + #![cfg_attr(not(feature = "std"), no_std)] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::indexing_slicing, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used -)] /// An append-only, limited-size collection. pub trait Sink<T> { diff --git a/nearby/presence/test_helper/Cargo.toml b/nearby/presence/test_helper/Cargo.toml index 488ea74..c52d6bf 100644 --- a/nearby/presence/test_helper/Cargo.toml +++ b/nearby/presence/test_helper/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] hex.workspace = true serde_json.workspace = true diff --git a/nearby/presence/test_helper/src/lib.rs b/nearby/presence/test_helper/src/lib.rs index 7c0694f..8b42623 100644 --- a/nearby/presence/test_helper/src/lib.rs +++ b/nearby/presence/test_helper/src/lib.rs @@ -11,11 +11,11 @@ // 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. -#![forbid(unsafe_code)] -#![deny(missing_docs)] //! Helper crate for common functions used in testing +#![allow(clippy::unwrap_used, clippy::expect_used)] + use std::fs; use std::io::Read; @@ -33,7 +33,7 @@ pub fn load_data_file_contents_as_string(file: &str) -> String { let full_path = get_data_file(file); let mut file = fs::File::open(full_path).expect("Should be able to open data file"); let mut data = String::new(); - file.read_to_string(&mut data).expect("should be able to read data file"); + let _ = file.read_to_string(&mut data).expect("should be able to read data file"); data } @@ -45,12 +45,12 @@ pub fn parse_json_data_file(file: &str) -> serde_json::Value { /// extract a string from a jsonvalue pub fn extract_key_str<'a>(value: &'a serde_json::Value, key: &str) -> &'a str { - value[key].as_str().unwrap() + value.get(key).unwrap().as_str().unwrap() } /// Decode a hex-encoded vec at `key` pub fn extract_key_vec(value: &serde_json::Value, key: &str) -> Vec<u8> { - hex::decode(value[key].as_str().unwrap()).unwrap() + hex::decode(value.get(key).unwrap().as_str().unwrap()).unwrap() } /// Decode a hex-encoded array at `key` diff --git a/nearby/presence/xts_aes/Cargo.toml b/nearby/presence/xts_aes/Cargo.toml index d0b88b6..86745c4 100644 --- a/nearby/presence/xts_aes/Cargo.toml +++ b/nearby/presence/xts_aes/Cargo.toml @@ -4,6 +4,9 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [features] default = [] std = [] @@ -14,9 +17,10 @@ crypto_provider.workspace = true 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..a08ab7c 100644 --- a/nearby/presence/xts_aes/fuzz/Cargo.lock +++ b/nearby/presence/xts_aes/fuzz/Cargo.lock @@ -25,6 +25,20 @@ dependencies = [ ] [[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 @@ dependencies = [ [[package]] name = "crypto_provider" version = "0.1.0" +dependencies = [ + "tinyvec", +] [[package]] name = "crypto_provider_rustcrypto" @@ -164,6 +181,7 @@ version = "0.1.0" dependencies = [ "aead", "aes", + "aes-gcm", "aes-gcm-siv", "cbc", "cfg-if", @@ -193,9 +211,9 @@ dependencies = [ [[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,14 +279,15 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-rc.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "sha2", + "subtle", ] [[package]] @@ -329,6 +348,16 @@ dependencies = [ ] [[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" @@ -531,9 +560,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[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", @@ -564,6 +593,12 @@ dependencies = [ ] [[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 +634,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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 0d8d1fd..e8a80a1 100644 --- a/nearby/presence/xts_aes/src/lib.rs +++ b/nearby/presence/xts_aes/src/lib.rs @@ -12,21 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![no_std] -#![forbid(unsafe_code)] -#![deny( - missing_docs, - clippy::unwrap_used, - clippy::panic, - clippy::expect_used, - clippy::indexing_slicing -)] - //! Implementation of the XTS-AES tweakable block cipher. //! //! See NIST docs [here](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf) //! and [here](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38e.pdf). +#![no_std] + #[cfg(feature = "std")] extern crate std; @@ -34,7 +26,7 @@ use array_ref::{array_mut_ref, array_ref}; 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 +50,7 @@ mod tweak_tests; /// 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 +96,6 @@ pub struct XtsDecrypter<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> { } #[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 +109,24 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> { 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 +134,7 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> { }; // 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 +143,7 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> { // 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 @@ -186,7 +181,6 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> { } #[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 +192,13 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> { 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 +211,7 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> { 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,21 +219,40 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> { }; // 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)); } @@ -289,10 +304,10 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey> } #[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 +326,9 @@ impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey> } #[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 +357,8 @@ pub trait XtsKey: for<'a> TryFrom<&'a [u8], Error = Self::TryFromError> { 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 +382,7 @@ impl TryFrom<&[u8]> for XtsAes128Key { 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 +391,10 @@ impl TryFrom<&[u8]> for XtsAes128Key { #[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 +422,8 @@ impl TweakableBlockCipherKey for XtsAes128Key { } } +const AES_256_KEY_SIZE: usize = 32; + /// An XTS-AES-256 key. pub struct XtsAes256Key { key_1: <Self as XtsKey>::BlockCipherKey, @@ -425,7 +447,7 @@ impl TryFrom<&[u8]> for XtsAes256Key { 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 +456,10 @@ impl TryFrom<&[u8]> for XtsAes256Key { #[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 +502,18 @@ impl XtsKeyTryFromSliceError { /// 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 +530,31 @@ pub(crate) struct TweakState { /// 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 +562,17 @@ impl TweakState { /// /// # 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 +588,11 @@ struct XtsEncrypterTweaked<'a, A: Aes> { 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 +611,7 @@ struct XtsDecrypterTweaked<'a, A: Aes> { 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. @@ -605,7 +623,7 @@ impl<'a, A: Aes> XtsDecrypterTweaked<'a, A> { 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 +635,7 @@ impl<'a, A: Aes> XtsDecrypterTweaked<'a, A> { /// 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 +644,6 @@ fn array_xor(base: &mut crypto_provider::aes::AesBlock, rhs: &crypto_provider::a } } -/// 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/compare_with_xts_mode_test.rs b/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs index 94ed066..abe1077 100644 --- a/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs +++ b/nearby/presence/xts_aes/tests/compare_with_xts_mode_test.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use aes::{cipher, cipher::KeyInit as _}; use alloc::vec::Vec; use crypto_provider::aes::*; 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..1f92f89 --- /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. + +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)] + +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/presence/xts_aes/tests/xts_nist_test_vectors.rs b/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs index 3d0cb50..6ceb8b9 100644 --- a/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs +++ b/nearby/presence/xts_aes/tests/xts_nist_test_vectors.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate core; +#![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)] use anyhow::anyhow; use crypto_provider::CryptoProvider; @@ -225,7 +225,7 @@ impl<I: Iterator<Item = String>> Iterator for TestVectorFileIterator<I> { // `key = value` in a test case chunk if let Some(captures) = regex::Regex::new("^(.*) = (.*)$").unwrap().captures(&line) { - map.insert( + let _ = map.insert( captures.get(1).unwrap().as_str().to_owned(), captures.get(2).unwrap().as_str().to_owned(), ); diff --git a/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs b/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs index 0e28dfa..924b665 100644 --- a/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs +++ b/nearby/presence/xts_aes/tests/xts_roundtrip_tests.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(clippy::unwrap_used)] + use alloc::vec::Vec; use crypto_provider::aes::*; use crypto_provider::CryptoProvider; diff --git a/nearby/rustfmt.toml b/nearby/rustfmt.toml index 10c1698..d45a5bf 100644 --- a/nearby/rustfmt.toml +++ b/nearby/rustfmt.toml @@ -1,3 +1,3 @@ edition = "2021" newline_style = "Unix" -use_small_heuristics = "Max" +use_small_heuristics = "Max"
\ No newline at end of file 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 @@ use std::{ 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 @@ pub fn build_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow:: 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 @@ pub fn build_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow:: 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 @@ pub fn prepare_patched_rust_openssl(root: &Path) -> anyhow::Result<()> { 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 @@ You can now build and test with boringssl using the following command 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 c1b8073..01fe58a 100644 --- a/nearby/src/ffi.rs +++ b/nearby/src/ffi.rs @@ -12,37 +12,42 @@ // 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 - "check --quiet", + // 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", @@ -52,16 +57,13 @@ pub fn check_ldt_ffi(root: &path::Path) -> anyhow::Result<()> { "check --quiet --features=std", // Turn off default features and try to build with std", "check --quiet --no-default-features --features=std", - // Turn off RustCrypto and use openssl - "check --quiet --no-default-features --features=openssl", // Turn off RustCrypto and use boringssl - "--config .cargo/config-boringssl.toml check --quiet --no-default-features --features=boringssl", + "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,53 +72,91 @@ pub fn check_ldt_ffi(root: &path::Path) -> anyhow::Result<()> { Ok(()) } -pub fn check_cmake_projects(root: &path::Path) -> anyhow::Result<()> { - // plain rustcrypto build to prepare a .a for the np_cpp_ffi tests below - // TODO: make this a target in the cmake build so there isn't an implicit dependency - let mut ldt_ffi_crate_dir = root.to_path_buf(); - ldt_ffi_crate_dir.push("presence/ldt_np_adv_ffi"); - let mut c_ffi_crate_dir = root.to_path_buf(); - c_ffi_crate_dir.push("presence/np_c_ffi"); - run_cmd_shell(&ldt_ffi_crate_dir, "cargo build --quiet --release")?; - run_cmd_shell(&c_ffi_crate_dir, "cargo build --quiet --release")?; - - 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)?; + 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 ..", )?; - run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?; - // 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")?; + // 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 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 --quiet --release", + format!("build {locked_arg} --quiet --release"), // test with std and default features - "build --quiet --features std --release", + format!("build {locked_arg} --quiet --features std --release"), // test with boringssl crypto feature flag - "--config .cargo/config-boringssl.toml build --quiet --no-default-features --features boringssl --release", - // test with openssl feature flag - "build --quiet --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 --quiet --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 ldt_np_c_sample/tests/ldt_ffi_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 bfb4bcc..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<()> { diff --git a/nearby/src/jni.rs b/nearby/src/jni.rs index 903ac6d..efd6e6c 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,17 @@ pub fn check_ldt_jni(root: &path::Path) -> anyhow::Result<()> { 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(()) +} +pub fn run_ukey2_jni_tests(root: &path::Path) -> anyhow::Result<()> { + let ukey2_jni_path = root.to_path_buf().join("connections/ukey2/ukey2_jni/java"); + run_cmd_shell(&ukey2_jni_path, "./gradlew :test")?; Ok(()) } diff --git a/nearby/src/license.rs b/nearby/src/license.rs index 657a62e..387ca27 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 @@ pub(crate) fn check_license_headers(root: &path::Path) -> anyhow::Result<()> { 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 @@ pub(crate) fn add_license_headers(root: &path::Path) -> anyhow::Result<()> { 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 @@ fn license_ignore_dirs() -> Vec<&'static str> { "**/.DS_Store", "**/fuzz/corpus/**", "**/.*.swp", - "**/Session.vim", + "**/*.vim", "**/*.properties", "**/third_party/**", "**/*.png", @@ -99,5 +106,8 @@ fn license_ignore_dirs() -> Vec<&'static str> { "**/node_modules/**", "**/.angular/**", "**/.editorconfig", + "**/*.class", + "**/fuzz/artifacts/**", + "**/cmake-build-debug/**", ] } diff --git a/nearby/src/main.rs b/nearby/src/main.rs index ab55b32..1d1ac16 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,31 @@ fn main() -> anyhow::Result<()> { ); 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::CleanEverything => clean_everything(&root_dir)?, 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::CheckBoringssl(ref bssl_options) => { - crypto_ffi::check_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::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::RunUkey2JniTests => jni::run_ukey2_jni_tests(&root_dir)?, 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(()) @@ -79,6 +80,9 @@ pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Res // 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) concat!( "RUSTDOCFLAGS='--deny warnings -Z unstable-options --enable-index-page --generate-link-to-definition' ", @@ -93,24 +97,33 @@ pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Res Ok(()) } -pub fn check_everything( - root: &path::Path, - check_options: &CheckOptions, - bssl_options: &BuildBoringSslOptions, -) -> anyhow::Result<()> { + +/// Runs checks to ensure lints are passing and all targets are building +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)?; + jni::run_ukey2_jni_tests(root)?; + ukey2::check_ukey2_ffi(root, &check_options.cargo_options)?; fuzzers::run_rust_fuzzers(root)?; fuzzers::build_ffi_fuzzers(root)?; Ok(()) } +pub fn clean_everything(root: &path::Path) -> anyhow::Result<()> { + run_cmd_shell(root, "cargo clean")?; + run_cmd_shell(&root.join("presence/ldt_np_adv_ffi"), "cargo clean")?; + run_cmd_shell(&root.join("presence/np_c_ffi"), "cargo clean")?; + run_cmd_shell(&root.join("crypto/crypto_provider_boringssl"), "cargo clean")?; + run_cmd_shell(&root.join("connections/ukey2/ukey2_c_ffi"), "cargo clean")?; + Ok(()) +} + #[derive(clap::Parser)] struct Cli { #[clap(subcommand)] @@ -123,56 +136,63 @@ enum Subcommand { CheckEverything { #[command(flatten)] check_options: CheckOptions, - #[command(flatten)] - bssl_options: BuildBoringSslOptions, }, + /// Cleans the main workspace and all sub projects - useful if upgrading rust compiler version + /// and need dependencies to be compiled with the same version + CleanEverything, /// 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, + /// Checks the build of the ukey2_jni wrapper and runs tests + RunUkey2JniTests, } #[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 e2547e7..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 --quiet --release --lib")?; + run_cmd_shell(&ffi_dir, format!("cargo build {locked_arg} --quiet --release --lib"))?; // OpenSSL - run_cmd_shell(&ffi_dir, "cargo build --quiet --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 --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 index 8f00bf7..13973d4 100644 --- a/nearby/presence/handle_map/Cargo.toml +++ b/nearby/util/handle_map/Cargo.toml @@ -4,15 +4,15 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] -hashbrown.workspace = true -lock_api.workspace = true -portable-atomic.workspace = true -spin.workspace = true -crypto_provider.workspace = true +lock_adapter.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 index f1ee427..f1988ba 100644 --- a/nearby/presence/handle_map/benches/benches.rs +++ b/nearby/util/handle_map/benches/benches.rs @@ -11,6 +11,9 @@ // 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. + +#![allow(missing_docs, unused_results, clippy::unwrap_used)] + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use handle_map::*; use std::sync::Arc; @@ -45,7 +48,7 @@ fn single_threaded_read_benchmark(c: &mut Criterion) { let handle = handle_map.allocate(|| 0xFF).unwrap(); let handle_map_ref = &handle_map; // Perform repeated reads - c.bench_function("single-threaded reads", |b| { + let _ = c.bench_function("single-threaded reads", |b| { b.iter(|| { let guard = handle_map_ref.get(black_box(handle)).unwrap(); black_box(*guard) 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..c19a012 --- /dev/null +++ b/nearby/util/handle_map/src/declare_handle_map.rs @@ -0,0 +1,168 @@ +// 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 try_allocate<E: core::fmt::Debug>( + initial_value_provider: impl FnOnce() -> Result<$wrapped_type, E>, + ) -> Result<Self, $crate::HandleMapTryAllocateError<E>> { + GLOBAL_HANDLE_MAP + .try_allocate(initial_value_provider) + .map(|derived_handle| Self { handle_id: derived_handle.get_id() }) + } + 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..0c8c9d7 --- /dev/null +++ b/nearby/util/handle_map/src/guard.rs @@ -0,0 +1,141 @@ +// 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 { + #[allow(clippy::expect_used)] + arg.get(&self.handle).expect("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.") + } + + fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret { + #[allow(clippy::expect_used)] + arg.get_mut(&self.handle).expect("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.") + } +} + +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 { + #[allow(clippy::expect_used)] + arg.get(&self.handle).expect("Caller must verify that provided hande exists") + } + + fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret { + #[allow(clippy::expect_used)] + arg.get_mut(&self.handle).expect("Caller must verify that provided hande exists") + } +} diff --git a/nearby/util/handle_map/src/lib.rs b/nearby/util/handle_map/src/lib.rs new file mode 100644 index 0000000..22009c1 --- /dev/null +++ b/nearby/util/handle_map/src/lib.rs @@ -0,0 +1,302 @@ +// 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. + +use core::fmt::Debug; +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; + +/// Errors which may be raised while attempting to allocate +/// a handle from contents given by a (fallible) value-provider. +#[derive(Debug)] +pub enum HandleMapTryAllocateError<E: Debug> { + /// The call to the value-provider for the allocation failed. + ValueProviderFailed(E), + /// We couldn't reserve a spot for the allocation, because + /// the handle-map was full. + HandleMapFull, +} + +/// 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`. + /// + /// If you want the passed closure to be able to possibly fail, see + /// [`Self::try_allocate`] instead. + pub fn allocate( + &self, + initial_value_provider: impl FnOnce() -> T, + ) -> Result<Handle, HandleMapFullError> { + let wrapped_value_provider = move || Ok(initial_value_provider()); + self.try_allocate::<core::convert::Infallible>(wrapped_value_provider).map_err( + |e| match e { + HandleMapTryAllocateError::ValueProviderFailed(never) => match never {}, + HandleMapTryAllocateError::HandleMapFull => HandleMapFullError, + }, + ) + } + + /// Attempts to allocate 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 `HandleMapTryAllocateError::HandleMapFull`, + /// or if the passed initial-value provider fails, in which case this + /// will return the error wrapped in `HandleMapTryAllocateError::ValueProviderFailed`. + /// + /// If your initial-value provider is infallible, see [`Self::allocate`] instead. + pub fn try_allocate<E: Debug>( + &self, + initial_value_provider: impl FnOnce() -> Result<T, E>, + ) -> Result<Handle, HandleMapTryAllocateError<E>> { + 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. + #[allow(clippy::expect_used)] + let shard_allocate_result = self + .handle_map_shards + .get(shard_index) + .expect("Shard index is always within range") + .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::ValueProviderFailed(e)) => { + return Err(HandleMapTryAllocateError::ValueProviderFailed(e)) + } + Err(ShardAllocationError::ExceedsAllocationLimit) => { + return Err(HandleMapTryAllocateError::HandleMapFull); + } + 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); + #[allow(clippy::expect_used)] + self.handle_map_shards + .get(shard_index) + .expect("shard index is always within range") + .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); + #[allow(clippy::expect_used)] + self.handle_map_shards + .get(shard_index) + .expect("shard_index is always in range") + .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); + #[allow(clippy::expect_used)] + self.handle_map_shards + .get(shard_index) + .expect("shard index is always in range") + .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 (fallible) + /// provider to construct the underlying stored object as + /// a new entry into the global handle table for this type. + fn try_allocate<E: Debug>( + initial_value_provider: impl FnOnce() -> Result<Self::Object, E>, + ) -> Result<Self, HandleMapTryAllocateError<E>>; + + /// Tries to allocate a new handle using the given (infallible) + /// 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..e5aad2f --- /dev/null +++ b/nearby/util/handle_map/src/shard.rs @@ -0,0 +1,198 @@ +// 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, E, F: FnOnce() -> Result<T, E>> { + /// 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 for when the initial value-provider call failed. + ValueProviderFailed(E), +} + +/// 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. + #[allow(clippy::expect_used)] + let removed_object = map_read_write_guard + .deref_mut() + .remove(&handle) + .expect("existence of handle is checked above"); + // 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<E, F>( + &self, + handle: Handle, + object_provider: F, + outstanding_allocations_counter: &AtomicU32, + max_active_handles: u32, + ) -> Result<(), ShardAllocationError<T, E, F>> + where + F: FnOnce() -> Result<T, E>, + { + 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, + // so attempt to call the value-provider. + match object_provider() { + Ok(object) => { + // Successfully obtained the initial value, + // so insert it into the vacant entry. + let _ = vacant_entry.insert(object); + Ok(()) + } + Err(e) => Err(ShardAllocationError::ValueProviderFailed(e)), + } + } + 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 index d97657f..90e773a 100644 --- a/nearby/presence/handle_map/src/tests.rs +++ b/nearby/util/handle_map/src/tests.rs @@ -11,9 +11,13 @@ // 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. + +#![allow(clippy::unwrap_used, clippy::expect_used)] + use crate::*; -use hashbrown::HashSet; +use core::ops::{Deref, DerefMut}; +use std::collections::HashSet; use std::sync::Arc; use std::thread; @@ -110,7 +114,7 @@ fn test_overload_allocations_deallocations() { let test_fn = Arc::new(move || { let allocation_result = handle_map_function_ref.allocate(|| 0xFF); if let Ok(handle) = allocation_result { - handle_map_function_ref.deallocate(handle).unwrap(); + let _ = handle_map_function_ref.deallocate(handle).unwrap(); } }); test_for_each_thread(test_fn, num_repetitions_per_thread); @@ -122,7 +126,7 @@ fn test_overload_allocations_deallocations() { assert_eq!((MAX_ACTIVE_HANDLES - 1) as usize, actual_num_active_handles); //Verify that we still have space for one more entry after all that. - handle_map_post_function_ref.allocate(|| 0xEE).unwrap(); + let _ = handle_map_post_function_ref.allocate(|| 0xEE).unwrap(); } /// Tests the progress of allocate/read/write/read/deallocate @@ -218,7 +222,7 @@ fn test_non_overwriting_old_handles() { let mut handle_map = build_handle_map::<u8>(); for _ in 0..(num_repetitions_per_thread * NUM_ACTIVE_THREADS) { let handle = handle_map.allocate(|| 0xFF).expect("Initial allocations shouldn't fail"); - all_handles.insert(handle); + let _ = all_handles.insert(handle); } // Reset the new-handle-id counter handle_map.set_new_handle_id_counter(0); @@ -253,8 +257,9 @@ fn test_non_overwriting_old_handles() { fn test_id_wraparound() { let mut handle_map = build_handle_map::<u8>(); handle_map.set_new_handle_id_counter(u64::MAX); - handle_map.allocate(|| 0xAB).expect("Counter wrap-around allocation should not fail"); - handle_map.allocate(|| 0xCD).expect("Post-counter-wrap-around allocation should not fail"); + let _ = handle_map.allocate(|| 0xAB).expect("Counter wrap-around allocation should not fail"); + let _ = + handle_map.allocate(|| 0xCD).expect("Post-counter-wrap-around allocation should not fail"); } #[test] diff --git a/nearby/connections/ukey2/lock_adapter/Cargo.toml b/nearby/util/lock_adapter/Cargo.toml index 713f224..6e3174b 100644 --- a/nearby/connections/ukey2/lock_adapter/Cargo.toml +++ b/nearby/util/lock_adapter/Cargo.toml @@ -4,10 +4,13 @@ version.workspace = true edition.workspace = true publish.workspace = true +[lints] +workspace = true + [dependencies] 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 index 098031b..6bf6f0f 100644 --- a/nearby/connections/ukey2/lock_adapter/src/lib.rs +++ b/nearby/util/lock_adapter/src/lib.rs @@ -12,6 +12,9 @@ // 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 + #![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 +61,23 @@ pub trait NoPoisonMutex<T> { /// 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 index 91b8fa0..55841b9 100644 --- a/remoteauth/Cargo.lock +++ b/remoteauth/Cargo.lock @@ -3,149 +3,261 @@ 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 = "async-trait" -version = "0.1.72" +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 = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +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 = "autocfg" -version = "1.1.0" +name = "clap_lex" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] -name = "ctap_protocol" +name = "cmd-runner" version = "0.1.0" dependencies = [ "anyhow", + "owo-colors", + "shell-escape", ] [[package]] -name = "futures" -version = "0.3.28" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "ctap_protocol" +version = "0.1.0" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "anyhow", ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "env_logger" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "futures-core", - "futures-sink", + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] -name = "futures-core" -version = "0.3.28" +name = "errno" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] [[package]] -name = "futures-executor" -version = "0.3.28" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "futures-core", - "futures-task", - "futures-util", + "cc", + "libc", ] [[package]] -name = "futures-io" -version = "0.3.28" +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 = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] -name = "futures-macro" -version = "0.3.28" +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "proc-macro2", - "quote", - "syn", + "hermit-abi", + "rustix", + "windows-sys", ] [[package]] -name = "futures-sink" -version = "0.3.28" +name = "libc" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] -name = "futures-task" -version = "0.3.28" +name = "linux-raw-sys" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] -name = "futures-util" -version = "0.3.28" +name = "log" +version = "0.4.20" 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", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae" [[package]] -name = "pin-project-lite" -version = "0.2.12" +name = "once_cell" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "owo-colors" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "platform" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", - "futures", ] [[package]] @@ -167,24 +279,67 @@ dependencies = [ ] [[package]] -name = "remote_auth_protool" +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", - "async-trait", - "futures", ] [[package]] -name = "slab" -version = "0.4.8" +name = "rustix" +version = "0.38.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" dependencies = [ - "autocfg", + "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" @@ -196,7 +351,119 @@ dependencies = [ ] [[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 index ca3213b..20b2ec3 100644 --- a/remoteauth/Cargo.toml +++ b/remoteauth/Cargo.toml @@ -2,7 +2,7 @@ members = [ "ctap_protocol", "platform", - "remote_auth_protool", + "remote_auth_protocol", ] [workspace.package] @@ -12,5 +12,16 @@ publish = false [workspace.dependencies] anyhow = "1.0.72" -async-trait = "0.1.72" -futures = "0.3.28" + +[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/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/remoteauth/ctap_protocol/src/lib.rs b/remoteauth/ctap_protocol/src/lib.rs index 9b7eef2..b42193e 100644 --- a/remoteauth/ctap_protocol/src/lib.rs +++ b/remoteauth/ctap_protocol/src/lib.rs @@ -17,74 +17,4 @@ //! 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 messages. -#[derive(Debug, PartialEq)] -pub enum CtapMessage { - AuthenticatorReset, -} - -impl CtapMessage { - /// Converts the given CTAP message into its binary representation. - pub fn to_bytes(&self) -> Vec<u8> { - match self { - CtapMessage::AuthenticatorReset => vec![0x07], - } - } - - /// Convert a binary message to its Rust representation. - pub fn from_bytes(bytes: Vec<u8>) -> anyhow::Result<CtapMessage> { - if bytes.len() == 0 { - Err(anyhow!("Binary message was empty.")) - } else { - match bytes[0] { - 0x07 => Ok(CtapMessage::AuthenticatorReset), - _ => Err(anyhow!("Unknown message type.")), - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn translate_message_to_bytes() { - let message = CtapMessage::AuthenticatorReset; - let bytes = message.to_bytes(); - - assert_eq!(bytes, vec![0x07]); - } - - #[test] - fn translate_bytes_to_message() { - let bytes = vec![0x07]; - let message = CtapMessage::from_bytes(bytes); - - assert_eq!(message.is_ok(), true); - assert_eq!(message.unwrap(), CtapMessage::AuthenticatorReset); - } - - #[test] - fn translate_empty_bytes() { - let bytes = vec![]; - let message = CtapMessage::from_bytes(bytes); - - assert_eq!(message.is_err(), true); - assert_eq!( - message.unwrap_err().to_string(), - "Binary message was empty." - ); - } - - #[test] - fn translate_unknown_message() { - let bytes = vec![0x01]; - let message = CtapMessage::from_bytes(bytes); - - assert_eq!(message.is_err(), true); - assert_eq!(message.unwrap_err().to_string(), "Unknown message type."); - } -} +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/remoteauth/platform/Cargo.toml b/remoteauth/platform/Cargo.toml index 730f196..0136c27 100644 --- a/remoteauth/platform/Cargo.toml +++ b/remoteauth/platform/Cargo.toml @@ -8,5 +8,3 @@ publish.workspace = true [dependencies] anyhow.workspace = true -async-trait.workspace = true -futures.workspace = true diff --git a/remoteauth/platform/src/lib.rs b/remoteauth/platform/src/lib.rs index 286ca58..798c756 100644 --- a/remoteauth/platform/src/lib.rs +++ b/remoteauth/platform/src/lib.rs @@ -12,14 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # RemoteAuthPlatform -//! -//! This trait represents the capabilities that RemoteAuth requires from the platform. +pub mod listeners; -use async_trait::async_trait; +use crate::listeners::SendRequestListener; -#[async_trait] +/// # 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. - async fn send_request(&self, connection_id: i32, request: &[u8]) -> anyhow::Result<Vec<u8>>; + fn send_request( + &self, + connection_id: i32, + request: &[u8], + listener: Box<dyn SendRequestListener + Send>, + ); } diff --git a/nearby/connections/ukey2/lock_adapter/src/spin.rs b/remoteauth/platform/src/listeners.rs index be363d4..e354fea 100644 --- a/nearby/connections/ukey2/lock_adapter/src/spin.rs +++ b/remoteauth/platform/src/listeners.rs @@ -12,22 +12,8 @@ // 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)) - } +/// 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 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 @@ publish.workspace = true # 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_protool/src/lib.rs b/remoteauth/remote_auth_protocol/src/lib.rs index f019785..bc8315b 100644 --- a/remoteauth/remote_auth_protool/src/lib.rs +++ b/remoteauth/remote_auth_protocol/src/lib.rs @@ -16,7 +16,7 @@ pub mod remote_auth_service; /// Struct representing the remote device. pub struct RemoteDevice { - id: i32, + pub id: i32, } /// Trait to be implemented by anything that wants to be notified when remote devices are discovered diff --git a/remoteauth/remote_auth_protool/src/remote_auth_service.rs b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs index 785976d..5da33c7 100644 --- a/remoteauth/remote_auth_protool/src/remote_auth_service.rs +++ b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs @@ -47,13 +47,13 @@ impl<'a, T: DeviceDiscoveryListener + PartialEq> DiscoveryPublisher<'a, T> fn device_discovered(&mut self, remote_device: &RemoteDevice) { for listener in self.listeners.iter_mut() { - listener.on_discovered(&remote_device); + 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); + listener.on_lost(remote_device); } } @@ -64,6 +64,12 @@ impl<'a, T: DeviceDiscoveryListener + PartialEq> DiscoveryPublisher<'a, T> } } +impl<'a, T: DeviceDiscoveryListener + PartialEq> Default for RemoteAuthService<'a, T> { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; @@ -198,12 +204,11 @@ mod tests { fn single_listener_timeout_notification() { let mut listener = TestListener::new(); let mut ras = RemoteAuthService::new(); - let remote_device = RemoteDevice { id: 42 }; ras.add_listener(&mut listener); ras.timed_out(); - assert_eq!(listener.got_timeout, true); + assert!(listener.got_timeout); } #[test] @@ -211,14 +216,13 @@ mod tests { 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.timed_out(); - assert_eq!(listener1.got_timeout, true); - assert_eq!(listener2.got_timeout, true); + assert!(listener1.got_timeout); + assert!(listener2.got_timeout); } #[test] diff --git a/remoteauth/remote_auth_protool/Cargo.toml b/remoteauth/remote_auth_protool/Cargo.toml deleted file mode 100644 index f6c03c3..0000000 --- a/remoteauth/remote_auth_protool/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "remote_auth_protool" -version.workspace = true -edition.workspace = true -publish.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow.workspace = true -async-trait.workspace = true -futures.workspace = true 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(()) +} |