Project import generated by Copybara.
GitOrigin-RevId: 06b0c815d958bef0a60978d0766d65af839e37b6
Change-Id: Ifac0c45ae509ca8a8ed793fac5c981d4e23d3bf9
diff --git a/.gitmodules b/.gitmodules
index caccb04..937bff2 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,9 @@
[submodule "third_party/abseil-cpp"]
path = third_party/abseil-cpp
url = https://github.com/abseil/abseil-cpp.git
+[submodule "third_party/benchmark"]
+ path = third_party/benchmark
+ url = https://github.com/google/benchmark.git
+[submodule "third_party/boringssl"]
+ path = third_party/boringssl
+ url = https://boringssl.googlesource.com/boringssl
diff --git a/Dockerfile b/Dockerfile
index 00dffc7..1f0462e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,7 +21,7 @@
RUN apt upgrade -y
# install cargo with default settings
-RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.68.1
+RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.72.0
ENV PATH="/root/.cargo/bin:${PATH}"
RUN cargo install --locked cargo-deny --color never 2>&1
RUN cargo install cargo-fuzz --color never 2>&1
@@ -36,6 +36,7 @@
RUN cargo install bindgen-cli --version 0.64.0
RUN cargo install wasm-pack --color never 2>&1
RUN rustup toolchain add nightly
+RUN rustup target add wasm32-unknown-unknown
# boringssl build wants go
RUN curl -L https://go.dev/dl/go1.20.2.linux-amd64.tar.gz | tar -C /usr/local -xz
ENV PATH="$PATH:/usr/local/go/bin"
diff --git a/cmd-runner/Cargo.lock b/cmd-runner/Cargo.lock
new file mode 100644
index 0000000..4dadf2e
--- /dev/null
+++ b/cmd-runner/Cargo.lock
@@ -0,0 +1,30 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "cmd-runner"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "owo-colors",
+ "shell-escape",
+]
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "shell-escape"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
diff --git a/cmd-runner/Cargo.toml b/cmd-runner/Cargo.toml
new file mode 100644
index 0000000..e79317e
--- /dev/null
+++ b/cmd-runner/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "cmd-runner"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+anyhow = "1.0.64"
+shell-escape = "0.1.5"
+owo-colors = "3.5.0"
+
diff --git a/nearby/src/support.rs b/cmd-runner/src/lib.rs
similarity index 91%
rename from nearby/src/support.rs
rename to cmd-runner/src/lib.rs
index cc81352..fa2f1f4 100644
--- a/nearby/src/support.rs
+++ b/cmd-runner/src/lib.rs
@@ -33,7 +33,12 @@
dir: &path::Path,
cmd: impl AsRef<ffi::OsStr>,
) -> anyhow::Result<SuccessOutput> {
- run::<C>(dir, process::Command::new("sh").current_dir(dir).args(["-c".as_ref(), cmd.as_ref()]))
+ run::<C>(
+ dir,
+ process::Command::new("sh")
+ .current_dir(dir)
+ .args(["-c".as_ref(), cmd.as_ref()]),
+ )
}
/// Run a cmd with explicit args directly without a shell.
@@ -53,7 +58,12 @@
A: Clone + IntoIterator<Item = S>,
S: AsRef<ffi::OsStr>,
{
- run::<C>(dir, process::Command::new(cmd.as_ref()).current_dir(dir).args(args))
+ run::<C>(
+ dir,
+ process::Command::new(cmd.as_ref())
+ .current_dir(dir)
+ .args(args),
+ )
}
/// Run the specified command.
@@ -73,8 +83,16 @@
},
);
- let context = format!("{} [{}]", cmd_with_args.to_string_lossy(), dir.to_string_lossy(),);
- println!("[{}] [{}]", cmd_with_args.to_string_lossy().green(), dir.to_string_lossy().blue());
+ let context = format!(
+ "{} [{}]",
+ cmd_with_args.to_string_lossy(),
+ dir.to_string_lossy(),
+ );
+ println!(
+ "[{}] [{}]",
+ cmd_with_args.to_string_lossy().green(),
+ dir.to_string_lossy().blue()
+ );
let mut child = command
.env_clear()
diff --git a/nearby/.cargo/config-boringssl.toml b/nearby/.cargo/config-boringssl.toml
index bab3518..2bde6df 100644
--- a/nearby/.cargo/config-boringssl.toml
+++ b/nearby/.cargo/config-boringssl.toml
@@ -1,13 +1,9 @@
# The packages to override
paths = [
- "../boringssl-build/boringssl/rust/bssl-crypto",
- "../boringssl-build/boringssl/rust/bssl-sys",
+ "../third_party/boringssl/rust/bssl-sys",
"../boringssl-build/rust-openssl/openssl",
"../boringssl-build/rust-openssl/openssl-sys",
]
[env]
WORKSPACE_DIR = { value = "", relative = true }
-
-
-
diff --git a/nearby/Cargo.lock b/nearby/Cargo.lock
index 76582bf..7441c1b 100644
--- a/nearby/Cargo.lock
+++ b/nearby/Cargo.lock
@@ -3,15 +3,6 @@
version = 3
[[package]]
-name = "addr2line"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
-dependencies = [
- "gimli",
-]
-
-[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -40,6 +31,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -55,17 +60,6 @@
]
[[package]]
-name = "ahash"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
-]
-
-[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -81,12 +75,6 @@
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
-name = "allocator-api2"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
-
-[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -188,21 +176,6 @@
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
-name = "backtrace"
-version = "0.3.68"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
-dependencies = [
- "addr2line",
- "cc",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
-]
-
-[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -268,6 +241,13 @@
[[package]]
name = "bssl-crypto"
version = "0.1.0"
+dependencies = [
+ "bssl-sys 0.1.0",
+]
+
+[[package]]
+name = "bssl-sys"
+version = "0.1.0"
[[package]]
name = "bssl-sys"
@@ -290,20 +270,20 @@
version = "0.1.0"
dependencies = [
"anyhow",
- "base64 0.21.2",
"chrono",
"clap 4.3.19",
+ "cmd-runner",
"crossbeam",
"env_logger",
+ "file-header",
"globset",
"log",
"owo-colors",
- "reqwest",
"semver",
+ "serde_json",
"shell-escape",
"tempfile",
"thiserror",
- "tinytemplate",
"walkdir",
"which",
]
@@ -471,6 +451,15 @@
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]]
+name = "cmd-runner"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "owo-colors",
+ "shell-escape",
+]
+
+[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -487,6 +476,10 @@
]
[[package]]
+name = "connections_adv"
+version = "0.1.0"
+
+[[package]]
name = "const-oid"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -652,6 +645,7 @@
"hex-literal",
"rand",
"rand_ext",
+ "tinyvec",
]
[[package]]
@@ -660,7 +654,6 @@
dependencies = [
"bssl-crypto",
"crypto_provider",
- "crypto_provider_stubs",
]
[[package]]
@@ -685,7 +678,7 @@
"hex-literal",
"openssl",
"ouroboros",
- "rstest 0.17.0",
+ "rstest",
]
[[package]]
@@ -694,6 +687,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -731,7 +725,7 @@
"hex-literal",
"rand",
"rand_ext",
- "rstest 0.17.0",
+ "rstest",
"rstest_reuse",
"test_helper",
"wycheproof",
@@ -748,9 +742,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -824,9 +818,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -862,15 +856,6 @@
]
[[package]]
-name = "encoding_rs"
-version = "0.8.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -922,9 +907,22 @@
[[package]]
name = "fiat-crypto"
-version = "0.1.20"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
+checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7"
+
+[[package]]
+name = "file-header"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5568149106e77ae33bc3a2c3ef3839cbe63ffa4a8dd4a81612a6f9dfdbc2e9f"
+dependencies = [
+ "crossbeam",
+ "lazy_static",
+ "license",
+ "thiserror",
+ "walkdir",
+]
[[package]]
name = "flate2"
@@ -958,110 +956,6 @@
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
-name = "form_urlencoded"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
-dependencies = [
- "percent-encoding",
-]
-
-[[package]]
-name = "futures"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-executor",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
-
-[[package]]
-name = "futures-core"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
-
-[[package]]
-name = "futures-executor"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
-dependencies = [
- "futures-core",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-io"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
-
-[[package]]
-name = "futures-macro"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.28",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
-
-[[package]]
-name = "futures-task"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
-
-[[package]]
-name = "futures-timer"
-version = "3.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
-
-[[package]]
-name = "futures-util"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-macro",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "pin-utils",
- "slab",
-]
-
-[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1084,10 +978,14 @@
]
[[package]]
-name = "gimli"
-version = "0.27.3"
+name = "ghash"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
[[package]]
name = "globset"
@@ -1114,25 +1012,6 @@
]
[[package]]
-name = "h2"
-version = "0.3.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
-dependencies = [
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "futures-util",
- "http",
- "indexmap",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1144,10 +1023,8 @@
dependencies = [
"criterion",
"crypto_provider",
- "hashbrown 0.14.0",
- "lock_api",
- "portable-atomic",
- "spin 0.9.8",
+ "lazy_static",
+ "lock_adapter",
]
[[package]]
@@ -1157,16 +1034,6 @@
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
-name = "hashbrown"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
-dependencies = [
- "ahash",
- "allocator-api2",
-]
-
-[[package]]
name = "hdrhistogram"
version = "7.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1232,84 +1099,12 @@
]
[[package]]
-name = "http"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
-dependencies = [
- "bytes",
- "fnv",
- "itoa",
-]
-
-[[package]]
-name = "http-body"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
-dependencies = [
- "bytes",
- "http",
- "pin-project-lite",
-]
-
-[[package]]
-name = "httparse"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
-
-[[package]]
-name = "httpdate"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
-
-[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
-name = "hyper"
-version = "0.14.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
-dependencies = [
- "bytes",
- "futures-channel",
- "futures-core",
- "futures-util",
- "h2",
- "http",
- "http-body",
- "httparse",
- "httpdate",
- "itoa",
- "pin-project-lite",
- "socket2",
- "tokio",
- "tower-service",
- "tracing",
- "want",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.24.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
-dependencies = [
- "futures-util",
- "http",
- "hyper",
- "rustls",
- "tokio",
- "tokio-rustls",
-]
-
-[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1333,23 +1128,13 @@
]
[[package]]
-name = "idna"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
-dependencies = [
- "unicode-bidi",
- "unicode-normalization",
-]
-
-[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
- "hashbrown 0.12.3",
+ "hashbrown",
]
[[package]]
@@ -1369,12 +1154,6 @@
]
[[package]]
-name = "ipnet"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
-
-[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1518,6 +1297,17 @@
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
+name = "license"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778718185117620a06e95d2b1e57d50166b1d6bfad93c8abfc1b3344c863ad8c"
+dependencies = [
+ "reword",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
name = "linux-raw-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1562,12 +1352,6 @@
]
[[package]]
-name = "mime"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
-
-[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1583,17 +1367,6 @@
]
[[package]]
-name = "mio"
-version = "0.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
-dependencies = [
- "libc",
- "wasi",
- "windows-sys 0.48.0",
-]
-
-[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1622,6 +1395,8 @@
"np_hkdf",
"rand",
"rand_ext",
+ "rmp-serde",
+ "serde",
"serde_json",
"sink",
"strum",
@@ -1649,8 +1424,9 @@
"crypto_provider",
"crypto_provider_default",
"handle_map",
+ "lazy_static",
+ "lock_adapter",
"np_adv",
- "spin 0.9.8",
]
[[package]]
@@ -1711,15 +1487,6 @@
]
[[package]]
-name = "object"
-version = "0.31.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
-dependencies = [
- "memchr",
-]
-
-[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1769,7 +1536,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
- "bssl-sys",
+ "bssl-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc",
"libc",
"pkg-config",
@@ -1823,22 +1590,10 @@
]
[[package]]
-name = "percent-encoding"
-version = "2.3.0"
+name = "paste"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pkcs8"
@@ -1903,12 +1658,6 @@
]
[[package]]
-name = "portable-atomic"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e"
-
-[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2140,69 +1889,34 @@
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
-name = "reqwest"
-version = "0.11.18"
+name = "reword"
+version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
+checksum = "fe272098dce9ed76b479995953f748d1851261390b08f8a0ff619c885a1f0765"
dependencies = [
- "base64 0.21.2",
- "bytes",
- "encoding_rs",
- "futures-core",
- "futures-util",
- "h2",
- "http",
- "http-body",
- "hyper",
- "hyper-rustls",
- "ipnet",
- "js-sys",
- "log",
- "mime",
- "once_cell",
- "percent-encoding",
- "pin-project-lite",
- "rustls",
- "rustls-pemfile",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "rmp"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20"
+dependencies = [
+ "byteorder",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "rmp-serde"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a"
+dependencies = [
+ "byteorder",
+ "rmp",
"serde",
- "serde_json",
- "serde_urlencoded",
- "tokio",
- "tokio-rustls",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
- "webpki-roots",
- "winreg",
-]
-
-[[package]]
-name = "ring"
-version = "0.16.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
-dependencies = [
- "cc",
- "libc",
- "once_cell",
- "spin 0.5.2",
- "untrusted",
- "web-sys",
- "winapi",
-]
-
-[[package]]
-name = "rstest"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b07f2d176c472198ec1e6551dc7da28f1c089652f66a7b722676c2238ebc0edf"
-dependencies = [
- "futures",
- "futures-timer",
- "rstest_macros 0.16.0",
- "rustc_version",
]
[[package]]
@@ -2211,26 +1925,12 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
dependencies = [
- "rstest_macros 0.17.0",
+ "rstest_macros",
"rustc_version",
]
[[package]]
name = "rstest_macros"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7"
-dependencies = [
- "cfg-if",
- "proc-macro2",
- "quote",
- "rustc_version",
- "syn 1.0.109",
- "unicode-ident",
-]
-
-[[package]]
-name = "rstest_macros"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
@@ -2245,23 +1945,17 @@
[[package]]
name = "rstest_reuse"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073"
+checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
dependencies = [
"quote",
"rand",
"rustc_version",
- "syn 1.0.109",
+ "syn 2.0.28",
]
[[package]]
-name = "rustc-demangle"
-version = "0.1.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
-
-[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2284,37 +1978,6 @@
]
[[package]]
-name = "rustls"
-version = "0.21.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
-dependencies = [
- "log",
- "ring",
- "rustls-webpki",
- "sct",
-]
-
-[[package]]
-name = "rustls-pemfile"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
-dependencies = [
- "base64 0.21.2",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.101.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59"
-dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2342,16 +2005,6 @@
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
-name = "sct"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
-dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2372,18 +2025,18 @@
[[package]]
name = "serde"
-version = "1.0.179"
+version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.179"
+version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
@@ -2402,18 +2055,6 @@
]
[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
name = "sha2"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2444,25 +2085,6 @@
]
[[package]]
-name = "slab"
-version = "0.4.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "socket2"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2501,21 +2123,21 @@
[[package]]
name = "strum"
-version = "0.24.1"
+version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
+checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
-version = "0.24.3"
+version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
- "syn 1.0.109",
+ "syn 2.0.28",
]
[[package]]
@@ -2617,88 +2239,6 @@
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
-dependencies = [
- "tinyvec_macros",
-]
-
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-
-[[package]]
-name = "tokio"
-version = "1.29.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
-dependencies = [
- "autocfg",
- "backtrace",
- "bytes",
- "libc",
- "mio",
- "num_cpus",
- "pin-project-lite",
- "socket2",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "tokio-rustls"
-version = "0.24.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
-dependencies = [
- "rustls",
- "tokio",
-]
-
-[[package]]
-name = "tokio-util"
-version = "0.7.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
-dependencies = [
- "bytes",
- "futures-core",
- "futures-sink",
- "pin-project-lite",
- "tokio",
- "tracing",
-]
-
-[[package]]
-name = "tower-service"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
-
-[[package]]
-name = "tracing"
-version = "0.1.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
-dependencies = [
- "cfg-if",
- "pin-project-lite",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
-dependencies = [
- "once_cell",
-]
-
-[[package]]
-name = "try-lock"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "typenum"
@@ -2711,8 +2251,7 @@
version = "0.1.0"
dependencies = [
"cfg-if",
- "crypto_provider_openssl",
- "crypto_provider_rustcrypto",
+ "crypto_provider_default",
"lazy_static",
"lock_adapter",
"log",
@@ -2729,11 +2268,10 @@
"bytes",
"criterion",
"crypto_provider",
- "crypto_provider_openssl",
+ "crypto_provider_default",
"crypto_provider_rustcrypto",
"nom",
"rand",
- "rstest 0.16.0",
"ukey2_proto",
"ukey2_rs",
]
@@ -2743,8 +2281,7 @@
version = "0.1.0"
dependencies = [
"cfg-if",
- "crypto_provider_openssl",
- "crypto_provider_rustcrypto",
+ "crypto_provider_default",
"jni",
"lazy_static",
"lock_adapter",
@@ -2769,13 +2306,11 @@
version = "0.1.0"
dependencies = [
"crypto_provider",
- "crypto_provider_openssl",
- "crypto_provider_rustcrypto",
+ "crypto_provider_default",
"derive-getters",
"log",
"num-bigint",
"rand",
- "rstest 0.16.0",
"sha2",
"ukey2_proto",
]
@@ -2791,25 +2326,16 @@
]
[[package]]
-name = "unicode-bidi"
-version = "0.3.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
-
-[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
-name = "unicode-normalization"
-version = "0.1.22"
+name = "unicode-segmentation"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
-dependencies = [
- "tinyvec",
-]
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "universal-hash"
@@ -2822,23 +2348,6 @@
]
[[package]]
-name = "untrusted"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
-
-[[package]]
-name = "url"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
-]
-
-[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2867,15 +2376,6 @@
]
[[package]]
-name = "want"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
-dependencies = [
- "try-lock",
-]
-
-[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2907,18 +2407,6 @@
]
[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
-dependencies = [
- "cfg-if",
- "js-sys",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2958,25 +2446,6 @@
]
[[package]]
-name = "webpki"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
-dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "0.22.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
-dependencies = [
- "webpki",
-]
-
-[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3160,21 +2629,12 @@
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
-name = "winreg"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
-dependencies = [
- "winapi",
-]
-
-[[package]]
name = "wycheproof"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "183c789620c674b79dac33cd3aadb6c8006b66cba6a680402235aaebc743e3df"
+checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12"
dependencies = [
- "base64 0.13.1",
+ "base64 0.21.2",
"hex",
"serde",
"serde_json",
@@ -3182,9 +2642,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core 0.6.4",
@@ -3217,6 +2677,7 @@
"rand_pcg",
"regex",
"test_helper",
+ "wycheproof",
"xts-mode",
]
diff --git a/nearby/Cargo.toml b/nearby/Cargo.toml
index 361628a..1416e24 100644
--- a/nearby/Cargo.toml
+++ b/nearby/Cargo.toml
@@ -1,13 +1,12 @@
[workspace]
members = [
- "connections/ukey2/lock_adapter",
+ "connections/connections_adv/connections_adv",
"connections/ukey2/ukey2",
"connections/ukey2/ukey2_connections",
"connections/ukey2/ukey2_c_ffi",
"connections/ukey2/ukey2_jni",
"connections/ukey2/ukey2_proto",
"connections/ukey2/ukey2_shell",
- "crypto/bssl-crypto",
"crypto/crypto_provider",
"crypto/crypto_provider_openssl",
"crypto/crypto_provider_rustcrypto",
@@ -16,7 +15,6 @@
"crypto/crypto_provider_default",
"crypto/rand_core_05_adapter",
"presence/array_view",
- "presence/handle_map",
"presence/ldt",
"presence/ldt_np_adv",
"presence/ldt_np_jni",
@@ -29,6 +27,8 @@
"presence/sink",
"presence/test_helper",
"presence/xts_aes",
+ "util/lock_adapter",
+ "util/handle_map",
]
# TODO: remove ldt_np_adv_ffi once support for no_std + alloc no longer requires nightly
@@ -44,14 +44,15 @@
# local crates
array_ref = { path = "presence/array_ref" }
array_view = { path = "presence/array_view" }
-crypto_provider = { path = "crypto/crypto_provider" }
+crypto_provider = { path = "crypto/crypto_provider", default-features = false }
crypto_provider_default = { path = "crypto/crypto_provider_default", default-features = false }
crypto_provider_openssl = { path = "crypto/crypto_provider_openssl" }
+crypto_provider_boringssl = { path = "crypto/crypto_provider_boringssl" }
crypto_provider_rustcrypto = { path = "crypto/crypto_provider_rustcrypto" }
crypto_provider_stubs = { path = "crypto/crypto_provider_stubs" }
crypto_provider_test = { path = "crypto/crypto_provider_test" }
-lock_adapter = { path = "connections/ukey2/lock_adapter" }
-handle_map = { path = "presence/handle_map" }
+lock_adapter = { path = "util/lock_adapter" }
+handle_map = { path = "util/handle_map" }
rand_core_05_adapter = { path = "crypto/rand_core_05_adapter" }
rand_ext = { path = "presence/rand_ext" }
test_helper = { path = "presence/test_helper" }
@@ -68,33 +69,33 @@
# from crates.io
rand = { version = "0.8.5", default-features = false }
-rand_core = "0.6.4"
+rand_core = { version = "0.6.4", features = ["getrandom"] }
rand_pcg = "0.3.1"
sha2 = { version = "0.10.6", default-features = false }
aes = "0.8.2"
-cbc = { version = "0.1.2", features = ["alloc", "block-padding"] }
+cbc = { version = "0.1.2", features = ["block-padding"] }
ctr = "0.9.1"
-hashbrown = "0.14.0"
hkdf = "0.12.3"
hmac = "0.12.1"
-ed25519-dalek = { version = "2.0.0-rc.3", default-features = false }
+ed25519-dalek = { version = "2.0.0", default-features = false }
ed25519 = "2.2.0"
-aes-gcm = "0.10.1"
+aes-gcm = "0.10.3"
hex = "0.4.3"
+serde = { version = "1.0.189" }
serde_json = { version = "1.0.96", features = [
"alloc",
], default-features = false }
base64 = "0.21.0"
-x25519-dalek = { version = "2.0.0-rc.3", default-features = false }
+x25519-dalek = { version = "2.0.0", default-features = false }
subtle = { version = "2.5.0", default-features = false }
rand_chacha = { version = "0.3.1", default-features = false }
p256 = { version = "0.13.2", default-features = false }
sec1 = "0.7.2"
-portable-atomic = "1.3.2"
protobuf = "3.2.0"
protobuf-codegen = "3.2.0"
+reqwest = { version = "0.11.19", default-features = false, features = ["blocking", "rustls-tls"] }
jni = "0.21.1"
-lock_api = "0.4.9"
+lock_api = "0.4.11"
spin = { version = "0.9.8", features = ["once", "lock_api", "rwlock"] }
anyhow = "1.0.64"
log = "0.4.17"
@@ -108,18 +109,19 @@
blake2 = "0.10.4"
hdrhistogram = "7.5.0"
regex = "1.7.0"
-tokio = { version = "1.20.3", features = ["full"] }
+tokio = { version = "1.32.0", features = ["full"] }
xts-mode = "0.5.1"
rstest = { version = "0.17.0", default-features = false }
-rstest_reuse = "0.5.0"
-wycheproof = "0.4.0"
-chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
+rstest_reuse = "0.6.0"
+wycheproof = "0.5.1"
+chrono = { version = "0.4.26", default-features = false, features = ["clock"] }
tempfile = "3.5.0"
thiserror = "1.0.40"
-tinyvec = {version = "1.6.0", features = ["rustc_1_55"]}
+tinyvec = { version = "1.6.0", features = ["rustc_1_55"] }
mlua = "0.8.8"
-strum = "0.24.1"
-strum_macros = "0.24.2"
+strum = { version = "0.25.0", default-features=false }
+strum_macros = { version = "0.25.3", default-features=false }
+owo-colors = "3.5.0"
[workspace.package]
version = "0.1.0"
@@ -150,27 +152,25 @@
version.workspace = true
edition.workspace = true
publish.workspace = true
+rust-version = "1.71.0"
[dependencies]
clap.workspace = true
+cmd-runner = { path = "../cmd-runner" }
anyhow.workspace = true
shell-escape = "0.1.5"
-owo-colors = "3.5.0"
-reqwest = { version = "0.11.17", default-features = false, features = [
- "blocking",
- "rustls-tls",
-] }
+owo-colors.workspace = true
semver = "1.0.17"
-base64.workspace = true
walkdir = "2.3.3"
globset = "0.4.10"
crossbeam = "0.8.2"
-tinytemplate = "1.2.1"
chrono.workspace = true
thiserror.workspace = true
log.workspace = true
env_logger.workspace = true
which = "4.4.0"
+file-header = "0.1.2"
+serde_json.workspace = true
[dev-dependencies]
tempfile.workspace = true
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/nearby/connections/connections_adv/connections_adv/Cargo.toml
similarity index 88%
rename from nearby/crypto/bssl-crypto/Cargo.toml
rename to nearby/connections/connections_adv/connections_adv/Cargo.toml
index bfe3964..ff17ad2 100644
--- a/nearby/crypto/bssl-crypto/Cargo.toml
+++ b/nearby/connections/connections_adv/connections_adv/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "bssl-crypto"
+name = "connections_adv"
version.workspace = true
edition.workspace = true
publish.workspace = true
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/nearby/connections/connections_adv/connections_adv/src/lib.rs
similarity index 72%
copy from nearby/crypto/bssl-crypto/src/lib.rs
copy to nearby/connections/connections_adv/connections_adv/src/lib.rs
index 89e6968..4f7198b 100644
--- a/nearby/crypto/bssl-crypto/src/lib.rs
+++ b/nearby/connections/connections_adv/connections_adv/src/lib.rs
@@ -12,5 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the
-//! `build-boringssl` subcommand of the top level crate.
+pub fn add(left: usize, right: usize) -> usize {
+ left + right
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let result = add(2, 2);
+ assert_eq!(result, 4);
+ }
+}
diff --git a/nearby/connections/ukey2/lock_adapter/src/spin.rs b/nearby/connections/ukey2/lock_adapter/src/spin.rs
deleted file mode 100644
index be363d4..0000000
--- a/nearby/connections/ukey2/lock_adapter/src/spin.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use crate::NoPoisonMutex;
-
-pub struct Mutex<T>(spin::Mutex<T>);
-
-impl<T> NoPoisonMutex<T> for Mutex<T> {
- type MutexGuard<'a> = spin::MutexGuard<'a, T> where T: 'a;
-
- fn lock(&self) -> Self::MutexGuard<'_> {
- self.0.lock()
- }
-
- fn try_lock(&self) -> Option<Self::MutexGuard<'_>> {
- self.0.try_lock()
- }
-
- fn new(value: T) -> Self {
- Self(spin::Mutex::new(value))
- }
-}
diff --git a/nearby/connections/ukey2/lock_adapter/src/std.rs b/nearby/connections/ukey2/lock_adapter/src/std.rs
deleted file mode 100644
index ffd3ecc..0000000
--- a/nearby/connections/ukey2/lock_adapter/src/std.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use crate::NoPoisonMutex;
-
-pub struct Mutex<T>(std::sync::Mutex<T>);
-
-impl<T> NoPoisonMutex<T> for Mutex<T> {
- type MutexGuard<'a> = std::sync::MutexGuard<'a, T> where T: 'a;
-
- fn lock(&self) -> Self::MutexGuard<'_> {
- self.0.lock().unwrap_or_else(|poison| poison.into_inner())
- }
-
- fn try_lock(&self) -> Option<Self::MutexGuard<'_>> {
- match self.0.try_lock() {
- Ok(guard) => Some(guard),
- Err(std::sync::TryLockError::Poisoned(guard)) => Some(guard.into_inner()),
- Err(std::sync::TryLockError::WouldBlock) => None,
- }
- }
-
- fn new(value: T) -> Self {
- Self(std::sync::Mutex::new(value))
- }
-}
diff --git a/nearby/connections/ukey2/ukey2/Cargo.toml b/nearby/connections/ukey2/ukey2/Cargo.toml
index 7c10b41..afae71e 100644
--- a/nearby/connections/ukey2/ukey2/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2/Cargo.toml
@@ -4,6 +4,12 @@
edition.workspace = true
publish.workspace = true
+[features]
+default = []
+test_rustcrypto = ["crypto_provider_default/rustcrypto"]
+test_openssl = ["crypto_provider_default/openssl"]
+test_boringssl = ["crypto_provider_default/boringssl"]
+
[dependencies]
crypto_provider.workspace = true
rand.workspace = true
@@ -14,8 +20,6 @@
num-bigint = "0.4.3"
[dev-dependencies]
-rand = { workspace = true, features = ["std_rng"] }
-crypto_provider_rustcrypto = {workspace = true, features = ["std"] }
-crypto_provider_openssl.workspace = true
-rstest = "0.16.0"
+rand = { workspace = true, features = ["std_rng", "getrandom"] }
+crypto_provider_default.workspace = true
sha2.workspace = true
diff --git a/nearby/connections/ukey2/ukey2/src/tests.rs b/nearby/connections/ukey2/ukey2/src/tests.rs
index b58cee6..65274a6 100644
--- a/nearby/connections/ukey2/ukey2/src/tests.rs
+++ b/nearby/connections/ukey2/ukey2/src/tests.rs
@@ -21,42 +21,41 @@
use crypto_provider::p256::P256;
use crypto_provider::x25519::X25519;
use crypto_provider::{CryptoProvider, CryptoRng};
-use crypto_provider_openssl::Openssl;
-use crypto_provider_rustcrypto::RustCrypto;
+use crypto_provider_default::CryptoProviderImpl;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
-use rstest::rstest;
use sha2::Digest;
use std::collections::hash_set;
use ukey2_proto::protobuf::Message;
use ukey2_proto::ukey2_all_proto::ukey;
-#[rstest]
-fn advance_from_init_to_finish_client_test<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+type X25519EphemeralSecret =
+ <<CryptoProviderImpl as CryptoProvider>::X25519 as EcdhProvider<X25519>>::EphemeralSecret;
+type X25519PublicKey =
+ <<CryptoProviderImpl as CryptoProvider>::X25519 as EcdhProvider<X25519>>::PublicKey;
+type P256EphemeralSecret =
+ <<CryptoProviderImpl as CryptoProvider>::P256 as EcdhProvider<P256>>::EphemeralSecret;
+
+#[test]
+fn advance_from_init_to_finish_client_test() {
let mut rng = StdRng::from_entropy();
- let client1 = Ukey2ClientStage1::<C>::from(
+ let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from(
&mut rng,
"next protocol".to_string(),
HandshakeImplementation::Spec,
);
let secret =
- <C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random(
- &mut <<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret<
- X25519,
- >>::Rng::new(),
- );
- let public_key =
- <C::X25519 as EcdhProvider<X25519>>::PublicKey::from_bytes(&secret.public_key_bytes())
- .unwrap();
+ X25519EphemeralSecret::generate_random(&mut <X25519EphemeralSecret as EphemeralSecret<
+ X25519,
+ >>::Rng::new());
+ let public_key = X25519PublicKey::from_bytes(secret.public_key_bytes().as_ref()).unwrap();
let random: [u8; 32] = rng.gen();
let message_data: ukey::Ukey2ServerInit = ukey::Ukey2ServerInit {
version: Some(1),
random: Some(random.to_vec()),
handshake_cipher: Some(ukey::Ukey2HandshakeCipher::CURVE25519_SHA512.into()),
- public_key: Some(public_key.to_bytes()),
+ public_key: Some(public_key.to_bytes().as_ref().to_vec()),
..Default::default()
};
@@ -66,25 +65,24 @@
// TODO assertions on client state
}
-#[rstest]
-fn advance_from_init_to_complete_server_x25519_test<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn advance_from_init_to_complete_server_x25519_test() {
let mut rng = StdRng::from_entropy();
let mut next_protocols = hash_set::HashSet::new();
let _ = next_protocols.insert("AES_256_CBC-HMAC_SHA256".to_string());
- let server1 = Ukey2ServerStage1::<C>::from(next_protocols, HandshakeImplementation::Spec);
+ let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from(
+ next_protocols,
+ HandshakeImplementation::Spec,
+ );
// We construct a ClientInit message for the server to get it into the state to handle
// ClientFinish messages.
let secret =
- <C::X25519 as EcdhProvider<X25519>>::EphemeralSecret::generate_random(
- &mut <<C::X25519 as EcdhProvider<X25519>>::EphemeralSecret as EphemeralSecret<
- X25519,
- >>::Rng::new(),
- );
+ X25519EphemeralSecret::generate_random(&mut <X25519EphemeralSecret as EphemeralSecret<
+ X25519,
+ >>::Rng::new());
let client_finished_msg = {
let mut msg = ukey::Ukey2ClientFinished::default();
- msg.set_public_key(secret.public_key_bytes());
+ msg.set_public_key(secret.public_key_bytes().as_ref().to_vec());
msg.to_wrapped_msg()
};
let client_finished_bytes = client_finished_msg.write_to_bytes().unwrap();
@@ -120,23 +118,23 @@
// TODO assertions on server state
}
-#[rstest]
-fn advance_from_init_to_complete_server_p256_test<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn advance_from_init_to_complete_server_p256_test() {
let mut rng = StdRng::from_entropy();
let mut next_protocols = hash_set::HashSet::new();
let _ = next_protocols.insert("AES_256_CBC-HMAC_SHA256".to_string());
- let server1 = Ukey2ServerStage1::<C>::from(next_protocols, HandshakeImplementation::Spec);
+ let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from(
+ next_protocols,
+ HandshakeImplementation::Spec,
+ );
// We construct a ClientInit message for the server to get it into the state to handle
// ClientFinish messages.
- let secret = <C::P256 as EcdhProvider<P256>>::EphemeralSecret::generate_random(
- &mut <<C::P256 as EcdhProvider<P256>>::EphemeralSecret as EphemeralSecret<P256>>::Rng::new(
- ),
+ let secret = P256EphemeralSecret::generate_random(
+ &mut <P256EphemeralSecret as EphemeralSecret<P256>>::Rng::new(),
);
let client_finished_msg = {
let mut msg = ukey::Ukey2ClientFinished::default();
- msg.set_public_key(secret.public_key_bytes());
+ msg.set_public_key(secret.public_key_bytes().as_ref().to_vec());
msg.to_wrapped_msg()
};
let client_finished_bytes = client_finished_msg.write_to_bytes().unwrap();
diff --git a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
index a22c0ed..70db4a3 100644
--- a/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
+++ b/nearby/connections/ukey2/ukey2/src/ukey2_handshake.rs
@@ -133,7 +133,7 @@
match public_key {
GenericPublicKey::Ec256(key) => {
debug_assert_eq!(cipher, HandshakeCipher::P256Sha512);
- Some(key.to_bytes())
+ Some(key.to_bytes().to_vec())
}
}
}
@@ -267,9 +267,12 @@
server_init.set_random(random.to_vec());
server_init.set_handshake_cipher(commitment.cipher().as_proto());
server_init.set_public_key(match &key_pair {
- ServerKeyPair::Curve25519(es) => es.public_key_bytes(),
+ ServerKeyPair::Curve25519(es) => es.public_key_bytes().as_ref().to_vec(),
ServerKeyPair::P256(es) => handshake_impl
- .encode_public_key::<C>(es.public_key_bytes(), HandshakeCipher::P256Sha512)
+ .encode_public_key::<C>(
+ es.public_key_bytes().as_ref().to_vec(),
+ HandshakeCipher::P256Sha512,
+ )
.unwrap(),
});
@@ -392,7 +395,7 @@
);
let curve25519_client_finished_bytes = {
let client_finished = ukey::Ukey2ClientFinished {
- public_key: Some(curve25519_secret.public_key_bytes()),
+ public_key: Some(curve25519_secret.public_key_bytes().as_ref().to_vec()),
..Default::default()
};
client_finished.to_wrapped_msg().write_to_bytes().unwrap()
@@ -411,7 +414,7 @@
public_key: Some(
handshake_impl
.encode_public_key::<C>(
- p256_secret.public_key_bytes(),
+ p256_secret.public_key_bytes().as_ref().to_vec(),
HandshakeCipher::P256Sha512,
)
.expect("Output of p256_secret.public_key_bytes should always be valid input for encode_public_key"),
diff --git a/nearby/connections/ukey2/ukey2/tests/tests.rs b/nearby/connections/ukey2/ukey2/tests/tests.rs
index 31ba416..2f51a8c 100644
--- a/nearby/connections/ukey2/ukey2/tests/tests.rs
+++ b/nearby/connections/ukey2/ukey2/tests/tests.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crypto_provider_rustcrypto::RustCrypto;
+use crypto_provider_default::CryptoProviderImpl;
use rand::{rngs::StdRng, SeedableRng};
use std::collections::hash_set;
use ukey2_rs::*;
@@ -22,10 +22,12 @@
let mut next_protocols = hash_set::HashSet::new();
let next_protocol = "AES_256_CBC-HMAC_SHA256".to_string();
let _ = next_protocols.insert(next_protocol.clone());
- let server1 =
- Ukey2ServerStage1::<RustCrypto>::from(next_protocols, HandshakeImplementation::Spec);
+ let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from(
+ next_protocols,
+ HandshakeImplementation::Spec,
+ );
let mut rng = StdRng::from_entropy();
- let client1 = Ukey2ClientStage1::<RustCrypto>::from(
+ let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from(
&mut rng,
next_protocol,
HandshakeImplementation::Spec,
@@ -37,12 +39,18 @@
let server3 = server2.advance_state(&mut rng, client2.client_finished_msg()).unwrap();
assert_eq!(
- server3.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>(),
- client2.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>()
+ server3.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>(),
+ client2.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>()
);
assert_eq!(
- server3.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>(),
- client2.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>()
+ server3
+ .completed_handshake()
+ .next_protocol_secret::<CryptoProviderImpl>()
+ .derive_array::<32>(),
+ client2
+ .completed_handshake()
+ .next_protocol_secret::<CryptoProviderImpl>()
+ .derive_array::<32>()
);
}
@@ -51,12 +59,12 @@
let mut next_protocols = hash_set::HashSet::new();
let next_protocol = "AES_256_CBC-HMAC_SHA256".to_string();
let _ = next_protocols.insert(next_protocol.clone());
- let server1 = Ukey2ServerStage1::<RustCrypto>::from(
+ let server1 = Ukey2ServerStage1::<CryptoProviderImpl>::from(
next_protocols,
HandshakeImplementation::PublicKeyInProtobuf,
);
let mut rng = StdRng::from_entropy();
- let client1 = Ukey2ClientStage1::<RustCrypto>::from(
+ let client1 = Ukey2ClientStage1::<CryptoProviderImpl>::from(
&mut rng,
next_protocol,
HandshakeImplementation::PublicKeyInProtobuf,
@@ -68,11 +76,17 @@
let server3 = server2.advance_state(&mut rng, client2.client_finished_msg()).unwrap();
assert_eq!(
- server3.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>(),
- client2.completed_handshake().auth_string::<RustCrypto>().derive_array::<32>()
+ server3.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>(),
+ client2.completed_handshake().auth_string::<CryptoProviderImpl>().derive_array::<32>()
);
assert_eq!(
- server3.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>(),
- client2.completed_handshake().next_protocol_secret::<RustCrypto>().derive_array::<32>()
+ server3
+ .completed_handshake()
+ .next_protocol_secret::<CryptoProviderImpl>()
+ .derive_array::<32>(),
+ client2
+ .completed_handshake()
+ .next_protocol_secret::<CryptoProviderImpl>()
+ .derive_array::<32>()
);
}
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
index 3ca55f4..182ecf2 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_c_ffi/Cargo.toml
@@ -8,9 +8,8 @@
ukey2_connections = { path = "../ukey2_connections" }
ukey2_rs = { path = "../ukey2" }
cfg-if.workspace = true
-crypto_provider_openssl = { workspace = true, optional = true }
-crypto_provider_rustcrypto = { workspace = true, optional = true, features = ["alloc"] }
-lock_adapter.workspace = true
+crypto_provider_default.workspace = true
+lock_adapter = {workspace = true, features = ["spin"]}
lazy_static.workspace = true
log.workspace = true
@@ -18,11 +17,11 @@
rand_chacha.workspace = true
[features]
-default = ["rustcrypto"]
-std = ["crypto_provider_rustcrypto/std", "lock_adapter/std"]
-openssl = ["dep:crypto_provider_openssl", "std"]
-rustcrypto = ["crypto_provider_rustcrypto"]
-crypto_provider_rustcrypto = ["dep:crypto_provider_rustcrypto"]
+default = ["rustcrypto", "std"]
+std = ["lock_adapter/std"]
+openssl = ["crypto_provider_default/openssl", "std"]
+boringssl = ["crypto_provider_default/boringssl", "std"]
+rustcrypto = ["crypto_provider_default/rustcrypto"]
[lib]
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
index c1247eb..165d861 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/CMakeLists.txt
@@ -14,6 +14,8 @@
cmake_minimum_required(VERSION 3.14)
+project(Ukey2)
+
enable_testing()
include_directories(
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
index b269da8..654e293 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_ffi.h
@@ -13,6 +13,7 @@
// limitations under the License.
#include "ukey2_bindings.h"
+
#include <string>
struct D2DRestoreConnectionContextV1Result;
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
index 1e913b5..962ada8 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
+++ b/nearby/connections/ukey2/ukey2_c_ffi/cpp/ukey2_test.cc
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "ukey2_ffi.h"
+
#include <string>
-#include <gtest/gtest.h>
-#include "ukey2_ffi.h"
+#include "gtest/gtest.h"
namespace {
diff --git a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
index a79fe60..e5cc548 100644
--- a/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2_c_ffi/src/lib.rs
@@ -15,6 +15,7 @@
use std::collections::HashMap;
use std::ptr::null_mut;
+use crypto_provider_default::CryptoProviderImpl as CryptoProvider;
use lazy_static::lazy_static;
use lock_adapter::NoPoisonMutex;
use rand::Rng;
@@ -31,13 +32,6 @@
InitiatorD2DHandshakeContext, ServerD2DHandshakeContext,
};
-cfg_if::cfg_if! {
- if #[cfg(feature = "rustcrypto")] {
- use crypto_provider_rustcrypto::RustCrypto as CryptoProvider;
- } else {
- use crypto_provider_openssl::Openssl as CryptoProvider;
- }
-}
#[repr(C)]
pub struct RustFFIByteArray {
ptr: *mut u8,
diff --git a/nearby/connections/ukey2/ukey2_connections/Cargo.toml b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
index 41fe10d..8337e96 100644
--- a/nearby/connections/ukey2/ukey2_connections/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_connections/Cargo.toml
@@ -4,6 +4,12 @@
edition.workspace = true
publish.workspace = true
+[features]
+default = []
+test_boringssl = ["crypto_provider_default/boringssl"]
+test_rustcrypto = ["crypto_provider_default/rustcrypto"]
+test_openssl = ["crypto_provider_default/openssl"]
+
[dependencies]
ukey2_rs = { path = "../ukey2" }
@@ -12,12 +18,13 @@
ukey2_proto.workspace = true
nom = { version = "7.1.1", features = ["alloc"] }
bytes = "1.2.1"
-criterion = "0.4.0"
+criterion.workspace = true
[dev-dependencies]
-crypto_provider_openssl.workspace = true
+crypto_provider_default.workspace = true
+# This would only be used when the feature "test_rustcrypto" is set, but optional dev-dependencies
+# are not allowed ¯\_(ツ)_/¯
crypto_provider_rustcrypto = { workspace = true, features = ["alloc", "std"] }
-rstest = "0.16.0"
[[bench]]
name = "ukey2_benches"
diff --git a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
index b609761..e48400a 100644
--- a/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
+++ b/nearby/connections/ukey2/ukey2_connections/benches/ukey2_benches.rs
@@ -16,7 +16,7 @@
use rand::{Rng, SeedableRng};
use crypto_provider::CryptoProvider;
-use crypto_provider_rustcrypto::RustCrypto;
+use crypto_provider_default::CryptoProviderImpl;
use ukey2_connections::{
D2DConnectionContextV1, D2DHandshakeContext, InitiatorD2DHandshakeContext,
ServerD2DHandshakeContext,
@@ -63,16 +63,18 @@
let mut group = c.benchmark_group("throughput");
let mut plaintext = Vec::new();
let (mut initiator_ctx, mut server_ctx) =
- run_handshake_with_rng::<RustCrypto, _>(rand::rngs::StdRng::from_entropy());
+ run_handshake_with_rng::<CryptoProviderImpl, _>(rand::rngs::StdRng::from_entropy());
for len in [10 * kib, 1024 * kib] {
group.throughput(Throughput::Bytes(len as u64));
plaintext.resize(len, 0);
rand::thread_rng().fill(&mut plaintext[..]);
group.bench_function(format!("UKEY2 encrypt/decrypt {}KiB", len / kib), |b| {
b.iter(|| {
- let msg =
- initiator_ctx.encode_message_to_peer::<RustCrypto, &[u8]>(&plaintext, None);
- black_box(server_ctx.decode_message_from_peer::<RustCrypto, &[u8]>(&msg, None))
+ let msg = initiator_ctx
+ .encode_message_to_peer::<CryptoProviderImpl, &[u8]>(&plaintext, None);
+ black_box(
+ server_ctx.decode_message_from_peer::<CryptoProviderImpl, &[u8]>(&msg, None),
+ )
})
});
}
diff --git a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
index 4a00416..3f72ae0 100644
--- a/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
+++ b/nearby/connections/ukey2/ukey2_connections/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -336,6 +350,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_rustcrypto"
@@ -343,6 +360,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -372,9 +390,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -451,9 +469,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -555,6 +573,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1233,6 +1261,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1495,9 +1529,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/connections/ukey2/ukey2_connections/src/tests.rs b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
index 35a1e70..32a6b28 100644
--- a/nearby/connections/ukey2/ukey2_connections/src/tests.rs
+++ b/nearby/connections/ukey2/ukey2_connections/src/tests.rs
@@ -12,14 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crypto_provider_rustcrypto::RustCryptoImpl;
+use crypto_provider::CryptoProvider;
+use crypto_provider_default::CryptoProviderImpl;
use rand::SeedableRng;
use rand::{rngs::StdRng, CryptoRng, RngCore};
-use rstest::rstest;
-
-use crypto_provider::CryptoProvider;
-use crypto_provider_openssl::Openssl;
-use crypto_provider_rustcrypto::RustCrypto;
use ukey2_rs::HandshakeImplementation;
use crate::{
@@ -28,27 +24,25 @@
InitiatorD2DHandshakeContext, ServerD2DHandshakeContext,
};
-#[rstest]
-fn crypto_test_encrypt_decrypt<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+type AesCbcPkcs7Padded = <CryptoProviderImpl as CryptoProvider>::AesCbcPkcs7Padded;
+
+#[test]
+fn crypto_test_encrypt_decrypt() {
let message = b"Hello World!";
let key = b"42424242424242424242424242424242";
let (ciphertext, iv) =
- encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rand::rngs::StdRng::from_entropy());
- let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(key, ciphertext.as_slice(), &iv);
+ encrypt::<_, AesCbcPkcs7Padded>(key, message, &mut rand::rngs::StdRng::from_entropy());
+ let decrypt_result = decrypt::<AesCbcPkcs7Padded>(key, ciphertext.as_slice(), &iv);
let ptext = decrypt_result.expect("Decrypt should be successful");
assert_eq!(ptext, message.to_vec());
}
-#[rstest]
-fn crypto_test_encrypt_seeded<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn crypto_test_encrypt_seeded() {
let message = b"Hello World!";
let key = b"42424242424242424242424242424242";
let mut rng = MockRng;
- let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>(key, message, &mut rng);
+ let (ciphertext, iv) = encrypt::<_, AesCbcPkcs7Padded>(key, message, &mut rng);
// Expected values extracted from the results of the current implementation.
// This test makes sure that we don't accidentally change the encryption logic that
// causes incompatibility between versions.
@@ -59,43 +53,36 @@
);
}
-#[rstest]
-fn crypto_test_decrypt_seeded<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn crypto_test_decrypt_seeded() {
let iv = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
let ciphertext = [20, 59, 195, 101, 11, 208, 245, 128, 247, 196, 81, 80, 158, 77, 174, 61];
let key = b"42424242424242424242424242424242";
- let plaintext = decrypt::<C::AesCbcPkcs7Padded>(key, &ciphertext, &iv).unwrap();
+ let plaintext = decrypt::<AesCbcPkcs7Padded>(key, &ciphertext, &iv).unwrap();
assert_eq!(plaintext, b"Hello World!");
}
-#[rstest]
-fn decrypt_test_wrong_key<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn decrypt_test_wrong_key() {
let message = b"Hello World!";
let good_key = b"42424242424242424242424242424242";
- let (ciphertext, iv) = encrypt::<_, C::AesCbcPkcs7Padded>(
- good_key,
- message,
- &mut rand::rngs::StdRng::from_entropy(),
- );
+ let (ciphertext, iv) =
+ encrypt::<_, AesCbcPkcs7Padded>(good_key, message, &mut rand::rngs::StdRng::from_entropy());
let bad_key = b"43434343434343434343434343434343";
- let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(bad_key, ciphertext.as_slice(), &iv);
+ let decrypt_result = decrypt::<AesCbcPkcs7Padded>(bad_key, ciphertext.as_slice(), &iv);
match decrypt_result {
// The padding is valid, but the decrypted value should be bad since the keys don't match
Ok(decrypted_bad) => assert_ne!(decrypted_bad, message),
// The padding is bad, so it returns an error and is unable to decrypt
Err(crypto_provider::aes::cbc::DecryptionError::BadPadding) => (),
}
- let decrypt_result = decrypt::<C::AesCbcPkcs7Padded>(good_key, ciphertext.as_slice(), &iv);
+ let decrypt_result = decrypt::<AesCbcPkcs7Padded>(good_key, ciphertext.as_slice(), &iv);
let ptext = decrypt_result.unwrap();
assert_eq!(ptext, message.to_vec());
}
-fn run_handshake<C: CryptoProvider>() -> (D2DConnectionContextV1, D2DConnectionContextV1) {
- run_handshake_with_rng::<C, _>(rand::rngs::StdRng::from_entropy())
+fn run_handshake() -> (D2DConnectionContextV1, D2DConnectionContextV1) {
+ run_handshake_with_rng::<CryptoProviderImpl, _>(rand::rngs::StdRng::from_entropy())
}
fn run_handshake_with_rng<C, R>(
@@ -133,15 +120,17 @@
(initiator_ctx.to_connection_context().unwrap(), server_ctx.to_connection_context().unwrap())
}
-#[rstest]
-fn send_receive_message_seeded<C: CryptoProvider>(
- // TODO: Find a way to inject RNG / generated ephemeral secrets in openSSL and test them here
- #[values(RustCryptoImpl::< MockRng >::new())] _crypto_provider: C,
-) {
+// TODO: Find a way to inject RNG / generated ephemeral secrets in openSSL and test them here
+#[cfg(feature = "test_rustcrypto")]
+#[test]
+fn send_receive_message_seeded() {
+ use crypto_provider_rustcrypto::RustCryptoImpl;
let rng = MockRng;
let message = b"Hello World!";
- let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake_with_rng::<C, _>(rng);
- let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
+ let (mut init_conn_ctx, mut server_conn_ctx) =
+ run_handshake_with_rng::<RustCryptoImpl<MockRng>, _>(rng);
+ let encoded =
+ init_conn_ctx.encode_message_to_peer::<RustCryptoImpl<MockRng>, &[u8]>(message, None);
// Expected values extracted from the results of the current implementation.
// This test makes sure that we don't accidentally change the encryption logic that
// causes incompatibility between versions.
@@ -155,102 +144,104 @@
45, 239, 234, 248, 148, 9, 150, 204, 117, 32, 216, 5, 126, 224, 39
]
);
- let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(&encoded, None).unwrap();
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(&encoded, None)
+ .unwrap();
assert_eq!(message, &decoded[..]);
}
-#[rstest]
-fn send_receive_message<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn send_receive_message() {
let message = b"Hello World!";
- let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
- let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
- let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake();
+ let encoded = init_conn_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None);
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None);
assert_eq!(message.to_vec(), decoded.expect("Decode should be successful"));
}
-#[rstest]
-fn send_receive_message_associated_data<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
+#[test]
+fn send_receive_message_associated_data() {
let message = b"Hello World!";
- let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
- let encoded = init_conn_ctx.encode_message_to_peer::<C, _>(message, Some(b"associated data"));
- let decoded = server_conn_ctx
- .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"associated data"));
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake();
+ let encoded = init_conn_ctx
+ .encode_message_to_peer::<CryptoProviderImpl, _>(message, Some(b"associated data"));
+ let decoded = server_conn_ctx.decode_message_from_peer::<CryptoProviderImpl, _>(
+ encoded.as_slice(),
+ Some(b"associated data"),
+ );
assert_eq!(message.to_vec(), decoded.expect("Decode should be successful"));
// Make sure decode fails with missing associated data.
- let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None);
assert!(decoded.is_err());
// Make sure decode fails with different associated data.
- let decoded = server_conn_ctx
- .decode_message_from_peer::<C, _>(encoded.as_slice(), Some(b"assoc1ated data"));
+ let decoded = server_conn_ctx.decode_message_from_peer::<CryptoProviderImpl, _>(
+ encoded.as_slice(),
+ Some(b"assoc1ated data"),
+ );
assert!(decoded.is_err());
}
-#[rstest]
-fn test_save_restore_session<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
- let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>();
+#[test]
+fn test_save_restore_session() {
+ let (init_conn_ctx, server_conn_ctx) = run_handshake();
let init_session = init_conn_ctx.save_session();
let server_session = server_conn_ctx.save_session();
let mut init_restored_ctx =
- D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice())
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice())
.expect("failed to restore client session");
let mut server_restored_ctx =
- D2DConnectionContextV1::from_saved_session::<C>(server_session.as_slice())
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(server_session.as_slice())
.expect("failed to restore server session");
let message = b"Hello World!";
- let encoded = init_restored_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
- let decoded =
- server_restored_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ let encoded =
+ init_restored_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None);
+ let decoded = server_restored_ctx
+ .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None);
assert_eq!(message.to_vec(), decoded.expect("Decode should be successful"));
}
-#[rstest]
-fn test_save_restore_bad_session<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
- let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>();
+#[test]
+fn test_save_restore_bad_session() {
+ let (init_conn_ctx, server_conn_ctx) = run_handshake();
let init_session = init_conn_ctx.save_session();
let server_session = server_conn_ctx.save_session();
- let _ = D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice())
- .expect("failed to restore client session");
+ let _ =
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice())
+ .expect("failed to restore client session");
let server_restored_ctx =
- D2DConnectionContextV1::from_saved_session::<C>(&server_session[0..60]);
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(&server_session[0..60]);
assert_eq!(server_restored_ctx.unwrap_err(), DeserializeError::BadDataLength);
}
-#[rstest]
-fn test_save_restore_bad_protocol_version<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
- let (init_conn_ctx, server_conn_ctx) = run_handshake::<C>();
+#[test]
+fn test_save_restore_bad_protocol_version() {
+ let (init_conn_ctx, server_conn_ctx) = run_handshake();
let init_session = init_conn_ctx.save_session();
let mut server_session = server_conn_ctx.save_session();
- let _ = D2DConnectionContextV1::from_saved_session::<C>(init_session.as_slice())
- .expect("failed to restore client session");
+ let _ =
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(init_session.as_slice())
+ .expect("failed to restore client session");
server_session[0] = 0; // Change the protocol version to an invalid one (0)
- let server_restored_ctx = D2DConnectionContextV1::from_saved_session::<C>(&server_session);
+ let server_restored_ctx =
+ D2DConnectionContextV1::from_saved_session::<CryptoProviderImpl>(&server_session);
assert_eq!(server_restored_ctx.unwrap_err(), DeserializeError::BadProtocolVersion);
}
-#[rstest]
-fn test_unique_session<C: CryptoProvider>(
- #[values(RustCrypto::new(), Openssl)] _crypto_provider: C,
-) {
- let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake::<C>();
- let init_session = init_conn_ctx.get_session_unique::<C>();
- let server_session = server_conn_ctx.get_session_unique::<C>();
+#[test]
+fn test_unique_session() {
+ let (mut init_conn_ctx, mut server_conn_ctx) = run_handshake();
+ let init_session = init_conn_ctx.get_session_unique::<CryptoProviderImpl>();
+ let server_session = server_conn_ctx.get_session_unique::<CryptoProviderImpl>();
let message = b"Hello World!";
- let encoded = init_conn_ctx.encode_message_to_peer::<C, &[u8]>(message, None);
- let decoded = server_conn_ctx.decode_message_from_peer::<C, &[u8]>(encoded.as_slice(), None);
+ let encoded = init_conn_ctx.encode_message_to_peer::<CryptoProviderImpl, &[u8]>(message, None);
+ let decoded = server_conn_ctx
+ .decode_message_from_peer::<CryptoProviderImpl, &[u8]>(encoded.as_slice(), None);
assert_eq!(message.to_vec(), decoded.expect("Decode should be successful"));
- let init_session_after = init_conn_ctx.get_session_unique::<C>();
- let server_session_after = server_conn_ctx.get_session_unique::<C>();
- let bad_server_ctx = D2DConnectionContextV1::new::<C>(
+ let init_session_after = init_conn_ctx.get_session_unique::<CryptoProviderImpl>();
+ let server_session_after = server_conn_ctx.get_session_unique::<CryptoProviderImpl>();
+ let bad_server_ctx = D2DConnectionContextV1::new::<CryptoProviderImpl>(
server_conn_ctx.get_sequence_number_for_decoding(),
server_conn_ctx.get_sequence_number_for_encoding(),
Aes256Key::default(),
@@ -260,7 +251,7 @@
assert_eq!(init_session, init_session_after);
assert_eq!(server_session, server_session_after);
assert_eq!(init_session, server_session);
- assert_ne!(server_session, bad_server_ctx.get_session_unique::<C>());
+ assert_ne!(server_session, bad_server_ctx.get_session_unique::<CryptoProviderImpl>());
}
#[test]
diff --git a/nearby/connections/ukey2/ukey2_jni/Cargo.toml b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
index 7a399d3..4852444 100644
--- a/nearby/connections/ukey2/ukey2_jni/Cargo.toml
+++ b/nearby/connections/ukey2/ukey2_jni/Cargo.toml
@@ -9,11 +9,10 @@
[dependencies]
ukey2_connections = { path = "../ukey2_connections" }
ukey2_rs = { path = "../ukey2" }
-lock_adapter.workspace = true
+lock_adapter = {workspace = true, features = ["spin"]}
cfg-if.workspace = true
-crypto_provider_openssl = { workspace = true, optional = true }
-crypto_provider_rustcrypto = { workspace = true, optional = true, features = [ "alloc" ] }
+crypto_provider_default = { workspace = true }
lazy_static.workspace = true
rand.workspace = true
rand_chacha.workspace = true
@@ -22,10 +21,10 @@
[features]
default = ["rustcrypto"]
-openssl = ["dep:crypto_provider_openssl", "std"]
-rustcrypto = ["crypto_provider_rustcrypto"]
-std = ["lock_adapter/std", "crypto_provider_rustcrypto/std"]
-crypto_provider_rustcrypto = ["dep:crypto_provider_rustcrypto"]
+openssl = ["crypto_provider_default/openssl", "std"]
+rustcrypto = ["crypto_provider_default/rustcrypto"]
+boringssl = ["crypto_provider_default/boringssl"]
+std = ["lock_adapter/std"]
[lib]
crate_type = ["cdylib"]
\ No newline at end of file
diff --git a/nearby/connections/ukey2/ukey2_jni/src/lib.rs b/nearby/connections/ukey2/ukey2_jni/src/lib.rs
index 570e74b..82d317e 100644
--- a/nearby/connections/ukey2/ukey2_jni/src/lib.rs
+++ b/nearby/connections/ukey2/ukey2_jni/src/lib.rs
@@ -34,13 +34,7 @@
ServerD2DHandshakeContext,
};
-cfg_if::cfg_if! {
- if #[cfg(feature = "rustcrypto")] {
- use crypto_provider_rustcrypto::RustCrypto as CryptoProvider;
- } else {
- use crypto_provider_openssl::Openssl as CryptoProvider;
- }
-}
+use crypto_provider_default::CryptoProviderImpl as CryptoProvider;
// Handle management
type D2DBox = Box<dyn D2DHandshakeContext>;
diff --git a/nearby/crypto/crypto_provider/Cargo.toml b/nearby/crypto/crypto_provider/Cargo.toml
index 60f8626..e7dd8ba 100644
--- a/nearby/crypto/crypto_provider/Cargo.toml
+++ b/nearby/crypto/crypto_provider/Cargo.toml
@@ -4,11 +4,14 @@
edition.workspace = true
publish.workspace = true
+[dependencies]
+tinyvec.workspace = true
+
[dev-dependencies]
criterion.workspace = true
hex-literal.workspace = true
crypto_provider_openssl.workspace = true
-crypto_provider_rustcrypto.workspace = true
+crypto_provider_rustcrypto = { workspace = true, features = ["std"] }
rand_ext.workspace = true
rand.workspace = true
@@ -17,6 +20,7 @@
std = []
alloc = []
test_vectors = []
+raw_private_key_permit = []
[[bench]]
name = "hmac_bench"
diff --git a/nearby/crypto/crypto_provider/src/aead.rs b/nearby/crypto/crypto_provider/src/aead.rs
new file mode 100644
index 0000000..165272e
--- /dev/null
+++ b/nearby/crypto/crypto_provider/src/aead.rs
@@ -0,0 +1,86 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+/// An implementation of AES-GCM-SIV.
+///
+/// An AesGcmSiv impl may be used for encryption and decryption.
+pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {}
+
+/// An implementation of AES-GCM.
+///
+/// An AesGcm impl may be used for encryption and decryption.
+pub trait AesGcm: Aead<Nonce = [u8; 12]> {}
+
+/// Error returned on unsuccessful AEAD operation.
+#[derive(Debug)]
+pub struct AeadError;
+
+/// Initializes an AEAD
+pub trait AeadInit<K: crate::aes::AesKey> {
+ /// Instantiates a new instance of the AEAD from key material.
+ fn new(key: &K) -> Self;
+}
+
+/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the
+/// Nonce. Encrypts and decrypts buffers in-place.
+pub trait Aead {
+ /// The size of the authentication tag, this is appended to the message on the encrypt operation
+ /// and truncated from the plaintext after decrypting.
+ const TAG_SIZE: usize;
+
+ /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with
+ /// the same key. This is critically important - nonce reuse may completely undermine the
+ /// security of the AEAD. Nonces may be predictable and public, so long as they are unique.
+ type Nonce: AsRef<[u8]>;
+
+ /// The type of the tag, which should always be [u8; Self::TAG_SIZE].
+ type Tag: AsRef<[u8]>;
+
+ /// Encrypt the given buffer containing a plaintext message. On success returns the encrypted
+ /// `msg` and appended auth tag, which will result in a Vec which is `Self::TAG_SIZE` bytes
+ /// greater than the initial message.
+ #[cfg(feature = "alloc")]
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>;
+
+ /// Encrypt the given buffer containing a plaintext message in-place, and returns the tag in the
+ /// result value.
+ fn encrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError>;
+
+ /// Decrypt the message, returning the decrypted plaintext or an error in the event the
+ /// provided authentication tag does not match the given ciphertext. On success the returned
+ /// Vec will only contain the plaintext and so will be `Self::TAG_SIZE` bytes less than the
+ /// initial message.
+ #[cfg(feature = "alloc")]
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError>;
+
+ /// Decrypt the message in-place, returning an error and leaving the input `msg` unchanged in
+ /// the event the provided authentication tag does not match the given ciphertext.
+ fn decrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ tag: &Self::Tag,
+ ) -> Result<(), AeadError>;
+}
diff --git a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs
deleted file mode 100644
index 3be7db3..0000000
--- a/nearby/crypto/crypto_provider/src/aead/aes_gcm_siv.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Traits for AES-GCM-SIV.
-
-extern crate alloc;
-use crate::aead::Aead;
-
-/// An implementation of AES-GCM-SIV.
-///
-/// An AesGcmSiv impl may be used for encryption and decryption.
-pub trait AesGcmSiv: Aead<Nonce = [u8; 12]> {}
diff --git a/nearby/crypto/crypto_provider/src/aead/mod.rs b/nearby/crypto/crypto_provider/src/aead/mod.rs
deleted file mode 100644
index 27284a9..0000000
--- a/nearby/crypto/crypto_provider/src/aead/mod.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-extern crate alloc;
-use alloc::vec::Vec;
-
-/// Contains traits for the AES-GCM-SIV AEAD algorithm.
-pub mod aes_gcm_siv;
-
-/// Error returned on unsuccessful AEAD operation.
-pub struct AeadError;
-
-/// Authenticated Encryption with Associated Data (AEAD) algorithm, where `N` is the size of the
-/// Nonce. Encrypts and decrypts buffers in-place.
-pub trait Aead {
- /// The size of the authentication tag, this is appended to the message on the encrypt operation
- /// and truncated from the plaintext after decrypting.
- const TAG_SIZE: usize;
-
- /// The cryptographic nonce used by the AEAD. The nonce must be unique for all messages with
- /// the same key. This is critically important - nonce reuse may completely undermine the
- /// security of the AEAD. Nonces may be predictable and public, so long as they are unique.
- type Nonce;
-
- /// The key material used to initialize the AEAD.
- type Key;
-
- /// Instantiates a new instance of the AEAD from key material.
- fn new(key: &Self::Key) -> Self;
-
- /// Encrypt the given buffer containing a plaintext message in-place. On success increases the
- /// buffer by `Self::TAG_SIZE` bytes and appends the auth tag to the end of `msg`.
- fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>;
-
- /// Decrypt the message in-place, returning an error in the event the provided authentication
- /// tag does not match the given ciphertext. The buffer will be truncated to the length of the
- /// original plaintext message upon success.
- fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &Self::Nonce) -> Result<(), AeadError>;
-}
diff --git a/nearby/crypto/crypto_provider/src/aes/cbc.rs b/nearby/crypto/crypto_provider/src/aes/cbc.rs
index e32c588..59ce52d 100644
--- a/nearby/crypto/crypto_provider/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider/src/aes/cbc.rs
@@ -14,7 +14,10 @@
//! Traits for AES-CBC 256 with PKCS7 padding.
+#[cfg(feature = "alloc")]
extern crate alloc;
+use crate::tinyvec::SliceVec;
+#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::Aes256Key;
@@ -24,15 +27,53 @@
/// Trait for implementing AES-CBC with PKCS7 padding.
pub trait AesCbcPkcs7Padded {
+ /// Calculate the padded output length (e.g. output of `encrypt`) from the unpadded length (e.g.
+ /// input message of `encrypt`).
+ fn padded_output_len(unpadded_len: usize) -> usize {
+ (unpadded_len - (unpadded_len % 16))
+ .checked_add(16)
+ .expect("Padded output length is larger than usize::MAX")
+ }
+
/// Encrypt message using `key` and `iv`, returning a ciphertext.
+ #[cfg(feature = "alloc")]
fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8>;
+
+ /// Encrypt message using `key` and `iv` in-place in the given `message` vec. The given slice
+ /// vec should have enough capacity to contain both the ciphertext and the padding (which can be
+ /// calculated from `padded_output_len()`). If it doesn't have enough capacity, an error will be
+ /// returned. The contents of the input `message` buffer is undefined in that case.
+ fn encrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError>;
+
/// Decrypt ciphertext using `key` and `iv`, returning the original message if `Ok()` otherwise
/// a `DecryptionError` indicating the type of error that occurred while decrypting.
+ #[cfg(feature = "alloc")]
fn decrypt(
key: &Aes256Key,
iv: &AesCbcIv,
ciphertext: &[u8],
) -> Result<Vec<u8>, DecryptionError>;
+
+ /// Decrypt ciphertext using `key` and `iv` and unpad it in-place. Returning the original
+ /// message if `Ok()` otherwise a `DecryptionError` indicating the type of error that occurred
+ /// while decrypting. In that case, the contents of the `ciphertext` buffer is undefined.
+ fn decrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError>;
+}
+
+/// Error type for describing what went wrong encrypting a message.
+#[derive(Debug, PartialEq, Eq)]
+pub enum EncryptionError {
+ /// Failed to add PKCS7 padding to the output when encrypting a message. This typically happens
+ /// when the given output buffer does not have enough capacity to append the padding.
+ PaddingFailed,
}
/// Error type for describing what went wrong decrypting a ciphertext.
@@ -43,3 +84,65 @@
/// correctly. Exposing padding errors can cause a padding oracle vulnerability.
BadPadding,
}
+
+#[cfg(test)]
+mod test {
+ #[cfg(feature = "alloc")]
+ extern crate alloc;
+ #[cfg(feature = "alloc")]
+ use alloc::vec::Vec;
+
+ use crate::aes::Aes256Key;
+ use crate::tinyvec::SliceVec;
+
+ use super::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError};
+
+ #[test]
+ fn test_padded_output_len() {
+ assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(0), 16);
+ assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(15), 16);
+ assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(16), 32);
+ assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(30), 32);
+ assert_eq!(AesCbcPkcs7PaddedStub::padded_output_len(32), 48);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_padded_output_len_overflow() {
+ AesCbcPkcs7PaddedStub::padded_output_len(usize::MAX);
+ }
+
+ struct AesCbcPkcs7PaddedStub;
+
+ impl AesCbcPkcs7Padded for AesCbcPkcs7PaddedStub {
+ #[cfg(feature = "alloc")]
+ fn encrypt(_key: &Aes256Key, _iv: &AesCbcIv, _message: &[u8]) -> Vec<u8> {
+ unimplemented!()
+ }
+
+ fn encrypt_in_place(
+ _key: &Aes256Key,
+ _iv: &AesCbcIv,
+ _message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError> {
+ unimplemented!()
+ }
+
+ #[cfg(feature = "alloc")]
+ fn decrypt(
+ _key: &Aes256Key,
+ _iv: &AesCbcIv,
+ _ciphertext: &[u8],
+ ) -> Result<Vec<u8>, DecryptionError> {
+ unimplemented!()
+ }
+
+ fn decrypt_in_place(
+ _key: &Aes256Key,
+ _iv: &AesCbcIv,
+ _ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError> {
+ unimplemented!()
+ }
+ }
+}
diff --git a/nearby/crypto/crypto_provider/src/aes/ctr.rs b/nearby/crypto/crypto_provider/src/aes/ctr.rs
index 73d4f55..6779fe8 100644
--- a/nearby/crypto/crypto_provider/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider/src/aes/ctr.rs
@@ -36,10 +36,8 @@
/// Build a `Self` from key material.
fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self;
- /// Encrypt the data in place, advancing the counter state appropriately.
- fn encrypt(&mut self, data: &mut [u8]);
- /// Decrypt the data in place, advancing the counter state appropriately.
- fn decrypt(&mut self, data: &mut [u8]);
+ /// Applies the key stream to the data in place, advancing the counter state appropriately.
+ fn apply_keystream(&mut self, data: &mut [u8]);
}
/// The combined nonce and counter that CTR increments and encrypts to form the keystream.
diff --git a/nearby/crypto/crypto_provider/src/aes/mod.rs b/nearby/crypto/crypto_provider/src/aes/mod.rs
index 83e48de..e359bad 100644
--- a/nearby/crypto/crypto_provider/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider/src/aes/mod.rs
@@ -20,7 +20,6 @@
pub mod ctr;
-#[cfg(feature = "alloc")]
pub mod cbc;
/// Block size in bytes for AES (and XTS-AES)
diff --git a/nearby/crypto/crypto_provider/src/ed25519.rs b/nearby/crypto/crypto_provider/src/ed25519.rs
index 48c2c50..b435ca2 100644
--- a/nearby/crypto/crypto_provider/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider/src/ed25519.rs
@@ -43,6 +43,59 @@
/// A byte buffer the size of a ed25519 `PrivateKey`.
pub type RawPrivateKey = [u8; PRIVATE_KEY_LENGTH];
+/// A permission token which may be supplied to methods which allow
+/// converting private keys to/from raw bytes.
+///
+/// In general, operations of this kind should only be done in
+/// development-tools, tests, or in credential storage layers
+/// to prevent accidental exposure of the private key.
+pub struct RawPrivateKeyPermit {
+ _marker: (),
+}
+
+impl RawPrivateKeyPermit {
+ pub(crate) fn new() -> Self {
+ Self { _marker: () }
+ }
+}
+
+#[cfg(feature = "raw_private_key_permit")]
+impl core::default::Default for RawPrivateKeyPermit {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// A crypto-provider-independent representation of the private
+/// key of an ed25519 key-pair, kept in such a way that
+/// it does not permit de-structuring it into raw bytes,
+/// nor constructing one from raw bytes.
+///
+/// Useful for when you want a data-structure to be
+/// crypto-provider independent and contain a private key.
+#[derive(Clone)]
+pub struct PrivateKey {
+ wrapped: RawPrivateKey,
+}
+
+impl PrivateKey {
+ /// Derives the public key corresponding to this private key.
+ pub fn derive_public_key<E: Ed25519Provider>(&self) -> E::PublicKey {
+ let key_pair = E::KeyPair::from_private_key(self);
+ key_pair.public()
+ }
+ /// Returns the raw bytes of this private key.
+ /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+ pub fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
+ self.wrapped
+ }
+ /// Constructs a private key from the raw bytes of the key.
+ /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+ pub fn from_raw_private_key(wrapped: RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
+ Self { wrapped }
+ }
+}
+
/// The keypair which includes both public and secret halves of an asymmetric key.
pub trait KeyPair: Sized {
/// The ed25519 public key, used when verifying a message
@@ -52,15 +105,37 @@
type Signature: Signature;
/// Returns the private key bytes of the `KeyPair`.
- /// This method should only ever be called by code which securely stores private credentials.
- fn private_key(&self) -> RawPrivateKey;
+ /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+ fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey;
/// Builds a key-pair from a `RawPrivateKey` array of bytes.
- /// This should only ever be called by code which securely stores private credentials.
- fn from_private_key(bytes: &RawPrivateKey) -> Self
+ /// This operation is only possible while holding a [`RawPrivateKeyPermit`].
+ fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
where
Self: Sized;
+ /// Returns the private key of the `KeyPair` in an opaque form.
+ fn private_key(&self) -> PrivateKey {
+ // We're okay to reach in and grab the bytes of the private key,
+ // since the way that we're exposing it would require a valid
+ // [`RawPrivateKeyPermit`] to extract them again.
+ let wrapped = self.raw_private_key(&RawPrivateKeyPermit::new());
+ PrivateKey { wrapped }
+ }
+
+ /// Builds a key-pair from a [`PrivateKey`], given in an opaque form.
+ fn from_private_key(private_key: &PrivateKey) -> Self
+ where
+ Self: Sized,
+ {
+ // We're okay to reach in and construct an instance from
+ // the bytes of the private key, since the way that they
+ // were originally expressed would still require a valid
+ // [`RawPrivateKeyPermit`] to access them.
+ let raw_private_key = &private_key.wrapped;
+ Self::from_raw_private_key(raw_private_key, &RawPrivateKeyPermit::new())
+ }
+
/// Sign the given message and return a digital signature
fn sign(&self, msg: &[u8]) -> Self::Signature;
diff --git a/nearby/crypto/crypto_provider/src/elliptic_curve.rs b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
index d06a21d..d176769 100644
--- a/nearby/crypto/crypto_provider/src/elliptic_curve.rs
+++ b/nearby/crypto/crypto_provider/src/elliptic_curve.rs
@@ -12,12 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-extern crate alloc;
-
use core::fmt::Debug;
-use alloc::vec::Vec;
-
/// Marker trait for an elliptic curve used for diffie-hellman.
pub trait Curve {}
@@ -42,12 +38,16 @@
/// The random number generator to be used for generating a secret
type Rng: crate::CryptoRng;
+ /// The for encoded public key bytes, for example a `[u8; N]` array if the size is fixed, or
+ /// `ArrayVec<[u8; N]>` if the size is bounded but not fixed.
+ type EncodedPublicKey: AsRef<[u8]> + Debug;
+
/// Generates a new random ephemeral secret.
fn generate_random(rng: &mut Self::Rng) -> Self;
/// Returns the bytes of the public key for this ephemeral secret that is suitable for sending
/// over the wire for key exchange.
- fn public_key_bytes(&self) -> Vec<u8>;
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey;
/// Performs diffie-hellman key exchange using this ephemeral secret with the given public key
/// `other_pub`.
@@ -59,6 +59,8 @@
/// Trait for a public key used for elliptic curve diffie hellman.
pub trait PublicKey<E: Curve>: Sized + PartialEq + Debug {
+ /// The type for an encoded public key.
+ type EncodedPublicKey: AsRef<[u8]> + Debug;
/// The error type associated with Public Key.
type Error: Debug;
@@ -71,5 +73,5 @@
/// the sec1 encoding, may return equivalent but different byte-representations due to point
/// compression, so it is not necessarily true that `from_bytes(bytes)?.to_bytes() == bytes`
/// (but it is always true that `from_bytes(key.to_bytes())? == key`).
- fn to_bytes(&self) -> Vec<u8>;
+ fn to_bytes(&self) -> Self::EncodedPublicKey;
}
diff --git a/nearby/crypto/crypto_provider/src/lib.rs b/nearby/crypto/crypto_provider/src/lib.rs
index 624072a..0031ebb 100644
--- a/nearby/crypto/crypto_provider/src/lib.rs
+++ b/nearby/crypto/crypto_provider/src/lib.rs
@@ -48,6 +48,8 @@
/// mod containing traits for ed25519 key generation, signing, and verification
pub mod ed25519;
+pub use tinyvec;
+
/// Uber crypto trait which defines the traits for all crypto primitives as associated types
pub trait CryptoProvider: Clone + Debug + PartialEq + Eq + Send {
/// The Hkdf type which implements the hkdf trait
@@ -80,10 +82,13 @@
/// using SHA-512 (SHA-2) and Curve25519
type Ed25519: ed25519::Ed25519Provider;
/// The trait defining AES-128-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 16 bytes.
- type Aes128GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes128Key>;
+ type Aes128GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes128Key>;
/// The trait defining AES-256-GCM-SIV, a nonce-misuse resistant AEAD with a key size of 32 bytes.
- type Aes256GcmSiv: aead::aes_gcm_siv::AesGcmSiv<Key = Aes256Key>;
-
+ type Aes256GcmSiv: aead::AesGcmSiv + aead::AeadInit<Aes256Key>;
+ /// The trait defining AES-128-GCM, an AEAD with a key size of 16 bytes.
+ type Aes128Gcm: aead::AesGcm + aead::AeadInit<Aes128Key>;
+ /// The trait defining AES-256-GCM, an AEAD with a key size of 32 bytes.
+ type Aes256Gcm: aead::AesGcm + aead::AeadInit<Aes256Key>;
/// The cryptographically secure random number generator
type CryptoRng: CryptoRng;
diff --git a/nearby/crypto/crypto_provider/src/p256.rs b/nearby/crypto/crypto_provider/src/p256.rs
index 90d3542..fd7b531 100644
--- a/nearby/crypto/crypto_provider/src/p256.rs
+++ b/nearby/crypto/crypto_provider/src/p256.rs
@@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-extern crate alloc;
+use tinyvec::ArrayVec;
use crate::elliptic_curve::{Curve, EcdhProvider, PublicKey};
-use alloc::vec::Vec;
use core::fmt::Debug;
/// Marker type for P256 implementation. This is used by EcdhProvider as its type parameter.
@@ -23,6 +22,24 @@
pub enum P256 {}
impl Curve for P256 {}
+/// Longest length for a sec-1 encoded P256 public key, which is the uncompressed format
+/// `04 || X || Y` as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography")
+/// standard.
+const P256_PUBLIC_KEY_MAX_LENGTH: usize = 65;
+
+/// Whether an elliptic curve point should be compressed or not.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PointCompression {
+ /// The elliptic curve point should be compressed (`02 || X` or `03 || X`),
+ /// as defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve
+ /// Cryptography").
+ Compressed,
+ /// The elliptic curve point should be uncompressed (`04 || X || Y`), as
+ /// defined in section 2.3.3 of the SECG SEC 1 ("Elliptic Curve
+ /// Cryptography").
+ Uncompressed,
+}
+
/// Trait for a NIST-P256 public key.
pub trait P256PublicKey: Sized + PartialEq + Debug {
/// The error type associated with this implementation.
@@ -36,7 +53,10 @@
/// ("Elliptic Curve Cryptography") standard. Note that it is not necessarily true that
/// `from_sec1_bytes(bytes)?.to_sec1_bytes() == bytes` because of point compression. (But it is
/// always true that `from_sec1_bytes(key.to_sec1_bytes())? == key`).
- fn to_sec1_bytes(&self) -> Vec<u8>;
+ fn to_sec1_bytes(
+ &self,
+ point_compression: PointCompression,
+ ) -> ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>;
/// Converts this public key's x and y coordinates on the elliptic curve to big endian octet
/// strings.
@@ -48,13 +68,14 @@
impl<P: P256PublicKey> PublicKey<P256> for P {
type Error = <Self as P256PublicKey>::Error;
+ type EncodedPublicKey = ArrayVec<[u8; P256_PUBLIC_KEY_MAX_LENGTH]>;
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_sec1_bytes(bytes)
}
- fn to_bytes(&self) -> Vec<u8> {
- Self::to_sec1_bytes(self)
+ fn to_bytes(&self) -> Self::EncodedPublicKey {
+ Self::to_sec1_bytes(self, PointCompression::Uncompressed)
}
}
diff --git a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml b/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml
deleted file mode 100644
index f5ab7fa..0000000
--- a/nearby/crypto/crypto_provider_boringssl/.cargo/config.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-paths = [
- "../../../boringssl-build/boringssl/rust/bssl-crypto",
-]
\ No newline at end of file
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.lock b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
index 14402c1..7773e56 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.lock
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.lock
@@ -4,13 +4,20 @@
[[package]]
name = "base64"
-version = "0.13.1"
+version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]]
name = "bssl-crypto"
version = "0.1.0"
+dependencies = [
+ "bssl-sys",
+]
+
+[[package]]
+name = "bssl-sys"
+version = "0.1.0"
[[package]]
name = "cfg-if"
@@ -21,6 +28,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_boringssl"
@@ -28,18 +38,10 @@
dependencies = [
"bssl-crypto",
"crypto_provider",
- "crypto_provider_stubs",
"crypto_provider_test",
]
[[package]]
-name = "crypto_provider_stubs"
-version = "0.1.0"
-dependencies = [
- "crypto_provider",
-]
-
-[[package]]
name = "crypto_provider_test"
version = "0.1.0"
dependencies = [
@@ -56,9 +58,9 @@
[[package]]
name = "getrandom"
-version = "0.2.9"
+version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
@@ -79,24 +81,21 @@
[[package]]
name = "itoa"
-version = "1.0.6"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "libc"
-version = "0.2.144"
+version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
-version = "0.4.17"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "ppv-lite86"
@@ -106,18 +105,18 @@
[[package]]
name = "proc-macro2"
-version = "1.0.56"
+version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.27"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
@@ -197,14 +196,14 @@
[[package]]
name = "rstest_reuse"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073"
+checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4"
dependencies = [
"quote",
"rand",
"rustc_version",
- "syn 1.0.109",
+ "syn 2.0.29",
]
[[package]]
@@ -218,41 +217,41 @@
[[package]]
name = "ryu"
-version = "1.0.13"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "semver"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
[[package]]
name = "serde"
-version = "1.0.162"
+version = "1.0.187"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
+checksum = "30a7fe14252655bd1e578af19f5fa00fe02fd0013b100ca6b49fde31c41bae4c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.162"
+version = "1.0.187"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
+checksum = "e46b2a6ca578b3f1d4501b12f78ed4692006d79d82a1a7c561c12dbc3d625eb8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.15",
+ "syn 2.0.29",
]
[[package]]
name = "serde_json"
-version = "1.0.96"
+version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
@@ -272,9 +271,9 @@
[[package]]
name = "syn"
-version = "2.0.15"
+version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
@@ -290,10 +289,16 @@
]
[[package]]
-name = "unicode-ident"
-version = "1.0.8"
+name = "tinyvec"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "wasi"
@@ -303,9 +308,9 @@
[[package]]
name = "wycheproof"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "183c789620c674b79dac33cd3aadb6c8006b66cba6a680402235aaebc743e3df"
+checksum = "e639f57253b80c6584b378011aec0fed61c4c21d7a4b97c4d9d7eaf35ca77d12"
dependencies = [
"base64",
"hex",
diff --git a/nearby/crypto/crypto_provider_boringssl/Cargo.toml b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
index 24ce66c..fb3bfdf 100644
--- a/nearby/crypto/crypto_provider_boringssl/Cargo.toml
+++ b/nearby/crypto/crypto_provider_boringssl/Cargo.toml
@@ -6,10 +6,9 @@
[dependencies]
crypto_provider = { path = "../crypto_provider", features = ["alloc", "std"] }
-crypto_provider_stubs = { path = "../crypto_provider_stubs" }
-# Note: before this crate will work you need to run `scripts/prepare-boringssl.sh`
-bssl-crypto = {path = "../bssl-crypto"}
+# Note: before this crate will work you need to run `cargo run -p build_scripts -- build-boringssl`
+bssl-crypto = {path = "../../../third_party/boringssl/rust/bssl-crypto"}
[dev-dependencies]
crypto_provider_test = {path = "../crypto_provider_test"}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..03bb087
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm.rs
@@ -0,0 +1,89 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate alloc;
+
+use alloc::vec::Vec;
+use bssl_crypto::aead::Aead as _;
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+
+pub struct AesGcm(bssl_crypto::aead::AesGcm);
+
+impl AeadInit<Aes128Key> for AesGcm {
+ fn new(key: &Aes128Key) -> Self {
+ Self(bssl_crypto::aead::new_aes_128_gcm(key.as_array()))
+ }
+}
+
+impl AeadInit<Aes256Key> for AesGcm {
+ fn new(key: &Aes256Key) -> Self {
+ Self(bssl_crypto::aead::new_aes_256_gcm(key.as_array()))
+ }
+}
+
+impl crypto_provider::aead::AesGcm for AesGcm {}
+
+impl Aead for AesGcm {
+ const TAG_SIZE: usize = 16;
+ type Nonce = [u8; 12];
+ type Tag = [u8; 16];
+
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError)
+ }
+
+ fn encrypt_detached(
+ &self,
+ _msg: &mut [u8],
+ _aad: &[u8],
+ _nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
+ unimplemented!("Not yet supported by boringssl")
+ }
+
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError)
+ }
+
+ fn decrypt_detached(
+ &self,
+ _msg: &mut [u8],
+ _aad: &[u8],
+ _nonce: &Self::Nonce,
+ _tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
+ unimplemented!("Not yet supported by boringssl")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use core::marker::PhantomData;
+
+ use crypto_provider_test::aead::aes_gcm::*;
+ use crypto_provider_test::aes::*;
+
+ use super::*;
+
+ #[apply(aes_128_gcm_test_cases)]
+ fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_gcm_test_cases)]
+ fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs
new file mode 100644
index 0000000..d1fb0e7
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/aes_gcm_siv.rs
@@ -0,0 +1,88 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+extern crate alloc;
+
+use alloc::vec::Vec;
+use bssl_crypto::aead::Aead as _;
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+
+pub struct AesGcmSiv(bssl_crypto::aead::AesGcmSiv);
+
+impl AeadInit<Aes128Key> for AesGcmSiv {
+ fn new(key: &Aes128Key) -> Self {
+ Self(bssl_crypto::aead::new_aes_128_gcm_siv(key.as_array()))
+ }
+}
+
+impl AeadInit<Aes256Key> for AesGcmSiv {
+ fn new(key: &Aes256Key) -> Self {
+ Self(bssl_crypto::aead::new_aes_256_gcm_siv(key.as_array()))
+ }
+}
+
+impl crypto_provider::aead::AesGcmSiv for AesGcmSiv {}
+
+impl Aead for AesGcmSiv {
+ const TAG_SIZE: usize = 16;
+ type Nonce = [u8; 12];
+ type Tag = [u8; 16];
+
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0.encrypt(msg, aad, nonce).map_err(|_| AeadError)
+ }
+
+ fn encrypt_detached(
+ &self,
+ _msg: &mut [u8],
+ _aad: &[u8],
+ _nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
+ unimplemented!("Not yet supported by boringssl")
+ }
+
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0.decrypt(msg, aad, nonce).map_err(|_| AeadError)
+ }
+
+ fn decrypt_detached(
+ &self,
+ _msg: &mut [u8],
+ _aad: &[u8],
+ _nonce: &Self::Nonce,
+ _tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
+ unimplemented!("Not yet supported by boringssl")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use core::marker::PhantomData;
+
+ use crypto_provider_test::aead::aes_gcm_siv::*;
+ use crypto_provider_test::aes::*;
+
+ use super::*;
+
+ #[apply(aes_128_gcm_siv_test_cases)]
+ fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_gcm_siv_test_cases)]
+ fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
similarity index 73%
copy from nearby/crypto/bssl-crypto/src/lib.rs
copy to nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
index 89e6968..424f16e 100644
--- a/nearby/crypto/bssl-crypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/aead/mod.rs
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,5 +12,5 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the
-//! `build-boringssl` subcommand of the top level crate.
+pub(crate) mod aes_gcm;
+pub(crate) mod aes_gcm_siv;
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs
new file mode 100644
index 0000000..4d3fd3f
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/cbc.rs
@@ -0,0 +1,81 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate alloc;
+
+use alloc::vec::Vec;
+use bssl_crypto::cipher::BlockCipher;
+use crypto_provider::{
+ aes::{
+ cbc::{AesCbcIv, DecryptionError, EncryptionError},
+ Aes256Key, AesKey,
+ },
+ tinyvec::SliceVec,
+};
+
+/// BoringSSL implementation of AES-CBC with PKCS7 padding
+pub enum AesCbcPkcs7Padded {}
+impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
+ #[allow(clippy::expect_used)]
+ fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
+ let encryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_encrypt(key.as_array(), iv);
+ encryptor.encrypt_padded(message).expect("Encrypting AES-CBC should be infallible")
+ }
+
+ fn encrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError> {
+ let result = Self::encrypt(key, iv, message);
+ if message.capacity() < result.len() {
+ return Err(EncryptionError::PaddingFailed);
+ }
+ message.clear();
+ message.extend_from_slice(&result);
+ Ok(())
+ }
+
+ fn decrypt(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &[u8],
+ ) -> Result<Vec<u8>, DecryptionError> {
+ let decryptor = bssl_crypto::cipher::aes_cbc::Aes256Cbc::new_decrypt(key.as_array(), iv);
+ decryptor.decrypt_padded(ciphertext).map_err(|_| DecryptionError::BadPadding)
+ }
+
+ fn decrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError> {
+ Self::decrypt(key, iv, ciphertext).map(|result| {
+ ciphertext.clear();
+ ciphertext.extend_from_slice(&result);
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::AesCbcPkcs7Padded;
+ use core::marker::PhantomData;
+ use crypto_provider_test::aes::cbc::*;
+
+ #[apply(aes_256_cbc_test_cases)]
+ fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs
new file mode 100644
index 0000000..c13ab31
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/ctr.rs
@@ -0,0 +1,74 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use bssl_crypto::cipher::StreamCipher;
+use crypto_provider::aes::{ctr::NonceAndCounter, Aes128Key, Aes256Key, AesKey};
+
+/// BoringSSL implementation of AES-CTR 128.
+pub struct AesCtr128(bssl_crypto::cipher::aes_ctr::Aes128Ctr);
+
+impl crypto_provider::aes::ctr::AesCtr for AesCtr128 {
+ type Key = Aes128Key;
+
+ fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self {
+ Self(bssl_crypto::cipher::aes_ctr::Aes128Ctr::new(
+ key.as_array(),
+ &nonce_and_counter.as_block_array(),
+ ))
+ }
+
+ #[allow(clippy::expect_used)]
+ fn apply_keystream(&mut self, data: &mut [u8]) {
+ assert!(data.len() < i32::MAX as usize);
+ self.0.apply_keystream(data).expect("Data length should fit inside of a i32")
+ }
+}
+
+/// BoringSSL implementation of AES-CTR 256.
+pub struct AesCtr256(bssl_crypto::cipher::aes_ctr::Aes256Ctr);
+
+impl crypto_provider::aes::ctr::AesCtr for AesCtr256 {
+ type Key = Aes256Key;
+
+ fn new(key: &Self::Key, nonce_and_counter: NonceAndCounter) -> Self {
+ Self(bssl_crypto::cipher::aes_ctr::Aes256Ctr::new(
+ key.as_array(),
+ &nonce_and_counter.as_block_array(),
+ ))
+ }
+
+ #[allow(clippy::expect_used)]
+ fn apply_keystream(&mut self, data: &mut [u8]) {
+ assert!(data.len() < i32::MAX as usize);
+ self.0.apply_keystream(data).expect("Data length should fit inside of a i32")
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use core::marker::PhantomData;
+ use crypto_provider_test::aes::ctr::*;
+ use crypto_provider_test::aes::*;
+
+ #[apply(aes_128_ctr_test_cases)]
+ fn aes_128_ctr_test(testcase: CryptoProviderTestCase<AesCtr128>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_ctr_test_cases)]
+ fn aes_256_ctr_test(testcase: CryptoProviderTestCase<AesCtr256>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/aes.rs b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
similarity index 97%
rename from nearby/crypto/crypto_provider_boringssl/src/aes.rs
rename to nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
index 88bfeac..d5cbe58 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/aes/mod.rs
@@ -17,6 +17,12 @@
Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
};
+/// AES_CTR implementations.
+pub mod ctr;
+
+/// AES_CBC implementations.
+pub mod cbc;
+
/// BoringSSL AES-128 operations
pub struct Aes128;
impl Aes for Aes128 {
diff --git a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
index 7c671ae..b5c374a 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/ed25519.rs
@@ -13,7 +13,8 @@
// limitations under the License.
use crypto_provider::ed25519::{
- InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
+ InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+ SignatureError,
};
pub struct Ed25519;
@@ -30,11 +31,11 @@
type PublicKey = PublicKey;
type Signature = Signature;
- fn private_key(&self) -> RawPrivateKey {
+ fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
self.0.to_seed()
}
- fn from_private_key(bytes: &RawPrivateKey) -> Self
+ fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
where
Self: Sized,
{
diff --git a/nearby/crypto/crypto_provider_boringssl/src/lib.rs b/nearby/crypto/crypto_provider_boringssl/src/lib.rs
index 3b78898..75758b6 100644
--- a/nearby/crypto/crypto_provider_boringssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_boringssl/src/lib.rs
@@ -21,14 +21,12 @@
clippy::expect_used
)]
-//! Crate which provides impls for CryptoProvider backed by BoringSSL.
-
-use bssl_crypto::digest::{Sha256, Sha512};
+//! Crate which provides impls for CryptoProvider backed by BoringSSL
+//!
use bssl_crypto::rand::rand_bytes;
use crypto_provider::{CryptoProvider, CryptoRng};
-use crypto_provider_stubs::*;
-/// Implementation of `crypto_provider::aes` types using BoringSSL.
+/// Implementation of `crypto_provider::aes` types using BoringSSL
pub mod aes;
/// Implementations of crypto_provider::hkdf traits backed by BoringSSL
@@ -40,31 +38,45 @@
/// Implementations of crypto_provider::ed25519 traits backed by BoringSSL
mod ed25519;
+/// Implementations of crypto_provider::aead traits backed by BoringSSL
+mod aead;
+
+/// Implementations of crypto_provider::p256 traits backed by BoringSSL
+mod p256;
+
+/// Implementations of crypto_provider::x25519 traits backed by BoringSSL
+mod x25519;
+
+/// Implementations of crypto_provider::sha2 traits backed by BoringSSL
+mod sha2;
+
/// The BoringSSL backed struct which implements CryptoProvider
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct Boringssl;
impl CryptoProvider for Boringssl {
- type HkdfSha256 = hkdf::Hkdf<Sha256>;
+ type HkdfSha256 = hkdf::Hkdf<bssl_crypto::digest::Sha256>;
type HmacSha256 = hmac::HmacSha256;
- type HkdfSha512 = hkdf::Hkdf<Sha512>;
+ type HkdfSha512 = hkdf::Hkdf<bssl_crypto::digest::Sha512>;
type HmacSha512 = hmac::HmacSha512;
- type AesCbcPkcs7Padded = AesCbcPkcs7PaddedStubs;
- type X25519 = X25519Stubs;
- type P256 = P256Stubs;
- type Sha256 = Sha2Stubs;
- type Sha512 = Sha2Stubs;
+ type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded;
+ type X25519 = x25519::X25519Ecdh;
+ type P256 = p256::P256Ecdh;
+ type Sha256 = sha2::Sha256;
+ type Sha512 = sha2::Sha512;
type Aes128 = aes::Aes128;
type Aes256 = aes::Aes256;
- type AesCtr128 = Aes128Stubs;
- type AesCtr256 = Aes256Stubs;
+ type AesCtr128 = aes::ctr::AesCtr128;
+ type AesCtr256 = aes::ctr::AesCtr256;
type Ed25519 = ed25519::Ed25519;
- type Aes128GcmSiv = Aes128Stubs;
- type Aes256GcmSiv = Aes256Stubs;
+ type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv;
+ type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv;
+ type Aes128Gcm = aead::aes_gcm::AesGcm;
+ type Aes256Gcm = aead::aes_gcm::AesGcm;
type CryptoRng = BoringSslRng;
- fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool {
- unimplemented!()
+ fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
+ bssl_crypto::mem::crypto_memcmp(a, b)
}
}
@@ -86,3 +98,17 @@
rand_bytes(dest)
}
}
+
+#[cfg(test)]
+mod tests {
+ use core::marker::PhantomData;
+ use crypto_provider_test::prelude::*;
+ use crypto_provider_test::sha2::*;
+
+ use crate::Boringssl;
+
+ #[apply(sha2_test_cases)]
+ fn sha2_tests(testcase: CryptoProviderTestCase<Boringssl>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/p256.rs b/nearby/crypto/crypto_provider_boringssl/src/p256.rs
new file mode 100644
index 0000000..7fefecd
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/p256.rs
@@ -0,0 +1,117 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate alloc;
+
+use bssl_crypto::ecdh::{PrivateKey, PublicKey};
+use crypto_provider::{
+ elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey as _},
+ p256::{PointCompression, P256},
+ tinyvec::ArrayVec,
+};
+
+/// Implementation of NIST-P256 using bssl-crypto crate.
+pub struct P256Ecdh;
+
+impl EcdhProvider<P256> for P256Ecdh {
+ type PublicKey = P256PublicKey;
+ type EphemeralSecret = P256EphemeralSecret;
+ type SharedSecret = [u8; 32];
+}
+
+/// A NIST-P256 public key.
+#[derive(Debug, PartialEq, Eq)]
+pub struct P256PublicKey(PublicKey<bssl_crypto::ecdh::P256>);
+impl crypto_provider::p256::P256PublicKey for P256PublicKey {
+ type Error = bssl_crypto::ecdh::Error;
+
+ fn from_sec1_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
+ PublicKey::try_from(bytes).map(Self)
+ }
+
+ fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+ match point_compression {
+ PointCompression::Compressed => unimplemented!(),
+ PointCompression::Uncompressed => {
+ let mut bytes = ArrayVec::<[u8; 65]>::new();
+ bytes.extend_from_slice(&self.0.to_vec());
+ bytes
+ }
+ }
+ }
+
+ #[allow(clippy::expect_used)]
+ fn to_affine_coordinates(&self) -> Result<([u8; 32], [u8; 32]), Self::Error> {
+ Ok(self.0.to_affine_coordinates())
+ }
+ fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> {
+ PublicKey::<bssl_crypto::ecdh::P256>::from_affine_coordinates(x, y).map(Self)
+ }
+}
+
+/// Ephemeral secrect for use in a P256 Diffie-Hellman
+pub struct P256EphemeralSecret {
+ secret: PrivateKey<bssl_crypto::ecdh::P256>,
+}
+
+impl EphemeralSecret<P256> for P256EphemeralSecret {
+ type Impl = P256Ecdh;
+ type Error = bssl_crypto::ecdh::Error;
+ type Rng = ();
+ type EncodedPublicKey =
+ <P256PublicKey as crypto_provider::elliptic_curve::PublicKey<P256>>::EncodedPublicKey;
+
+ fn generate_random(_rng: &mut Self::Rng) -> Self {
+ Self { secret: PrivateKey::generate() }
+ }
+
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+ P256PublicKey((&self.secret).into()).to_bytes()
+ }
+
+ fn diffie_hellman(
+ self,
+ other_pub: &P256PublicKey,
+ ) -> Result<<Self::Impl as EcdhProvider<P256>>::SharedSecret, Self::Error> {
+ let shared_secret = self.secret.diffie_hellman(&other_pub.0)?;
+ let bytes: <Self::Impl as EcdhProvider<P256>>::SharedSecret = shared_secret.to_bytes();
+ Ok(bytes)
+ }
+}
+
+#[cfg(test)]
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<P256> for P256EphemeralSecret {
+ #[allow(clippy::unwrap_used)]
+ fn from_private_components(
+ private_bytes: &[u8; 32],
+ _public_key: &P256PublicKey,
+ ) -> Result<Self, Self::Error> {
+ Ok(Self { secret: PrivateKey::from_private_bytes(private_bytes).unwrap() })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::P256Ecdh;
+ use core::marker::PhantomData;
+ use crypto_provider_test::p256::*;
+
+ #[apply(p256_test_cases)]
+ fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, name: &str) {
+ if name == "to_bytes_compressed" {
+ return; // EC point compression not supported by bssl-crypto yet
+ }
+ testcase(PhantomData::<P256Ecdh>)
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/sha2.rs b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs
new file mode 100644
index 0000000..d95294d
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/sha2.rs
@@ -0,0 +1,35 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// BoringSSL implementation for SHA256
+pub struct Sha256;
+
+impl crypto_provider::sha2::Sha256 for Sha256 {
+ fn sha256(input: &[u8]) -> [u8; 32] {
+ let mut digest = bssl_crypto::digest::Sha256::new_digest();
+ digest.update(input);
+ digest.finalize()
+ }
+}
+
+/// BoringSSL implementation for SHA512
+pub struct Sha512;
+
+impl crypto_provider::sha2::Sha512 for Sha512 {
+ fn sha512(input: &[u8]) -> [u8; 64] {
+ let mut digest = bssl_crypto::digest::Sha512::new_digest();
+ digest.update(input);
+ digest.finalize()
+ }
+}
diff --git a/nearby/crypto/crypto_provider_boringssl/src/x25519.rs b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs
new file mode 100644
index 0000000..58d040b
--- /dev/null
+++ b/nearby/crypto/crypto_provider_boringssl/src/x25519.rs
@@ -0,0 +1,102 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey};
+use crypto_provider::x25519::X25519;
+
+/// The BoringSSL implementation of X25519 ECDH.
+pub struct X25519Ecdh;
+
+impl EcdhProvider<X25519> for X25519Ecdh {
+ type PublicKey = X25519PublicKey;
+ type EphemeralSecret = X25519EphemeralSecret;
+ type SharedSecret = [u8; 32];
+}
+
+/// A X25519 ephemeral secret used for Diffie-Hellman.
+pub struct X25519EphemeralSecret {
+ secret: bssl_crypto::x25519::PrivateKey,
+}
+
+impl EphemeralSecret<X25519> for X25519EphemeralSecret {
+ type Impl = X25519Ecdh;
+ type Error = bssl_crypto::x25519::DiffieHellmanError;
+ type Rng = ();
+ type EncodedPublicKey = [u8; 32];
+
+ fn generate_random(_rng: &mut Self::Rng) -> Self {
+ Self { secret: bssl_crypto::x25519::PrivateKey::generate() }
+ }
+
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+ let pubkey: bssl_crypto::x25519::PublicKey = (&self.secret).into();
+ pubkey.to_bytes()
+ }
+
+ fn diffie_hellman(
+ self,
+ other_pub: &<X25519Ecdh as EcdhProvider<X25519>>::PublicKey,
+ ) -> Result<<X25519Ecdh as EcdhProvider<X25519>>::SharedSecret, Self::Error> {
+ self.secret.diffie_hellman(&other_pub.0).map(|secret| secret.to_bytes())
+ }
+}
+
+#[cfg(test)]
+impl crypto_provider_test::elliptic_curve::EphemeralSecretForTesting<X25519>
+ for X25519EphemeralSecret
+{
+ fn from_private_components(
+ private_bytes: &[u8; 32],
+ _public_key: &X25519PublicKey,
+ ) -> Result<Self, Self::Error> {
+ Ok(Self { secret: bssl_crypto::x25519::PrivateKey::from_private_bytes(private_bytes) })
+ }
+}
+
+/// A X25519 public key.
+#[derive(Debug, PartialEq, Eq)]
+pub struct X25519PublicKey(bssl_crypto::x25519::PublicKey);
+
+impl PublicKey<X25519> for X25519PublicKey {
+ type Error = Error;
+ type EncodedPublicKey = [u8; 32];
+
+ fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
+ let byte_arr: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?;
+ Ok(Self(bssl_crypto::x25519::PublicKey::from(&byte_arr)))
+ }
+
+ fn to_bytes(&self) -> Self::EncodedPublicKey {
+ self.0.to_bytes()
+ }
+}
+
+/// Error type for the BoringSSL implementation of x25519.
+#[derive(Debug)]
+pub enum Error {
+ /// Unexpected size for the given input.
+ WrongSize,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::X25519Ecdh;
+ use core::marker::PhantomData;
+ use crypto_provider_test::x25519::*;
+
+ #[apply(x25519_test_cases)]
+ fn x25519_tests(testcase: CryptoProviderTestCase<X25519Ecdh>) {
+ testcase(PhantomData::<X25519Ecdh>)
+ }
+}
diff --git a/nearby/crypto/crypto_provider_default/src/lib.rs b/nearby/crypto/crypto_provider_default/src/lib.rs
index 4b1b01e..b880ba8 100644
--- a/nearby/crypto/crypto_provider_default/src/lib.rs
+++ b/nearby/crypto/crypto_provider_default/src/lib.rs
@@ -15,6 +15,10 @@
//! Provides multiple implementations of CryptoProvider through the same struct, configurable by
//! feature flag.
+#![no_std]
+#![forbid(unsafe_code)]
+#![deny(missing_docs)]
+
cfg_if::cfg_if! {
if #[cfg(feature = "rustcrypto")] {
pub use crypto_provider_rustcrypto::RustCrypto as CryptoProviderImpl;
diff --git a/nearby/crypto/crypto_provider_openssl/src/aes.rs b/nearby/crypto/crypto_provider_openssl/src/aes.rs
index f5d5b00..c2ac5b1 100644
--- a/nearby/crypto/crypto_provider_openssl/src/aes.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/aes.rs
@@ -26,10 +26,13 @@
use openssl::symm::{Cipher, Crypter, Mode};
-use crypto_provider::aes::{
- cbc::{AesCbcIv, DecryptionError},
- ctr::NonceAndCounter,
- Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
+use crypto_provider::{
+ aes::{
+ cbc::{AesCbcIv, DecryptionError, EncryptionError},
+ ctr::NonceAndCounter,
+ Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher, AesKey,
+ },
+ tinyvec::SliceVec,
};
/// Uber struct which contains impls for AES-128 fns
@@ -126,7 +129,23 @@
impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for OpenSslAesCbcPkcs7 {
fn encrypt(key: &crypto_provider::aes::Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
openssl::symm::encrypt(Cipher::aes_256_cbc(), key.as_slice(), Some(iv.as_slice()), message)
- .unwrap()
+ // The output buffer is allocated by the openssl crate and guarantees to have enough
+ // space to hold the output value and does not overlap with the input slice.
+ .expect("encrypt should always succeed")
+ }
+
+ fn encrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError> {
+ let encrypted = Self::encrypt(key, iv, message);
+ if encrypted.len() > message.capacity() {
+ return Err(EncryptionError::PaddingFailed);
+ }
+ message.clear();
+ message.extend_from_slice(&encrypted);
+ Ok(())
}
fn decrypt(
@@ -142,12 +161,22 @@
)
.map_err(|_| DecryptionError::BadPadding)
}
+
+ fn decrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError> {
+ Self::decrypt(key, iv, ciphertext).map(|result| {
+ ciphertext.clear();
+ ciphertext.extend_from_slice(&result);
+ })
+ }
}
/// OpenSSL implementation of AES-CTR-128
pub struct OpenSslAesCtr128 {
enc_cipher: Crypter,
- dec_cipher: Crypter,
}
impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr128 {
@@ -161,33 +190,19 @@
Some(&nonce_and_counter.as_block_array()),
)
.unwrap(),
- dec_cipher: Crypter::new(
- Cipher::aes_128_ctr(),
- Mode::Decrypt,
- key.as_slice(),
- Some(&nonce_and_counter.as_block_array()),
- )
- .unwrap(),
}
}
- fn encrypt(&mut self, data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
let mut in_slice = vec![0u8; data.len()];
in_slice.copy_from_slice(data);
let _ = self.enc_cipher.update(&in_slice, data);
}
-
- fn decrypt(&mut self, data: &mut [u8]) {
- let mut in_slice = vec![0u8; data.len()];
- in_slice.copy_from_slice(data);
- let _ = self.dec_cipher.update(&in_slice, data);
- }
}
/// OpenSSL implementation of AES-CTR-256
pub struct OpenSslAesCtr256 {
enc_cipher: Crypter,
- dec_cipher: Crypter,
}
impl crypto_provider::aes::ctr::AesCtr for OpenSslAesCtr256 {
@@ -201,27 +216,14 @@
Some(&nonce_and_counter.as_block_array()),
)
.unwrap(),
- dec_cipher: Crypter::new(
- Cipher::aes_256_ctr(),
- Mode::Decrypt,
- key.as_slice(),
- Some(&nonce_and_counter.as_block_array()),
- )
- .unwrap(),
}
}
- fn encrypt(&mut self, data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
let mut in_slice = vec![0u8; data.len()];
in_slice.copy_from_slice(data);
let _ = self.enc_cipher.update(&in_slice, data);
}
-
- fn decrypt(&mut self, data: &mut [u8]) {
- let mut in_slice = vec![0u8; data.len()];
- in_slice.copy_from_slice(data);
- let _ = self.dec_cipher.update(&in_slice, data);
- }
}
#[cfg(test)]
diff --git a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
index 110daf2..111348a 100644
--- a/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/ed25519.rs
@@ -13,7 +13,8 @@
// limitations under the License.
use crypto_provider::ed25519::{
- InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
+ InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+ SignatureError,
};
use openssl::pkey::{Id, PKey, Private};
use openssl::sign::{Signer, Verifier};
@@ -32,7 +33,7 @@
type PublicKey = PublicKey;
type Signature = Signature;
- fn private_key(&self) -> RawPrivateKey {
+ fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
let private_key = self.0.raw_private_key().unwrap();
let mut public_key = self.0.raw_public_key().unwrap();
let mut result = private_key;
@@ -40,7 +41,7 @@
result.try_into().unwrap()
}
- fn from_private_key(bytes: &RawPrivateKey) -> Self {
+ fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
Self(PKey::private_key_from_raw_bytes(bytes, Id::ED25519).unwrap())
}
diff --git a/nearby/crypto/crypto_provider_openssl/src/lib.rs b/nearby/crypto/crypto_provider_openssl/src/lib.rs
index d8157d2..8d1db1d 100644
--- a/nearby/crypto/crypto_provider_openssl/src/lib.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/lib.rs
@@ -82,6 +82,8 @@
type Ed25519 = ed25519::Ed25519;
type Aes128GcmSiv = crypto_provider_stubs::Aes128Stubs;
type Aes256GcmSiv = crypto_provider_stubs::Aes256Stubs;
+ type Aes128Gcm = crypto_provider_stubs::Aes128Stubs;
+ type Aes256Gcm = crypto_provider_stubs::Aes256Stubs;
type CryptoRng = OpenSslRng;
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
@@ -113,7 +115,7 @@
use core::marker::PhantomData;
use crypto_provider_test::sha2::*;
- use crypto_provider_test::*;
+ use crypto_provider_test::{prelude::*, *};
use crate::Openssl;
diff --git a/nearby/crypto/crypto_provider_openssl/src/p256.rs b/nearby/crypto/crypto_provider_openssl/src/p256.rs
index 2f5b0b1..17dd6ce 100644
--- a/nearby/crypto/crypto_provider_openssl/src/p256.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/p256.rs
@@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret};
-use crypto_provider::p256::P256;
+use crypto_provider::{
+ elliptic_curve::{EcdhProvider, EphemeralSecret},
+ p256::{PointCompression, P256},
+ tinyvec::ArrayVec,
+};
use openssl::bn::{BigNum, BigNumContext};
use openssl::derive::Deriver;
use openssl::ec::{EcGroup, EcKey, EcPoint, PointConversionForm};
@@ -65,15 +68,24 @@
Ok(Self(eckey.try_into()?))
}
- fn to_sec1_bytes(&self) -> Vec<u8> {
+ fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+ let point_conversion_form = match point_compression {
+ PointCompression::Compressed => PointConversionForm::COMPRESSED,
+ PointCompression::Uncompressed => PointConversionForm::UNCOMPRESSED,
+ };
let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let mut bncontext = BigNumContext::new().unwrap();
- self.0
- .ec_key()
- .unwrap()
- .public_key()
- .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
- .unwrap()
+ let mut bytes = ArrayVec::<[u8; 65]>::new();
+ bytes.extend_from_slice(
+ &self
+ .0
+ .ec_key()
+ .unwrap()
+ .public_key()
+ .to_bytes(&ecgroup, point_conversion_form, &mut bncontext)
+ .unwrap(),
+ );
+ bytes
}
fn from_affine_coordinates(x: &[u8; 32], y: &[u8; 32]) -> Result<Self, Self::Error> {
@@ -115,6 +127,7 @@
type Impl = P256Ecdh;
type Error = Error;
type Rng = ();
+ type EncodedPublicKey = ArrayVec<[u8; 65]>;
fn generate_random(_rng: &mut Self::Rng) -> Self {
let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
@@ -122,15 +135,20 @@
Self(eckey.try_into().unwrap())
}
- fn public_key_bytes(&self) -> Vec<u8> {
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
let ecgroup = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let mut bncontext = BigNumContext::new().unwrap();
- self.0
- .ec_key()
- .unwrap()
- .public_key()
- .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
- .unwrap()
+ let mut bytes = Self::EncodedPublicKey::new();
+ bytes.extend_from_slice(
+ &self
+ .0
+ .ec_key()
+ .unwrap()
+ .public_key()
+ .to_bytes(&ecgroup, PointConversionForm::COMPRESSED, &mut bncontext)
+ .unwrap(),
+ );
+ bytes
}
fn diffie_hellman(
@@ -178,7 +196,7 @@
use crypto_provider_test::p256::*;
#[apply(p256_test_cases)]
- fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>) {
- testcase(PhantomData::<P256Ecdh>)
+ fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh>, _name: &str) {
+ testcase(PhantomData)
}
}
diff --git a/nearby/crypto/crypto_provider_openssl/src/x25519.rs b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
index ff6f3b0..2cb4cab 100644
--- a/nearby/crypto/crypto_provider_openssl/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_openssl/src/x25519.rs
@@ -30,14 +30,15 @@
impl PublicKey<X25519> for X25519PublicKey {
type Error = ErrorStack;
+ type EncodedPublicKey = [u8; 32];
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
let key = PKey::public_key_from_raw_bytes(bytes, Id::X25519)?;
Ok(X25519PublicKey(key))
}
- fn to_bytes(&self) -> Vec<u8> {
- self.0.raw_public_key().unwrap()
+ fn to_bytes(&self) -> Self::EncodedPublicKey {
+ self.0.raw_public_key().unwrap().try_into().unwrap()
}
}
@@ -48,14 +49,15 @@
type Impl = X25519Ecdh;
type Error = ErrorStack;
type Rng = ();
+ type EncodedPublicKey = [u8; 32];
fn generate_random(_rng: &mut Self::Rng) -> Self {
let private_key = openssl::pkey::PKey::generate_x25519().unwrap();
Self(private_key)
}
- fn public_key_bytes(&self) -> Vec<u8> {
- self.0.raw_public_key().unwrap()
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+ self.0.raw_public_key().unwrap().try_into().unwrap()
}
fn diffie_hellman(
diff --git a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
index e0bf534..a0638ff 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
+++ b/nearby/crypto/crypto_provider_rustcrypto/Cargo.toml
@@ -6,15 +6,22 @@
[dependencies]
aead = "0.5.1"
-aes-gcm-siv = { version = "0.11.1", features = ["aes"] }
-crypto_provider.workspace = true
+aes-gcm-siv = { version = "0.11.1", features = [
+ "aes",
+], default-features = false }
+aes-gcm = { version = "0.10.3", features = [
+ "aes",
+], default-features = false }
+crypto_provider = { workspace = true }
hmac.workspace = true
hkdf.workspace = true
sha2.workspace = true
x25519-dalek.workspace = true
p256 = { workspace = true, features = ["ecdh"], default-features = false }
sec1.workspace = true
-ed25519-dalek = { workspace = true, default-features = false, features = ["rand_core"] }
+ed25519-dalek = { workspace = true, default-features = false, features = [
+ "rand_core",
+] }
rand = { workspace = true, default-features = false }
rand_core.workspace = true
subtle.workspace = true
@@ -31,5 +38,12 @@
[features]
default = ["alloc", "rand_chacha"]
-std = ["ed25519-dalek/default", "rand/std", "rand/std_rng", "crypto_provider/std", "crypto_provider/alloc"]
-alloc = ["aead/bytes"]
+std = [
+ "alloc",
+ "ed25519-dalek/default",
+ "rand/std",
+ "rand/std_rng",
+ "crypto_provider/std",
+ "crypto_provider/alloc",
+]
+alloc = ["aead/bytes", "aead/alloc", "cbc/alloc", "crypto_provider/alloc"]
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..f1d9e0f
--- /dev/null
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm.rs
@@ -0,0 +1,123 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+#[cfg(feature = "alloc")]
+use aead::Payload;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+// RustCrypto defined traits and types
+use aes::cipher::typenum::consts::{U12, U16};
+use aes::cipher::BlockCipher;
+use aes::cipher::BlockEncrypt;
+#[cfg(feature = "alloc")]
+use aes_gcm::aead::Aead as _;
+use aes_gcm::aead::KeyInit;
+use aes_gcm::AeadInPlace as _;
+
+// CryptoProvider traits and types
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
+
+pub struct AesGcm<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>(
+ aes_gcm::AesGcm<A, U12>,
+);
+
+impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcm
+ for AesGcm<A>
+{
+}
+
+impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>
+ AeadInit<K> for AesGcm<A>
+{
+ fn new(key: &K) -> Self {
+ Self(aes_gcm::AesGcm::<A, U12>::new(key.as_slice().into()))
+ }
+}
+
+impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcm<A> {
+ const TAG_SIZE: usize = 16;
+ type Nonce = [u8; 12];
+ type Tag = [u8; 16];
+
+ #[cfg(feature = "alloc")]
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0
+ .encrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad })
+ .map_err(|_| AeadError)
+ }
+
+ fn encrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
+ self.0
+ .encrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg)
+ .map(|arr| arr.into())
+ .map_err(|_| AeadError)
+ }
+
+ #[cfg(feature = "alloc")]
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0
+ .decrypt(aes_gcm::Nonce::from_slice(nonce), Payload { msg, aad })
+ .map_err(|_| AeadError)
+ }
+
+ fn decrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
+ self.0
+ .decrypt_in_place_detached(aes_gcm::Nonce::from_slice(nonce), aad, msg, tag.into())
+ .map_err(|_| AeadError)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use core::marker::PhantomData;
+
+ use crypto_provider_test::aead::aes_gcm::*;
+ use crypto_provider_test::aes::*;
+
+ use super::*;
+
+ #[apply(aes_128_gcm_test_cases)]
+ fn aes_gcm_128_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_128_gcm_test_cases_detached)]
+ fn aes_128_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes128>>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_gcm_test_cases)]
+ fn aes_gcm_256_test(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_gcm_test_cases_detached)]
+ fn aes_256_gcm_test_detached(testcase: CryptoProviderTestCase<AesGcm<aes::Aes256>>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
index 402c2ed..408017f 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/aes_gcm_siv.rs
@@ -12,74 +12,118 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use aes_gcm_siv::{AeadInPlace, Aes128GcmSiv, Aes256GcmSiv, KeyInit, Nonce};
+#[cfg(feature = "alloc")]
extern crate alloc;
+#[cfg(feature = "alloc")]
+use aead::Payload;
+#[cfg(feature = "alloc")]
use alloc::vec::Vec;
-use crypto_provider::aead::{Aead, AeadError};
-use crypto_provider::aead::aes_gcm_siv::AesGcmSiv;
-use crypto_provider::aes::{Aes128Key, Aes256Key, AesKey};
+// RustCrypto defined traits and types
+use aes::cipher::typenum::consts::U16;
+use aes::cipher::BlockCipher;
+use aes::cipher::BlockEncrypt;
+#[cfg(feature = "alloc")]
+use aes_gcm_siv::aead::Aead as _;
+use aes_gcm_siv::aead::KeyInit;
+use aes_gcm_siv::AeadInPlace as _;
-pub struct AesGcmSiv128(Aes128GcmSiv);
+// CryptoProvider traits and types
+use crypto_provider::aead::{Aead, AeadError, AeadInit};
-impl AesGcmSiv for AesGcmSiv128 {}
+pub struct AesGcmSiv<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>(
+ aes_gcm_siv::AesGcmSiv<A>,
+);
-impl Aead for AesGcmSiv128 {
- const TAG_SIZE: usize = 16;
- type Nonce = [u8; 12];
- type Key = Aes128Key;
+impl<A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> crypto_provider::aead::AesGcmSiv
+ for AesGcmSiv<A>
+{
+}
- fn new(key: &Self::Key) -> Self {
- Self(Aes128GcmSiv::new(key.as_slice().into()))
- }
-
- fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
- self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
- }
-
- fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
- self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+impl<K: crypto_provider::aes::AesKey, A: BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit>
+ AeadInit<K> for AesGcmSiv<A>
+{
+ fn new(key: &K) -> Self {
+ Self(aes_gcm_siv::AesGcmSiv::<A>::new(key.as_slice().into()))
}
}
-pub struct AesGcmSiv256(Aes256GcmSiv);
-
-impl AesGcmSiv for AesGcmSiv256 {}
-
-impl Aead for AesGcmSiv256 {
+impl<A: aes::cipher::BlockCipher<BlockSize = U16> + BlockEncrypt + KeyInit> Aead for AesGcmSiv<A> {
const TAG_SIZE: usize = 16;
type Nonce = [u8; 12];
- type Key = Aes256Key;
+ type Tag = [u8; 16];
- fn new(key: &Self::Key) -> Self {
- Self(Aes256GcmSiv::new(key.as_slice().into()))
+ #[cfg(feature = "alloc")]
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0
+ .encrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad })
+ .map_err(|_| AeadError)
}
- fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
- self.0.encrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+ fn encrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
+ self.0
+ .encrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg)
+ .map(|arr| arr.into())
+ .map_err(|_| AeadError)
}
- fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
- self.0.decrypt_in_place(Nonce::from_slice(nonce), aad, msg).map_err(|_| AeadError)
+ #[cfg(feature = "alloc")]
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &Self::Nonce) -> Result<Vec<u8>, AeadError> {
+ self.0
+ .decrypt(aes_gcm_siv::Nonce::from_slice(nonce), Payload { msg, aad })
+ .map_err(|_| AeadError)
+ }
+
+ fn decrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
+ self.0
+ .decrypt_in_place_detached(aes_gcm_siv::Nonce::from_slice(nonce), aad, msg, tag.into())
+ .map_err(|_| AeadError)
}
}
#[cfg(test)]
mod tests {
use core::marker::PhantomData;
-
use crypto_provider_test::aead::aes_gcm_siv::*;
use crypto_provider_test::aes::*;
-
- use super::*;
+ use crypto_provider_test::prelude::apply;
#[apply(aes_128_gcm_siv_test_cases)]
- fn aes_gcm_siv_128_test(testcase: CryptoProviderTestCase<AesGcmSiv128>) {
+ fn aes_gcm_siv_128_test(
+ testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>,
+ ) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_128_gcm_siv_test_cases_detached)]
+ fn aes_gcm_siv_128_test_detached(
+ testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>>,
+ ) {
testcase(PhantomData);
}
#[apply(aes_256_gcm_siv_test_cases)]
- fn aes_gcm_siv_256_test(testcase: CryptoProviderTestCase<AesGcmSiv256>) {
+ fn aes_gcm_siv_256_test(
+ testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>,
+ ) {
+ testcase(PhantomData);
+ }
+
+ #[apply(aes_256_gcm_siv_test_cases_detached)]
+ fn aes_gcm_siv_256_test_detached(
+ testcase: CryptoProviderTestCase<crate::aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>>,
+ ) {
testcase(PhantomData);
}
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
index 7fc561b..e4c2c60 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aead/mod.rs
@@ -13,3 +13,5 @@
// limitations under the License.
pub(crate) mod aes_gcm_siv;
+
+pub(crate) mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
deleted file mode 100644
index 06d7224..0000000
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/cbc.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2022 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-extern crate alloc;
-use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
-use aes::Aes256;
-use alloc::vec::Vec;
-use crypto_provider::aes::{
- cbc::{AesCbcIv, DecryptionError},
- Aes256Key, AesKey,
-};
-
-/// RustCrypto implementation of AES-CBC with PKCS7 padding
-pub enum AesCbcPkcs7Padded {}
-impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
- fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
- let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
- encryptor.encrypt_padded_vec_mut::<Pkcs7>(message)
- }
-
- fn decrypt(
- key: &Aes256Key,
- iv: &AesCbcIv,
- ciphertext: &[u8],
- ) -> Result<Vec<u8>, DecryptionError> {
- cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
- .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
- .map_err(|_| DecryptionError::BadPadding)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::AesCbcPkcs7Padded;
- use core::marker::PhantomData;
- use crypto_provider_test::aes::cbc::*;
-
- #[apply(aes_256_cbc_test_cases)]
- fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
- testcase(PhantomData);
- }
-}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs
new file mode 100644
index 0000000..c6d74f0
--- /dev/null
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/cbc.rs
@@ -0,0 +1,102 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
+use aes::Aes256;
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+use crypto_provider::{
+ aes::{
+ cbc::{AesCbcIv, DecryptionError, EncryptionError},
+ Aes256Key, AesKey,
+ },
+ tinyvec::SliceVec,
+};
+
+/// RustCrypto implementation of AES-CBC with PKCS7 padding
+pub enum AesCbcPkcs7Padded {}
+impl crypto_provider::aes::cbc::AesCbcPkcs7Padded for AesCbcPkcs7Padded {
+ #[cfg(feature = "alloc")]
+ fn encrypt(key: &Aes256Key, iv: &AesCbcIv, message: &[u8]) -> Vec<u8> {
+ let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
+ encryptor.encrypt_padded_vec_mut::<Pkcs7>(message)
+ }
+
+ fn encrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError> {
+ let encryptor = cbc::Encryptor::<Aes256>::new(key.as_array().into(), iv.into());
+ let message_len = message.len();
+ // Set the length so encrypt_padded_mut can write using the full capacity
+ // (Unlike `Vec.set_len`, `SliceVec.set_len` is safe and won't panic if len <= capacity)
+ message.set_len(message.capacity());
+ encryptor
+ .encrypt_padded_mut::<Pkcs7>(message, message_len)
+ .map(|result| result.len())
+ // `SliceVec.set_len` is safe, and won't panic because `encrypt_padded_mut` never
+ // returns a slice longer than the given buffer.
+ .map(|new_len| message.set_len(new_len))
+ .map_err(|_| {
+ message.set_len(message_len); // Set the buffer back to its original length
+ EncryptionError::PaddingFailed
+ })
+ }
+
+ #[cfg(feature = "alloc")]
+ fn decrypt(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &[u8],
+ ) -> Result<Vec<u8>, DecryptionError> {
+ cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
+ .decrypt_padded_vec_mut::<Pkcs7>(ciphertext)
+ .map_err(|_| DecryptionError::BadPadding)
+ }
+
+ fn decrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError> {
+ // Decrypted size is always smaller than the input size because of padding, so we don't need
+ // to set the length to the full capacity.
+ cbc::Decryptor::<Aes256>::new(key.as_array().into(), iv.into())
+ .decrypt_padded_mut::<Pkcs7>(ciphertext)
+ .map(|result| result.len())
+ // `SliceVec.set_len` is safe, and won't panic because decrypted result length is always
+ // smaller than the input size.
+ .map(|new_len| ciphertext.set_len(new_len))
+ .map_err(|_| {
+ ciphertext.as_mut().fill(0);
+ DecryptionError::BadPadding
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::AesCbcPkcs7Padded;
+ use core::marker::PhantomData;
+ use crypto_provider_test::aes::cbc::*;
+
+ #[apply(aes_256_cbc_test_cases)]
+ fn aes_256_cbc_test(testcase: CryptoProviderTestCase<AesCbcPkcs7Padded>) {
+ testcase(PhantomData);
+ }
+}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
similarity index 88%
rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
index 85cdac6..09ae557 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/ctr.rs
@@ -33,11 +33,7 @@
}
}
- fn encrypt(&mut self, data: &mut [u8]) {
- self.cipher.apply_keystream(data);
- }
-
- fn decrypt(&mut self, data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
self.cipher.apply_keystream(data);
}
}
@@ -59,11 +55,7 @@
}
}
- fn encrypt(&mut self, data: &mut [u8]) {
- self.cipher.apply_keystream(data);
- }
-
- fn decrypt(&mut self, data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
self.cipher.apply_keystream(data);
}
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
similarity index 98%
rename from nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
index c71f2ec..4c04bf5 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/aes/mod.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/aes_cp/mod.rs
@@ -22,7 +22,6 @@
};
/// Module implementing AES-CBC.
-#[cfg(feature = "alloc")]
pub(crate) mod cbc;
pub(crate) mod ctr;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
index d11a5ea..ce8089e 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/ed25519.rs
@@ -15,8 +15,8 @@
use ed25519_dalek::Signer;
use crypto_provider::ed25519::{
- InvalidBytes, RawPrivateKey, RawPublicKey, RawSignature, Signature as _, SignatureError,
- PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
+ InvalidBytes, RawPrivateKey, RawPrivateKeyPermit, RawPublicKey, RawSignature, Signature as _,
+ SignatureError, PRIVATE_KEY_LENGTH, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
};
pub struct Ed25519;
@@ -33,11 +33,11 @@
type PublicKey = PublicKey;
type Signature = Signature;
- fn private_key(&self) -> [u8; PRIVATE_KEY_LENGTH] {
+ fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> [u8; PRIVATE_KEY_LENGTH] {
self.0.to_bytes()
}
- fn from_private_key(bytes: &RawPrivateKey) -> Self {
+ fn from_raw_private_key(bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self {
Self(ed25519_dalek::SigningKey::from_bytes(bytes))
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
similarity index 86%
rename from nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
index aba0d4a..48380fa 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hkdf_cp.rs
@@ -22,7 +22,7 @@
use hmac::digest::{HashMarker, OutputSizeUser};
/// RustCrypto based hkdf implementation
-pub struct Hkdf<D>
+pub struct Hkdf<D>(hkdf::Hkdf<D>)
where
D: OutputSizeUser,
D: CoreProxy,
@@ -33,10 +33,7 @@
+ Default
+ Clone,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
- Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
- hkdf_impl: hkdf::Hkdf<D>,
-}
+ Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
impl<D> crypto_provider::hkdf::Hkdf for Hkdf<D>
where
@@ -52,7 +49,7 @@
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self {
- Hkdf { hkdf_impl: hkdf::Hkdf::new(salt, ikm) }
+ Hkdf(hkdf::Hkdf::new(salt, ikm))
}
fn expand_multi_info(
@@ -60,11 +57,11 @@
info_components: &[&[u8]],
okm: &mut [u8],
) -> Result<(), InvalidLength> {
- self.hkdf_impl.expand_multi_info(info_components, okm).map_err(|_| InvalidLength)
+ self.0.expand_multi_info(info_components, okm).map_err(|_| InvalidLength)
}
fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength> {
- self.hkdf_impl.expand(info, okm).map_err(|_| InvalidLength)
+ self.0.expand(info, okm).map_err(|_| InvalidLength)
}
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
similarity index 72%
rename from nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
index dfda208..d8cef33 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/hmac_rc.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/hmac_cp.rs
@@ -23,7 +23,7 @@
use hmac::Mac;
/// RustCrypto based hmac implementation
-pub struct Hmac<D>
+pub struct Hmac<D>(hmac::Hmac<D>)
where
D: OutputSizeUser,
D: CoreProxy,
@@ -34,43 +34,38 @@
+ Default
+ Clone,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
- Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
- hmac_impl: hmac::Hmac<D>,
-}
+ Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
impl crypto_provider::hmac::Hmac<32> for Hmac<sha2::Sha256> {
#[allow(clippy::expect_used)]
fn new_from_key(key: [u8; 32]) -> Self {
hmac::Hmac::new_from_slice(&key)
- .map(|hmac| Self { hmac_impl: hmac })
+ .map(Self)
.expect("length will always be valid because input key is of fixed size")
}
fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
- hmac::Hmac::new_from_slice(key)
- .map(|hmac| Self { hmac_impl: hmac })
- .map_err(|_| InvalidLength)
+ hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength)
}
fn update(&mut self, data: &[u8]) {
- self.hmac_impl.update(data);
+ self.0.update(data);
}
fn finalize(self) -> [u8; 32] {
- self.hmac_impl.finalize().into_bytes().into()
+ self.0.finalize().into_bytes().into()
}
fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> {
- self.hmac_impl.verify_slice(tag).map_err(|_| MacError)
+ self.0.verify_slice(tag).map_err(|_| MacError)
}
fn verify(self, tag: [u8; 32]) -> Result<(), MacError> {
- self.hmac_impl.verify(&tag.into()).map_err(|_| MacError)
+ self.0.verify(&tag.into()).map_err(|_| MacError)
}
fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> {
- self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError)
+ self.0.verify_truncated_left(tag).map_err(|_| MacError)
}
}
@@ -78,34 +73,32 @@
#[allow(clippy::expect_used)]
fn new_from_key(key: [u8; 64]) -> Self {
hmac::Hmac::new_from_slice(&key)
- .map(|hmac| Self { hmac_impl: hmac })
+ .map(Self)
.expect("length will always be valid because input key is of fixed size")
}
fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
- hmac::Hmac::new_from_slice(key)
- .map(|hmac| Self { hmac_impl: hmac })
- .map_err(|_| InvalidLength)
+ hmac::Hmac::new_from_slice(key).map(Self).map_err(|_| InvalidLength)
}
fn update(&mut self, data: &[u8]) {
- self.hmac_impl.update(data);
+ self.0.update(data);
}
fn finalize(self) -> [u8; 64] {
- self.hmac_impl.finalize().into_bytes().into()
+ self.0.finalize().into_bytes().into()
}
fn verify_slice(self, tag: &[u8]) -> Result<(), MacError> {
- self.hmac_impl.verify_slice(tag).map_err(|_| MacError)
+ self.0.verify_slice(tag).map_err(|_| MacError)
}
fn verify(self, tag: [u8; 64]) -> Result<(), MacError> {
- self.hmac_impl.verify(&tag.into()).map_err(|_| MacError)
+ self.0.verify(&tag.into()).map_err(|_| MacError)
}
fn verify_truncated_left(self, tag: &[u8]) -> Result<(), MacError> {
- self.hmac_impl.verify_truncated_left(tag).map_err(|_| MacError)
+ self.0.verify_truncated_left(tag).map_err(|_| MacError)
}
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
index e95712c..628f679 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/lib.rs
@@ -25,6 +25,7 @@
use core::{fmt::Debug, marker::PhantomData};
+pub use aes;
use cfg_if::cfg_if;
pub use hkdf;
pub use hmac;
@@ -35,17 +36,17 @@
/// Contains the RustCrypto backed impls for AES-GCM-SIV operations
mod aead;
/// Contains the RustCrypto backed AES impl for CryptoProvider
-pub mod aes;
+pub mod aes_cp;
/// Contains the RustCrypto backed impl for ed25519 key generation, signing, and verification
mod ed25519;
/// Contains the RustCrypto backed hkdf impl for CryptoProvider
-mod hkdf_rc;
+mod hkdf_cp;
/// Contains the RustCrypto backed hmac impl for CryptoProvider
-mod hmac_rc;
+mod hmac_cp;
/// Contains the RustCrypto backed P256 impl for CryptoProvider
mod p256;
/// Contains the RustCrypto backed SHA2 impl for CryptoProvider
-mod sha2_rc;
+mod sha2_cp;
/// Contains the RustCrypto backed X25519 impl for CryptoProvider
mod x25519;
@@ -54,9 +55,11 @@
/// Providing a type alias for compatibility with existing usage of RustCrypto
/// by default we use StdRng for the underlying csprng
pub type RustCrypto = RustCryptoImpl<rand::rngs::StdRng>;
- } else {
+ } else if #[cfg(feature = "rand_chacha")] {
/// A no_std compatible implementation of CryptoProvider backed by RustCrypto crates
pub type RustCrypto = RustCryptoImpl<rand_chacha::ChaCha20Rng>;
+ } else {
+ compile_error!("Must specify either --features std or --features rand_chacha");
}
}
@@ -76,23 +79,24 @@
impl<R: CryptoRng + SeedableRng + RngCore + Eq + PartialEq + Debug + Clone + Send>
crypto_provider::CryptoProvider for RustCryptoImpl<R>
{
- type HkdfSha256 = hkdf_rc::Hkdf<sha2::Sha256>;
- type HmacSha256 = hmac_rc::Hmac<sha2::Sha256>;
- type HkdfSha512 = hkdf_rc::Hkdf<sha2::Sha512>;
- type HmacSha512 = hmac_rc::Hmac<sha2::Sha512>;
- #[cfg(feature = "alloc")]
- type AesCbcPkcs7Padded = aes::cbc::AesCbcPkcs7Padded;
+ type HkdfSha256 = hkdf_cp::Hkdf<sha2::Sha256>;
+ type HmacSha256 = hmac_cp::Hmac<sha2::Sha256>;
+ type HkdfSha512 = hkdf_cp::Hkdf<sha2::Sha512>;
+ type HmacSha512 = hmac_cp::Hmac<sha2::Sha512>;
+ type AesCbcPkcs7Padded = aes_cp::cbc::AesCbcPkcs7Padded;
type X25519 = x25519::X25519Ecdh<R>;
type P256 = p256::P256Ecdh<R>;
- type Sha256 = sha2_rc::RustCryptoSha256;
- type Sha512 = sha2_rc::RustCryptoSha512;
- type Aes128 = aes::Aes128;
- type Aes256 = aes::Aes256;
- type AesCtr128 = aes::ctr::AesCtr128;
- type AesCtr256 = aes::ctr::AesCtr256;
+ type Sha256 = sha2_cp::RustCryptoSha256;
+ type Sha512 = sha2_cp::RustCryptoSha512;
+ type Aes128 = aes_cp::Aes128;
+ type Aes256 = aes_cp::Aes256;
+ type AesCtr128 = aes_cp::ctr::AesCtr128;
+ type AesCtr256 = aes_cp::ctr::AesCtr256;
type Ed25519 = ed25519::Ed25519;
- type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv128;
- type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv256;
+ type Aes128GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes128>;
+ type Aes256GcmSiv = aead::aes_gcm_siv::AesGcmSiv<aes::Aes256>;
+ type Aes128Gcm = aead::aes_gcm::AesGcm<aes::Aes128>;
+ type Aes256Gcm = aead::aes_gcm::AesGcm<aes::Aes256>;
type CryptoRng = RcRng<R>;
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
@@ -124,6 +128,7 @@
mod tests {
use core::marker::PhantomData;
+ use crypto_provider_test::prelude::*;
use crypto_provider_test::sha2::*;
use crate::RustCrypto;
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
index 539ccc0..98a6cd4 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/p256.rs
@@ -12,14 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-extern crate alloc;
-
use crate::RcRng;
-use alloc::vec::Vec;
use core::marker::PhantomData;
use crypto_provider::{
elliptic_curve::{EcdhProvider, EphemeralSecret},
- p256::P256,
+ p256::{PointCompression, P256},
+ tinyvec::ArrayVec,
};
use p256::{
elliptic_curve,
@@ -52,8 +50,12 @@
p256::PublicKey::from_sec1_bytes(bytes).map(Self)
}
- fn to_sec1_bytes(&self) -> Vec<u8> {
- self.0.to_encoded_point(true).as_bytes().to_vec()
+ fn to_sec1_bytes(&self, point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
+ let mut bytes = ArrayVec::<[u8; 65]>::new();
+ bytes.extend_from_slice(
+ self.0.to_encoded_point(point_compression == PointCompression::Compressed).as_bytes(),
+ );
+ bytes
}
#[allow(clippy::expect_used)]
@@ -87,6 +89,7 @@
type Impl = P256Ecdh<R>;
type Error = sec1::Error;
type Rng = RcRng<R>;
+ type EncodedPublicKey = ArrayVec<[u8; 65]>;
fn generate_random(rng: &mut Self::Rng) -> Self {
Self {
@@ -95,8 +98,10 @@
}
}
- fn public_key_bytes(&self) -> Vec<u8> {
- self.secret.public_key().to_encoded_point(false).as_bytes().into()
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
+ let mut bytes = Self::EncodedPublicKey::new();
+ bytes.extend_from_slice(self.secret.public_key().to_encoded_point(false).as_bytes());
+ bytes
}
fn diffie_hellman(
@@ -136,7 +141,7 @@
use rand::rngs::StdRng;
#[apply(p256_test_cases)]
- fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>) {
+ fn p256_tests(testcase: CryptoProviderTestCase<P256Ecdh<StdRng>>, _name: &str) {
testcase(PhantomData::<P256Ecdh<StdRng>>)
}
}
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs b/nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs
similarity index 100%
rename from nearby/crypto/crypto_provider_rustcrypto/src/sha2_rc.rs
rename to nearby/crypto/crypto_provider_rustcrypto/src/sha2_cp.rs
diff --git a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
index 445e858..ad67777 100644
--- a/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_rustcrypto/src/x25519.rs
@@ -12,10 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-extern crate alloc;
-
use crate::RcRng;
-use alloc::vec::Vec;
use core::marker::PhantomData;
use crypto_provider::elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey};
use crypto_provider::x25519::X25519;
@@ -45,6 +42,7 @@
type Impl = X25519Ecdh<R>;
type Error = Error;
type Rng = RcRng<R>;
+ type EncodedPublicKey = [u8; 32];
fn generate_random(rng: &mut Self::Rng) -> Self {
Self {
@@ -53,9 +51,9 @@
}
}
- fn public_key_bytes(&self) -> Vec<u8> {
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
let pubkey: x25519_dalek::PublicKey = (&self.secret).into();
- pubkey.to_bytes().into()
+ pubkey.to_bytes()
}
fn diffie_hellman(
@@ -90,14 +88,15 @@
impl PublicKey<X25519> for X25519PublicKey {
type Error = Error;
+ type EncodedPublicKey = [u8; 32];
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
let byte_sized: [u8; 32] = bytes.try_into().map_err(|_| Error::WrongSize)?;
Ok(Self(byte_sized.into()))
}
- fn to_bytes(&self) -> Vec<u8> {
- self.0.as_bytes().to_vec()
+ fn to_bytes(&self) -> Self::EncodedPublicKey {
+ self.0.to_bytes()
}
}
diff --git a/nearby/crypto/crypto_provider_stubs/Cargo.toml b/nearby/crypto/crypto_provider_stubs/Cargo.toml
index d7c4d45..4e8bdec 100644
--- a/nearby/crypto/crypto_provider_stubs/Cargo.toml
+++ b/nearby/crypto/crypto_provider_stubs/Cargo.toml
@@ -5,4 +5,4 @@
publish.workspace = true
[dependencies]
-crypto_provider = {workspace = true, features = ["std", "alloc"] }
\ No newline at end of file
+crypto_provider = {workspace = true, features = ["std", "alloc"] }
diff --git a/nearby/crypto/crypto_provider_stubs/src/lib.rs b/nearby/crypto/crypto_provider_stubs/src/lib.rs
index 5d54b72..32c9381 100644
--- a/nearby/crypto/crypto_provider_stubs/src/lib.rs
+++ b/nearby/crypto/crypto_provider_stubs/src/lib.rs
@@ -20,21 +20,23 @@
use std::fmt::Debug;
-use crypto_provider::ed25519::{RawPrivateKey, RawPublicKey, RawSignature};
+use crypto_provider::aead::AeadInit;
use crypto_provider::{
- aead::aes_gcm_siv::AesGcmSiv,
- aead::{Aead, AeadError},
+ aead::{Aead, AeadError, AesGcm, AesGcmSiv},
aes::{
- cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError},
+ cbc::{AesCbcIv, AesCbcPkcs7Padded, DecryptionError, EncryptionError},
ctr::{AesCtr, NonceAndCounter},
Aes, Aes128Key, Aes256Key, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher,
},
- ed25519,
- ed25519::{Ed25519Provider, InvalidBytes, KeyPair, Signature, SignatureError},
+ ed25519::{
+ self, Ed25519Provider, InvalidBytes, KeyPair, RawPrivateKey, RawPrivateKeyPermit,
+ RawPublicKey, RawSignature, Signature, SignatureError,
+ },
elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
hkdf::{Hkdf, InvalidLength},
hmac::{Hmac, MacError},
- p256::{P256PublicKey, P256},
+ p256::{P256PublicKey, PointCompression, P256},
+ tinyvec::{ArrayVec, SliceVec},
x25519::X25519,
};
@@ -58,6 +60,8 @@
type Ed25519 = Ed25519Stubs;
type Aes128GcmSiv = Aes128Stubs;
type Aes256GcmSiv = Aes256Stubs;
+ type Aes128Gcm = Aes128Stubs;
+ type Aes256Gcm = Aes256Stubs;
type CryptoRng = ();
fn constant_time_eq(_a: &[u8], _b: &[u8]) -> bool {
@@ -171,6 +175,14 @@
unimplemented!()
}
+ fn encrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ message: &mut SliceVec<u8>,
+ ) -> Result<(), EncryptionError> {
+ unimplemented!()
+ }
+
fn decrypt(
_key: &Aes256Key,
_iv: &AesCbcIv,
@@ -178,6 +190,14 @@
) -> Result<Vec<u8>, DecryptionError> {
unimplemented!()
}
+
+ fn decrypt_in_place(
+ key: &Aes256Key,
+ iv: &AesCbcIv,
+ ciphertext: &mut SliceVec<u8>,
+ ) -> Result<(), DecryptionError> {
+ unimplemented!()
+ }
}
pub struct X25519Stubs;
@@ -194,12 +214,13 @@
type Impl = X25519Stubs;
type Error = ();
type Rng = ();
+ type EncodedPublicKey = [u8; 32];
fn generate_random(_rng: &mut Self::Rng) -> Self {
unimplemented!()
}
- fn public_key_bytes(&self) -> Vec<u8> {
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
unimplemented!()
}
@@ -215,12 +236,13 @@
type Impl = P256Stubs;
type Error = ();
type Rng = ();
+ type EncodedPublicKey = ArrayVec<[u8; 65]>;
fn generate_random(_rng: &mut Self::Rng) -> Self {
unimplemented!()
}
- fn public_key_bytes(&self) -> Vec<u8> {
+ fn public_key_bytes(&self) -> Self::EncodedPublicKey {
unimplemented!()
}
@@ -237,12 +259,13 @@
impl PublicKey<X25519> for EcdhPubKey {
type Error = ();
+ type EncodedPublicKey = [u8; 32];
fn from_bytes(_bytes: &[u8]) -> Result<Self, Self::Error> {
unimplemented!()
}
- fn to_bytes(&self) -> Vec<u8> {
+ fn to_bytes(&self) -> Self::EncodedPublicKey {
unimplemented!()
}
}
@@ -257,7 +280,7 @@
unimplemented!()
}
- fn to_sec1_bytes(&self) -> Vec<u8> {
+ fn to_sec1_bytes(&self, _point_compression: PointCompression) -> ArrayVec<[u8; 65]> {
unimplemented!()
}
@@ -294,6 +317,12 @@
pub struct Aes128Stubs;
+impl AeadInit<Aes128Key> for Aes128Stubs {
+ fn new(key: &Aes128Key) -> Self {
+ unimplemented!()
+ }
+}
+
impl AesCipher for Aes128Stubs {
type Key = Aes128Key;
@@ -321,11 +350,7 @@
unimplemented!()
}
- fn encrypt(&mut self, _data: &mut [u8]) {
- unimplemented!()
- }
-
- fn decrypt(&mut self, _data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
unimplemented!()
}
}
@@ -333,25 +358,48 @@
impl Aead for Aes128Stubs {
const TAG_SIZE: usize = 16;
type Nonce = [u8; 12];
- type Key = Aes128Key;
+ type Tag = [u8; 16];
- fn new(key: &Self::Key) -> Self {
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
unimplemented!()
}
- fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+ fn encrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
unimplemented!()
}
- fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
+ unimplemented!()
+ }
+
+ fn decrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
unimplemented!()
}
}
impl AesGcmSiv for Aes128Stubs {}
+impl AesGcm for Aes128Stubs {}
+
pub struct Aes256Stubs;
+impl AeadInit<Aes256Key> for Aes256Stubs {
+ fn new(key: &Aes256Key) -> Self {
+ unimplemented!()
+ }
+}
+
impl AesCipher for Aes256Stubs {
type Key = Aes256Key;
@@ -379,11 +427,7 @@
unimplemented!()
}
- fn encrypt(&mut self, _data: &mut [u8]) {
- unimplemented!()
- }
-
- fn decrypt(&mut self, _data: &mut [u8]) {
+ fn apply_keystream(&mut self, data: &mut [u8]) {
unimplemented!()
}
}
@@ -391,23 +435,40 @@
impl Aead for Aes256Stubs {
const TAG_SIZE: usize = 16;
type Nonce = [u8; 12];
- type Key = Aes256Key;
+ type Tag = [u8; 16];
- fn new(key: &Self::Key) -> Self {
+ fn encrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
unimplemented!()
}
- fn encrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+ fn encrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ ) -> Result<Self::Tag, AeadError> {
unimplemented!()
}
- fn decrypt(&self, msg: &mut Vec<u8>, aad: &[u8], nonce: &[u8; 12]) -> Result<(), AeadError> {
+ fn decrypt(&self, msg: &[u8], aad: &[u8], nonce: &[u8; 12]) -> Result<Vec<u8>, AeadError> {
+ unimplemented!()
+ }
+
+ fn decrypt_detached(
+ &self,
+ msg: &mut [u8],
+ aad: &[u8],
+ nonce: &Self::Nonce,
+ tag: &Self::Tag,
+ ) -> Result<(), AeadError> {
unimplemented!()
}
}
impl AesGcmSiv for Aes256Stubs {}
+impl AesGcm for Aes256Stubs {}
+
pub struct Ed25519Stubs;
impl Ed25519Provider for Ed25519Stubs {
@@ -457,11 +518,11 @@
type PublicKey = PublicKeyStubs;
type Signature = SignatureStubs;
- fn private_key(&self) -> RawPrivateKey {
+ fn raw_private_key(&self, _permit: &RawPrivateKeyPermit) -> RawPrivateKey {
unimplemented!()
}
- fn from_private_key(_bytes: &RawPrivateKey) -> Self
+ fn from_raw_private_key(_bytes: &RawPrivateKey, _permit: &RawPrivateKeyPermit) -> Self
where
Self: Sized,
{
diff --git a/nearby/crypto/crypto_provider_test/Cargo.toml b/nearby/crypto/crypto_provider_test/Cargo.toml
index a4d92ec..4064330 100644
--- a/nearby/crypto/crypto_provider_test/Cargo.toml
+++ b/nearby/crypto/crypto_provider_test/Cargo.toml
@@ -5,7 +5,7 @@
publish.workspace = true
[dependencies]
-crypto_provider = { workspace = true, features = ["test_vectors"] }
+crypto_provider = { workspace = true, features = ["raw_private_key_permit", "test_vectors", "alloc"] }
rand_ext.workspace = true
test_helper.workspace = true
@@ -14,4 +14,4 @@
rstest.workspace = true
rstest_reuse.workspace = true
wycheproof.workspace = true
-hex.workspace = true
\ No newline at end of file
+hex.workspace = true
diff --git a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
index 9e1d140..bd8f6f9 100644
--- a/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
+++ b/nearby/crypto/crypto_provider_test/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -165,6 +179,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_default"
@@ -193,6 +210,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -239,9 +257,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -307,9 +325,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -390,6 +408,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -732,6 +760,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -773,9 +807,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs
new file mode 100644
index 0000000..88eaf32
--- /dev/null
+++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm.rs
@@ -0,0 +1,316 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use alloc::vec::Vec;
+use core::marker;
+
+use hex_literal::hex;
+use rstest_reuse::template;
+
+pub use crate::prelude;
+use crypto_provider::aead::{AeadInit, AesGcm};
+
+/// Test AES-GCM-128 encryption
+pub fn aes_128_gcm_test_encrypt<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC4
+ let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d");
+ let nonce = hex!("438a547a94ea88dce46c6c85");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("");
+ let tag = hex!("960247ba5cde02e41a313c4c0136edc3");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed");
+ assert_eq!(&result[..], &tag);
+}
+
+pub fn aes_128_gcm_test_encrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC4
+ let test_key = hex!("bedcfb5a011ebc84600fcb296c15af0d");
+ let nonce = hex!("438a547a94ea88dce46c6c85");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("");
+ let tag = hex!("960247ba5cde02e41a313c4c0136edc3");
+ let mut buf = Vec::from(msg.as_slice());
+ let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &[0_u8; 0]);
+ assert_eq!(&actual_tag, &tag);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC2
+ let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9");
+ let nonce = hex!("921d2507fa8007b7bd067d34");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("001d0c231287c1182784554ca3a21908");
+ let ct = hex!("49d8b9783e911913d87094d1f63cc765");
+ let ad = hex!("00112233445566778899aabbccddeeff");
+ let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92");
+ let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed");
+ assert_eq!(&result[..16], &ct);
+ assert_eq!(&result[16..], &tag);
+ assert_eq!(A::TAG_SIZE, result[16..].len());
+ let result = aes.decrypt(&result[..], &ad, &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC2
+ let test_key = hex!("5b9604fe14eadba931b0ccf34843dab9");
+ let nonce = hex!("921d2507fa8007b7bd067d34");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("001d0c231287c1182784554ca3a21908");
+ let ct = hex!("49d8b9783e911913d87094d1f63cc765");
+ let ad = hex!("00112233445566778899aabbccddeeff");
+ let tag = hex!("1e348ba07cca2cf04c618cb4d43a5b92");
+ let mut buf = Vec::from(msg.as_slice());
+ let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(actual_tag, tag);
+ assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &tag).is_ok());
+ assert_eq!(&buf[..], &msg);
+}
+
+/// Test AES-GCM-128 decryption
+pub fn aes_128_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC23
+ let test_key = hex!("000102030405060708090a0b0c0d0e0f");
+ let nonce = hex!("505152535455565758595a5b");
+ let aes = A::new(&test_key.into());
+ let ct = hex!("eb156d081ed6b6b55f4612f021d87b39");
+ let mut buf = Vec::from(ct.as_slice());
+ let bad_tag = hex!("d9847dbc326a06e988c77ad3863e6083");
+ aes.decrypt_detached(&mut buf, b"", &nonce, &bad_tag)
+ .expect_err("decryption tag verification should fail");
+ // assert that the buffer does not change if tag verification fails
+ assert_eq!(buf, ct);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc74<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC74
+ let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123");
+ let nonce = hex!("00112233445566778899aabb");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("");
+ let ad = hex!("aabbccddeeff");
+ let tag = hex!("2a7d77fa526b8250cb296078926b5020");
+ let result = aes.encrypt(&msg, &ad, &nonce).expect("should succeed");
+ assert_eq!(&result[..], &tag);
+ assert_eq!(A::TAG_SIZE, result.len());
+ let result = aes.decrypt(&result, &ad, &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc74_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC74
+ let test_key = hex!("29d3a44f8723dc640239100c365423a312934ac80239212ac3df3421a2098123");
+ let nonce = hex!("00112233445566778899aabb");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("");
+ let ad = hex!("aabbccddeeff");
+ let ct = hex!("");
+ let tag = hex!("2a7d77fa526b8250cb296078926b5020");
+ let mut buf = Vec::new();
+ buf.extend_from_slice(&msg);
+ let actual_tag = aes.encrypt_detached(&mut buf, &ad, &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(&actual_tag, &tag);
+ assert_eq!(A::TAG_SIZE, tag.len());
+ assert!(aes.decrypt_detached(&mut buf, &ad, &nonce, &actual_tag).is_ok());
+ assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc79<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC79
+ let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a");
+ let nonce = hex!("2fcb1b38a99e71b84740ad9b");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("549b365af913f3b081131ccb6b825588");
+ let ct = hex!("f58c16690122d75356907fd96b570fca");
+ let tag = hex!("28752c20153092818faba2a334640d6e");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..16], &ct);
+ assert_eq!(&result[16..], &tag);
+ let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption
+pub fn aes_256_gcm_test_tc79_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC79
+ let test_key = hex!("59d4eafb4de0cfc7d3db99a8f54b15d7b39f0acc8da69763b019c1699f87674a");
+ let nonce = hex!("2fcb1b38a99e71b84740ad9b");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("549b365af913f3b081131ccb6b825588");
+ let ct = hex!("f58c16690122d75356907fd96b570fca");
+ let tag = hex!("28752c20153092818faba2a334640d6e");
+ let mut buf = Vec::from(msg.as_slice());
+ let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(&actual_tag, &tag);
+ assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
+ assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM encryption/decryption where the tag given to decryption doesn't match.
+pub fn aes_256_gcm_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcm<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_test.json
+ // TC94
+ let test_key = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
+ let nonce = hex!("505152535455565758595a5b");
+ let aes = A::new(&test_key.into());
+ let aad = hex!("");
+ let ct = hex!("b2061457c0759fc1749f174ee1ccadfa");
+ let bad_tag = hex!("9de8fef6d8ab1bf1bf887232eab590dd");
+ let mut buf = Vec::from(ct.as_slice());
+ aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag)
+ .expect_err("Decrypting with bad tag should fail");
+ // assert that the buffer does not change if tag verification fails
+ assert_eq!(buf, ct);
+}
+
+/// Generates the test cases to validate the AES-128-GCM implementation.
+/// For example, to test `MyAesGcm128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_128_gcm_test_cases)]
+/// fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) {
+/// testcase(MyAesGcm128Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt(aes_128_gcm_test_encrypt)]
+#[case::decrypt(aes_128_gcm_test_decrypt)]
+fn aes_128_gcm_test_cases<F: AesGcmFactory<Key = Aes128Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-128-GCM implementation.
+/// For example, to test `MyAesGcm128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_128_gcm_test_cases_detached)]
+/// fn aes_128_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcmImpl>) {
+/// testcase(MyAesGcm128Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt_detached(aes_128_gcm_test_encrypt_detached)]
+#[case::decrypt_detached(aes_128_gcm_test_decrypt_detached)]
+#[case::decrypt_detached_bad_tag(aes_128_gcm_test_decrypt_detached_bad_tag)]
+fn aes_128_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes128Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-GCM implementation.
+/// For example, to test `MyAesGcm256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_256_gcm_test_cases)]
+/// fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) {
+/// testcase(MyAesGcm256Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc74(aes_256_gcm_test_tc74)]
+#[case::tc79(aes_256_gcm_test_tc79)]
+fn aes_256_gcm_test_cases<F: AesGcmFactory<Key = Aes256Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
+/// Generates the test cases to validate the AES-256-GCM implementation.
+/// For example, to test `MyAesGcm256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_256_gcm_test_cases_detached)]
+/// fn aes_256_gcm_tests(testcase: CryptoProviderTestCase<MyAesGcm256Impl>) {
+/// testcase(MyAesGcm256Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc74_detached(aes_256_gcm_test_tc74_detached)]
+#[case::tc79_detached(aes_256_gcm_test_tc79_detached)]
+#[case::decrypt_detached_bad_tag(aes_256_gcm_test_decrypt_detached_bad_tag)]
+fn aes_256_gcm_test_cases_detached<F: AesGcmFactory<Key = Aes256Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
index 893fba2..56d2215 100644
--- a/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
+++ b/nearby/crypto/crypto_provider_test/src/aead/aes_gcm_siv.rs
@@ -18,12 +18,28 @@
use rstest_reuse::template;
pub use crate::prelude;
-use crypto_provider::aes::{Aes128Key, Aes256Key};
+use crypto_provider::aead::{AeadInit, AesGcmSiv};
-use crypto_provider::aead::aes_gcm_siv::AesGcmSiv;
+/// Test AES-GCM-SIV-128 encryption
+pub fn aes_128_gcm_siv_test_encrypt<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC1
+ let test_key = hex!("01000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("");
+ let tag = hex!("dc20e2d83f25705bb49e439eca56de25");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("Should succeed");
+ assert_eq!(&result[..], &tag);
+}
-/// Test AES-GCM-SIV-128 encryption/decryption
-pub fn aes_128_gcm_siv_test<A: AesGcmSiv<Key = Aes128Key>>(_marker: marker::PhantomData<A>) {
+pub fn aes_128_gcm_siv_test_encrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
// https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
// TC1
let test_key = hex!("01000000000000000000000000000000");
@@ -32,23 +48,102 @@
let msg = hex!("");
let mut buf = Vec::from(msg.as_slice());
let tag = hex!("dc20e2d83f25705bb49e439eca56de25");
- assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..], &tag);
+ let actual_tag: [u8; 16] = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &[0_u8; 0]);
+ assert_eq!(&actual_tag, &tag);
+}
+
+/// Test AES-GCM-SIV-128 decryption
+pub fn aes_128_gcm_siv_test_decrypt<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
// TC2
+ let test_key = hex!("01000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("0100000000000000");
+ let ct = hex!("b5d839330ac7b786");
+ let tag = hex!("578782fff6013b815b287c22493a364c");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..8], &ct);
+ assert_eq!(&result[8..], &tag);
+ assert_eq!(A::TAG_SIZE, result[8..].len());
+ let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-GCM-SIV-128 decryption
+pub fn aes_128_gcm_siv_test_decrypt_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC2
+ let test_key = hex!("01000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
let msg = hex!("0100000000000000");
let ct = hex!("b5d839330ac7b786");
let tag = hex!("578782fff6013b815b287c22493a364c");
let mut buf = Vec::from(msg.as_slice());
- assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..8], &ct);
- assert_eq!(&buf[8..], &tag);
- assert_eq!(A::TAG_SIZE, buf[8..].len());
- assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
+ let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(actual_tag, tag);
+ assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
assert_eq!(&buf[..], &msg);
}
+/// Test AES-GCM-SIV-128 decryption where the tag given to decryption doesn't match
+pub fn aes_128_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes128Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC45
+ let test_key = hex!("00112233445566778899aabbccddeeff");
+ let nonce = hex!("000000000000000000000000");
+ let aad = hex!("9ea3371e258288d5a01b15384e2c99ee");
+ let aes = A::new(&test_key.into());
+ // Use a longer ciphertext as the test case to make sure it's not unchanged only for the most
+ // recent block.
+ let ct = hex!(
+ "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
+ "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
+ );
+ let bad_tag = hex!("13a1883272188b4c8d2727178198fe95");
+ let mut buf = Vec::from(ct.as_slice());
+ aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag).expect_err("Decryption should fail");
+ assert_eq!(&buf, &ct); // Buffer should be unchanged if decryption failed
+}
+
/// Test AES-256-GCM-SIV encryption/decryption
-pub fn aes_256_gcm_siv_test<A: AesGcmSiv<Key = Aes256Key>>(_marker: marker::PhantomData<A>) {
+pub fn aes_256_gcm_siv_test_tc77<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC77
+ let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("0100000000000000");
+ let ct = hex!("c2ef328e5c71c83b");
+ let tag = hex!("843122130f7364b761e0b97427e3df28");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..8], &ct);
+ assert_eq!(&result[8..], &tag);
+ assert_eq!(A::TAG_SIZE, result[8..].len());
+ let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc77_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
// https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
// TC77
let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
@@ -59,22 +154,72 @@
buf.extend_from_slice(&msg);
let ct = hex!("c2ef328e5c71c83b");
let tag = hex!("843122130f7364b761e0b97427e3df28");
- assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..8], &ct);
- assert_eq!(&buf[8..], &tag);
- assert_eq!(A::TAG_SIZE, buf[8..].len());
- assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..], &msg);
+ let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(&actual_tag, &tag);
+ assert_eq!(A::TAG_SIZE, tag.len());
+ assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &actual_tag).is_ok());
+ assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc78<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
// TC78
+ let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
+ let msg = hex!("010000000000000000000000");
+ let ct = hex!("9aab2aeb3faa0a34aea8e2b1");
+ let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e");
+ let result = aes.encrypt(&msg, b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..12], &ct);
+ assert_eq!(&result[12..], &tag);
+ let result = aes.decrypt(&result[..], b"", &nonce).expect("should succeed");
+ assert_eq!(&result[..], &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption
+pub fn aes_256_gcm_siv_test_tc78_detached<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC78
+ let test_key = hex!("0100000000000000000000000000000000000000000000000000000000000000");
+ let nonce = hex!("030000000000000000000000");
+ let aes = A::new(&test_key.into());
let msg = hex!("010000000000000000000000");
let ct = hex!("9aab2aeb3faa0a34aea8e2b1");
let tag = hex!("8ca50da9ae6559e48fd10f6e5c9ca17e");
let mut buf = Vec::from(msg.as_slice());
- assert!(aes.encrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..12], &ct);
- assert_eq!(&buf[12..], &tag);
- assert!(aes.decrypt(&mut buf, b"", &nonce).is_ok());
- assert_eq!(&buf[..], &msg);
+ let actual_tag = aes.encrypt_detached(&mut buf, b"", &nonce).unwrap();
+ assert_eq!(&buf, &ct);
+ assert_eq!(&actual_tag, &tag);
+ assert!(aes.decrypt_detached(&mut buf, b"", &nonce, &tag).is_ok());
+ assert_eq!(&buf, &msg);
+}
+
+/// Test AES-256-GCM-SIV encryption/decryption where the tag given to decryption doesn't match.
+pub fn aes_256_gcm_siv_test_decrypt_detached_bad_tag<A>(_marker: marker::PhantomData<A>)
+where
+ A: AesGcmSiv<Tag = [u8; 16]> + AeadInit<crypto_provider::aes::Aes256Key>,
+{
+ // https://github.com/google/wycheproof/blob/master/testvectors/aes_gcm_siv_test.json
+ // TC122
+ let test_key = hex!("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff");
+ let nonce = hex!("000000000000000000000000");
+ let aes = A::new(&test_key.into());
+ let aad = hex!("0289eaa93eb084107d2088435ef2a0cd");
+ let ct = hex!("ffffffffffffffff");
+ let bad_tag = hex!("ffffffffffffffffffffffffffffffff");
+ let mut buf = Vec::from(ct.as_slice());
+ aes.decrypt_detached(&mut buf, &aad, &nonce, &bad_tag)
+ .expect_err("Decrypting with bad tag should fail");
+ assert_eq!(&buf, &ct); // The buffer should be unchanged if the decryption failed
}
/// Generates the test cases to validate the AES-128-GCM-SIV implementation.
@@ -93,13 +238,37 @@
#[template]
#[export]
#[rstest]
-#[case::encrypt(aes_128_gcm_siv_test)]
-#[case::decrypt(aes_128_gcm_siv_test)]
+#[case::encrypt(aes_128_gcm_siv_test_encrypt)]
+#[case::decrypt(aes_128_gcm_siv_test_decrypt)]
fn aes_128_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes128Key>>(
#[case] testcase: CryptoProviderTestCase<F>,
) {
}
+/// Generates the test cases to validate the AES-128-GCM-SIV implementation.
+/// For example, to test `MyAesGcmSiv128Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm_siv::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_128_gcm_siv_test_cases_detached)]
+/// fn aes_128_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSivImpl>) {
+/// testcase(MyAesGcmSiv128Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::encrypt_detached(aes_128_gcm_siv_test_encrypt_detached)]
+#[case::decrypt_detached(aes_128_gcm_siv_test_decrypt_detached)]
+#[case::decrypt_detached_bad_tag(aes_128_gcm_siv_test_decrypt_detached_bad_tag)]
+fn aes_128_gcm_siv_test_cases_detached<F: AesGcmSivFactory<Key = Aes128Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
+
/// Generates the test cases to validate the AES-256-GCM-SIV implementation.
/// For example, to test `MyAesGcmSiv256Impl`:
///
@@ -116,9 +285,33 @@
#[template]
#[export]
#[rstest]
-#[case::encrypt(aes_256_gcm_siv_test)]
-#[case::decrypt(aes_256_gcm_siv_test)]
+#[case::tc77(aes_256_gcm_siv_test_tc77)]
+#[case::tc78(aes_256_gcm_siv_test_tc78)]
fn aes_256_gcm_siv_test_cases<F: AesGcmSivFactory<Key = Aes256Key>>(
#[case] testcase: CryptoProviderTestCase<F>,
) {
}
+
+/// Generates the test cases to validate the AES-256-GCM-SIV implementation.
+/// For example, to test `MyAesGcmSiv256Impl`:
+///
+/// ```
+/// use crypto_provider::aes::aes_gcm_siv::testing::*;
+///
+/// mod tests {
+/// #[apply(aes_256_gcm_siv_test_cases_detached)]
+/// fn aes_256_gcm_siv_tests(testcase: CryptoProviderTestCase<MyAesGcmSiv256Impl>) {
+/// testcase(MyAesGcmSiv256Impl);
+/// }
+/// }
+/// ```
+#[template]
+#[export]
+#[rstest]
+#[case::tc77_detached(aes_256_gcm_siv_test_tc77_detached)]
+#[case::tc78_detached(aes_256_gcm_siv_test_tc78_detached)]
+#[case::decrypt_detached_bad_tag(aes_256_gcm_siv_test_decrypt_detached_bad_tag)]
+fn aes_256_gcm_siv_test_cases_detached<F: AesGcmSivFactory<Key = Aes256Key>>(
+ #[case] testcase: CryptoProviderTestCase<F>,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/aead/mod.rs b/nearby/crypto/crypto_provider_test/src/aead/mod.rs
index 962aa49..3fe857c 100644
--- a/nearby/crypto/crypto_provider_test/src/aead/mod.rs
+++ b/nearby/crypto/crypto_provider_test/src/aead/mod.rs
@@ -14,3 +14,6 @@
/// Contains test cases for aes_gcm_siv implementations.
pub mod aes_gcm_siv;
+
+/// Contains test cases for aes_gcm implementations.
+pub mod aes_gcm;
diff --git a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
index b22c828..46cbebe 100644
--- a/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
+++ b/nearby/crypto/crypto_provider_test/src/aes/cbc.rs
@@ -15,13 +15,17 @@
use crate::aes::Aes256Key;
pub use crate::prelude::*;
use core::marker::PhantomData;
-use crypto_provider::aes::cbc::{AesCbcIv, AesCbcPkcs7Padded};
+use crypto_provider::{
+ aes::cbc::{AesCbcIv, AesCbcPkcs7Padded},
+ tinyvec::SliceVec,
+};
use hex_literal::hex;
use rstest_reuse::template;
/// Tests for AES-256-CBC encryption
pub fn aes_256_cbc_test_encrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
- // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+ // tcId: 132
let key: Aes256Key =
hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
@@ -30,9 +34,47 @@
assert_eq!(A::encrypt(&key, &iv, &msg), expected_ciphertext);
}
+/// Tests for AES-256-CBC in-place encryption
+pub fn aes_256_cbc_test_encrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+ // tcId: 132
+ let key: Aes256Key =
+ hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+ let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+ let msg = hex!("3f56935def3f");
+ let expected_ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+ let mut msg_buffer_backing = [0_u8; 16];
+ let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0);
+ msg_buffer.extend_from_slice(&msg);
+ A::encrypt_in_place(&key, &iv, &mut msg_buffer).unwrap();
+ assert_eq!(msg_buffer.as_slice(), &expected_ciphertext);
+}
+
+/// Tests for AES-256-CBC encryption, where the given buffer `SliceVec` is too short to contain the
+/// output.
+pub fn aes_256_cbc_test_encrypt_in_place_too_short<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1612
+ // tcId: 144
+ let key: Aes256Key =
+ hex!("4f097858a1aec62cf18f0966b2b120783aa4ae9149d3213109740506ae47adfe").into();
+ let iv: AesCbcIv = hex!("400aab92803bcbb44a96ef789655b34e");
+ let msg = hex!("ee53d8e5039e82d9fcca114e375a014febfea117a7e709d9008d43858e3660");
+ let mut msg_buffer_backing = [0_u8; 31];
+ let mut msg_buffer = SliceVec::from_slice_len(&mut msg_buffer_backing, 0);
+ msg_buffer.extend_from_slice(&msg);
+ A::encrypt_in_place(&key, &iv, &mut msg_buffer)
+ .expect_err("Encrypting AES with 15-byte buffer should fail");
+ // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data
+ assert!(
+ msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == msg,
+ "Unrecognized content in buffer after decryption failure"
+ )
+}
+
/// Tests for AES-256-CBC decryption
pub fn aes_256_cbc_test_decrypt<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
- // http://google3/third_party/wycheproof/testvectors/aes_cbc_pkcs5_test.json;l=1492;rcl=264817632
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+ // tcId: 132
let key: Aes256Key =
hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
@@ -41,6 +83,52 @@
assert_eq!(A::decrypt(&key, &iv, &ciphertext).unwrap(), expected_msg);
}
+/// Tests for AES-256-CBC decryption with bad padding
+pub fn aes_256_cbc_test_decrypt_bad_padding<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690
+ // tcId: 151
+ let key: Aes256Key =
+ hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into();
+ let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70");
+ let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf");
+ A::decrypt(&key, &iv, &ciphertext).expect_err("Decryption with bad padding should fail");
+}
+
+/// Tests for AES-256-CBC in-place decryption
+pub fn aes_256_cbc_test_decrypt_in_place<A: AesCbcPkcs7Padded>(_marker: PhantomData<A>) {
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1492
+ // tcId: 132
+ let key: Aes256Key =
+ hex!("665a02bc265a66d01775091da56726b6668bfd903cb7af66fb1b78a8a062e43c").into();
+ let iv: AesCbcIv = hex!("3fb0d5ecd06c71150748b599595833cb");
+ let mut ciphertext = hex!("3f3f39697bd7e88d85a14132be1cbc48");
+ let expected_msg = hex!("3f56935def3f");
+ let mut msg_buffer = SliceVec::from(&mut ciphertext);
+ A::decrypt_in_place(&key, &iv, &mut msg_buffer).unwrap();
+ assert_eq!(msg_buffer.as_slice(), expected_msg);
+}
+
+/// Tests for AES-256-CBC in-place decryption with bad padding
+pub fn aes_256_cbc_test_decrypt_in_place_bad_padding<A: AesCbcPkcs7Padded>(
+ _marker: PhantomData<A>,
+) {
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/aes_cbc_pkcs5_test.json#L1690
+ // tcId: 151
+ let key: Aes256Key =
+ hex!("7c78f34dbce8f0557d43630266f59babd1cb92ba624bd1a8f45a2a91c84a804a").into();
+ let iv: AesCbcIv = hex!("f010f61c31c9aa8fa0d5be5f6b0f2f70");
+ let ciphertext = hex!("8881e9e02fa9e3037b397957ba1fb7ce64679a46621b792f643542a735f0bbbf");
+ let mut msg_buffer_backing = ciphertext;
+ let mut msg_buffer = SliceVec::from(&mut msg_buffer_backing);
+ A::decrypt_in_place(&key, &iv, &mut msg_buffer)
+ .expect_err("Decryption with bad padding should fail");
+ // Buffer content is undefined, but test to make sure it doesn't contain half-decrypted data
+ assert!(
+ msg_buffer.as_slice() == [0_u8; 32] || msg_buffer.as_slice() == ciphertext,
+ "Unrecognized content in buffer after decryption failure"
+ )
+}
+
/// Generates the test cases to validate the AES-256-CBC implementation.
/// For example, to test `MyAesCbc256Impl`:
///
@@ -59,5 +147,9 @@
#[export]
#[rstest]
#[case::encrypt(aes_256_cbc_test_encrypt)]
+#[case::encrypt_in_place(aes_256_cbc_test_encrypt_in_place)]
+#[case::encrypt_in_place_too_short(aes_256_cbc_test_encrypt_in_place_too_short)]
#[case::decrypt(aes_256_cbc_test_decrypt)]
+#[case::decrypt_bad_padding(aes_256_cbc_test_decrypt_bad_padding)]
+#[case::decrypt_in_place_bad_padding(aes_256_cbc_test_decrypt_in_place_bad_padding)]
fn aes_256_cbc_test_cases<A: AesCbcPkcs7Padded>(#[case] testcase: CryptoProviderTestCases<F>) {}
diff --git a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
index b1d6b8b..de71740 100644
--- a/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
+++ b/nearby/crypto/crypto_provider_test/src/aes/ctr.rs
@@ -27,22 +27,22 @@
let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
block = hex!("6bc1bee22e409f96e93d7e117393172a");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_1 = hex!("874d6191b620e3261bef6864990db6ce");
assert_eq!(expected_ciphertext_1, block);
block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_2 = hex!("9806f66b7970fdff8617187bb9fffdff");
assert_eq!(expected_ciphertext_2, block);
block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_3 = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
assert_eq!(expected_ciphertext_3, block);
block = hex!("f69f2445df4f9b17ad2b417be66c3710");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_3 = hex!("1e031dda2fbe03d1792170a0f3009cee");
assert_eq!(expected_ciphertext_3, block);
}
@@ -56,22 +56,22 @@
let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
block = hex!("874d6191b620e3261bef6864990db6ce");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
assert_eq!(expected_plaintext_1, block);
block = hex!("9806f66b7970fdff8617187bb9fffdff");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
assert_eq!(expected_plaintext_2, block);
block = hex!("5ae4df3edbd5d35e5b4f09020db03eab");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
assert_eq!(expected_plaintext_3, block);
block = hex!("1e031dda2fbe03d1792170a0f3009cee");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
assert_eq!(expected_plaintext_3, block);
}
@@ -86,22 +86,22 @@
let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
block = hex!("6bc1bee22e409f96e93d7e117393172a");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_1 = hex!("601ec313775789a5b7a7f504bbf3d228");
assert_eq!(expected_ciphertext_1, block);
block = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_2 = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
assert_eq!(expected_ciphertext_2, block);
block = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_3 = hex!("2b0930daa23de94ce87017ba2d84988d");
assert_eq!(expected_ciphertext_3, block);
block = hex!("f69f2445df4f9b17ad2b417be66c3710");
- cipher.encrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_ciphertext_3 = hex!("dfc9c58db67aada613c2dd08457941a6");
assert_eq!(expected_ciphertext_3, block);
}
@@ -116,22 +116,22 @@
let mut cipher = A::new(&key, NonceAndCounter::from_block(iv));
block = hex!("601ec313775789a5b7a7f504bbf3d228");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_1 = hex!("6bc1bee22e409f96e93d7e117393172a");
assert_eq!(expected_plaintext_1, block);
block = hex!("f443e3ca4d62b59aca84e990cacaf5c5");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_2 = hex!("ae2d8a571e03ac9c9eb76fac45af8e51");
assert_eq!(expected_plaintext_2, block);
block = hex!("2b0930daa23de94ce87017ba2d84988d");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_3 = hex!("30c81c46a35ce411e5fbc1191a0a52ef");
assert_eq!(expected_plaintext_3, block);
block = hex!("dfc9c58db67aada613c2dd08457941a6");
- cipher.decrypt(&mut block);
+ cipher.apply_keystream(&mut block);
let expected_plaintext_3 = hex!("f69f2445df4f9b17ad2b417be66c3710");
assert_eq!(expected_plaintext_3, block);
}
diff --git a/nearby/crypto/crypto_provider_test/src/ed25519.rs b/nearby/crypto/crypto_provider_test/src/ed25519.rs
index d99605c..0fdb484 100644
--- a/nearby/crypto/crypto_provider_test/src/ed25519.rs
+++ b/nearby/crypto/crypto_provider_test/src/ed25519.rs
@@ -18,7 +18,9 @@
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec::Vec;
-use crypto_provider::ed25519::{Ed25519Provider, KeyPair, PublicKey, Signature};
+use crypto_provider::ed25519::{
+ Ed25519Provider, KeyPair, PublicKey, RawPrivateKeyPermit, RawSignature, Signature,
+};
use wycheproof::TestResult;
// These are test vectors from the creators of Ed25519: https://ed25519.cr.yp.to/ which are referenced
@@ -27,7 +29,7 @@
// also used for test cases in the above RFC:
// https://dev.gnupg.org/source/libgcrypt/browse/master/tests/t-ed25519.inp
const PATH_TO_RFC_VECTORS_FILE: &str =
- "crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt";
+ "crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt";
/// Runs set of Ed25519 wycheproof test vectors against a provided ed25519 implementation
/// Tests vectors from Project Wycheproof: <https://github.com/google/wycheproof>
@@ -39,10 +41,7 @@
.expect("should be able to load test set");
for test_group in test_set.test_groups {
- let key_pair = test_group.key;
- let public_key = key_pair.pk;
- let secret_key = key_pair.sk;
-
+ let public_key = test_group.key.pk.to_vec();
for test in test_group.tests {
let tc_id = test.tc_id;
let comment = test.comment;
@@ -53,8 +52,7 @@
TestResult::Invalid => false,
TestResult::Valid | TestResult::Acceptable => true,
};
- let result =
- run_test::<E>(public_key.clone(), secret_key.clone(), sig.clone(), msg.clone());
+ let result = run_wycheproof_test::<E>(public_key.to_vec(), sig.to_vec(), msg.to_vec());
if valid {
if let Err(desc) = result {
panic!(
@@ -73,6 +71,20 @@
}
}
+fn run_wycheproof_test<E>(pub_key: Vec<u8>, sig: Vec<u8>, msg: Vec<u8>) -> Result<(), &'static str>
+where
+ E: Ed25519Provider,
+{
+ let pub_key = E::PublicKey::from_bytes(pub_key.as_slice().try_into().unwrap())
+ .map_err(|_| "Invalid public key bytes")?;
+
+ let raw_sig: RawSignature =
+ sig.as_slice().try_into().map_err(|_| "Invalid length signature")?;
+ let signature = E::Signature::from_bytes(&raw_sig);
+
+ pub_key.verify_strict(msg.as_slice(), &signature).map_err(|_| "Signature verification failed")
+}
+
/// Runs the RFC specified test vectors against an Ed25519 implementation
pub fn run_rfc_test_vectors<E>()
where
@@ -126,7 +138,10 @@
{
let private_key_bytes: [u8; 32] =
private_key.as_slice().try_into().expect("Secret key is the wrong length");
- let kp = E::KeyPair::from_private_key(&private_key_bytes);
+
+ // Permits: Test-only code, not a production leak of the private key
+ let permit = RawPrivateKeyPermit::default();
+ let kp = E::KeyPair::from_raw_private_key(&private_key_bytes, &permit);
let sig_result = kp.sign(msg.as_slice());
(sig.as_slice() == sig_result.to_bytes()).then_some(()).ok_or("sig not matching expected")?;
diff --git a/nearby/crypto/crypto_provider_test/src/lib.rs b/nearby/crypto/crypto_provider_test/src/lib.rs
index 8e63f44..2209a3e 100644
--- a/nearby/crypto/crypto_provider_test/src/lib.rs
+++ b/nearby/crypto/crypto_provider_test/src/lib.rs
@@ -36,9 +36,11 @@
/// Common items that needs to be imported to use these test cases
pub mod prelude {
pub use super::CryptoProviderTestCase;
+ pub use ::rstest;
pub use rstest::rstest;
pub use rstest_reuse;
pub use rstest_reuse::apply;
+ pub extern crate std;
}
/// A test case for Crypto Provider. A test case is a function that panics if the test fails.
diff --git a/nearby/crypto/crypto_provider_test/src/p256.rs b/nearby/crypto/crypto_provider_test/src/p256.rs
index 9869c9e..5ebbd50 100644
--- a/nearby/crypto/crypto_provider_test/src/p256.rs
+++ b/nearby/crypto/crypto_provider_test/src/p256.rs
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-extern crate std;
use crate::elliptic_curve::EphemeralSecretForTesting;
pub use crate::prelude::*;
use crate::TestError;
use core::marker::PhantomData;
-use crypto_provider::p256::{P256PublicKey, P256};
+use core::ops::Deref;
+use crypto_provider::p256::{P256PublicKey, PointCompression, P256};
use crypto_provider::{
elliptic_curve::{EcdhProvider, EphemeralSecret, PublicKey},
CryptoRng,
@@ -62,9 +62,35 @@
8bbe76c6dc1643088107636deff8aa79e8002a157b92"
);
let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+ // Not part of the API contract, but `to_bytes()` should prefer to use uncompressed
+ // representation since support for compressed point is optional.
+ let key_bytes = key.to_bytes();
+ assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
+}
+
+/// Test for P256PublicKey::to_sec1_bytes(Compressed). Support for compressed representation is
+/// optional.
+pub fn to_bytes_compressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+ let sec1_bytes = hex!(
+ "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+ 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+ );
+ let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+ let key_bytes = key.to_sec1_bytes(PointCompression::Compressed);
let sec1_bytes_compressed =
hex!("02756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09");
- assert_eq!(sec1_bytes_compressed.to_vec(), key.to_bytes());
+ assert_eq!(sec1_bytes_compressed.to_vec(), key_bytes.deref());
+}
+
+/// Test for P256PublicKey::to_sec1_bytes(Uncompressed)
+pub fn to_bytes_uncompressed_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
+ let sec1_bytes = hex!(
+ "04756c07ba5b596fa96c9099e6619dc62deac4297a8fc1d803d74dc5caa9197c09f0b6da270d2a58a06022
+ 8bbe76c6dc1643088107636deff8aa79e8002a157b92"
+ );
+ let key = E::PublicKey::from_sec1_bytes(&sec1_bytes).unwrap();
+ let key_bytes = key.to_sec1_bytes(PointCompression::Uncompressed);
+ assert_eq!(sec1_bytes.to_vec(), key_bytes.deref());
}
/// Random test for P256PublicKey::to_bytes
@@ -75,7 +101,7 @@
P256,
>>::Rng::new())
.public_key_bytes();
- let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+ let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
assert_eq!(
E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
public_key,
@@ -192,7 +218,7 @@
/// Test for P256 Diffie-Hellman key exchange.
pub fn p256_ecdh_test<E: EcdhProviderForP256Test>(_: PhantomData<E>) {
// From wycheproof ecdh_secp256r1_ecpoint_test.json, tcId 1
- // http://google3/third_party/wycheproof/testvectors/ecdh_secp256r1_ecpoint_test.json;l=22;rcl=375894991
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/ecdh_secp256r1_ecpoint_test.json#L22
// sec1 public key manually extracted from the ASN encoded test data
let public_key_sec1 = hex!(
"0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f
@@ -230,20 +256,24 @@
};
let result = p256_ecdh_test_impl::<E>(
&test.public_key,
- &test.private_key.try_into().expect("Private key should be 32 bytes long"),
+ &test
+ .private_key
+ .as_slice()
+ .try_into()
+ .expect("Private key should be 32 bytes long"),
);
match test.result {
wycheproof::TestResult::Valid => {
let shared_secret =
result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
- assert_eq!(test.shared_secret, shared_secret.into());
+ assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
}
wycheproof::TestResult::Invalid => {
result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
}
wycheproof::TestResult::Acceptable => {
if let Ok(shared_secret) = result {
- assert_eq!(test.shared_secret, shared_secret.into());
+ assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
}
// Test passes if `result` is an error because this test is "acceptable"
}
@@ -268,22 +298,43 @@
#[template]
#[export]
#[rstest]
-#[case::to_bytes(to_bytes_test)]
-#[case::to_bytes_random(to_bytes_random_test)]
-#[case::from_sec1_bytes_not_on_curve(from_sec1_bytes_not_on_curve_test)]
-#[case::from_sec1_bytes_not_on_infinity(from_sec1_bytes_at_infinity_test)]
-#[case::from_affine_coordinates(from_affine_coordinates_test)]
-#[case::from_affine_coordinates_not_on_curve(from_affine_coordinates_not_on_curve_test)]
-#[case::public_key_to_affine_coordinates(public_key_to_affine_coordinates_test)]
+#[case::to_bytes(to_bytes_test, "to_bytes")]
+#[case::to_bytes_compressed(to_bytes_compressed_test, "to_bytes_compressed")]
+#[case::to_bytes_uncompressed(to_bytes_uncompressed_test, "to_bytes_uncompressed")]
+#[case::to_bytes_random(to_bytes_random_test, "to_bytes_random")]
+#[case::from_sec1_bytes_not_on_curve(
+ from_sec1_bytes_not_on_curve_test,
+ "from_sec1_bytes_not_on_curve"
+)]
+#[case::from_sec1_bytes_not_on_infinity(
+ from_sec1_bytes_at_infinity_test,
+ "from_sec1_bytes_not_on_infinity"
+)]
+#[case::from_affine_coordinates(from_affine_coordinates_test, "from_affine_coordinates")]
+#[case::from_affine_coordinates_not_on_curve(
+ from_affine_coordinates_not_on_curve_test,
+ "from_affine_coordinates_not_on_curve"
+)]
+#[case::public_key_to_affine_coordinates(
+ public_key_to_affine_coordinates_test,
+ "public_key_to_affine_coordinates"
+)]
#[case::public_key_to_affine_coordinates_compressed02(
- public_key_to_affine_coordinates_compressed02_test
+ public_key_to_affine_coordinates_compressed02_test,
+ "public_key_to_affine_coordinates_compressed02"
)]
#[case::public_key_to_affine_coordinates_compressed03(
- public_key_to_affine_coordinates_compressed03_test
+ public_key_to_affine_coordinates_compressed03_test,
+ "public_key_to_affine_coordinates_compressed03"
)]
#[case::public_key_to_affine_coordinates_zero_top_byte(
- public_key_to_affine_coordinates_zero_top_byte_test
+ public_key_to_affine_coordinates_zero_top_byte_test,
+ "public_key_to_affine_coordinates_zero_top_byte"
)]
-#[case::p256_ecdh(p256_ecdh_test)]
-#[case::wycheproof_p256(wycheproof_p256_test)]
-fn p256_test_cases<C: CryptoProvider>(#[case] testcase: CryptoProviderTestCase<C>) {}
+#[case::p256_ecdh(p256_ecdh_test, "p256_ecdh")]
+#[case::wycheproof_p256(wycheproof_p256_test, "wycheproof_p256")]
+fn p256_test_cases<C: CryptoProvider>(
+ #[case] testcase: CryptoProviderTestCase<C>,
+ #[case] name: &str,
+) {
+}
diff --git a/nearby/crypto/crypto_provider_test/src/sha2.rs b/nearby/crypto/crypto_provider_test/src/sha2.rs
index 330f284..66a3025 100644
--- a/nearby/crypto/crypto_provider_test/src/sha2.rs
+++ b/nearby/crypto/crypto_provider_test/src/sha2.rs
@@ -14,7 +14,7 @@
extern crate alloc;
extern crate std;
-pub use crate::prelude::*;
+use crate::prelude::*;
use crate::CryptoProvider;
use alloc::vec::Vec;
use core::{marker::PhantomData, str::FromStr};
diff --git a/nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt b/nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt
similarity index 100%
rename from nearby/crypto/crypto_provider_test/src/testdata/ecdsa/rfc_test_vectors.txt
rename to nearby/crypto/crypto_provider_test/src/testdata/EdDSA/rfc_test_vectors.txt
diff --git a/nearby/crypto/crypto_provider_test/src/x25519.rs b/nearby/crypto/crypto_provider_test/src/x25519.rs
index 0fcaa12..1fe2019 100644
--- a/nearby/crypto/crypto_provider_test/src/x25519.rs
+++ b/nearby/crypto/crypto_provider_test/src/x25519.rs
@@ -58,7 +58,7 @@
pub fn x25519_to_bytes_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
let public_key_bytes = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
- assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes());
+ assert_eq!(public_key_bytes.to_vec(), public_key.to_bytes().as_ref());
}
/// Random test for `PublicKey<X25519>::to_bytes`
@@ -69,9 +69,9 @@
X25519,
>>::Rng::new())
.public_key_bytes();
- let public_key = E::PublicKey::from_bytes(&public_key_bytes).unwrap();
+ let public_key = E::PublicKey::from_bytes(public_key_bytes.as_ref()).unwrap();
assert_eq!(
- E::PublicKey::from_bytes(&public_key.to_bytes()).unwrap(),
+ E::PublicKey::from_bytes(public_key.to_bytes().as_ref()).unwrap(),
public_key,
"from_bytes should return the same key for `{public_key_bytes:?}`",
);
@@ -81,7 +81,7 @@
/// Test for X25519 Diffie-Hellman key exchange.
pub fn x25519_ecdh_test<E: EcdhProviderForX25519Test>(_: PhantomData<E>) {
// From wycheproof ecdh_secx25519r1_ecpoint_test.json, tcId 1
- // http://google3/third_party/wycheproof/testvectors/ecdh_secx25519r1_ecpoint_test.json;l=22;rcl=375894991
+ // https://github.com/google/wycheproof/blob/b063b4a/testvectors/x25519_test.json#L23
// sec1 public key manually extracted from the ASN encoded test data
let public_key = hex!("504a36999f489cd2fdbc08baff3d88fa00569ba986cba22548ffde80f9806829");
let private = hex!("c8a9d5a91091ad851c668b0736c1c9a02936c0d3ad62670858088047ba057475");
@@ -109,20 +109,24 @@
for test in test_group.tests {
let result = x25519_ecdh_test_impl::<E>(
&test.public_key,
- &test.private_key.try_into().expect("Private keys should be 32 bytes long"),
+ &test
+ .private_key
+ .as_slice()
+ .try_into()
+ .expect("Private keys should be 32 bytes long"),
);
match test.result {
wycheproof::TestResult::Valid => {
let shared_secret =
result.unwrap_or_else(|_| panic!("Test {} should succeed", test.tc_id));
- assert_eq!(&test.shared_secret, &shared_secret.into());
+ assert_eq!(&test.shared_secret.as_slice(), &shared_secret.into());
}
wycheproof::TestResult::Invalid => {
result.err().unwrap_or_else(|| panic!("Test {} should fail", test.tc_id));
}
wycheproof::TestResult::Acceptable => {
if let Ok(shared_secret) = result {
- assert_eq!(test.shared_secret, shared_secret.into());
+ assert_eq!(test.shared_secret.as_slice(), shared_secret.into());
}
// Test passes if `result` is an error because this test is "acceptable"
}
diff --git a/nearby/presence/CMakeLists.txt b/nearby/presence/CMakeLists.txt
index 2b4b66e..d2a251b 100644
--- a/nearby/presence/CMakeLists.txt
+++ b/nearby/presence/CMakeLists.txt
@@ -32,15 +32,6 @@
set(CMAKE_CXX_FLAGS_DEBUG "-g ${CMAKE_C_FLAGS_DEBUG}")
endif ()
-if (UNIX)
- add_compile_options(-Wall -Wextra -Wimplicit-fallthrough -Wextra-semi
- -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi
- -Wshadow
- -Wsign-compare)
-elseif (MSVC)
- add_compile_options(-W4 -O1)
-endif ()
-
find_package(OpenSSL REQUIRED)
if (OPENSSL_FOUND)
message(STATUS "OpenSSL Found: ${OPENSSL_VERSION}")
@@ -48,6 +39,10 @@
message(STATUS "OpenSSL Libraries: ${OPENSSL_LIBRARIES}")
endif ()
+if (MSVC)
+ add_compile_options(-W4 -O1 -MD)
+endif ()
+
if (ENABLE_TESTS)
message(STATUS "Enabling workspace wide tests")
@@ -62,8 +57,8 @@
enable_testing()
include(GoogleTest)
- # Find GoogleBenchmark
- find_package(benchmark REQUIRED)
+ # Include google benchmark
+ add_subdirectory(${THIRD_PARTY_DIR}/benchmark ${THIRD_PARTY_DIR}/benchmark/build)
# Setup jsoncpp
set(JSONCPP_DIR ${THIRD_PARTY_DIR}/jsoncpp)
@@ -74,6 +69,15 @@
)
endif ()
+# Add these compile options for our own code, not needed for the above deps
+if (UNIX)
+ add_compile_options(-Wall -Wextra -Wimplicit-fallthrough -Wextra-semi
+ -Wno-missing-field-initializers -Wno-unused-parameter -Wno-psabi
+ -Wshadow
+ -Wsign-compare)
+endif()
+
+
add_subdirectory(np_cpp_ffi)
add_subdirectory(ldt_np_c_sample)
diff --git a/nearby/presence/array_ref/README.md b/nearby/presence/array_ref/README.md
new file mode 100644
index 0000000..2ff0748
--- /dev/null
+++ b/nearby/presence/array_ref/README.md
@@ -0,0 +1,4 @@
+This crate was originally forked from crates.io: https://github.com/droundy/arrayref
+
+This fork removes unsafe code which is no longer needed in stable rust while
+keeping the behavior the same. The owner of the upstream repo has been unresponsive with regards to landing these changes upstream ie: https://github.com/droundy/arrayref/pull/24
\ No newline at end of file
diff --git a/nearby/presence/handle_map/src/lib.rs b/nearby/presence/handle_map/src/lib.rs
deleted file mode 100644
index 745aec4..0000000
--- a/nearby/presence/handle_map/src/lib.rs
+++ /dev/null
@@ -1,608 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//! A thread-safe implementation of a map for managing object handles,
-//! a safer alternative to raw pointers for FFI interop.
-#![cfg_attr(not(test), no_std)]
-#![forbid(unsafe_code)]
-#![deny(missing_docs)]
-extern crate alloc;
-extern crate core;
-
-use alloc::boxed::Box;
-use alloc::vec::Vec;
-use core::ops::{Deref, DerefMut};
-use core::sync::atomic::Ordering;
-use hashbrown::hash_map::EntryRef;
-use portable_atomic::{AtomicU32, AtomicU64};
-use spin::lock_api::*;
-
-#[cfg(test)]
-mod tests;
-
-/// A RAII read lock guard for an object in a [`HandleMap`]
-/// pointed-to by a given [`Handle`]. When this struct is
-/// dropped, the underlying read lock on the associated
-/// shard will be dropped.
-pub struct ObjectReadGuard<'a, T> {
- guard: lock_api::MappedRwLockReadGuard<'a, spin::RwLock<()>, T>,
-}
-
-impl<'a, T> Deref for ObjectReadGuard<'a, T> {
- type Target = T;
-
- fn deref(&self) -> &Self::Target {
- self.guard.deref()
- }
-}
-
-/// A RAII read-write lock guard for an object in a [`HandleMap`]
-/// pointed-to by a given [`Handle`]. When this struct is
-/// dropped, the underlying read-write lock on the associated
-/// shard will be dropped.
-pub struct ObjectReadWriteGuard<'a, T> {
- guard: lock_api::MappedRwLockWriteGuard<'a, spin::RwLock<()>, T>,
-}
-
-impl<'a, T> Deref for ObjectReadWriteGuard<'a, T> {
- type Target = T;
-
- fn deref(&self) -> &Self::Target {
- self.guard.deref()
- }
-}
-
-impl<'a, T> DerefMut for ObjectReadWriteGuard<'a, T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.guard.deref_mut()
- }
-}
-
-/// FFI-transmissible structure expressing the dimensions
-/// (max # of allocatable slots, number of shards) of a handle-map
-/// to be used upon initialization.
-#[repr(C)]
-#[derive(Clone, Copy)]
-pub struct HandleMapDimensions {
- /// The number of shards which are employed
- /// by the associated handle-map.
- pub num_shards: u8,
- /// The maximum number of active handles which may be
- /// stored within the associated handle-map.
- pub max_active_handles: u32,
-}
-
-/// Trait for marker structs containing information about a
-/// given [`SingletonHandleMap`], including the underlying
-/// object type and the dimensions of the map.
-/// Used primarily to enforce type-level constraints
-/// on `SingletoneHandleMap`s, including
-/// ensuring that they are truly singletons.
-pub trait SingletonHandleMapInfo {
- /// The underlying kind of object pointed-to by handles
- /// derived from the associated handle-map.
- type Object: Send + Sync;
-
- /// Gets the dimensions of the corresponding handle-map.
- fn dimensions(&self) -> HandleMapDimensions;
-
- /// Gets a static reference to the global handle-map
- fn get_handle_map() -> &'static HandleMap<Self::Object>;
-}
-
-/// An individual handle to be given out by a [`HandleMap`].
-/// This representation is untyped, and just a wrapper
-/// around a handle-id, in contrast to implementors of `HandleLike`.
-#[derive(Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Handle {
- handle_id: u64,
-}
-
-impl From<&Handle> for Handle {
- fn from(handle: &Handle) -> Self {
- *handle
- }
-}
-
-impl Handle {
- /// Constructs a handle wrapping the given ID.
- ///
- /// No validity checks are done on the wrapped ID
- /// to ensure that the given ID is active in
- /// any specific handle-map, and the type
- /// of the handle is not represented.
- ///
- /// As a result, this method is only useful for
- /// allowing access to handles from an FFI layer.
- pub fn from_id(handle_id: u64) -> Self {
- Self { handle_id }
- }
- /// Gets the ID for this handle.
- ///
- /// Since the underlying handle is un-typed,`
- /// this method is only suitable for
- /// transmitting handles across an FFI layer.
- pub fn get_id(&self) -> u64 {
- self.handle_id
- }
- /// Derives the shard index from the handle id
- fn get_shard_index(&self, num_shards: u8) -> usize {
- (self.handle_id % (num_shards as u64)) as usize
- }
-}
-
-/// Error raised when attempting to allocate into a full handle-map.
-#[derive(Debug)]
-pub struct HandleMapFullError;
-
-/// Internal error enum for failed allocations into a given shard.
-enum ShardAllocationError<T, F: FnOnce() -> T> {
- /// Error for when the entry for the handle is occupied,
- /// in which case we spit out the object-provider to try again
- /// with a new handle-id.
- EntryOccupied(F),
- /// Error for when we would exceed the maximum number of allocations.
- ExceedsAllocationLimit,
-}
-
-/// Error raised when the entry for a given [`Handle`] doesn't exist.
-#[derive(Debug)]
-pub struct HandleNotPresentError;
-
-/// Wrapper around a `HandleMap` which is specifically tailored for maintaining
-/// a global singleton `'static` reference to a handle-map.
-/// The wrapper handles initialization of the singleton in a const-context
-/// and subsequent accesses to ensure that the underlying map is always
-/// initialized upon attempts to access it.
-pub struct SingletonHandleMap<I: SingletonHandleMapInfo> {
- info: I,
- wrapped: spin::once::Once<HandleMap<I::Object>, spin::relax::Spin>,
-}
-
-impl<I: SingletonHandleMapInfo> SingletonHandleMap<I> {
- /// Constructs a new global handle-map using the given
- /// singleton handle-map info. This method is callable
- /// from a `const`-context to allow it to be used to initialize
- /// `static` variables.
- pub const fn with_info(info: I) -> Self {
- Self { info, wrapped: spin::once::Once::new() }
- }
- /// Initialize the handle-map if it's not already initialized,
- /// and return a reference to the result.
- pub fn get(&self) -> &HandleMap<I::Object> {
- self.wrapped.call_once(|| {
- let dimensions = self.info.dimensions();
- HandleMap::with_dimensions(dimensions)
- })
- }
-}
-
-/// A thread-safe mapping from "handle"s [like pointers, but safer]
-/// to underlying structures, supporting allocations, reads, writes,
-/// and deallocations of objects behind handles.
-pub struct HandleMap<T: Send + Sync> {
- /// The dimensions of this handle-map
- dimensions: HandleMapDimensions,
-
- /// The individually-lockable "shards" of the handle-map,
- /// among which the keys will be roughly uniformly-distributed.
- handle_map_shards: Box<[HandleMapShard<T>]>,
-
- /// An atomically-incrementing counter which tracks the
- /// next handle ID which allocations will attempt to use.
- new_handle_id_counter: AtomicU64,
-
- /// An atomic integer roughly tracking the number of
- /// currently-outstanding allocated entries in this
- /// handle-map among all [`HandleMapShard`]s.
- outstanding_allocations_counter: AtomicU32,
-}
-
-impl<T: Send + Sync> HandleMap<T> {
- /// Creates a new handle-map with the given `HandleMapDimensions`.
- pub fn with_dimensions(dimensions: HandleMapDimensions) -> Self {
- let mut handle_map_shards = Vec::with_capacity(dimensions.num_shards as usize);
- for _ in 0..dimensions.num_shards {
- handle_map_shards.push(HandleMapShard::default());
- }
- let handle_map_shards = handle_map_shards.into_boxed_slice();
- Self {
- dimensions,
- handle_map_shards,
- new_handle_id_counter: AtomicU64::new(0),
- outstanding_allocations_counter: AtomicU32::new(0),
- }
- }
-}
-
-impl<T: Send + Sync> HandleMap<T> {
- /// Allocates a new object within the given handle-map, returning
- /// a handle to the location it was stored at. This operation
- /// may fail if attempting to allocate over the `dimensions.max_active_handles`
- /// limit imposed on the handle-map, in which case this method
- /// will return a `HandleMapFullError`.
- pub fn allocate(
- &self,
- initial_value_provider: impl FnOnce() -> T,
- ) -> Result<Handle, HandleMapFullError> {
- let mut initial_value_provider = initial_value_provider;
- loop {
- // Increment the new-handle-ID counter using relaxed memory ordering,
- // since the only invariant that we want to enforce is that concurrently-running
- // threads always get distinct new handle-ids.
- let new_handle_id = self.new_handle_id_counter.fetch_add(1, Ordering::Relaxed);
- let new_handle = Handle::from_id(new_handle_id);
- let shard_index = new_handle.get_shard_index(self.dimensions.num_shards);
-
- // Now, check the shard to see if we can actually allocate into it.
- let shard_allocate_result = self.handle_map_shards[shard_index].try_allocate(
- new_handle,
- initial_value_provider,
- &self.outstanding_allocations_counter,
- self.dimensions.max_active_handles,
- );
- match shard_allocate_result {
- Ok(_) => {
- return Ok(new_handle);
- }
- Err(ShardAllocationError::ExceedsAllocationLimit) => {
- return Err(HandleMapFullError);
- }
- Err(ShardAllocationError::EntryOccupied(thrown_back_provider)) => {
- // We need to do the whole thing again with a new ID
- initial_value_provider = thrown_back_provider;
- }
- }
- }
- }
-
- /// Gets a read-only reference to an object within the given handle-map,
- /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`].
- pub fn get(&self, handle: Handle) -> Result<ObjectReadGuard<T>, HandleNotPresentError> {
- let shard_index = handle.get_shard_index(self.dimensions.num_shards);
- self.handle_map_shards[shard_index].get(handle)
- }
-
- /// Gets a read+write reference to an object within the given handle-map,
- /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`].
- pub fn get_mut(
- &self,
- handle: Handle,
- ) -> Result<ObjectReadWriteGuard<T>, HandleNotPresentError> {
- let shard_index = handle.get_shard_index(self.dimensions.num_shards);
- self.handle_map_shards[shard_index].get_mut(handle)
- }
-
- /// Removes the object pointed to by the given handle in
- /// the handle-map, returning the removed object if it
- /// exists. Otherwise, returns [`HandleNotPresentError`].
- pub fn deallocate(&self, handle: Handle) -> Result<T, HandleNotPresentError> {
- let shard_index = handle.get_shard_index(self.dimensions.num_shards);
- self.handle_map_shards[shard_index]
- .deallocate(handle, &self.outstanding_allocations_counter)
- }
-
- /// Gets the actual number of elements stored in the entire map.
- /// Only suitable for single-threaded sections of tests.
- #[cfg(test)]
- pub(crate) fn len(&self) -> usize {
- self.handle_map_shards.iter().map(|s| s.len()).sum()
- }
-
- /// Sets the new-handle-id counter to the given value.
- /// Only suitable for tests.
- #[cfg(test)]
- pub(crate) fn set_new_handle_id_counter(&mut self, value: u64) {
- self.new_handle_id_counter = AtomicU64::new(value);
- }
-}
-
-// Bunch o' type aliases to make talking about them much easier in the shard code.
-type ShardMapType<T> = hashbrown::HashMap<Handle, T>;
-type ShardReadWriteLock<T> = RwLock<ShardMapType<T>>;
-type ShardReadGuard<'a, T> = RwLockReadGuard<'a, ShardMapType<T>>;
-type ShardUpgradableReadGuard<'a, T> = RwLockUpgradableReadGuard<'a, ShardMapType<T>>;
-type ShardReadWriteGuard<'a, T> = RwLockWriteGuard<'a, ShardMapType<T>>;
-
-/// An individual handle-map shard, which is ultimately
-/// just a hash-map behind a lock.
-pub(crate) struct HandleMapShard<T: Send + Sync> {
- data: RwLock<ShardMapType<T>>,
-}
-
-impl<T: Send + Sync> Default for HandleMapShard<T> {
- fn default() -> Self {
- Self { data: RwLock::new(hashbrown::HashMap::new()) }
- }
-}
-
-impl<T: Send + Sync> HandleMapShard<T> {
- fn get(&self, handle: Handle) -> Result<ObjectReadGuard<T>, HandleNotPresentError> {
- let map_read_guard = ShardReadWriteLock::<T>::read(&self.data);
- let read_only_map_ref = map_read_guard.deref();
- if read_only_map_ref.contains_key(&handle) {
- let object_read_guard = ShardReadGuard::<T>::map(map_read_guard, move |map_ref| {
- // We know that the entry exists, since we've locked the
- // shard and already checked that it exists prior to
- // handing out this new, mapped read-lock.
- map_ref.get(&handle).unwrap()
- });
- Ok(ObjectReadGuard { guard: object_read_guard })
- } else {
- // Auto-drop the read guard, and return an error
- Err(HandleNotPresentError)
- }
- }
- /// Gets a read-write guard on the entire shard map if an entry for the given
- /// handle exists, but if not, yield [`HandleNotPresentError`].
- fn get_read_write_guard_if_entry_exists(
- &self,
- handle: Handle,
- ) -> Result<ShardReadWriteGuard<T>, HandleNotPresentError> {
- // Start with an upgradable read lock and then upgrade to a write lock.
- // By doing this, we prevent new readers from entering (see `spin` documentation)
- let map_upgradable_read_guard = ShardReadWriteLock::<T>::upgradable_read(&self.data);
- let read_only_map_ref = map_upgradable_read_guard.deref();
- if read_only_map_ref.contains_key(&handle) {
- // If we know that the entry exists, and we're currently
- // holding a read-lock, we know that we're safe to request
- // an upgrade to a write lock, since only one write or
- // upgradable read lock can be outstanding at any one time.
- let map_read_write_guard =
- ShardUpgradableReadGuard::<T>::upgrade(map_upgradable_read_guard);
- Ok(map_read_write_guard)
- } else {
- // Auto-drop the read guard, we don't need to allow a write.
- Err(HandleNotPresentError)
- }
- }
-
- fn get_mut(&self, handle: Handle) -> Result<ObjectReadWriteGuard<T>, HandleNotPresentError> {
- let map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?;
- // Expose only the pointed-to object with a mapped read-write guard
- let object_read_write_guard =
- ShardReadWriteGuard::<T>::map(map_read_write_guard, move |map_ref| {
- // Already checked that the entry exists while holding the lock
- map_ref.get_mut(&handle).unwrap()
- });
- Ok(ObjectReadWriteGuard { guard: object_read_write_guard })
- }
- fn deallocate(
- &self,
- handle: Handle,
- outstanding_allocations_counter: &AtomicU32,
- ) -> Result<T, HandleNotPresentError> {
- let mut map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?;
- // We don't need to worry about double-decrements, since the above call
- // got us an upgradable read guard for our read, which means it's the only
- // outstanding upgradeable guard on the shard. See `spin` documentation.
- // Remove the pointed-to object from the map, and return it,
- // releasing the lock when the guard goes out of scope.
- let removed_object = map_read_write_guard.deref_mut().remove(&handle).unwrap();
- // Decrement the allocations counter. Release ordering because we want
- // to ensure that clearing the map entry never gets re-ordered to after when
- // this counter gets decremented.
- outstanding_allocations_counter.sub(1, Ordering::Release);
- Ok(removed_object)
- }
-
- fn try_allocate<F>(
- &self,
- handle: Handle,
- object_provider: F,
- outstanding_allocations_counter: &AtomicU32,
- max_active_handles: u32,
- ) -> Result<(), ShardAllocationError<T, F>>
- where
- F: FnOnce() -> T,
- {
- // Use an upgradeable read guard -> write guard to provide greater fairness
- // toward writers (see `spin` documentation)
- let map_upgradable_read_guard = ShardReadWriteLock::<T>::upgradable_read(&self.data);
- let mut map_read_write_guard =
- ShardUpgradableReadGuard::<T>::upgrade(map_upgradable_read_guard);
- match map_read_write_guard.entry_ref(&handle) {
- EntryRef::Occupied(_) => {
- // We've already allocated for that handle-id, so yield
- // the object provider back to the caller.
- Err(ShardAllocationError::EntryOccupied(object_provider))
- }
- EntryRef::Vacant(vacant_entry) => {
- // An entry is open, but we haven't yet checked the allocations count.
- // Try to increment the total allocations count atomically.
- // Use acquire ordering on a successful bump, because we don't want
- // to invoke the allocation closure before we have a guaranteed slot.
- // On the other hand, upon failure, we don't care about ordering
- // of surrounding operations, and so we use a relaxed ordering there.
- let allocation_count_bump_result = outstanding_allocations_counter.fetch_update(
- Ordering::Acquire,
- Ordering::Relaxed,
- |old_total_allocations| {
- if old_total_allocations >= max_active_handles {
- None
- } else {
- Some(old_total_allocations + 1)
- }
- },
- );
- match allocation_count_bump_result {
- Ok(_) => {
- // We're good to actually allocate
- let object = object_provider();
- vacant_entry.insert(object);
- Ok(())
- }
- Err(_) => {
- // The allocation would cause us to exceed the allowed allocations,
- // so release all locks and error.
- Err(ShardAllocationError::ExceedsAllocationLimit)
- }
- }
- }
- }
- }
- /// Gets the actual number of elements stored in this shard.
- /// Only suitable for single-threaded sections of tests.
- #[cfg(test)]
- fn len(&self) -> usize {
- let guard = ShardReadWriteLock::<T>::read(&self.data);
- guard.deref().len()
- }
-}
-
-/// Externally-facing trait for things which behave like handle-map handles
-/// with a globally-defined handle-map for the type.
-pub trait HandleLike: Sized {
- /// The underlying object type pointed-to by this handle
- type Object: Send + Sync;
-
- /// Tries to allocate a new handle using the given provider
- /// to construct the underlying stored object as a new
- /// entry into the global handle table for this type.
- fn allocate(
- initial_value_provider: impl FnOnce() -> Self::Object,
- ) -> Result<Self, HandleMapFullError>;
-
- /// Gets a RAII read-guard on the contents behind this handle.
- fn get(&self) -> Result<ObjectReadGuard<Self::Object>, HandleNotPresentError>;
-
- /// Gets a RAII read-write guard on the contents behind this handle.
- fn get_mut(&self) -> Result<ObjectReadWriteGuard<Self::Object>, HandleNotPresentError>;
-
- /// Deallocates the contents behind this handle.
- fn deallocate(self) -> Result<Self::Object, HandleNotPresentError>;
-}
-
-#[macro_export]
-/// `declare_handle_map! { handle_module_name, handle_type_name, wrapped_type,
-/// map_dimension_provider }`
-///
-/// Declares a new public module with name `handle_module_name` which includes a new type
-/// `handle_type_name` which is `#[repr(C)]` and represents FFI-accessible handles
-/// to values of type `wrapped_type`.
-///
-/// Internal to the generated module, a new static `SingletonHandleMap` is created, where the
-/// maximum number of active handles and the number of shards are given by
-/// the dimensions returned by evaluation of the `map_dimension_provider` expression.
-///
-/// Note: `map_dimension_provider` will be evaluated within the defined module's scope,
-/// so you will likely need to use `super` to refer to definitions in the enclosing scope.
-///
-/// # Example
-/// The following code defines an FFI-safe type `StringHandle` which references
-/// the `String` data-type, and uses it to define a (contrived)
-/// function `sample` which will print "Hello World".
-///
-/// ```
-/// mod sample {
-/// use core::ops::Deref;
-/// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike};
-///
-/// fn get_string_handle_map_dimensions() -> HandleMapDimensions {
-/// HandleMapDimensions {
-/// num_shards: 8,
-/// max_active_handles: 100,
-/// }
-/// }
-///
-/// declare_handle_map! { string_handle, StringHandle, String,
-/// super::get_string_handle_map_dimensions() }
-///
-/// use string_handle::StringHandle;
-///
-/// fn sample() {
-/// // Note: this method could panic if there are
-/// // more than 99 outstanding handles.
-///
-/// // Allocate a new string-handle pointing to the string "Hello"
-/// let handle = StringHandle::allocate(|| { "Hello".to_string() }).unwrap();
-/// {
-/// // Obtain a write-guard on the contents of our handle
-/// let mut handle_write_guard = handle.get_mut().unwrap();
-/// handle_write_guard.push_str(" World");
-/// // Write guard is auto-dropped at the end of this block.
-/// }
-/// {
-/// // Obtain a read-guard on the contents of our handle.
-/// // Note that we had to ensure that the write-guard was
-/// // dropped prior to doing this, or else execution
-/// // could potentially hang.
-/// let handle_read_guard = handle.get().unwrap();
-/// println!("{}", handle_read_guard.deref());
-/// }
-/// // Clean up the data behind the created handle
-/// handle.deallocate().unwrap();
-/// }
-/// }
-/// ```
-macro_rules! declare_handle_map {
- ($handle_module_name:ident, $handle_type_name:ident, $wrapped_type:ty,
- $map_dimension_provider:expr) => {
- #[doc = concat!("Macro-generated (via `handle_map::declare_handle_map!`) module",
- " which defines the `", stringify!($handle_module_name), "::",
- stringify!($handle_type_name), "` FFI-transmissible handle type ",
- " which references values of type `", stringify!($wrapped_type), "`.")]
- pub mod $handle_module_name {
- use $crate::SingletonHandleMapInfo;
-
- struct SingletonInfo;
-
- static GLOBAL_HANDLE_MAP: $crate::SingletonHandleMap<SingletonInfo> =
- $crate::SingletonHandleMap::with_info(SingletonInfo);
-
- impl $crate::SingletonHandleMapInfo for SingletonInfo {
- type Object = $wrapped_type;
-
- fn dimensions(&self) -> $crate::HandleMapDimensions {
- $map_dimension_provider
- }
- fn get_handle_map() -> &'static $crate::HandleMap<$wrapped_type> {
- GLOBAL_HANDLE_MAP.get()
- }
- }
-
- #[doc = concat!("A `#[repr(C)]` handle to a value of type `",
- stringify!($wrapped_type), "`.")]
- #[repr(C)]
- #[derive(Clone, Copy, PartialEq, Eq)]
- pub struct $handle_type_name {
- handle_id: u64,
- }
-
- impl $handle_type_name {
- fn get_as_handle(&self) -> $crate::Handle {
- $crate::Handle::from_id(self.handle_id)
- }
- }
- impl $crate::HandleLike for $handle_type_name {
- type Object = $wrapped_type;
- fn allocate(initial_value_provider: impl FnOnce() -> $wrapped_type,
- ) -> Result<Self, $crate::HandleMapFullError> {
- SingletonInfo::get_handle_map().allocate(initial_value_provider)
- .map(|derived_handle| Self {
- handle_id: derived_handle.get_id(),
- })
- }
- fn get(&self) -> Result<$crate::ObjectReadGuard<$wrapped_type>, $crate::HandleNotPresentError> {
- SingletonInfo::get_handle_map().get(self.get_as_handle())
- }
- fn get_mut(&self) -> Result<$crate::ObjectReadWriteGuard<$wrapped_type>, $crate::HandleNotPresentError> {
- SingletonInfo::get_handle_map().get_mut(self.get_as_handle())
- }
- fn deallocate(self) -> Result<$wrapped_type, $crate::HandleNotPresentError> {
- SingletonInfo::get_handle_map().deallocate(self.get_as_handle())
- }
- }
- }
- }
-}
diff --git a/nearby/presence/ldt/Cargo.toml b/nearby/presence/ldt/Cargo.toml
index c4456cc..2ee7b4c 100644
--- a/nearby/presence/ldt/Cargo.toml
+++ b/nearby/presence/ldt/Cargo.toml
@@ -13,7 +13,7 @@
ldt_tbc.workspace = true
[dev-dependencies]
-crypto_provider_default = {workspace = true}
+crypto_provider_default = {workspace = true, features = ["rustcrypto"]}
rand_ext.workspace = true
test_helper.workspace = true
xts_aes.workspace = true
diff --git a/nearby/presence/ldt/fuzz/Cargo.lock b/nearby/presence/ldt/fuzz/Cargo.lock
index 767108f..419d9e9 100644
--- a/nearby/presence/ldt/fuzz/Cargo.lock
+++ b/nearby/presence/ldt/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -157,6 +171,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_rustcrypto"
@@ -164,6 +181,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -193,9 +211,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -261,9 +279,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -329,6 +347,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -583,6 +611,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -618,9 +652,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/presence/ldt/src/lib.rs b/nearby/presence/ldt/src/lib.rs
index f05c14b..9065e12 100644
--- a/nearby/presence/ldt/src/lib.rs
+++ b/nearby/presence/ldt/src/lib.rs
@@ -31,7 +31,6 @@
/// `B` is the block size.
/// `T` is the provided implementation of a Tweakable Block Cipher
/// `M` is the implementation of a [pure mix function](https://eprint.iacr.org/2017/841.pdf)
-#[repr(C)]
pub struct LdtEncryptCipher<const B: usize, T: TweakableBlockCipher<B>, M: Mix> {
cipher_1: T::EncryptionCipher,
cipher_2: T::EncryptionCipher,
@@ -124,7 +123,7 @@
// Encrypt or decrypt in place with a tweak
O: Fn(&C, T::Tweak, &mut [u8; B]),
// Mix a/b into block-sized chunks
- X: Fn(&[u8], &[u8]) -> ([u8; B], [u8; B]),
+ X: for<'a, 'b> Fn(&'a [u8], &'b [u8]) -> (&'b [u8], &'a [u8]),
P: Padder<B, T>,
{
if data.len() < B || data.len() >= B * 2 {
@@ -147,20 +146,23 @@
// |z| = B - s, |m3| = s
let (z, m3) = m1_ciphertext.split_at(B - s);
debug_assert_eq!(s, m3.len());
+
// c3 and c2 are the last s bytes of their size-B arrays, respectively
- let (mut c3, c2) = mix(m3, m2);
+ let (c3, c2) = mix(m3, m2);
+
let c1 = {
- // constructing z || c3 is easy since c3 is already the last s bytes
- c3[0..(B - s)].copy_from_slice(z);
- let mut z_c3 = c3;
- let tweak = padder.pad_tweak(&c2[B - s..]);
+ let mut z_c3 = [0; B];
+ z_c3[(B - s)..].copy_from_slice(c3);
+ z_c3[0..(B - s)].copy_from_slice(z);
+
+ let tweak = padder.pad_tweak(c2);
cipher_op(second_cipher, tweak, &mut z_c3);
z_c3
};
- let len = data.len();
- data.get_mut(0..B).ok_or(LdtError::InvalidLength(len))?.copy_from_slice(&c1);
- data.get_mut(B..).ok_or(LdtError::InvalidLength(len))?.copy_from_slice(&c2[B - s..]);
+ let (left, right) = data.split_at_mut(B);
+ left.copy_from_slice(&c1);
+ right.copy_from_slice(c2);
Ok(())
}
@@ -219,31 +221,23 @@
/// Mix `a` and `b`, writing into the last `s` bytes of the output arrays.
/// `a` and `b` must be the same length `s`, and no longer than the block size `B`.
/// Must be the inverse of [Mix::backwards].
- fn forwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]);
+ fn forwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]);
/// Mix `a` and `b`, writing into the last `s` bytes of the output arrays.
/// `a` and `b` must be the same length, and no longer than the block size `B`.
/// Must be the inverse of [Mix::forwards].
- fn backwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]);
+ fn backwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]);
}
/// Per section 2.4, swapping `a` and `b` is a valid mix function
pub struct Swap {}
impl Mix for Swap {
- fn forwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]) {
+ fn forwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]) {
debug_assert_eq!(a.len(), b.len());
- // implies b length as well
- debug_assert!(a.len() <= B);
- let mut out1 = [0; B];
- let mut out2 = [0; B];
-
- let start = B - a.len();
- out1[start..].copy_from_slice(b);
- out2[start..].copy_from_slice(a);
- (out1, out2)
+ (b, a)
}
- fn backwards<const B: usize>(a: &[u8], b: &[u8]) -> ([u8; B], [u8; B]) {
+ fn backwards<'a, 'b>(a: &'a [u8], b: &'b [u8]) -> (&'b [u8], &'a [u8]) {
// backwards is the same as forwards.
Self::forwards(a, b)
}
@@ -262,7 +256,7 @@
fn pad_tweak(&self, data: &[u8]) -> T::Tweak;
}
-/// The default padding algorithm per section 2 of LDT paper.
+/// The default padding algorithm per section 2 of [LDT paper](https://eprint.iacr.org/2017/841.pdf)
#[derive(Default)]
pub struct DefaultPadder;
diff --git a/nearby/presence/ldt_np_adv/Cargo.toml b/nearby/presence/ldt_np_adv/Cargo.toml
index 02592ce..522e97b 100644
--- a/nearby/presence/ldt_np_adv/Cargo.toml
+++ b/nearby/presence/ldt_np_adv/Cargo.toml
@@ -17,14 +17,14 @@
ldt_tbc.workspace = true
[dev-dependencies]
-crypto_provider_default.workspace = true
+crypto_provider_default = { workspace = true, features = ["rustcrypto", "std"] }
crypto_provider_openssl.workspace = true
rand_ext.workspace = true
test_helper.workspace = true
rand.workspace = true
base64.workspace = true
-serde_json = {workspace = true, features=["std"]}
+serde_json = { workspace = true, features = ["std"] }
hex.workspace = true
anyhow.workspace = true
criterion.workspace = true
diff --git a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
index 5050ad0..6b6ef4b 100644
--- a/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
+++ b/nearby/presence/ldt_np_adv/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -161,6 +175,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_rustcrypto"
@@ -168,6 +185,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -197,9 +215,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -265,9 +283,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -333,6 +351,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -610,6 +638,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -645,9 +679,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/presence/ldt_np_adv/src/lib.rs b/nearby/presence/ldt_np_adv/src/lib.rs
index 3785797..31aa50d 100644
--- a/nearby/presence/ldt_np_adv/src/lib.rs
+++ b/nearby/presence/ldt_np_adv/src/lib.rs
@@ -41,10 +41,13 @@
/// Max LDT-XTS-AES data size: `(2 * AES block size) - 1`
pub const LDT_XTS_AES_MAX_LEN: usize = 31;
-/// Legacy (v0) format uses 14-byte metadata key
+
+/// Legacy (v0) format uses a 14-byte metadata key
pub const NP_LEGACY_METADATA_KEY_LEN: usize = 14;
-/// The salt included in an NP advertisement
+/// The salt included in an NP advertisement.
+/// LDT does not use an IV but can instead incorporate the 2 byte, regularly rotated,
+/// salt from the advertisement payload and XOR it with the padded tweak data.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LegacySalt {
/// Salt bytes extracted from the incoming NP advertisement
@@ -104,6 +107,20 @@
/// Decrypts and validates a NP legacy format advertisement encrypted with LDT.
///
+/// A NP legacy advertisement will always be in the format of:
+///
+/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) | repeated
+/// { DE header | DE payload }
+///
+/// Example:
+/// Header (1 byte) | Identity DE header (1 byte) | Salt (2 bytes) | Identity (14 bytes) |
+/// Tx power DE header (1 byte) | Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes)
+///
+/// The ciphertext bytes will always start with the Identity through the end of the
+/// advertisement, for example in the above [ Identity (14 bytes) | Tx power DE header (1 byte) |
+/// Tx power (1 byte) | Action DE header(1 byte) | action (1-3 bytes) ] will be the ciphertext section
+/// passed as the input to `decrypt_and_verify`
+///
/// `B` is the underlying block cipher block size.
/// `O` is the max output size (must be 2 * B - 1).
/// `T` is the tweakable block cipher used by LDT.
@@ -130,6 +147,10 @@
///
/// If the plaintext's metadata key matches this item's MAC, return the plaintext, otherwise `None`.
///
+ /// NOTE: because LDT acts as a PRP over the entire message, tampering with any bit scrambles
+ /// the whole message, so we can leverage the MAC on just the metadata key to ensure integrity
+ /// for the whole message.
+ ///
/// # Errors
/// - If `payload` has a length outside of `[B, B * 2)`.
/// - If the decrypted plaintext fails its HMAC validation
diff --git a/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml b/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml
deleted file mode 100644
index 5a4a047..0000000
--- a/nearby/presence/ldt_np_adv_ffi/.cargo/config-boringssl.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-paths = [
- "../../../boringssl-build/boringssl/rust/bssl-crypto",
-]
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.lock b/nearby/presence/ldt_np_adv_ffi/Cargo.lock
index 1d13887..3f7e2a7 100644
--- a/nearby/presence/ldt_np_adv_ffi/Cargo.lock
+++ b/nearby/presence/ldt_np_adv_ffi/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -98,6 +112,13 @@
[[package]]
name = "bssl-crypto"
version = "0.1.0"
+dependencies = [
+ "bssl-sys",
+]
+
+[[package]]
+name = "bssl-sys"
+version = "0.1.0"
[[package]]
name = "bytes"
@@ -177,6 +198,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_boringssl"
@@ -184,7 +208,6 @@
dependencies = [
"bssl-crypto",
"crypto_provider",
- "crypto_provider_stubs",
]
[[package]]
@@ -204,6 +227,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -240,9 +264,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -299,9 +323,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -384,6 +408,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -841,6 +875,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -882,9 +922,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/presence/ldt_np_adv_ffi/Cargo.toml b/nearby/presence/ldt_np_adv_ffi/Cargo.toml
index d53809f..829f932 100644
--- a/nearby/presence/ldt_np_adv_ffi/Cargo.toml
+++ b/nearby/presence/ldt_np_adv_ffi/Cargo.toml
@@ -6,20 +6,20 @@
[dependencies]
crypto_provider = { path = "../../crypto/crypto_provider" }
-crypto_provider_openssl = {path = "../../crypto/crypto_provider_openssl", optional = true}
-crypto_provider_rustcrypto = {path = "../../crypto/crypto_provider_rustcrypto", optional = true}
-crypto_provider_boringssl = {path = "../../crypto/crypto_provider_boringssl", optional = true}
+crypto_provider_openssl = { path = "../../crypto/crypto_provider_openssl", optional = true }
+crypto_provider_rustcrypto = { path = "../../crypto/crypto_provider_rustcrypto", optional = true }
+crypto_provider_boringssl = { path = "../../crypto/crypto_provider_boringssl", optional = true }
ldt = { path = "../ldt" }
ldt_np_adv = { path = "../ldt_np_adv" }
-np_hkdf = {path = "../np_hkdf"}
+np_hkdf = { path = "../np_hkdf" }
cfg-if = "1.0.0"
-lazy_static = { version = "1.4.0"}
+lazy_static = { version = "1.4.0" }
# no_std only deps
-libc_alloc = {version = "1.0.4", optional = true }
+libc_alloc = { version = "1.0.4", optional = true }
panic-abort = { version = "0.3.2", optional = true }
-spin = {version = "0.9.8", optional = true }
+spin = { version = "0.9.8", optional = true }
[dev-dependencies]
rand = "0.8.5"
diff --git a/nearby/presence/ldt_np_adv_ffi/src/lib.rs b/nearby/presence/ldt_np_adv_ffi/src/lib.rs
index 2ed8d95..1534857 100644
--- a/nearby/presence/ldt_np_adv_ffi/src/lib.rs
+++ b/nearby/presence/ldt_np_adv_ffi/src/lib.rs
@@ -20,6 +20,8 @@
clippy::panic,
clippy::expect_used
)]
+#![allow(internal_features)]
+// TODO: Remove usage of `lang_items` when ffi is no longer alloc
// These features are needed to support no_std + alloc
#![feature(lang_items)]
@@ -128,7 +130,7 @@
get_enc_handle_map()
.remove(&handle.handle)
.ok_or(CloseCipherError::InvalidHandle)
- .map(|_| 0)
+ .map(|_| SUCCESS)
})
}
@@ -138,14 +140,11 @@
get_dec_handle_map()
.remove(&handle.handle)
.ok_or(CloseCipherError::InvalidHandle)
- .map(|_| 0)
+ .map(|_| SUCCESS)
})
}
#[no_mangle]
-// continue to use LdtAdvDecrypter::encrypt() for now, but we should expose a higher level API
-// and get rid of this.
-#[allow(deprecated)]
extern "C" fn NpLdtEncrypt(
handle: NpLdtEncryptHandle,
buffer: *mut u8,
diff --git a/nearby/presence/ldt_np_c_sample/CMakeLists.txt b/nearby/presence/ldt_np_c_sample/CMakeLists.txt
index 588078a..aefc0ba 100644
--- a/nearby/presence/ldt_np_c_sample/CMakeLists.txt
+++ b/nearby/presence/ldt_np_c_sample/CMakeLists.txt
@@ -31,7 +31,6 @@
ldt_c_sample
optimized "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}"
debug "${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/debug/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}"
- OpenSSL::SSL
)
if(UNIX)
diff --git a/nearby/presence/ldt_np_c_sample/main.c b/nearby/presence/ldt_np_c_sample/main.c
index 1ae9244..9348a70 100644
--- a/nearby/presence/ldt_np_c_sample/main.c
+++ b/nearby/presence/ldt_np_c_sample/main.c
@@ -14,12 +14,12 @@
* limitations under the License.
*/
+#include "np_ldt.h"
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "np_ldt.h"
-
static const uint8_t KEY_SEED_BYTES[] = {204, 219, 36, 137, 233, 252, 172, 66, 179, 147, 72, 184, 148, 30, 209, 154, 29, 54, 14, 117, 224, 152, 200, 193, 94, 107, 28, 194, 182, 32, 205, 57};
static const uint8_t KNOWN_HMAC_BYTES[] = {223, 185, 10, 31, 155, 31, 226, 141, 24, 187, 204, 165, 34, 64, 181, 204, 44, 203, 95, 141, 82, 137, 163, 203, 100, 235, 53, 65, 202, 97, 75, 180};
static const uint8_t TEST_DATA_BYTES[] = {205, 104, 63, 225, 161, 209, 248, 70, 84, 61, 10, 19, 212, 174, 164, 0, 64, 200, 214, 123};
diff --git a/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt b/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt
index 6edfd63..141563d 100644
--- a/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt
+++ b/nearby/presence/ldt_np_c_sample/tests/CMakeLists.txt
@@ -25,7 +25,6 @@
"${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}"
jsoncpp
GTest::gtest_main
- OpenSSL::SSL
)
if(UNIX)
target_link_libraries(
@@ -51,7 +50,6 @@
ldt_benchmarks
"${CMAKE_SOURCE_DIR}/ldt_np_adv_ffi/target/release/${CMAKE_STATIC_LIBRARY_PREFIX}ldt_np_adv_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}"
benchmark::benchmark
- OpenSSL::SSL
)
if(UNIX)
diff --git a/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc b/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc
index d0cfb67..2559954 100644
--- a/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc
+++ b/nearby/presence/ldt_np_c_sample/tests/ldt_benchmarks.cc
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <benchmark/benchmark.h>
+#include "np_ldt.h"
+
#include <ctime>
-#include "np_ldt.h"
+#include "benchmark/benchmark.h"
using std::vector;
using std::tuple;
diff --git a/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc b/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc
index e66af65..7cd1fb3 100644
--- a/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc
+++ b/nearby/presence/ldt_np_c_sample/tests/ldt_ffi_tests.cc
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <gtest/gtest.h>
-#include <json/json.h>
-
extern "C" {
#include "np_ldt.h"
}
@@ -27,6 +24,9 @@
#include <pthread.h>
#endif
+#include "gtest/gtest.h"
+#include "json/json.h"
+
#ifdef LDT_TEST_VECTORS
static const char *PATH_TO_DATA_FILE = LDT_TEST_VECTORS;
#else
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml b/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml
new file mode 100644
index 0000000..978460f
--- /dev/null
+++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2022 Google LLC
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.nearby.LdtNpJni">
+ <uses-sdk android:minSdkVersion="19" />
+</manifest>
\ No newline at end of file
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts b/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts
index 3cc0692..0f0991e 100644
--- a/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts
+++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/build.gradle.kts
@@ -15,24 +15,35 @@
*/
plugins {
- id("java")
- kotlin("jvm") version "1.8.0"
+ id("java")
+ kotlin("jvm") version "1.9.0"
+}
+
+kotlin {
+ jvmToolchain {
+ languageVersion.set(JavaLanguageVersion.of("17"))
+ }
}
group = "com.google.android.gms.nearby.presence.hazmat"
version = "1.0-SNAPSHOT"
repositories {
- mavenCentral()
+ mavenCentral()
+ google()
}
dependencies {
- testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
- testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
- implementation(kotlin("stdlib"))
+ testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
+ implementation(kotlin("stdlib"))
+ implementation("androidx.annotation:annotation:1.6.0")
}
tasks.getByName<Test>("test") {
- useJUnitPlatform()
- jvmArgs = mutableListOf("-Djava.library.path=../../../../target/debug")
+ useJUnitPlatform()
+ jvmArgs = mutableListOf("-Djava.library.path=../../../../target/debug")
+ testLogging {
+ events("passed", "skipped", "failed")
+ }
}
\ No newline at end of file
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java
deleted file mode 100644
index ecd0a85..0000000
--- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpCipher.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright 2022 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.gms.nearby.presence.hazmat;
-
-/**
- * LDT-XTS-AES128 implementation using the default "swap" mix function. It is suitable for
- * encryption or decryption of individual Nearby Presence BLE 4.2 legacy format encrypted payloads.
- *
- * <p>To avoid leaking resources, call close() once an instance is no longer needed.
- *
- * <p>This class is not thread safe.
- */
-public class LdtNpCipher implements AutoCloseable {
-
- /**
- * Size in bytes of key seed used to derive further keys used for in np ldt operations
- */
- private static final int KEY_SEED_SIZE = 32;
- /**
- * Size in bytes of the metadata keys calculated hmac tag
- */
- private static final int TAG_SIZE = 32;
- /** Block size of AES. */
- private static final int BLOCK_SIZE = 16;
-
- private final long ldtHandle;
- private boolean closed = false;
-
- private LdtNpCipher(long ldtHandle) {
- this.ldtHandle = ldtHandle;
- }
-
- /**
- * Create a new Ldt instance using LDT-XTS-AES128 with the "swap" mix function.
- *
- * @param key_seed 64-byte key material from the credential for the identity used to broadcast. The
- * supplied byte[] can be zeroed out once this method returns, as the contents are copied.
- * @return an instance configured with the supplied key
- * @throws LdtException if the key is the wrong size
- * @throws LdtException if the tag is the wrong size
- * @throws LdtException if creating the instance fails
- */
- public static LdtNpCipher fromKey(byte[] key_seed, byte[] metadata_key_tag) throws LdtException {
- if (key_seed.length != KEY_SEED_SIZE) {
- throw new LdtException("Key must be " + KEY_SEED_SIZE + " bytes");
- }
- if (metadata_key_tag.length != TAG_SIZE) {
- throw new LdtException("Tag must be " + TAG_SIZE + " bytes");
- }
-
- long handle = LdtNpJni.createLdtCipher(key_seed, metadata_key_tag);
- if (handle == 0) {
- throw new LdtException("Creating Ldt native resources failed");
- }
-
- return new LdtNpCipher(handle);
- }
-
- /**
- * Encode a 2 byte salt as a big-endian char.
- *
- * @return a char with b1 in the high bits and b2 in the low bits
- */
- public static char saltAsChar(byte b1, byte b2) {
- // byte widening conversion to int sign-extends
- int highBits = b1 << 8;
- int lowBits = b2 & 0xFF;
- // narrowing conversion truncates to low 16 bits
- return (char) (highBits | lowBits);
- }
-
- /**
- * Encrypt data in place, XORing bytes derived from the salt into the LDT tweaks.
- *
- * @param salt the salt that will be used in the advertisement with this encrypted payload. See
- * {@link LdtNpCipher#saltAsChar(byte, byte)} for constructing the char
- * representation.
- * @param data plaintext to encrypt in place: the metadata key followed by the data elements to be
- * encrypted. The length must be in [16, 31).
- * @throws IllegalStateException if this instance has already been closed
- * @throws IllegalArgumentException if data is the wrong length
- * @throws LdtException if encryption fails
- */
- public void encrypt(char salt, byte[] data) throws LdtException {
- checkPreconditions(data);
-
- int res = LdtNpJni.encrypt(ldtHandle, salt, data);
- if (res < 0) {
- // TODO is it possible for this to fail if the length is correct?
- throw new LdtException("Could not encrypt: error code " + res);
- }
- }
-
- /**
- * Decrypt the data in place, XORing the LDT tweak with the provided bytes.
- *
- * @param salt the salt extracted from the advertisement that contained this payload. See {@link
- * LdtNpCipher#saltAsChar(byte, byte)} for constructing the char representation.
- * @param data ciphertext to decrypt in place: the metadata key followed by the data elements to
- * be decrypted. The length must be in [16, 31).
- * @throws IllegalStateException if this instance has already been closed
- * @throws IllegalArgumentException if data is the wrong length
- * @throws LdtException if decryption fails
- */
- public void decrypt_and_verify(char salt, byte[] data) throws LdtException {
- checkPreconditions(data);
-
- int res = LdtNpJni.decrypt_and_verify(ldtHandle, salt, data);
- if (res < 0) {
- // TODO is it possible for this to fail if the length is correct?
- throw new LdtException("Could not decrypt: error code " + res);
- }
- }
-
- private void checkPreconditions(byte[] data) {
- if (closed) {
- throw new IllegalStateException("Instance has been closed");
- }
- if (data.length < BLOCK_SIZE || data.length >= BLOCK_SIZE * 2) {
- throw new IllegalArgumentException(
- "Data must be at least " + BLOCK_SIZE + " and less than " + BLOCK_SIZE * 2 + " bytes");
- }
- }
-
- /**
- * Releases native resources.
- *
- * <p>Once closed, an Ldt instance cannot be used further.
- */
- @Override
- public void close() {
- if (closed) {
- return;
- }
- closed = true;
-
- int res = LdtNpJni.closeLdtCipher(ldtHandle);
- if (res < 0) {
- throw new RuntimeException("Could not close Ldt: error code " + res);
- }
- }
-
- public static class LdtException extends Exception {
- LdtException(String message) {
- super(message);
- }
- }
-}
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java
index bbb0067..6af362a 100644
--- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java
+++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJni.java
@@ -15,48 +15,90 @@
*/
package com.google.android.gms.nearby.presence.hazmat;
-/** JNI for LDT-XTS-AES128 with the "swap" mix function. */
+import androidx.annotation.IntDef;
+
+
+/** JNI for a Nearby Presence LDT-XTS-AES128 cipher with the "swap" mix function. */
class LdtNpJni {
+ /** Error codes which map to return values on the native side. */
+ @IntDef({DecryptErrorCode.DATA_LEN_ERROR, DecryptErrorCode.JNI_OP_ERROR, DecryptErrorCode.MAC_MISMATCH} )
+ public @interface DecryptErrorCode {
+ int DATA_LEN_ERROR = -1;
+ int JNI_OP_ERROR = -2;
+ int MAC_MISMATCH = -3;
+ }
+
+ /** Error codes which map to return values on the native side. */
+ @IntDef({EncryptErrorCode.DATA_LEN_ERROR, EncryptErrorCode.JNI_OP_ERROR} )
+ public @interface EncryptErrorCode {
+ int DATA_LEN_ERROR = -1;
+ int JNI_OP_ERROR = -2;
+ }
+
static {
System.loadLibrary("ldt_np_jni");
}
/**
- * Create an instance of LDT-XTS-AES-128 using the Swap mix function.
+ * Create a new LDT-XTS-AES128 Encryption cipher using the "swap" mix function.
*
- * @param key key bytes, must be 4x AES key size = 64 bytes
- * @return 0 on error, and any other value for success
+ * @param keySeed is the 32-byte key material from the Nearby Presence credential from which
+ * the LDT key will be derived.
+ * @return 0 on error, or a non-zero handle on success.
*/
- static native long createLdtCipher(byte[] key_seed, byte[] metadata_key_hmac_tag);
+ static native long createEncryptionCipher(byte[] keySeed);
/**
- * Close the native resources for an Ldt instance.
+ * Create a new LDT-XTS-AES128 Decryption cipher using the "swap" mix function.
*
- * @param ldtHandle An ldt handle returned from {@link LdtNpJni#createLdtCipher}.
- * @return 0 on success, <0 for any error
+ * @param keySeed is the 32-byte key material from the Nearby Presence credential from which
+ * the LDT key will be derived.
+ * @param hmacTag is the hmac auth tag calculated on the metadata key used to verify
+ * decryption was successful.
+ * @return 0 on error, or a non-zero handle on success.
*/
- static native int closeLdtCipher(long ldtHandle);
+ static native long createDecryptionCipher(byte[] keySeed, byte[] hmacTag);
/**
- * Encrypt the data in place.
- *
- * @param ldtHandle An ldt handle returned from {@link LdtNpJni#createLdtCipher}.
- * @param salt big-endian salt to be expanded into bytes XORd into the LDT tweaks
- * @param data size must be between 16 and 31 bytes
- * @return 0 on success, -1 if the data size is wrong, or another negative number for any other
- * error
+ * Close the native resources for an LdtEncryptCipher instance.
+ * @param ldtEncryptHandle a ldt handle returned from {@link LdtNpJni#createEncryptionCipher}.
*/
- static native int encrypt(long ldtHandle, char salt, byte[] data);
+ static native void closeEncryptCipher(long ldtEncryptHandle);
/**
- * Decrypt the data in place using the default LDT tweak padding scheme.
+ * Close the native resources for an LdtDecryptCipher instance.
*
- * @param ldtHandle An ldt address returned from {@link LdtNpJni#createLdtCipher}.
- * @param salt big-endian salt to be expanded into bytes XORd into the LDT tweaks
- * @param data size must be between 16 and 31 bytes
- * @return 0 on success, -1 if the data size is wrong, -2 if the calculated hmac
- * does not match the provided tag or another negative number for any other error
+ * @param ldtDecryptHandle a ldt handle returned from {@link LdtNpJni#createDecryptionCipher}.
*/
- static native int decrypt_and_verify(long ldtHandle, char salt, byte[] data);
+ static native void closeDecryptCipher(long ldtDecryptHandle);
+
+ /**
+ * Encrypt a 16-31 byte buffer in-place.
+ *
+ * @param ldtEncryptHandle a ldt encryption handle returned from {@link LdtNpJni#createEncryptionCipher}.
+ * @param salt is the big-endian 2 byte salt that will be used in the Nearby
+ * Presence advertisement, which will be incorporated into the tweaks LDT uses
+ * while encrypting.
+ * @param data 16-31 bytes of plaintext data to be encrypted
+ * @return 0 on success, in which case `buffer` will now contain ciphertext or a non-zero
+ * an error code on failure
+ */
+ @EncryptErrorCode
+ static native int encrypt(long ldtEncryptHandle, char salt, byte[] data);
+
+ /**
+ * Decrypt a 16-31 byte buffer in-place and verify the plaintext metadata key matches
+ * this item's MAC, if not the buffer will not be decrypted.
+ *
+ * @param ldtDecryptHandle a ldt encryption handle returned from {@link LdtNpJni#createDecryptionCipher}.
+ * @param salt is the big-endian 2 byte salt that will be used in the Nearby
+ * Presence advertisement, which will be incorporated into the tweaks LDT uses
+ * while encrypting.
+ * @param data 16-31 bytes of ciphertext data to be decrypted
+ * @return 0 on success, in which case `buffer` will now contain ciphertext or a non-zero
+ * an error code on failure, in which case data will remain unchanged.
+ */
+ @DecryptErrorCode
+ static native int decryptAndVerify(long ldtDecryptHandle, char salt, byte[] data);
}
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt
new file mode 100644
index 0000000..8f080e8
--- /dev/null
+++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/main/java/com/google/android/gms/nearby/presence/hazmat/NpLdtCipher.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.gms.nearby.presence.hazmat
+
+import androidx.annotation.IntDef
+import com.google.android.gms.nearby.presence.hazmat.LdtNpJni.DecryptErrorCode
+import com.google.android.gms.nearby.presence.hazmat.LdtNpJni.EncryptErrorCode
+
+private const val KEY_SEED_SIZE = 32
+private const val TAG_SIZE = 32
+private const val BLOCK_SIZE = 16
+
+// Error return value for create operations
+private const val CREATE_HANDLE_ERROR = 0L
+
+// Status code returned on successful cipher operations
+private const val SUCCESS = 0
+
+/**
+ * A 2 byte salt that will be used in the advertisement with this encrypted payload.
+ */
+class Salt(b1: Byte, b2: Byte) {
+ private val saltBytes: Char
+
+ // Returns the bytes of the salt represented as a 2 byte Char type
+ internal fun getBytesAsChar(): Char {
+ return saltBytes
+ }
+
+ init {
+ // byte widening conversion to int sign-extends
+ val highBits = b1.toInt() shl 8
+ val lowBits = b2.toInt() and 0xFF
+ // narrowing conversion truncates to low 16 bits
+ saltBytes = (highBits or lowBits).toChar()
+ }
+}
+
+/** A runtime exception thrown if something fails during a Ldt jni call*/
+class LdtJniException internal constructor(message: String?) : RuntimeException(message)
+
+/**
+ * A checked exception which occurs if the calculated hmac tag does not match the expected hmac
+ * tag during decrypt operations
+ */
+class MacMismatchException internal constructor(message: String?) : Exception(message)
+
+/**
+ * Create a new LdtEncryptionCipher instance using LDT-XTS-AES128 with the "swap" mix function.
+ *
+ * @constructor Creates a new instance from the provided keySeed
+ * @param keySeed is the key material from the Nearby Presence credential from which
+ * the LDT key will be derived.
+ * @return an instance configured with the supplied key seed
+ * @throws IllegalArgumentException if the keySeed is the wrong size
+ * @throws LdtJniException if creating the instance fails
+ */
+class LdtEncryptionCipher @Throws(
+ LdtJniException::class,
+ IllegalArgumentException::class
+) constructor(keySeed: ByteArray) :
+ AutoCloseable {
+ @Volatile
+ private var closed = false
+ private val handle: Long
+
+ init {
+ require(keySeed.size == KEY_SEED_SIZE)
+ handle = LdtNpJni.createEncryptionCipher(keySeed)
+ if (handle == CREATE_HANDLE_ERROR) {
+ throw LdtJniException("Creating ldt encryption cipher native resources failed")
+ }
+ }
+
+ /**
+ * Encrypt a 16-31 byte buffer in-place.
+ *
+ * @param salt the salt that will be used in the advertisement with this encrypted payload.
+ * @param data plaintext to encrypt in place: the metadata key followed by the data elements to be
+ * encrypted. The length must be in [16, 31).
+ * @throws IllegalStateException if this instance has already been closed
+ * @throws IllegalArgumentException if data is the wrong length
+ * @throws LdtJniException if encryption fails
+ */
+ @Throws(LdtJniException::class, IllegalArgumentException::class, IllegalStateException::class)
+ fun encrypt(salt: Salt, data: ByteArray) {
+ check(!closed) { "Use after free! This instance has already been closed" }
+ requireValidSizeData(data.size)
+
+ when (val res = LdtNpJni.encrypt(handle, salt.getBytesAsChar(), data)) {
+ EncryptErrorCode.JNI_OP_ERROR -> throw LdtJniException("Error during jni encrypt operation: error code $res");
+ EncryptErrorCode.DATA_LEN_ERROR -> check(false) // this will never happen, lengths checked above
+ else -> check(res == SUCCESS)
+ }
+ }
+
+ /**
+ * Releases native resources.
+ *
+ * <p>Once closed, a Ldt instance cannot be used further.
+ */
+ override fun close() {
+ if (!closed) {
+ closed = true
+ LdtNpJni.closeEncryptCipher(handle)
+ }
+ }
+}
+
+/**
+ * A LdtDecryptionCipher instance which uses LDT-XTS-AES128 with the "swap" mix function.
+ *
+ * @constructor Creates a new instance from the provided keySeed and hmacTag
+ * @param keySeed is the key material from the Nearby Presence credential from which
+ * the LDT key will be derived.
+ * @param hmacTag is the hmac auth tag calculated on the metadata key used to verify
+ * decryption was successful.
+ * @return an instance configured with the supplied key seed
+ * @throws IllegalArgumentException if the keySeed is the wrong size
+ * @throws IllegalArgumentException if the hmacTag is the wrong size
+ * @throws LdtJniException if creating the instance fails
+ */
+class LdtDecryptionCipher @Throws(
+ LdtJniException::class,
+ IllegalArgumentException::class
+) constructor(keySeed: ByteArray, hmacTag: ByteArray) : AutoCloseable {
+ @Volatile
+ private var closed = false
+ private val handle: Long
+
+ init {
+ require(keySeed.size == KEY_SEED_SIZE)
+ require(hmacTag.size == TAG_SIZE)
+ handle = LdtNpJni.createDecryptionCipher(keySeed, hmacTag)
+ if (handle == CREATE_HANDLE_ERROR) {
+ throw LdtJniException("Creating ldt decryption cipher native resources failed")
+ }
+ }
+
+ /** Error codes which map to return values on the native side. */
+ @IntDef(DecryptAndVerifyResultCode.SUCCESS, DecryptAndVerifyResultCode.MAC_MISMATCH)
+ annotation class DecryptAndVerifyResultCode {
+ companion object {
+ const val SUCCESS = 0
+ const val MAC_MISMATCH = -1
+ }
+ }
+
+ /**
+ * Decrypt a 16-31 byte buffer in-place and verify the plaintext metadata key matches
+ * this item's MAC, if not the buffer will not be decrypted.
+ *
+ * @param salt the salt extracted from the advertisement that contained this payload.
+ * @param data ciphertext to decrypt in place: the metadata key followed by the data elements to
+ * be decrypted. The length must be in [16, 31).
+ * @return a [DecryptAndVerifyResultCode] indicating of the decrypt operation failed or succeeded.
+ * In the case of a failed decrypt, the provided plaintext will not change.
+ * @throws IllegalStateException if this instance has already been closed
+ * @throws IllegalArgumentException if data is the wrong length
+ * @throws LdtJniException if decryption fails
+ */
+ @Throws(
+ IllegalStateException::class,
+ IllegalArgumentException::class,
+ LdtJniException::class
+ )
+ @DecryptAndVerifyResultCode
+ fun decryptAndVerify(salt: Salt, data: ByteArray): Int {
+ check(!closed) { "Double free! Close should only ever be called once" }
+ requireValidSizeData(data.size)
+
+ when (val res = LdtNpJni.decryptAndVerify(handle, salt.getBytesAsChar(), data)) {
+ DecryptErrorCode.MAC_MISMATCH -> return DecryptAndVerifyResultCode.MAC_MISMATCH
+ DecryptErrorCode.DATA_LEN_ERROR -> check(false); // This condition is impossible, we validated data length above
+ DecryptErrorCode.JNI_OP_ERROR -> throw LdtJniException("Error occurred during jni encrypt operation")
+ else -> check(res == SUCCESS)
+ }
+ return DecryptAndVerifyResultCode.SUCCESS
+ }
+
+ /**
+ * Releases native resources.
+ *
+ * <p>Once closed, a Ldt instance cannot be used further.
+ */
+ override fun close() {
+ if (!closed) {
+ closed = true
+ LdtNpJni.closeEncryptCipher(handle)
+ }
+ }
+}
+
+private fun requireValidSizeData(size: Int) {
+ require(size >= BLOCK_SIZE && size < BLOCK_SIZE * 2) { "Invalid size data: $size" }
+}
+
diff --git a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt
index dc861c6..07d29a3 100644
--- a/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt
+++ b/nearby/presence/ldt_np_jni/java/LdtNpJni/src/test/java/com/google/android/gms/nearby/presence/hazmat/LdtNpJniTests.kt
@@ -16,27 +16,143 @@
package com.google.android.gms.nearby.presence.hazmat
-import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Assertions.assertArrayEquals
+import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+const val KEY_SEED = "CCDB2489E9FCAC42B39348B8941ED19A1D360E75E098C8C15E6B1CC2B620CD39"
+const val HMAC_TAG = "DFB90A1F9B1FE28D18BBCCA52240B5CC2CCB5F8D5289A3CB64EB3541CA614BB4"
+const val PLAINTEXT = "CD683FE1A1D1F846543D0A13D4AEA40040C8D67B"
+const val SALT_BYTES = "32EE"
+const val EXPECTED_CIPHER_TEXT = "04344411F1E57C841FE0F7150636BC782455059A"
class LdtNpJniTests {
@Test
- fun ldtRoundTripTest() {
+ fun roundTripTest() {
// Data taken from first test case in ldt_np_adv/resources/test/np_adv_test_vectors.json
- val key_seed = "CCDB2489E9FCAC42B39348B8941ED19A1D360E75E098C8C15E6B1CC2B620CD39".decodeHex()
- val hmac_tag = "DFB90A1F9B1FE28D18BBCCA52240B5CC2CCB5F8D5289A3CB64EB3541CA614BB4".decodeHex()
- val plaintext = "CD683FE1A1D1F846543D0A13D4AEA40040C8D67B".decodeHex()
- val salt_bytes = "32EE".decodeHex()
- val expected_ciphertext = "04344411F1E57C841FE0F7150636BC782455059A".decodeHex()
- val salt = LdtNpCipher.saltAsChar(salt_bytes[0], salt_bytes[1])
+ val keySeed = KEY_SEED.decodeHex()
+ val hmacTag = HMAC_TAG.decodeHex()
+ val plaintext = PLAINTEXT.decodeHex()
+ val saltBytes = SALT_BYTES.decodeHex()
+ val expectedCiphertext = EXPECTED_CIPHER_TEXT.decodeHex()
+ val salt = Salt(saltBytes[0], saltBytes[1])
val data = plaintext.copyOf()
- val LdtCipher = LdtNpCipher.fromKey(key_seed, hmac_tag)
- LdtCipher.encrypt(salt, data)
- Assertions.assertArrayEquals(expected_ciphertext, data)
+ val encryptionCipher = LdtEncryptionCipher(keySeed)
+ encryptionCipher.encrypt(salt, data)
+ assertArrayEquals(expectedCiphertext, data)
+ encryptionCipher.close()
- LdtCipher.decrypt_and_verify(salt, data)
- Assertions.assertArrayEquals(plaintext, data)
+
+ val decryptionCipher = LdtDecryptionCipher(keySeed, hmacTag)
+ val result = decryptionCipher.decryptAndVerify(salt, data)
+ assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.SUCCESS, result)
+ assertArrayEquals(plaintext, data)
+ decryptionCipher.close()
+ }
+
+ @Test
+ fun createEncryptionCipherInvalidLength() {
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(31)
+ LdtEncryptionCipher(keySeed)
+ }
+
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(33)
+ LdtEncryptionCipher(keySeed)
+ }
+ }
+
+ @Test
+ fun encryptInvalidLengthData() {
+ val keySeed = KEY_SEED.decodeHex()
+ val cipher = LdtEncryptionCipher(keySeed)
+ assertThrows<IllegalArgumentException> {
+ var data = ByteArray(15)
+ cipher.encrypt(Salt(0x0, 0x0), data)
+ }
+ assertThrows<IllegalArgumentException> {
+ var data = ByteArray(32)
+ cipher.encrypt(Salt(0x0, 0x0), data)
+ }
+ }
+
+ @Test
+ fun encryptUseAfterClose() {
+ val keySeed = KEY_SEED.decodeHex()
+ val cipher = LdtEncryptionCipher(keySeed)
+ val data = ByteArray(20)
+ cipher.close()
+ assertThrows<IllegalStateException> { cipher.encrypt(Salt(0x0, 0x0), data) }
+ }
+
+ @Test
+ fun createDecryptionCipherInvalidLengths() {
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(31)
+ val hmacTag = ByteArray(31)
+ LdtDecryptionCipher(keySeed, hmacTag)
+ }
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(33)
+ val hmacTag = ByteArray(33)
+ LdtDecryptionCipher(keySeed, hmacTag)
+ }
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(32)
+ val hmacTag = ByteArray(33)
+ LdtDecryptionCipher(keySeed, hmacTag)
+ }
+ assertThrows<IllegalArgumentException> {
+ val keySeed = ByteArray(33)
+ val hmacTag = ByteArray(32)
+ LdtDecryptionCipher(keySeed, hmacTag)
+ }
+ }
+
+ @Test
+ fun decryptInvalidLengthData() {
+ val keySeed = KEY_SEED.decodeHex()
+ val hmacTag = HMAC_TAG.decodeHex()
+ val cipher = LdtDecryptionCipher(keySeed, hmacTag)
+ assertThrows<IllegalArgumentException> {
+ var data = ByteArray(15)
+ cipher.decryptAndVerify(Salt(0x0, 0x0), data)
+ }
+ assertThrows<IllegalArgumentException> {
+ var data = ByteArray(32)
+ cipher.decryptAndVerify(Salt(0x0, 0x0), data)
+ }
+ }
+
+ @Test
+ fun decryptMacMismatch() {
+ val keySeed = KEY_SEED.decodeHex()
+ val hmacTag = HMAC_TAG.decodeHex()
+
+ // alter first byte in the hmac tag
+ hmacTag[0] = 0x00
+ val cipher = LdtDecryptionCipher(keySeed, hmacTag)
+
+ val cipherText = EXPECTED_CIPHER_TEXT.decodeHex()
+ val saltBytes = SALT_BYTES.decodeHex()
+ val salt = Salt(saltBytes[0], saltBytes[1])
+
+ val result = cipher.decryptAndVerify(salt, cipherText);
+ assertEquals(LdtDecryptionCipher.DecryptAndVerifyResultCode.MAC_MISMATCH, result)
+ }
+
+ @Test
+ fun decryptUseAfterClose() {
+ val keySeed = KEY_SEED.decodeHex()
+ val hmacTag = HMAC_TAG.decodeHex()
+ val cipher = LdtDecryptionCipher(keySeed, hmacTag)
+ cipher.close()
+
+ val data = ByteArray(20)
+ assertThrows<IllegalStateException> { cipher.decryptAndVerify(Salt(0x0, 0x0), data) }
}
}
diff --git a/nearby/presence/ldt_np_jni/src/lib.rs b/nearby/presence/ldt_np_jni/src/lib.rs
index 8f7958e..538a44e 100644
--- a/nearby/presence/ldt_np_jni/src/lib.rs
+++ b/nearby/presence/ldt_np_jni/src/lib.rs
@@ -20,6 +20,8 @@
//! - <https://www.iitk.ac.in/esc101/05Aug/tutorial/native1.1/index.html>
//! - <https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html>
+// We are not actually no_std because the jni crate is pulling it in, but at least this enforces
+// that this lib isn't using anything from the std lib
#![no_std]
#![deny(missing_docs)]
@@ -28,10 +30,9 @@
use alloc::boxed::Box;
-use jni::objects::JByteArray;
use jni::{
- objects::JClass,
- sys::{jbyte, jbyteArray, jchar, jint, jlong},
+ objects::{JByteArray, JClass},
+ sys::{jbyte, jchar, jint, jlong},
JNIEnv,
};
@@ -45,149 +46,199 @@
const MIN_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE;
const MAX_DATA_LEN: usize = crypto_provider::aes::BLOCK_SIZE * 2 - 1;
-/// Error return value for creating handles
+/// Required size constraints of input parameters
+const KEY_SEED_SIZE: usize = 32;
+const TAG_SIZE: usize = 32;
+
+/// Error return value for create operations
const CREATE_ERROR: jlong = 0;
-// TODO: don't allow panics to cross FFI boundary
-// TODO: JNI null checks? (only if jni crate isn't doing them already).
+/// Status code returned on successful cipher operations
+const SUCCESS: jint = 0;
-// TODO: split this into separate APIs for encrypt and decrypt
-struct Ldt {
- ldt_enc: LdtEncrypterXtsAes128<CryptoProviderImpl>,
- ldt_dec: LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>,
-}
+type LdtAdvDecrypter = LdtNpAdvDecrypterXtsAes128<CryptoProviderImpl>;
+type LdtAdvEncrypter = LdtEncrypterXtsAes128<CryptoProviderImpl>;
-/// Create an LDT cipher.
+/// Marker trait to ensure above types are thread safe
+trait JniThreadSafe: Send + Sync {}
+
+impl JniThreadSafe for LdtAdvDecrypter {}
+
+impl JniThreadSafe for LdtAdvEncrypter {}
+
+/// Create a LDT Encryption cipher.
///
-/// Returns 0 for error, or the pointer as a jlong/i64.
-/// Safety: We know the key pointer is safe as it is coming directly from the JVM.
+/// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success.
#[no_mangle]
-#[allow(clippy::not_unsafe_ptr_arg_deref)]
-pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createLdtCipher(
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createEncryptionCipher(
env: JNIEnv,
_class: JClass,
- key_seed: jbyteArray,
- metadata_key_hmac_tag: jbyteArray,
+ java_key_seed: JByteArray,
) -> jlong {
- env.get_array_length(unsafe { &JByteArray::from_raw(key_seed) })
- .map_err(|_| CREATE_ERROR)
- // check length
- .and_then(|len| if len as usize != 32 { Err(CREATE_ERROR) } else { Ok(len) })
- // extract u8 array
- .and_then(|len| {
- let mut jbyte_buf = [jbyte::default(); 32];
- env.get_byte_array_region(
- unsafe { &JByteArray::from_raw(key_seed) },
- 0,
- &mut jbyte_buf[..],
- )
- .map_err(|_| CREATE_ERROR)
- .map(|_| (len, jbyte_array_to_u8_array(jbyte_buf)))
- })
- // initialize ldt -- we already know the key is the right length
- .and_then(|(_len, key_seed_buf)| {
- let hkdf_key_seed = NpKeySeedHkdf::new(&key_seed_buf);
- let ldt_enc = ldt_np_adv::LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(
- &hkdf_key_seed.legacy_ldt_key(),
- );
+ create_map_to_error(|| {
+ let key_seed =
+ env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| {
+ if seed.len() != KEY_SEED_SIZE {
+ Err(CREATE_ERROR)
+ } else {
+ Ok(seed)
+ }
+ })?;
- let mut tag_buff = [jbyte::default(); 32];
- let tag = env
- .get_byte_array_region(
- unsafe { &JByteArray::from_raw(metadata_key_hmac_tag) },
- 0,
- &mut tag_buff[..],
- )
- .map_err(|_| CREATE_ERROR)
- .map(|_| jbyte_array_to_u8_array(tag_buff))
- .unwrap();
- // TODO: Error handling
+ let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
+ key_seed.as_slice().try_into().expect("Length is checked above"),
+ );
- let ldt_dec = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>(
- &hkdf_key_seed,
- tag,
- );
- box_to_handle(Ldt { ldt_enc, ldt_dec }).map_err(|_| CREATE_ERROR)
- })
- .unwrap_or_else(|e| e)
+ let cipher = LdtAdvEncrypter::new(&hkdf_key_seed.legacy_ldt_key());
+ box_to_handle(cipher).map_err(|_| CREATE_ERROR)
+ })
}
-/// Close an LDT cipher.
+/// Create a LDT Decryption cipher.
+///
+/// Returns 0 on failure, or the non-zero handle as a jlong/i64 on success.
#[no_mangle]
-pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeLdtCipher(
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_createDecryptionCipher(
+ env: JNIEnv,
+ _class: JClass,
+ java_key_seed: JByteArray,
+ java_hmac_tag: JByteArray,
+) -> jlong {
+ create_map_to_error(|| {
+ let key_seed =
+ env.convert_byte_array(&java_key_seed).map_err(|_| CREATE_ERROR).and_then(|seed| {
+ if seed.len() != KEY_SEED_SIZE {
+ Err(CREATE_ERROR)
+ } else {
+ Ok(seed)
+ }
+ })?;
+ let hmac_tag =
+ env.convert_byte_array(&java_hmac_tag).map_err(|_| CREATE_ERROR).and_then(|tag| {
+ if tag.len() != TAG_SIZE {
+ Err(CREATE_ERROR)
+ } else {
+ Ok(tag)
+ }
+ })?;
+ let hkdf_key_seed = NpKeySeedHkdf::<CryptoProviderImpl>::new(
+ key_seed.as_slice().try_into().expect("Length is checked above"),
+ );
+
+ let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed::<CryptoProviderImpl>(
+ &hkdf_key_seed,
+ hmac_tag.as_slice().try_into().expect("Length is checked above"),
+ );
+ box_to_handle(cipher).map_err(|_| CREATE_ERROR)
+ })
+}
+
+fn create_map_to_error<F: Fn() -> Result<jlong, jlong>>(f: F) -> jlong {
+ f().unwrap_or_else(|e| e)
+}
+
+/// Close an LDT Encryption Cipher
+#[no_mangle]
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeEncryptCipher(
_env: JNIEnv,
_class: JClass,
- ldt_handle: jlong,
-) -> jint {
+ handle: jlong,
+) {
// create the box, let it be dropped
- let _ = boxed_from_handle::<Ldt>(ldt_handle);
- // success -- are there any meaningful error condtions we can even detect?
- 0
+ let _ = boxed_from_handle::<LdtAdvEncrypter>(handle);
+}
+
+/// Close an LDT Decryption Cipher
+#[no_mangle]
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_closeDecryptCipher(
+ _env: JNIEnv,
+ _class: JClass,
+ handle: jlong,
+) {
+ // create the box, let it be dropped
+ let _ = boxed_from_handle::<LdtAdvDecrypter>(handle);
}
/// Encrypt a buffer in place.
-/// Safety: We know the data jArray pointer is safe because it is coming directly from the JVM.
#[no_mangle]
-#[allow(clippy::not_unsafe_ptr_arg_deref)]
-pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt(
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_encrypt(
env: JNIEnv,
_class: JClass,
- ldt_handle: jlong,
+ handle: jlong,
salt: jchar,
- data: jbyteArray,
+ data: JByteArray,
) -> jint {
- jbyte_cipher_data_as_u8_array(&env, data)
- .and_then(|(len, mut data_u8)| {
- with_handle::<Ldt, _, _>(ldt_handle, |ldt| {
- ldt.ldt_enc.encrypt(&mut data_u8[..len], &expand_np_salt_to_padder(salt)).map_err(
- |err| match err {
- ldt::LdtError::InvalidLength(_) => CipherOpError::DataLen,
- },
- )?;
- env.set_byte_array_region(
- unsafe { &JByteArray::from_raw(data) },
- 0,
- &u8_slice_to_jbyte_array(data_u8)[..len],
- )
- .map_err(|_| CipherOpError::JniOp)
- .map(|_| 0) // success
- })
+ map_to_error_code(|| {
+ let mut buffer =
+ env.convert_byte_array(&data).map_err(|_| EncryptError::JniOp).and_then(|data| {
+ if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) {
+ Err(EncryptError::DataLen)
+ } else {
+ Ok(data)
+ }
+ })?;
+
+ with_handle::<LdtAdvEncrypter, _, _>(handle, |cipher| {
+ cipher.encrypt(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt)).map_err(
+ |err| match err {
+ ldt::LdtError::InvalidLength(_) => EncryptError::DataLen,
+ },
+ )?;
+
+ // Avoid a copy since transmuting from a &[u8] to a &[i8] is safe
+ // Safety:
+ // - u8 and jbyte/i8 are the same size have the same alignment
+ let jbyte_buffer = bytes_to_jbytes(buffer.as_slice());
+
+ env.set_byte_array_region(&data, 0, jbyte_buffer)
+ .map_err(|_| EncryptError::JniOp)
+ .map(|_| SUCCESS)
})
- .unwrap_or_else(|e| e.to_jni_error_code())
+ })
}
/// Decrypt a buffer in place.
/// Safety: We know the data pointer is safe because it is coming directly from the JVM.
#[no_mangle]
-#[allow(clippy::not_unsafe_ptr_arg_deref)]
-pub extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decrypt_1and_1verify(
+extern "system" fn Java_com_google_android_gms_nearby_presence_hazmat_LdtNpJni_decryptAndVerify(
env: JNIEnv,
_class: JClass,
- ldt_handle: jlong,
+ handle: jlong,
salt: jchar,
- data: jbyteArray,
+ data: JByteArray,
) -> jint {
- jbyte_cipher_data_as_u8_array(&env, data)
- .and_then(|(len, mut data_u8)| {
- with_handle::<Ldt, _, _>(ldt_handle, |ldt| {
- let result = ldt
- .ldt_dec
- .decrypt_and_verify(&data_u8[..len], &expand_np_salt_to_padder(salt))
- .map_err(|err| match err {
- LdtAdvDecryptError::InvalidLength(_) => CipherOpError::DataLen,
- LdtAdvDecryptError::MacMismatch => CipherOpError::MacMisMatch,
- })?;
- data_u8[..result.len()].copy_from_slice(result.as_slice());
- env.set_byte_array_region(
- unsafe { &JByteArray::from_raw(data) },
- 0,
- &u8_slice_to_jbyte_array(data_u8)[..len],
- )
- .map_err(|_| CipherOpError::JniOp)
- .map(|_| 0) // success
- })
+ map_to_error_code(|| {
+ let mut buffer =
+ env.convert_byte_array(&data).map_err(|_| DecryptError::JniOp).and_then(|data| {
+ if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data.len()) {
+ Err(DecryptError::DataLen)
+ } else {
+ Ok(data)
+ }
+ })?;
+
+ with_handle::<LdtAdvDecrypter, _, _>(handle, |cipher| {
+ let result = cipher
+ .decrypt_and_verify(buffer.as_mut_slice(), &expand_np_salt_to_padder(salt))
+ .map_err(|err| match err {
+ LdtAdvDecryptError::InvalidLength(_) => DecryptError::DataLen,
+ LdtAdvDecryptError::MacMismatch => DecryptError::MacMisMatch,
+ })?;
+
+ let jbyte_buffer = bytes_to_jbytes(result.as_slice());
+
+ env.set_byte_array_region(&data, 0, jbyte_buffer)
+ .map_err(|_| DecryptError::JniOp)
+ .map(|_| SUCCESS)
})
- .unwrap_or_else(|e| e.to_jni_error_code())
+ })
+}
+
+/// A zero-copy conversion from a u8 slice to a jbyte slice
+fn bytes_to_jbytes(bytes: &[u8]) -> &[jbyte] {
+ // Safety:
+ // - u8 and jbyte/i8 are the same size have the same alignment
+ unsafe { alloc::slice::from_raw_parts(bytes.as_ptr() as *const jbyte, bytes.len()) }
}
/// Reconstruct a `Box<T>` from `handle`, and invoke `f` with the resulting `&T`.
@@ -202,7 +253,6 @@
// don't consume the box -- need to keep the handle alive
Box::leak(boxed);
-
ret
}
@@ -243,52 +293,6 @@
.map(|ptr_64: u64| ptr_64 as jlong)
}
-/// Extract data suitable for Ldt128 cipher ops from a JNI jbyteArray.
-///
-/// Returns `(data len in buffer, buffer)`, or `Err` if any JNI ops fail.
-fn jbyte_cipher_data_as_u8_array(
- env: &JNIEnv,
- cipher_data: jbyteArray,
-) -> Result<(usize, [u8; MAX_DATA_LEN]), CipherOpError> {
- let data_len = env
- .get_array_length(unsafe { &JByteArray::from_raw(cipher_data) })
- .map_err(|_| CipherOpError::JniOp)? as usize;
- if !(MIN_DATA_LEN..=MAX_DATA_LEN).contains(&data_len) {
- return Err(CipherOpError::DataLen);
- }
-
- let mut buf = [jbyte::default(); MAX_DATA_LEN];
- env.get_byte_array_region(
- unsafe { &JByteArray::from_raw(cipher_data) },
- 0,
- &mut buf[0..data_len],
- )
- .map_err(|_| CipherOpError::JniOp)?;
-
- Ok((data_len, jbyte_array_to_u8_array(buf)))
-}
-
-/// Convert a jbyte array to a u8 array
-fn jbyte_array_to_u8_array<const N: usize>(src: [jbyte; N]) -> [u8; N] {
- let mut dest = [0_u8; N];
- for i in 0..N {
- // numeric cast doesn't alter bits, which is what we want
- // https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics
- dest[i] = src[i] as u8;
- }
- dest
-}
-
-fn u8_slice_to_jbyte_array<const N: usize>(src: [u8; N]) -> [jbyte; N] {
- let mut dest = [0_i8; N];
- for i in 0..N {
- // numeric cast doesn't alter bits, which is what we want
- // https://doc.rust-lang.org/reference/expressions/operator-expr.html#semantics
- dest[i] = src[i] as jbyte;
- }
- dest
-}
-
/// Expand the NP salt to the size needed to be an LDT XorPadder.
///
/// Returns a XorPadder containing the HKDF of the salt.
@@ -297,23 +301,48 @@
ldt_np_adv::salt_padder::<16, CryptoProviderImpl>(salt_bytes.into())
}
+fn map_to_error_code<E: JniError, F: Fn() -> Result<jint, E>>(f: F) -> jint {
+ f().unwrap_or_else(|e| e.to_jni_error_code())
+}
+
+trait JniError {
+ fn to_jni_error_code(&self) -> jint;
+}
+
#[derive(Debug)]
-enum CipherOpError {
- /// The mac did not match the provided tag
- MacMisMatch,
+enum EncryptError {
/// Data is the wrong length
DataLen,
/// JNI op failed
JniOp,
}
-impl CipherOpError {
+impl JniError for EncryptError {
+ fn to_jni_error_code(&self) -> jint {
+ match self {
+ Self::DataLen => -1,
+ Self::JniOp => -2,
+ }
+ }
+}
+
+#[derive(Debug)]
+enum DecryptError {
+ /// Data is the wrong length
+ DataLen,
+ /// The mac did not match the provided tag
+ MacMisMatch,
+ /// JNI op failed
+ JniOp,
+}
+
+impl JniError for DecryptError {
/// Returns an error code suitable for returning from Ldt encrypt/decrypt JNI calls.
fn to_jni_error_code(&self) -> jint {
match self {
- CipherOpError::DataLen => -1,
- CipherOpError::MacMisMatch => -2,
- CipherOpError::JniOp => -3,
+ Self::DataLen => -1,
+ Self::JniOp => -2,
+ Self::MacMisMatch => -3,
}
}
}
diff --git a/nearby/presence/np_adv/Cargo.toml b/nearby/presence/np_adv/Cargo.toml
index 4c8aff5..5123737 100644
--- a/nearby/presence/np_adv/Cargo.toml
+++ b/nearby/presence/np_adv/Cargo.toml
@@ -14,29 +14,43 @@
crypto_provider.workspace = true
strum.workspace = true
strum_macros.workspace = true
-nom = { version = "7.1.1", features = ["alloc"] }
+nom = { version = "7.1.3", default-features = false, features = ["alloc"] }
lazy_static.workspace = true
sink.workspace = true
tinyvec.workspace = true
-rand.workspace = true
[features]
default = []
devtools = []
testing = []
+alloc = ["crypto_provider/alloc"]
[dev-dependencies]
hex.workspace = true
+rand.workspace = true
rand_ext = { path = "../rand_ext" }
init_with = "1.1.0"
-serde_json.workspace = true
+serde_json = {workspace = true, features = ["std"]}
+serde.workspace = true
anyhow.workspace = true
test_helper = { path = "../test_helper" }
criterion.workspace = true
-crypto_provider_default = {workspace = true, features = ["std", "rustcrypto"]}
-np_ed25519 = {workspace = true, features = ["std"]}
-sink = {workspace = true, features = ["std"]}
+crypto_provider_default = { workspace = true, features = ["std", "rustcrypto"] }
+np_ed25519 = { workspace = true, features = ["std"] }
+rmp-serde = "1.1.2"
+sink = { workspace = true, features = ["std"] }
[[bench]]
name = "deser_adv"
harness = false
+required-features = ["alloc", "devtools"]
+
+[[test]]
+name = "examples_v0"
+path = "tests/examples_v0.rs"
+required-features = ["alloc"]
+
+[[test]]
+name = "examples_v1"
+path = "tests/examples_v1.rs"
+required-features = ["alloc"]
diff --git a/nearby/presence/np_adv/benches/deser_adv.rs b/nearby/presence/np_adv/benches/deser_adv.rs
index 9699ebf..e998047 100644
--- a/nearby/presence/np_adv/benches/deser_adv.rs
+++ b/nearby/presence/np_adv/benches/deser_adv.rs
@@ -16,26 +16,29 @@
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use crypto_provider::{CryptoProvider, CryptoRng};
use crypto_provider_default::CryptoProviderImpl;
-use ldt_np_adv::{LdtEncrypterXtsAes128, LegacySalt};
+use ldt_np_adv::LegacySalt;
+use np_adv::deserialization_arena;
+use np_adv::extended::serialize::AdvertisementType;
use np_adv::{
- credential::{simple::*, source::*, v0::*, v1::*},
+ credential::{book::*, v0::*, v1::*, *},
de_type::EncryptedIdentityDataElementType,
- deserialize_advertisement, deserialize_v0_advertisement, deserialize_v1_advertisement,
+ deserialize_advertisement,
extended::{
data_elements::{GenericDataElement, TxPowerDataElement},
deserialize::VerificationMode,
serialize::{
- AdvBuilder as ExtendedAdvBuilder, MicEncrypted, SectionBuilder, SectionIdentity,
- SignedEncrypted,
+ AdvBuilder as ExtendedAdvBuilder, MicEncryptedSectionEncoder, PublicSectionEncoder,
+ SectionBuilder, SectionEncoder, SignedEncryptedSectionEncoder,
},
},
legacy::{
actions::{ActionBits, ActionsDataElement},
serialize::{AdvBuilder as LegacyAdvBuilder, LdtIdentity},
+ ShortMetadataKey,
},
shared_data::{ContextSyncSeqNum, TxPower},
- PublicIdentity,
+ MetadataKey, PublicIdentity,
};
use rand::{Rng as _, SeedableRng as _};
use strum::IntoEnumIterator;
@@ -58,21 +61,22 @@
.map(|_| V1Identity::random(&mut crypto_rng))
.collect::<Vec<_>>();
- let mut adv_builder = ExtendedAdvBuilder::new();
+ let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Encrypted);
// take the first n identities, one section per identity
for identity in identities.iter().take(num_sections) {
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(
- &identity.key_seed,
+ let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new(
+ identity.key_seed,
+ identity.extended_metadata_key,
+ identity.key_pair.private_key(),
);
match identity_type {
VerificationMode::Mic => {
let mut sb = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Private,
- &identity.extended_metadata_key,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -81,12 +85,10 @@
}
VerificationMode::Signature => {
let mut sb = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Private,
- &identity.extended_metadata_key,
- &identity.key_pair,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -98,22 +100,11 @@
let adv = adv_builder.into_advertisement();
- match crypto_type {
- CryptoMaterialType::MinFootprint => run_with_v1_creds::<
- MinimumFootprintV1CryptoMaterial,
- CryptoProviderImpl,
- >(
- b, identities, adv.as_slice()
- ),
- CryptoMaterialType::Precalculated => {
- run_with_v1_creds::<
- PrecalculatedV1CryptoMaterial,
- CryptoProviderImpl,
- >(
- b, identities, adv.as_slice()
- )
- }
- }
+ run_with_v1_creds::<
+ CryptoProviderImpl
+ >(
+ b, crypto_type, identities, adv.as_slice()
+ )
},
);
}
@@ -123,31 +114,23 @@
}
pub fn deser_adv_v1_plaintext(c: &mut Criterion) {
- let _rng = rand::rngs::StdRng::from_entropy();
+ c.bench_function("Deser V1 plaintext: sections=1", |b| {
+ let mut adv_builder = ExtendedAdvBuilder::new(AdvertisementType::Plaintext);
- for &num_sections in &[1, 2, 5, 8] {
- // measure worst-case performance -- the correct identities will be the last
- // num_sections of the identities to be tried
- c.bench_function(&format!("Deser V1 plaintext: sections={num_sections}"), |b| {
- let mut adv_builder = ExtendedAdvBuilder::new();
+ let mut sb = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
- // take the first n identities, one section per identity
- for _ in 0..num_sections {
- let mut sb = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ add_des(&mut sb);
+ sb.add_to_advertisement();
- add_des(&mut sb);
- sb.add_to_advertisement();
- }
+ let adv = adv_builder.into_advertisement();
- let adv = adv_builder.into_advertisement();
-
- run_with_v1_creds::<MinimumFootprintV1CryptoMaterial, CryptoProviderImpl>(
- b,
- vec![],
- adv.as_slice(),
- )
- });
- }
+ run_with_v1_creds::<CryptoProviderImpl>(
+ b,
+ CryptoMaterialType::MinFootprint,
+ vec![],
+ adv.as_slice(),
+ )
+ });
}
pub fn deser_adv_v0_encrypted(c: &mut Criterion) {
@@ -164,17 +147,17 @@
.collect::<Vec<_>>();
let identity = &identities[0];
- let hkdf =
- np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&identity.key_seed);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(
+ identity.key_seed,
+ identity.legacy_metadata_key,
+ );
let mut adv_builder =
LegacyAdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
EncryptedIdentityDataElementType::Private,
LegacySalt::from(rng.gen::<[u8; 2]>()),
- identity.legacy_metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(
- &hkdf.legacy_ldt_key(),
- ),
+ &broadcast_cm,
));
let mut action_bits = ActionBits::default();
@@ -183,46 +166,31 @@
let adv = adv_builder.into_advertisement().unwrap();
- match crypto_type {
- CryptoMaterialType::MinFootprint => run_with_v0_creds::<
- MinimumFootprintV0CryptoMaterial,
- CryptoProviderImpl,
- >(
- b, identities, adv.as_slice()
- ),
- CryptoMaterialType::Precalculated => run_with_v0_creds::<
- PrecalculatedV0CryptoMaterial,
- CryptoProviderImpl,
- >(
- b, identities, adv.as_slice()
- ),
- }
+ run_with_v0_creds::<CryptoProviderImpl>(
+ b,
+ crypto_type,
+ identities,
+ adv.as_slice(),
+ )
},
);
}
}
}
-type DefaultV0Credential = np_adv::credential::simple::SimpleV0Credential<
- np_adv::credential::v0::MinimumFootprintV0CryptoMaterial,
- (),
->;
-type DefaultV1Credential = np_adv::credential::simple::SimpleV1Credential<
- np_adv::credential::v1::MinimumFootprintV1CryptoMaterial,
- (),
->;
-type DefaultBothCredentialSource =
- np_adv::credential::source::OwnedBothCredentialSource<DefaultV0Credential, DefaultV1Credential>;
-
pub fn deser_adv_v0_plaintext(c: &mut Criterion) {
- let mut adv_builder = LegacyAdvBuilder::new(PublicIdentity::default());
+ let mut adv_builder = LegacyAdvBuilder::new(PublicIdentity);
let mut action_bits = ActionBits::default();
action_bits.set_action(ContextSyncSeqNum::try_from(3).unwrap());
adv_builder.add_data_element(ActionsDataElement::from(action_bits)).unwrap();
let adv = adv_builder.into_advertisement().unwrap();
- let cred_source: DefaultBothCredentialSource = DefaultBothCredentialSource::new_empty();
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
for &num_advs in &[1, 10, 100, 1000] {
c.bench_function(
@@ -231,9 +199,10 @@
b.iter(|| {
for _ in 0..num_advs {
black_box(
- deserialize_advertisement::<_, _, _, _, CryptoProviderImpl>(
+ deserialize_advertisement::<_, CryptoProviderImpl>(
+ deserialization_arena!(),
black_box(adv.as_slice()),
- black_box(&cred_source),
+ black_box(&cred_book),
)
.expect("Should succeed"),
);
@@ -246,22 +215,20 @@
/// Benchmark decrypting a V0 advertisement with credentials built from the reversed list of
/// identities
-fn run_with_v0_creds<M, C>(b: &mut Bencher, identities: Vec<V0Identity<C>>, adv: &[u8])
-where
- M: V0CryptoMaterialExt,
+fn run_with_v0_creds<C>(
+ b: &mut Bencher,
+ crypto_material_type: CryptoMaterialType,
+ identities: Vec<V0Identity<C>>,
+ adv: &[u8],
+) where
C: CryptoProvider,
{
let mut creds = identities
.into_iter()
- .map(|i| {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&i.key_seed);
- SimpleV0Credential::new(
- M::build_cred::<C>(
- i.key_seed,
- hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&i.legacy_metadata_key),
- ),
- i.key_seed,
- )
+ .map(|identity| identity.into_discovery_credential())
+ .map(|discovery_credential| MatchableCredential {
+ discovery_credential,
+ match_data: EmptyMatchedCredential,
})
.collect::<Vec<_>>();
@@ -269,34 +236,55 @@
// cred source for predictably bad performance
creds.reverse();
- let cred_source = SliceCredentialSource::new(&creds);
- b.iter(|| {
- black_box(deserialize_v0_advertisement::<_, _, C>(adv, &cred_source).map(|_| 0_u8).unwrap())
- });
+ match crypto_material_type {
+ CryptoMaterialType::MinFootprint => {
+ // Cache size of 0 => only min-footprint creds
+ let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&creds, &[]);
+
+ b.iter(|| {
+ black_box(
+ deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
+ .map(|_| 0_u8)
+ .unwrap(),
+ )
+ });
+ }
+ CryptoMaterialType::Precalculated => {
+ let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>(
+ creds,
+ core::iter::empty(),
+ );
+ b.iter(|| {
+ black_box(
+ deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
+ .map(|_| 0_u8)
+ .unwrap(),
+ )
+ });
+ }
+ }
}
/// Benchmark decrypting a V1 advertisement with credentials built from the reversed list of
/// identities
-fn run_with_v1_creds<M, C>(b: &mut Bencher, identities: Vec<V1Identity<C>>, adv: &[u8])
-where
- M: V1CryptoMaterialExt,
+fn run_with_v1_creds<C>(
+ b: &mut Bencher,
+ crypto_material_type: CryptoMaterialType,
+ identities: Vec<V1Identity<C>>,
+ adv: &[u8],
+) where
C: CryptoProvider,
{
let mut creds = identities
.into_iter()
- .map(|i| {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&i.key_seed);
- SimpleV1Credential::new(
- M::build_cred::<C>(
- i.key_seed,
- hkdf.extended_unsigned_metadata_key_hmac_key()
- .calculate_hmac(&i.extended_metadata_key),
- hkdf.extended_signed_metadata_key_hmac_key()
- .calculate_hmac(&i.extended_metadata_key),
- i.key_pair.public(),
- ),
- i.key_seed,
- )
+ .map(|identity| identity.into_discovery_credential())
+ .map(|discovery_credential| MatchableCredential {
+ discovery_credential,
+ match_data: EmptyMatchedCredential,
})
.collect::<Vec<_>>();
@@ -304,12 +292,39 @@
// cred source for predictably bad performance
creds.reverse();
- let cred_source = SliceCredentialSource::new(&creds);
- b.iter(|| {
- black_box(deserialize_v1_advertisement::<_, _, C>(adv, &cred_source).map(|_| 0_u8).unwrap())
- });
+ match crypto_material_type {
+ CryptoMaterialType::MinFootprint => {
+ // Cache size of 0 => only min-footprint creds
+ let cred_book = CredentialBookBuilder::<_>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &creds);
+
+ b.iter(|| {
+ black_box(
+ deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
+ .map(|_| 0_u8)
+ .unwrap(),
+ )
+ });
+ }
+ CryptoMaterialType::Precalculated => {
+ let cred_book = CredentialBookBuilder::<_>::build_precalculated_owned_book::<C>(
+ core::iter::empty(),
+ creds,
+ );
+ b.iter(|| {
+ black_box(
+ deserialize_advertisement::<_, C>(deserialization_arena!(), adv, &cred_book)
+ .map(|_| 0_u8)
+ .unwrap(),
+ )
+ });
+ }
+ }
}
-fn add_des<I: SectionIdentity>(sb: &mut SectionBuilder<I>) {
+fn add_des<I: SectionEncoder>(sb: &mut SectionBuilder<I>) {
sb.add_de_res(|_| TxPower::try_from(17).map(TxPowerDataElement::from)).unwrap();
sb.add_de_res(|_| GenericDataElement::try_from(100_u32.into(), &[0; 10])).unwrap();
}
@@ -324,20 +339,29 @@
struct V0Identity<C: CryptoProvider> {
key_seed: [u8; 32],
- legacy_metadata_key: [u8; 14],
+ legacy_metadata_key: ShortMetadataKey,
_marker: PhantomData<C>,
}
impl<C: CryptoProvider> V0Identity<C> {
/// Generate a new identity with random crypto material
fn random<R: rand::Rng + rand::CryptoRng>(rng: &mut R) -> Self {
- Self { key_seed: rng.gen(), legacy_metadata_key: rng.gen(), _marker: PhantomData }
+ Self {
+ key_seed: rng.gen(),
+ legacy_metadata_key: ShortMetadataKey(rng.gen()),
+ _marker: PhantomData,
+ }
+ }
+ /// Convert this `V0Identity` into a V0 discovery credential.
+ fn into_discovery_credential(self) -> V0DiscoveryCredential {
+ SimpleBroadcastCryptoMaterial::<V0>::new(self.key_seed, self.legacy_metadata_key)
+ .derive_v0_discovery_credential::<C>()
}
}
struct V1Identity<C: CryptoProvider> {
key_seed: [u8; 32],
- extended_metadata_key: [u8; 16],
+ extended_metadata_key: MetadataKey,
key_pair: np_ed25519::KeyPair<C>,
}
@@ -346,77 +370,23 @@
fn random(rng: &mut C::CryptoRng) -> Self {
Self {
key_seed: rng.gen(),
- extended_metadata_key: rng.gen(),
+ extended_metadata_key: MetadataKey(rng.gen()),
key_pair: np_ed25519::KeyPair::<C>::generate(),
}
}
+ /// Convert this `V1Identity` into a `V1DiscoveryCredential`.
+ fn into_discovery_credential(self) -> V1DiscoveryCredential {
+ SimpleSignedBroadcastCryptoMaterial::new(
+ self.key_seed,
+ self.extended_metadata_key,
+ self.key_pair.private_key(),
+ )
+ .derive_v1_discovery_credential::<C>()
+ }
}
-#[derive(strum_macros::EnumIter, Debug)]
+#[derive(strum_macros::EnumIter, Clone, Copy, Debug)]
enum CryptoMaterialType {
MinFootprint,
Precalculated,
}
-
-// if we get confident this is a valid shared way, could move this to the main trait
-trait V0CryptoMaterialExt: V0CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- legacy_metadata_key_hmac: [u8; 32],
- ) -> Self;
-}
-
-trait V1CryptoMaterialExt: V1CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- unsigned_metadata_key_hmac: [u8; 32],
- signed_metadata_key_hmac: [u8; 32],
- pub_key: np_ed25519::PublicKey<C>,
- ) -> Self;
-}
-
-impl V0CryptoMaterialExt for MinimumFootprintV0CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- legacy_metadata_key_hmac: [u8; 32],
- ) -> Self {
- Self::new(key_seed, legacy_metadata_key_hmac)
- }
-}
-
-impl V0CryptoMaterialExt for PrecalculatedV0CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- legacy_metadata_key_hmac: [u8; 32],
- ) -> Self {
- Self::new::<C>(&key_seed, legacy_metadata_key_hmac)
- }
-}
-
-impl V1CryptoMaterialExt for MinimumFootprintV1CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- unsigned_metadata_key_hmac: [u8; 32],
- signed_metadata_key_hmac: [u8; 32],
- pub_key: np_ed25519::PublicKey<C>,
- ) -> Self {
- Self::new(key_seed, unsigned_metadata_key_hmac, signed_metadata_key_hmac, pub_key)
- }
-}
-
-impl V1CryptoMaterialExt for PrecalculatedV1CryptoMaterial {
- fn build_cred<C: CryptoProvider>(
- key_seed: [u8; 32],
- unsigned_metadata_key_hmac: [u8; 32],
- signed_metadata_key_hmac: [u8; 32],
- pub_key: np_ed25519::PublicKey<C>,
- ) -> Self {
- let min_foot = MinimumFootprintV1CryptoMaterial::new(
- key_seed,
- unsigned_metadata_key_hmac,
- signed_metadata_key_hmac,
- pub_key,
- );
- min_foot.to_precalculated::<C>()
- }
-}
diff --git a/nearby/presence/np_adv/src/credential/book.rs b/nearby/presence/np_adv/src/credential/book.rs
new file mode 100644
index 0000000..9e6361d
--- /dev/null
+++ b/nearby/presence/np_adv/src/credential/book.rs
@@ -0,0 +1,542 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Traits defining credential books. These are used in the deserialization path to provide
+//! the credentials to try for either advertisement version.
+//! See [`CredentialBookBuilder`] for batteries-included implementations.
+
+use crate::credential::source::{
+ CredentialSource, DiscoveryCredentialSource, SliceCredentialSource,
+};
+use crate::credential::v0::{V0DiscoveryCryptoMaterial, V0};
+use crate::credential::v1::{
+ SignedSectionIdentityResolutionMaterial, SignedSectionVerificationMaterial,
+ UnsignedSectionIdentityResolutionMaterial, UnsignedSectionVerificationMaterial,
+ V1DiscoveryCryptoMaterial, V1,
+};
+#[cfg(feature = "alloc")]
+use crate::credential::ReferencedMatchedCredential;
+use crate::credential::{
+ DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion,
+};
+use core::borrow::Borrow;
+use core::marker::PhantomData;
+use crypto_provider::CryptoProvider;
+
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+/// A collection of credentials to try when attempting to deserialize
+/// advertisements of either advertisement version which is
+/// valid for the given lifetime.
+pub trait CredentialBook<'a>
+where
+ Self: 'a,
+{
+ /// The type of the matched credentials for the credentials
+ /// held within this credential-book. May lend from the credential-book.
+ type Matched: MatchedCredential;
+
+ /// The type of V0 crypto-materials yielded by this credential-book.
+ type V0Crypto: V0DiscoveryCryptoMaterial;
+
+ /// The type of V1 crypto-materials yielded by this credential-book.
+ type V1Crypto: V1DiscoveryCryptoMaterial;
+
+ /// The iterator type returned from [`CredentialBook#v0_iter()`],
+ /// which yields `(crypto-material, match_data)` pairs.
+ /// This is a lending iterator which may borrow things from `self`.
+ type V0Iterator: Iterator<Item = (Self::V0Crypto, Self::Matched)>;
+
+ /// The iterator type returned from [`CredentialBook#v1_iter()`],
+ /// which yields `(crypto-material, match_data)` pairs.
+ /// This is a lending iterator which may borrow things from `self`.
+ type V1Iterator: Iterator<Item = (Self::V1Crypto, Self::Matched)>;
+
+ /// Returns an iterator over V0 credentials in this credential book.
+ fn v0_iter(&'a self) -> Self::V0Iterator;
+
+ /// Returns an iterator over V1 credentials in this credential book.
+ fn v1_iter(&'a self) -> Self::V1Iterator;
+}
+
+/// Convenient marker struct for building credential-books with
+/// some given match-data type.
+pub struct CredentialBookBuilder<M: MatchedCredential> {
+ _marker: PhantomData<M>,
+}
+
+impl<M: MatchedCredential> CredentialBookBuilder<M> {
+ /// Constructs a [`CachedSliceCredentialBook`] from the given slices of discovery credentials,
+ /// populating its internal buffers of cached credential crypto-materials for up to the
+ /// given number of credentials in each version (up to `N0` credentials cached in V0,
+ /// and up to `N1` credentials cached in `V1`.)
+ pub fn build_cached_slice_book<'a, const N0: usize, const N1: usize, P: CryptoProvider>(
+ v0_creds: &'a [MatchableCredential<V0, M>],
+ v1_creds: &'a [MatchableCredential<V1, M>],
+ ) -> CachedSliceCredentialBook<'a, M, N0, N1> {
+ let v0_source = SliceCredentialSource::new(v0_creds);
+ let v0_cache = init_cache_from_source::<V0, _, N0, P>(&v0_source);
+ let v0_source = CachedCredentialSource::<_, _, N0>::new(v0_source, v0_cache);
+
+ let v1_source = SliceCredentialSource::new(v1_creds);
+ let v1_cache = init_cache_from_source::<V1, _, N1, P>(&v1_source);
+ let v1_source = CachedCredentialSource::<_, _, N1>::new(v1_source, v1_cache);
+
+ CredentialBookFromSources::new(v0_source, v1_source)
+ }
+
+ #[cfg(feature = "alloc")]
+ /// Constructs a new credential-book which owns all of the given credentials,
+ /// and maintains pre-calculated cryptographic information about them
+ /// for speedy advertisement deserialization.
+ pub fn build_precalculated_owned_book<P: CryptoProvider>(
+ v0_iter: impl IntoIterator<Item = MatchableCredential<V0, M>>,
+ v1_iter: impl IntoIterator<Item = MatchableCredential<V1, M>>,
+ ) -> PrecalculatedOwnedCredentialBook<M> {
+ let v0_source = PrecalculatedOwnedCredentialSource::<V0, M>::new::<P>(v0_iter);
+ let v1_source = PrecalculatedOwnedCredentialSource::<V1, M>::new::<P>(v1_iter);
+ CredentialBookFromSources::new(v0_source, v1_source)
+ }
+}
+
+// Now, for the implementation details. External implementors still need
+// to be able to reference these types (since the returned types from
+// [`CredentialBookBuilder`]'s convenience methods are just type-aliases),
+// and if they want, they can use them as building-blocks to construct
+// their own [`CredentialBook`]s, but they're largely just scaffolding.
+
+/// A structure bundling both V0 and V1 credential-sources to define
+/// a credential-book which owns both of these independent sources.
+pub struct CredentialBookFromSources<S0, S1> {
+ /// The source for V0 credentials.
+ v0_source: S0,
+ /// The source for V1 credentials.
+ v1_source: S1,
+}
+
+impl<'a, S0, S1> CredentialBookFromSources<S0, S1>
+where
+ Self: 'a,
+ S0: CredentialSource<'a, V0>,
+ S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>,
+ <S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial,
+ <S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial,
+{
+ /// Constructs a new [`CredentialBook`] out of the two given
+ /// credential-sources, one for v0 and one for v1. The match-data
+ /// of both credential sources must be the same.
+ pub fn new(v0_source: S0, v1_source: S1) -> Self {
+ Self { v0_source, v1_source }
+ }
+}
+
+impl<'a, S0, S1> CredentialBook<'a> for CredentialBookFromSources<S0, S1>
+where
+ Self: 'a,
+ S0: CredentialSource<'a, V0>,
+ S1: CredentialSource<'a, V1, Matched = <S0 as CredentialSource<'a, V0>>::Matched>,
+ <S0 as CredentialSource<'a, V0>>::Crypto: V0DiscoveryCryptoMaterial,
+ <S1 as CredentialSource<'a, V1>>::Crypto: V1DiscoveryCryptoMaterial,
+{
+ type Matched = <S0 as CredentialSource<'a, V0>>::Matched;
+ type V0Crypto = <S0 as CredentialSource<'a, V0>>::Crypto;
+ type V1Crypto = <S1 as CredentialSource<'a, V1>>::Crypto;
+ type V0Iterator = <S0 as CredentialSource<'a, V0>>::Iterator;
+ type V1Iterator = <S1 as CredentialSource<'a, V1>>::Iterator;
+
+ fn v0_iter(&'a self) -> Self::V0Iterator {
+ self.v0_source.iter()
+ }
+ fn v1_iter(&'a self) -> Self::V1Iterator {
+ self.v1_source.iter()
+ }
+}
+
+/// Type-level function used internally
+/// by [`CachedCredentialSource`] in order to uniformly
+/// refer to the "precalculated" crypto-material variants
+/// for each protocol version.
+pub(crate) mod precalculated_for_version {
+ use crate::credential::v0::{
+ PrecalculatedV0DiscoveryCryptoMaterial, V0DiscoveryCredential, V0,
+ };
+ use crate::credential::v1::{
+ PrecalculatedV1DiscoveryCryptoMaterial, V1DiscoveryCredential, V1,
+ };
+ use crate::credential::{DiscoveryCryptoMaterial, ProtocolVersion};
+ use crypto_provider::CryptoProvider;
+
+ pub trait MappingTrait<V: ProtocolVersion> {
+ type Output: DiscoveryCryptoMaterial<V>;
+ /// Provides pre-calculated crypto-material for the given
+ /// discovery credential.
+ fn precalculate<C: CryptoProvider>(
+ discovery_credential: &V::DiscoveryCredential,
+ ) -> Self::Output;
+ }
+ pub enum Marker {}
+ impl MappingTrait<V0> for Marker {
+ type Output = PrecalculatedV0DiscoveryCryptoMaterial;
+ fn precalculate<C: CryptoProvider>(
+ discovery_credential: &V0DiscoveryCredential,
+ ) -> PrecalculatedV0DiscoveryCryptoMaterial {
+ PrecalculatedV0DiscoveryCryptoMaterial::new::<C>(discovery_credential)
+ }
+ }
+ impl MappingTrait<V1> for Marker {
+ type Output = PrecalculatedV1DiscoveryCryptoMaterial;
+ fn precalculate<C: CryptoProvider>(
+ discovery_credential: &V1DiscoveryCredential,
+ ) -> PrecalculatedV1DiscoveryCryptoMaterial {
+ discovery_credential.to_precalculated::<C>()
+ }
+ }
+}
+
+type PrecalculatedCryptoForProtocolVersion<V> =
+ <precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::Output;
+
+fn precalculate_crypto_material<V: ProtocolVersion, C: CryptoProvider>(
+ discovery_credential: &V::DiscoveryCredential,
+) -> PrecalculatedCryptoForProtocolVersion<V>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ <precalculated_for_version::Marker as precalculated_for_version::MappingTrait<V>>::precalculate::<
+ C,
+ >(discovery_credential)
+}
+
+/// Iterator type for [`CachedCredentialSource`].
+pub struct CachedCredentialSourceIterator<
+ 'a,
+ V: ProtocolVersion + 'a,
+ S: DiscoveryCredentialSource<'a, V> + 'a,
+ const N: usize,
+> where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ /// The current index of the (enumerated) elements that we're iterating over.
+ /// Always counts up at every iteration, and may lie outside of the range
+ /// of the `cache` slice for uncached elements.
+ current_index: usize,
+ /// The cache of pre-calculated crypto-materials from the original source.
+ ///
+ /// The [`self.source_iterator`]'s (up-to) first `N` elements
+ /// must match (up-to) the first `N` elements in the cache,
+ /// and they must both appear in the same order.
+ cache: &'a [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
+ /// The iterator over the credentials in the original source
+ source_iterator: S::Iterator,
+}
+
+impl<'a, V: ProtocolVersion + 'a, S: DiscoveryCredentialSource<'a, V> + 'a, const N: usize> Iterator
+ for CachedCredentialSourceIterator<'a, V, S, N>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ type Item = (PossiblyCachedDiscoveryCryptoMaterial<'a, V>, S::Matched);
+ fn next(&mut self) -> Option<Self::Item> {
+ // Regardless of what we're going to do with the crypto-materials, we still need
+ // to advance the underlying iterator, and bail if it runs out.
+ let (discovery_credential, match_data) = self.source_iterator.next()?;
+ // Check whether/not we have cached crypto-material for the current index
+ let crypto = match self.cache.get(self.current_index) {
+ Some(precalculated_crypto_entry) => {
+ let precalculated_crypto = precalculated_crypto_entry
+ .as_ref()
+ .expect("iterator still going, but cache is not full?");
+ PossiblyCachedDiscoveryCryptoMaterial::from_precalculated(precalculated_crypto)
+ }
+ None => PossiblyCachedDiscoveryCryptoMaterial::from_discovery_credential(
+ discovery_credential.borrow().clone(),
+ ),
+ };
+ // Advance the index forward to point to the next item in the cache.
+ self.current_index += 1;
+ Some((crypto, match_data))
+ }
+}
+
+/// A [`CredentialSource`] which augments the externally-provided [`DiscoveryCredentialSource`] with
+/// a cache containing up to the specified number of pre-calculated credentials.
+pub struct CachedCredentialSource<V: ProtocolVersion, S, const N: usize>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ wrapped: S,
+ cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
+}
+
+/// Helper function to construct a cache for a [`CachedCredentialSource`] from
+/// a reference to some source of discovery-credentials.
+///
+/// This function needs to be kept separate from [`CachedCredentialSource#new`]
+/// to get around otherwise-tricky lifetime issues around ephemerally-borrowing
+/// from a moved object within the body of a function.
+pub(crate) fn init_cache_from_source<'a, V: ProtocolVersion, S, const N: usize, P: CryptoProvider>(
+ original: &'a S,
+) -> [Option<PrecalculatedCryptoForProtocolVersion<V>>; N]
+where
+ S: DiscoveryCredentialSource<'a, V> + 'a,
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ let mut cache = [0u8; N].map(|_| None);
+ for (cache_entry, source_credential) in cache.iter_mut().zip(original.iter()) {
+ let (discovery_credential, _) = source_credential;
+ let precalculated_crypto_material =
+ precalculate_crypto_material::<V, P>(discovery_credential.borrow());
+ let _ = cache_entry.insert(precalculated_crypto_material);
+ }
+ cache
+}
+
+impl<'a, V: ProtocolVersion, S, const N: usize> CachedCredentialSource<V, S, N>
+where
+ S: DiscoveryCredentialSource<'a, V> + 'a,
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ /// Constructs a [`CachedCredentialSource`] from the given [`DiscoveryCredentialSource`]
+ /// and the given (initial) cache contents, as constructed via the
+ /// [`init_cache_from_source`] helper function.
+ pub(crate) fn new(
+ wrapped: S,
+ cache: [Option<PrecalculatedCryptoForProtocolVersion<V>>; N],
+ ) -> Self {
+ Self { wrapped, cache }
+ }
+}
+
+/// Internal implementation of [`PossiblyCachedDiscoveryCryptoMaterial`] to hide
+/// what crypto-material variants we're actually storing in cached
+/// credential-books.
+pub(crate) enum PossiblyCachedDiscoveryCryptoMaterialKind<'a, V: ProtocolVersion>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ Discovery(V::DiscoveryCredential),
+ Precalculated(&'a PrecalculatedCryptoForProtocolVersion<V>),
+}
+
+/// Crypto-materials that are potentially references to
+/// already-cached precomputed variants, or raw discovery
+/// credentials.
+pub struct PossiblyCachedDiscoveryCryptoMaterial<'a, V: ProtocolVersion>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ pub(crate) wrapped: PossiblyCachedDiscoveryCryptoMaterialKind<'a, V>,
+}
+
+impl<'a, V: ProtocolVersion> PossiblyCachedDiscoveryCryptoMaterial<'a, V>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ fn from_discovery_credential(discovery_credential: V::DiscoveryCredential) -> Self {
+ Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(discovery_credential) }
+ }
+ fn from_precalculated(precalculated: &'a PrecalculatedCryptoForProtocolVersion<V>) -> Self {
+ Self { wrapped: PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(precalculated) }
+ }
+}
+
+impl<'a, V: ProtocolVersion> DiscoveryCryptoMaterial<V>
+ for PossiblyCachedDiscoveryCryptoMaterial<'a, V>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.metadata_nonce::<C>(),
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.metadata_nonce::<C>(),
+ }
+ }
+}
+
+impl<'a> V0DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V0> {
+ fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => x.ldt_adv_cipher::<C>(),
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => x.ldt_adv_cipher::<C>(),
+ }
+ }
+}
+
+impl<'a> V1DiscoveryCryptoMaterial for PossiblyCachedDiscoveryCryptoMaterial<'a, V1> {
+ fn signed_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> SignedSectionIdentityResolutionMaterial {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
+ x.signed_identity_resolution_material::<C>()
+ }
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
+ x.signed_identity_resolution_material::<C>()
+ }
+ }
+ }
+
+ fn unsigned_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionIdentityResolutionMaterial {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
+ x.unsigned_identity_resolution_material::<C>()
+ }
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
+ x.unsigned_identity_resolution_material::<C>()
+ }
+ }
+ }
+
+ fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
+ x.signed_verification_material::<C>()
+ }
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
+ x.signed_verification_material::<C>()
+ }
+ }
+ }
+
+ fn unsigned_verification_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionVerificationMaterial {
+ match &self.wrapped {
+ PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(x) => {
+ x.unsigned_verification_material::<C>()
+ }
+ PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(x) => {
+ x.unsigned_verification_material::<C>()
+ }
+ }
+ }
+}
+
+impl<'a, V: ProtocolVersion, S, const N: usize> CredentialSource<'a, V>
+ for CachedCredentialSource<V, S, N>
+where
+ Self: 'a,
+ S: DiscoveryCredentialSource<'a, V> + 'a,
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+ PossiblyCachedDiscoveryCryptoMaterial<'a, V>: DiscoveryCryptoMaterial<V>,
+{
+ type Matched = <S as DiscoveryCredentialSource<'a, V>>::Matched;
+ type Crypto = PossiblyCachedDiscoveryCryptoMaterial<'a, V>;
+ type Iterator = CachedCredentialSourceIterator<'a, V, S, N>;
+
+ fn iter(&'a self) -> Self::Iterator {
+ CachedCredentialSourceIterator {
+ current_index: 0,
+ cache: &self.cache,
+ source_iterator: self.wrapped.iter(),
+ }
+ }
+}
+
+/// A simple credentials source for environments which are,
+/// for all practical purposes, not space-constrained, and hence
+/// can store an arbitrary amount of pre-calculated crypto-materials.
+///
+/// Requires `alloc` as a result of internally leveraging a `Vec`.
+#[cfg(feature = "alloc")]
+pub struct PrecalculatedOwnedCredentialSource<V: ProtocolVersion, M: MatchedCredential>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ credentials: Vec<(PrecalculatedCryptoForProtocolVersion<V>, M)>,
+}
+
+#[cfg(feature = "alloc")]
+impl<'a, V: ProtocolVersion + 'a, M: MatchedCredential + 'a>
+ PrecalculatedOwnedCredentialSource<V, M>
+where
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+{
+ /// Pre-calculates crypto material for the given credentials, and constructs a
+ /// credentials source which holds owned copies of this crypto-material.
+ pub fn new<P: CryptoProvider>(
+ credential_iter: impl IntoIterator<Item = MatchableCredential<V, M>>,
+ ) -> Self {
+ let credentials = credential_iter
+ .into_iter()
+ .map(|credential| {
+ (
+ precalculate_crypto_material::<V, P>(&credential.discovery_credential),
+ credential.match_data,
+ )
+ })
+ .collect();
+ Self { credentials }
+ }
+}
+
+#[cfg(feature = "alloc")]
+fn reference_crypto_and_match_data<C, M: MatchedCredential>(
+ pair_ref: &(C, M),
+) -> (&C, ReferencedMatchedCredential<M>) {
+ let (c, m) = pair_ref;
+ (c, m.into())
+}
+
+#[cfg(feature = "alloc")]
+impl<'a, V: ProtocolVersion, M: MatchedCredential> CredentialSource<'a, V>
+ for PrecalculatedOwnedCredentialSource<V, M>
+where
+ Self: 'a,
+ precalculated_for_version::Marker: precalculated_for_version::MappingTrait<V>,
+ &'a PrecalculatedCryptoForProtocolVersion<V>: DiscoveryCryptoMaterial<V>,
+{
+ type Matched = ReferencedMatchedCredential<'a, M>;
+ type Crypto = &'a PrecalculatedCryptoForProtocolVersion<V>;
+ type Iterator = core::iter::Map<
+ core::slice::Iter<'a, (PrecalculatedCryptoForProtocolVersion<V>, M)>,
+ fn(
+ &'a (PrecalculatedCryptoForProtocolVersion<V>, M),
+ )
+ -> (&'a PrecalculatedCryptoForProtocolVersion<V>, ReferencedMatchedCredential<'a, M>),
+ >;
+
+ fn iter(&'a self) -> Self::Iterator {
+ self.credentials.iter().map(reference_crypto_and_match_data)
+ }
+}
+
+/// Type-alias for credential sources which are provided via slice credential-sources
+/// with a pre-calculated credential cache layered on top.
+pub type CachedSliceCredentialSource<'a, V, M, const N: usize> =
+ CachedCredentialSource<V, SliceCredentialSource<'a, V, M>, N>;
+
+/// A [`CredentialBook`] whose sources for V0 and V1 credentials come from the given slices
+/// of discovery credentials, with crypto-materials for up to the given number of credentials
+/// from the beginning of each slice kept in an in-memory cache.
+pub type CachedSliceCredentialBook<'a, M, const N0: usize, const N1: usize> =
+ CredentialBookFromSources<
+ CachedSliceCredentialSource<'a, V0, M, N0>,
+ CachedSliceCredentialSource<'a, V1, M, N1>,
+ >;
+
+#[cfg(feature = "alloc")]
+/// A credential-book which owns all of its (non-matched) credential data,
+/// and maintains pre-calculated cryptographic information about all
+/// stored credentials for speedy advertisement deserialization.
+///
+/// Use this credential book if memory usage is not terribly tight,
+/// and you're operating in an environment with an allocator.
+pub type PrecalculatedOwnedCredentialBook<M> = CredentialBookFromSources<
+ PrecalculatedOwnedCredentialSource<V0, M>,
+ PrecalculatedOwnedCredentialSource<V1, M>,
+>;
diff --git a/nearby/presence/np_adv/src/credential/mod.rs b/nearby/presence/np_adv/src/credential/mod.rs
index a138f0b..3cab51e 100644
--- a/nearby/presence/np_adv/src/credential/mod.rs
+++ b/nearby/presence/np_adv/src/credential/mod.rs
@@ -18,68 +18,375 @@
//! efficiency gains with implementations tailored to suit (e.g. caching a few hot credentials
//! rather than reading from disk every time, etc).
+use crate::MetadataKey;
+
+use crate::credential::v0::{V0DiscoveryCredential, V0ProtocolVersion};
+
+use core::convert::Infallible;
use core::fmt::Debug;
-use crypto_provider::CryptoProvider;
+use crypto_provider::{CryptoProvider, CryptoRng};
-use self::{
- simple::{SimpleV0Credential, SimpleV1Credential},
- source::OwnedBothCredentialSource,
- v0::MinimumFootprintV0CryptoMaterial,
- v1::MinimumFootprintV1CryptoMaterial,
-};
-
-pub mod simple;
+pub mod book;
pub mod source;
+#[cfg(test)]
+pub mod tests;
pub mod v0;
pub mod v1;
-/// A credential which has an associated [`MatchedCredential`]
-pub trait MatchableCredential {
- /// The [MatchedCredential] provided by this [`MatchableCredential`].
- type Matched<'m>: MatchedCredential<'m>
- where
- Self: 'm;
-
- /// Returns the subset of credential data that should be associated with a successfully
- /// decrypted advertisement or advertisement section.
- fn matched(&self) -> Self::Matched<'_>;
+/// Information about a credential as supplied by the caller.
+#[derive(Clone)]
+pub struct MatchableCredential<V: ProtocolVersion, M: MatchedCredential> {
+ /// The discovery credential/cryptographic information associated
+ /// with this particular credential which is used for discovering
+ /// advertisements/advertisement sections generated via the
+ /// paired sender credential.
+ pub discovery_credential: V::DiscoveryCredential,
+ /// The data which will be yielded back to the caller upon a successful
+ /// identity-match against this credential.
+ pub match_data: M,
}
-/// Convenient type-level function for referring to the match data for a [`MatchableCredential`].
-pub type MatchedCredFromCred<'s, C> = <C as MatchableCredential>::Matched<'s>;
+impl<V: ProtocolVersion, M: MatchedCredential> MatchableCredential<V, M> {
+ /// De-structures this credential into the pairing of a discovery
+ /// credential and some matched credential data.
+ pub fn into_pair(self) -> (V::DiscoveryCredential, M) {
+ (self.discovery_credential, self.match_data)
+ }
+ /// Views this credential as a (borrowed) discovery-credential
+ /// combined with some matched credential data
+ /// (which is copied - see documentation on [`MatchedCredential`])
+ pub fn as_pair(&self) -> (&V::DiscoveryCredential, ReferencedMatchedCredential<M>) {
+ (&self.discovery_credential, ReferencedMatchedCredential::from(&self.match_data))
+ }
+}
/// The portion of a credential's data to be bundled with the advertisement content it was used to
-/// decrypt.
+/// decrypt. At a minimum, this includes any encrypted identity-specific metadata.
///
-/// As it is `Debug` and `Eq`, implementors should not hold any cryptographic material to avoid
+/// As it is `Debug` and `Eq`, implementors should not hold any cryptographic secrets to avoid
/// accidental logging, timing side channels on comparison, etc, or should use custom impls of
/// those traits rather than deriving them.
-pub trait MatchedCredential<'m>: Debug + PartialEq + Eq {}
+///
+/// Instances of `MatchedCredential` may be cloned whenever advertisement content is
+/// successfully associated with a credential (see [`crate::WithMatchedCredential`]). As a
+/// result, it's recommended to use matched-credentials which reference
+/// some underlying match-data, but don't necessarily own it.
+/// See [`ReferencedMatchedCredential`] for the most common case of shared references.
+pub trait MatchedCredential: Debug + PartialEq + Eq + Clone {
+ /// The type returned for successful calls to [`Self::fetch_encrypted_metadata`].
+ type EncryptedMetadata: AsRef<[u8]>;
-/// A V0 credential containing some [`v0::V0CryptoMaterial`]
-pub trait V0Credential: MatchableCredential {
- /// The [v0::V0CryptoMaterial] provided by this V0Credential impl.
- type CryptoMaterial: v0::V0CryptoMaterial;
+ /// The type of errors for [`Self::fetch_encrypted_metadata`].
+ type EncryptedMetadataFetchError: Debug;
- /// Returns the crypto material associated with the credential.
+ /// Attempts to obtain the (AES-GCM)-encrypted metadata bytes for the credential,
+ /// with possible failure based on the availability of the underlying data (i.e:
+ /// failing disk reads.)
///
- /// Used to decrypted encrypted advertisement content.
- fn crypto_material(&self) -> &Self::CryptoMaterial;
+ /// If your implementation does not maintain any encrypted metadata for each credential,
+ /// you may simply return an empty byte-array from this method.
+ ///
+ /// If your method for obtaining metadata cannot fail, use
+ /// the `core::convert::Infallible` type for the error type
+ /// [`Self::EncryptedMetadataFetchError`].
+ fn fetch_encrypted_metadata(
+ &self,
+ ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError>;
}
-/// A V1 credential containing some [`v1::V1CryptoMaterial`]
-pub trait V1Credential: MatchableCredential {
- /// The [v1::V1CryptoMaterial] provided by this Credential impl.
- type CryptoMaterial: v1::V1CryptoMaterial;
-
- /// Returns the crypto material associated with the credential.
- ///
- /// Used to decrypt encrypted advertisement content.
- fn crypto_material(&self) -> &Self::CryptoMaterial;
+/// [`MatchedCredential`] wrapper around a shared reference to a [`MatchedCredential`].
+/// This is done instead of providing a blanket impl of [`MatchedCredential`] for
+/// reference types to allow for downstream crates to impl [`MatchedCredential`] on
+/// specific reference types.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ReferencedMatchedCredential<'a, M: MatchedCredential> {
+ wrapped: &'a M,
}
-/// An owned credential store that contains minimum footprint crypto materials
-pub type MinimumFootprintCredentialSource<T> = OwnedBothCredentialSource<
- SimpleV0Credential<MinimumFootprintV0CryptoMaterial, T>,
- SimpleV1Credential<MinimumFootprintV1CryptoMaterial, T>,
->;
+impl<'a, M: MatchedCredential> From<&'a M> for ReferencedMatchedCredential<'a, M> {
+ fn from(wrapped: &'a M) -> Self {
+ Self { wrapped }
+ }
+}
+
+impl<'a, M: MatchedCredential> AsRef<M> for ReferencedMatchedCredential<'a, M> {
+ fn as_ref(&self) -> &M {
+ self.wrapped
+ }
+}
+
+impl<'a, M: MatchedCredential> MatchedCredential for ReferencedMatchedCredential<'a, M> {
+ type EncryptedMetadata = <M as MatchedCredential>::EncryptedMetadata;
+ type EncryptedMetadataFetchError = <M as MatchedCredential>::EncryptedMetadataFetchError;
+ fn fetch_encrypted_metadata(
+ &self,
+ ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> {
+ self.wrapped.fetch_encrypted_metadata()
+ }
+}
+
+/// A simple implementation of [`MatchedCredential`] where all match-data
+/// is contained in the encrypted metadata byte-field.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct MetadataMatchedCredential<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> {
+ encrypted_metadata: A,
+}
+
+#[cfg(any(test, feature = "alloc"))]
+impl MetadataMatchedCredential<alloc::vec::Vec<u8>> {
+ /// Builds a [`MetadataMatchedCredential`] whose contents are given
+ /// as plaintext to be encrypted using AES-GCM against the given
+ /// broadcast crypto-material.
+ pub fn encrypt_from_plaintext<V, B, C>(broadcast_cm: &B, plaintext_metadata: &[u8]) -> Self
+ where
+ V: ProtocolVersion,
+ B: BroadcastCryptoMaterial<V>,
+ C: CryptoProvider,
+ {
+ let encrypted_metadata = broadcast_cm.encrypt_metadata::<C>(plaintext_metadata);
+ Self { encrypted_metadata }
+ }
+}
+
+impl<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> MetadataMatchedCredential<A> {
+ /// Builds a new [`MetadataMatchedCredential`] with the given
+ /// encrypted metadata.
+ pub fn new(encrypted_metadata: A) -> Self {
+ Self { encrypted_metadata }
+ }
+}
+
+impl<A: AsRef<[u8]> + Clone + Debug + PartialEq + Eq> MatchedCredential
+ for MetadataMatchedCredential<A>
+{
+ type EncryptedMetadata = A;
+ type EncryptedMetadataFetchError = Infallible;
+ fn fetch_encrypted_metadata(&self) -> Result<Self::EncryptedMetadata, Infallible> {
+ Ok(self.encrypted_metadata.clone())
+ }
+}
+
+/// Trivial implementation of [`MatchedCredential`] which consists of no match-data.
+/// Suitable for usage scenarios where the decoded advertisement contents matter,
+/// but not necessarily which devices generated the contents.
+///
+/// Attempting to obtain the encrypted metadata from this type of credential
+/// will always yield an empty byte-array.
+#[derive(Default, Debug, PartialEq, Eq, Clone)]
+pub struct EmptyMatchedCredential;
+
+impl MatchedCredential for EmptyMatchedCredential {
+ type EncryptedMetadata = [u8; 0];
+ type EncryptedMetadataFetchError = Infallible;
+ fn fetch_encrypted_metadata(
+ &self,
+ ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> {
+ Ok([0u8; 0])
+ }
+}
+
+#[cfg(any(test, feature = "devtools"))]
+/// A [`MatchedCredential`] which consists only of the `key_seed` in the crypto-material
+/// for the credential. Note that this is unique per-credential by construction,
+/// and so this provides natural match-data for credentials in settings where
+/// there may not be any other information available.
+///
+/// Since this matched-credential type contains cryptographic information mirroring
+/// a credential's crypto-material, this structure is not suitable for production
+/// usage outside of unit tests and dev-tools.
+///
+/// Additionally, note that the metadata on this particular kind of matched credential
+/// is deliberately made inaccessible. This is done because a key-seed representation
+/// is only suitable in very limited circumstances where no other meaningful
+/// identifying information is available, such as that which is contained in metadata.
+/// Attempting to obtain the encrypted metadata from this type of matched credential
+/// will always yield an empty byte-array.
+#[derive(Default, Debug, PartialEq, Eq, Clone)]
+pub struct KeySeedMatchedCredential {
+ key_seed: [u8; 32],
+}
+
+#[cfg(any(test, feature = "devtools"))]
+impl From<[u8; 32]> for KeySeedMatchedCredential {
+ fn from(key_seed: [u8; 32]) -> Self {
+ Self { key_seed }
+ }
+}
+#[cfg(any(test, feature = "devtools"))]
+impl From<KeySeedMatchedCredential> for [u8; 32] {
+ fn from(matched: KeySeedMatchedCredential) -> Self {
+ matched.key_seed
+ }
+}
+
+#[cfg(any(test, feature = "devtools"))]
+impl MatchedCredential for KeySeedMatchedCredential {
+ type EncryptedMetadata = [u8; 0];
+ type EncryptedMetadataFetchError = Infallible;
+ fn fetch_encrypted_metadata(
+ &self,
+ ) -> Result<Self::EncryptedMetadata, Self::EncryptedMetadataFetchError> {
+ Ok([0u8; 0])
+ }
+}
+
+/// Error returned when metadata decryption fails.
+#[derive(Debug, Eq, PartialEq)]
+pub struct MetadataDecryptionError;
+
+/// Seal for protocol versions to enforce totality.
+pub(crate) mod protocol_version_seal {
+ /// Internal-only supertrait of protocol versions
+ /// for the purpose of sealing the trait.
+ pub trait ProtocolVersionSeal: Clone {}
+}
+
+/// Marker trait for protocol versions (V0/V1)
+/// and associated data about them.
+pub trait ProtocolVersion: protocol_version_seal::ProtocolVersionSeal {
+ /// The discovery credential type for this protocol version, which
+ /// is the minimal amount of cryptographic materials that we need
+ /// in order to discover advertisements/sections which make
+ /// use of the sender-paired version of the credential.
+ type DiscoveryCredential: DiscoveryCryptoMaterial<Self> + Clone;
+
+ /// The native-length metadata key for this protocol version
+ /// [i.e: if V0, a 14-byte metadata key, or if V1, a 16-byte
+ /// metadata key.]
+ type MetadataKey: Clone + AsRef<[u8]>;
+
+ /// Computes the metadata nonce for this version from the given key-seed.
+ fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12];
+
+ /// Expands the passed metadata key (if needed) to a 16-byte metadata key
+ /// which may be used for metadata encryption/decryption
+ fn expand_metadata_key<C: CryptoProvider>(metadata_key: Self::MetadataKey) -> MetadataKey;
+
+ /// Generates a random metadata key using the given cryptographically-secure Rng
+ fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> Self::MetadataKey;
+
+ #[cfg(any(test, feature = "alloc"))]
+ /// Decrypt the given metadata using the given metadata nonce and version-specific
+ /// metadata key. Returns [`MetadataDecryptionError`] in the case that
+ /// the decryption operation failed.
+ fn decrypt_metadata<C: CryptoProvider>(
+ metadata_nonce: [u8; 12],
+ metadata_key: Self::MetadataKey,
+ encrypted_metadata: &[u8],
+ ) -> Result<alloc::vec::Vec<u8>, MetadataDecryptionError> {
+ use crypto_provider::{
+ aead::{Aead, AeadInit},
+ aes::Aes128Key,
+ };
+
+ let metadata_key = Self::expand_metadata_key::<C>(metadata_key);
+ let metadata_key = Aes128Key::from(metadata_key.0);
+ let aead = <<C as CryptoProvider>::Aes128Gcm as AeadInit<Aes128Key>>::new(&metadata_key);
+ // No additional authenticated data for encrypted metadata.
+ aead.decrypt(encrypted_metadata, &[], &metadata_nonce).map_err(|_| MetadataDecryptionError)
+ }
+}
+
+/// Trait for structures which provide cryptographic
+/// materials for discovery in a particular protocol version.
+/// See [`crate::credential::v0::V0DiscoveryCryptoMaterial`]
+/// and [`crate::credential::v1::V1DiscoveryCryptoMaterial`]
+/// for V0 and V1 specializations.
+pub trait DiscoveryCryptoMaterial<V: ProtocolVersion> {
+ /// Constructs or copies the metadata nonce used for decryption of associated credential
+ /// metadata for the identity represented via this crypto material.
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12];
+}
+
+/// Cryptographic materials necessary for broadcasting encrypted
+/// advertisement contents with the given protocol version.
+pub trait BroadcastCryptoMaterial<V: ProtocolVersion> {
+ /// Yields a copy of the key seed to be used to derive other key materials used
+ /// in the encryption of broadcasted advertisement contents.
+ fn key_seed(&self) -> [u8; 32];
+
+ /// Yields a copy of the metadata-key (size dependent on protocol version)
+ /// to tag advertisement contents sent with this broadcast crypto-material.
+ fn metadata_key(&self) -> V::MetadataKey;
+
+ /// Yields the 16-byte expanded metadata key, suitable for metadata encryption.
+ fn expanded_metadata_key<C: CryptoProvider>(&self) -> MetadataKey {
+ V::expand_metadata_key::<C>(self.metadata_key())
+ }
+
+ /// Constructs the metadata nonce used for encryption of associated credential
+ /// metadata for the identity represented via this crypto material.
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ V::metadata_nonce_from_key_seed::<C>(&self.key_seed())
+ }
+
+ /// Derives a V0 discovery credential from this V0 broadcast crypto-material
+ /// which may be used to discover v0 advertisements broadcasted with this credential.`
+ fn derive_v0_discovery_credential<C: CryptoProvider>(&self) -> V0DiscoveryCredential
+ where
+ V: V0ProtocolVersion,
+ {
+ let key_seed = self.key_seed();
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
+ let metadata_key_hmac =
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(self.metadata_key().as_ref());
+ V0DiscoveryCredential::new(key_seed, metadata_key_hmac)
+ }
+
+ #[cfg(any(test, feature = "alloc"))]
+ /// Encrypts the given plaintext metadata bytes to allow that metadata
+ /// to be shared with receiving devices.
+ fn encrypt_metadata<C: CryptoProvider>(
+ &self,
+ plaintext_metadata: &[u8],
+ ) -> alloc::vec::Vec<u8> {
+ use crypto_provider::{
+ aead::{Aead, AeadInit},
+ aes::Aes128Key,
+ };
+ let plaintext_metadata_key = self.expanded_metadata_key::<C>();
+ let plaintext_metadata_key = Aes128Key::from(plaintext_metadata_key.0);
+
+ let aead =
+ <<C as CryptoProvider>::Aes128Gcm as AeadInit<Aes128Key>>::new(&plaintext_metadata_key);
+ // No additional authenticated data for encrypted metadata.
+ aead.encrypt(plaintext_metadata, &[], &self.metadata_nonce::<C>())
+ .expect("Metadata encryption should be infallible")
+ }
+}
+
+/// Concrete implementation of [`BroadcastCryptoMaterial<V>`] for
+/// a particular protocol version which keeps the key seed
+/// and the metadata key contiguous in memory.
+///
+/// Broadcast crypto-material specified in this way will only
+/// be usable for (unsigned) advertisement content broadcasts
+/// in the given protocol version.
+///
+/// For more flexible expression of broadcast credentials,
+/// feel free to directly implement one or more of the
+/// [`BroadcastCryptoMaterial`] and/or
+/// [`crate::credential::v1::SignedBroadcastCryptoMaterial`]
+/// traits on your own struct, dependent on the details
+/// of your own broadcast credentials.
+pub struct SimpleBroadcastCryptoMaterial<V: ProtocolVersion> {
+ key_seed: [u8; 32],
+ metadata_key: V::MetadataKey,
+}
+
+impl<V: ProtocolVersion> SimpleBroadcastCryptoMaterial<V> {
+ /// Builds some simple broadcast crypto-materials out of
+ /// the provided key-seed and version-specific metadata-key.
+ pub fn new(key_seed: [u8; 32], metadata_key: V::MetadataKey) -> Self {
+ Self { key_seed, metadata_key }
+ }
+}
+
+impl<V: ProtocolVersion> BroadcastCryptoMaterial<V> for SimpleBroadcastCryptoMaterial<V> {
+ fn key_seed(&self) -> [u8; 32] {
+ self.key_seed
+ }
+ fn metadata_key(&self) -> V::MetadataKey {
+ self.metadata_key.clone()
+ }
+}
diff --git a/nearby/presence/np_adv/src/credential/simple.rs b/nearby/presence/np_adv/src/credential/simple.rs
deleted file mode 100644
index 6ea2436..0000000
--- a/nearby/presence/np_adv/src/credential/simple.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Simple implementations of credentials. These can be combined with the provided crypto material
-//! implementations to have a working credential type.
-//!
-//! ```rust
-//! use np_adv::credential::{
-//! simple::SimpleV0Credential,
-//! v0::MinimumFootprintV0CryptoMaterial,
-//! };
-//! type MyV0Credential = SimpleV0Credential<MinimumFootprintV0CryptoMaterial, ()>;
-//! ```
-
-use core::fmt::Debug;
-
-use super::*;
-use super::{v0::*, v1::*};
-
-/// A simple implementation of [`V0Credential`] that wraps a [`V0CryptoMaterial`] and some `T` data that
-/// will be exposed via the [`MatchedCredential`].
-pub struct SimpleV0Credential<C, T>
-where
- C: V0CryptoMaterial,
- T: Debug + Eq,
-{
- material: C,
- match_data: T,
-}
-
-impl<C, T> SimpleV0Credential<C, T>
-where
- C: V0CryptoMaterial,
- T: Debug + Eq,
-{
- /// Construct a new credential.
- ///
- /// `material` will be returned by [V0Credential::crypto_material].
- /// `match_data` will be returned by [SimpleV0Credential::matched], wrapped in [SimpleMatchedCredential].
- pub fn new(material: C, match_data: T) -> Self {
- Self { material, match_data }
- }
-}
-
-impl<C, T> MatchableCredential for SimpleV0Credential<C, T>
-where
- C: V0CryptoMaterial,
- T: Debug + Eq,
-{
- type Matched<'m> = SimpleMatchedCredential<'m, T> where Self: 'm;
-
- fn matched(&'_ self) -> Self::Matched<'_> {
- SimpleMatchedCredential { data: &self.match_data }
- }
-}
-
-impl<C, T> V0Credential for SimpleV0Credential<C, T>
-where
- C: V0CryptoMaterial,
- T: Debug + Eq,
-{
- type CryptoMaterial = C;
-
- fn crypto_material(&self) -> &Self::CryptoMaterial {
- &self.material
- }
-}
-
-/// A simple implementation of [V1Credential] that wraps a [V1CryptoMaterial] and some `T` data that
-/// will be exposed via the [MatchedCredential].
-pub struct SimpleV1Credential<C, T>
-where
- C: V1CryptoMaterial,
- T: Debug + Eq,
-{
- material: C,
- match_data: T,
-}
-
-impl<C, T> SimpleV1Credential<C, T>
-where
- C: V1CryptoMaterial,
- T: Debug + Eq,
-{
- /// Construct a new credential.
- ///
- /// `material` will be returned by [V1Credential::crypto_material].
- /// `match_data` will be returned by [SimpleV1Credential::matched], wrapped in [SimpleMatchedCredential].
- pub fn new(material: C, match_data: T) -> Self {
- Self { material, match_data }
- }
-}
-
-impl<C, T> MatchableCredential for SimpleV1Credential<C, T>
-where
- C: V1CryptoMaterial,
- T: Debug + Eq,
-{
- type Matched<'m> = SimpleMatchedCredential<'m, T> where Self: 'm;
-
- fn matched(&'_ self) -> Self::Matched<'_> {
- SimpleMatchedCredential { data: &self.match_data }
- }
-}
-
-impl<C, T> V1Credential for SimpleV1Credential<C, T>
-where
- C: V1CryptoMaterial,
- T: Debug + Eq,
-{
- type CryptoMaterial = C;
-
- fn crypto_material(&self) -> &Self::CryptoMaterial {
- &self.material
- }
-}
-
-/// The [MatchedCredential] used by [SimpleV0Credential]
-/// and by [SimpleV1Credential].
-#[derive(Debug, PartialEq, Eq)]
-pub struct SimpleMatchedCredential<'m, T: Debug + PartialEq + Eq> {
- data: &'m T,
-}
-
-impl<'m, T: Debug + PartialEq + Eq> SimpleMatchedCredential<'m, T> {
- /// Construct a new instance that wraps `data`.
- pub fn new(data: &'m T) -> Self {
- Self { data }
- }
-
- /// Returns the underlying matched credential data.
- pub fn matched_data(&self) -> &'m T {
- self.data
- }
-}
-
-impl<'m, T: Debug + PartialEq + Eq> MatchedCredential<'m> for SimpleMatchedCredential<'m, T> {}
diff --git a/nearby/presence/np_adv/src/credential/source.rs b/nearby/presence/np_adv/src/credential/source.rs
index e102b7e..709bbd6 100644
--- a/nearby/presence/np_adv/src/credential/source.rs
+++ b/nearby/presence/np_adv/src/credential/source.rs
@@ -12,139 +12,135 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Traits defining sources for credentials. These are used in the deserialization path to provide
-//! the credentials to try. [`SliceCredentialSource`] and [`OwnedCredentialSource`] implementations
-//! are also defined in this module.
+//! Definitions of traits for structures which supply
+//! credentials for discovering advertisements/advertisement
+//! sections for a _particular_ protocol version.
-use super::*;
-use alloc::vec::Vec;
+use crate::credential::{
+ DiscoveryCryptoMaterial, MatchableCredential, MatchedCredential, ProtocolVersion,
+ ReferencedMatchedCredential,
+};
+use core::borrow::Borrow;
-/// A source of credentials to try when decrypting advertisements,
-/// which really just wraps an iterator over a given credential type.
-pub trait CredentialSource<C: MatchableCredential> {
- /// The iterator type produced that emits credentials
- type Iterator<'a>: Iterator<Item = &'a C>
- where
- Self: 'a,
- C: 'a;
+/// Specialized version of the [`CredentialSource`] trait for
+/// credential-sources which provide discovery credentials
+/// for a specific protocol version.
+///
+/// If you want ready-made structures which can provide
+/// credentials for both V0 and V1 protocol versions,
+/// see [`crate::credential::book::CredentialBook`]
+/// and [`crate::credential::book::CredentialBookBuilder`] instead.
+///
+/// It's preferred to use this kind of credential-source
+/// in client code, if possible, and then lift to a
+/// [`CredentialSource`] using [`AsCredentialSource`]
+/// instead of implementing [`CredentialSource`] directly,
+/// since it's better to trust this crate to handle
+/// the details of what's in [`DiscoveryCryptoMaterial`]s
+/// for specific protocol versions.
+pub trait DiscoveryCredentialSource<'a, V: ProtocolVersion>
+where
+ Self: 'a,
+{
+ /// The kind of data yielded to the caller upon a successful
+ /// identity-match.
+ type Matched: MatchedCredential;
+
+ /// The kind of crypto-material yielded from the wrapped
+ /// iterator, which allows borrowing a discovery credential.
+ type Crypto: DiscoveryCryptoMaterial<V> + Borrow<V::DiscoveryCredential>;
+
+ /// The iterator type produced which emits credentials.
+ /// This is a lending iterator which may borrow things from `self`.
+ type Iterator: Iterator<Item = (Self::Crypto, Self::Matched)>;
/// Iterate over the available credentials
- fn iter(&self) -> Self::Iterator<'_>;
+ fn iter(&'a self) -> Self::Iterator;
}
-/// Trait for combined credential sources able to yield credential sources for both V0 and V1.
-pub trait BothCredentialSource<C0, C1>
+/// A source of credentials for a particular protocol version,
+/// utilizing any [`DiscoveryCryptoMaterial`] which is usable
+/// for discovering advertisements in that protocol version.
+///
+/// This trait is largely leveraged as a tool for building
+/// new kinds of [`crate::credential::book::CredentialBook`]s
+/// via the [`crate::credential::book::CredentialBookFromSources`]
+/// wrapper. It differs from the [`DiscoveryCredentialSource`]
+/// trait in that the crypto-materials do not have to be
+/// discovery credentials, and can instead be some pre-calculated
+/// crypto-materials.
+///
+/// See [`crate::credential::book::CachedCredentialSource`]
+/// for an example of this pattern.
+pub trait CredentialSource<'a, V: ProtocolVersion>
where
- C0: V0Credential,
- C1: V1Credential,
+ Self: 'a,
{
- /// The type of the underlying credential-source for v0 credentials
- type V0Source: CredentialSource<C0>;
- /// The type of the underlying credential-source for v1 credentials
- type V1Source: CredentialSource<C1>;
+ /// The kind of data yielded to the caller upon a successful
+ /// identity-match.
+ type Matched: MatchedCredential;
- /// Gets a source for v0 credentials maintained by this `BothCredentialSource`.
- fn v0(&self) -> &Self::V0Source;
+ /// The kind of crypto-material yielded from the wrapped
+ /// iterator.
+ type Crypto: DiscoveryCryptoMaterial<V>;
- /// Gets a source for v1 credentials maintained by this `BothCredentialSource`.
- fn v1(&self) -> &Self::V1Source;
+ /// The iterator type produced which emits credentials.
+ /// This is a lending iterator which may borrow things from `self`.
+ type Iterator: Iterator<Item = (Self::Crypto, Self::Matched)>;
- /// Convenient function alias to [`self.v0().iter()`] for iterating
- /// over v0 credentials.
- fn iter_v0(&self) -> <Self::V0Source as CredentialSource<C0>>::Iterator<'_> {
- self.v0().iter()
- }
+ /// Iterate over the available credentials
+ fn iter(&'a self) -> Self::Iterator;
+}
- /// Convenient function alias to the [`CredentialSource<C1>#iter()`] for iterating
- /// over v0 credentials.
- fn iter_v1(&self) -> <Self::V1Source as CredentialSource<C1>>::Iterator<'_> {
- self.v1().iter()
+// Note: This is needed to get around coherence problems
+// with the [`CredentialSource`] trait's relationship
+// with [`DiscoveryCredentialSource`] if it were declared
+// as a sub-trait (i.e: conflicting impls)
+/// Wrapper which turns any [`DiscoveryCredentialSource`]
+/// into a [`CredentialSource`].
+pub struct AsCredentialSource<S>(pub S);
+
+impl<'a, V: ProtocolVersion, S: DiscoveryCredentialSource<'a, V>> CredentialSource<'a, V>
+ for AsCredentialSource<S>
+{
+ type Matched = <S as DiscoveryCredentialSource<'a, V>>::Matched;
+ type Crypto = <S as DiscoveryCredentialSource<'a, V>>::Crypto;
+ type Iterator = <S as DiscoveryCredentialSource<'a, V>>::Iterator;
+
+ fn iter(&'a self) -> Self::Iterator {
+ self.0.iter()
}
}
-/// A simple [CredentialSource] that just iterates over a provided slice of credentials
-pub struct SliceCredentialSource<'c, C: MatchableCredential> {
- credentials: &'c [C],
+/// A simple [`DiscoveryCredentialSource`] which iterates over a provided slice of credentials
+pub struct SliceCredentialSource<'c, V: ProtocolVersion, M: MatchedCredential> {
+ credentials: &'c [MatchableCredential<V, M>],
}
-impl<'c, C: MatchableCredential> SliceCredentialSource<'c, C> {
- /// Construct the credential source from the provided credentials.
- pub fn new(credentials: &'c [C]) -> Self {
+impl<'c, V: ProtocolVersion, M: MatchedCredential> SliceCredentialSource<'c, V, M> {
+ /// Construct the credential supplier from the provided slice of credentials.
+ pub fn new(credentials: &'c [MatchableCredential<V, M>]) -> Self {
Self { credentials }
}
}
-impl<'c, C: MatchableCredential> CredentialSource<C> for SliceCredentialSource<'c, C> {
- type Iterator<'i> = core::slice::Iter<'i, C>
- where Self: 'i;
-
- fn iter(&'_ self) -> Self::Iterator<'_> {
- self.credentials.iter()
- }
-}
-
-/// A simple credential source which owns all of its credentials.
-pub struct OwnedCredentialSource<C: MatchableCredential> {
- credentials: Vec<C>,
-}
-
-impl<C: MatchableCredential> OwnedCredentialSource<C> {
- /// Constructs an owned credential source from the given credentials
- pub fn new(credentials: Vec<C>) -> Self {
- Self { credentials }
- }
-}
-
-impl<C: MatchableCredential> CredentialSource<C> for OwnedCredentialSource<C> {
- type Iterator<'i> = core::slice::Iter<'i, C>
- where Self: 'i;
-
- fn iter(&'_ self) -> Self::Iterator<'_> {
- self.credentials.iter()
- }
-}
-
-/// An owned credential source for both v0 and v1 credentials,
-pub struct OwnedBothCredentialSource<C0, C1>
+impl<'a, 'b, V: ProtocolVersion, M: MatchedCredential> DiscoveryCredentialSource<'a, V>
+ for SliceCredentialSource<'b, V, M>
where
- C0: V0Credential,
- C1: V1Credential,
+ 'b: 'a,
+ Self: 'b,
+ &'a <V as ProtocolVersion>::DiscoveryCredential: DiscoveryCryptoMaterial<V>,
{
- v0_source: OwnedCredentialSource<C0>,
- v1_source: OwnedCredentialSource<C1>,
-}
+ type Matched = ReferencedMatchedCredential<'a, M>;
+ type Crypto = &'a V::DiscoveryCredential;
+ type Iterator = core::iter::Map<
+ core::slice::Iter<'a, MatchableCredential<V, M>>,
+ fn(
+ &'a MatchableCredential<V, M>,
+ ) -> (&'a V::DiscoveryCredential, ReferencedMatchedCredential<M>),
+ >;
-impl<C0, C1> OwnedBothCredentialSource<C0, C1>
-where
- C0: V0Credential,
- C1: V1Credential,
-{
- /// Creates a new `OwnedBothCredentialSource` from credential-lists
- /// for both V0 and V1
- pub fn new(v0_credentials: Vec<C0>, v1_credentials: Vec<C1>) -> Self {
- let v0_source = OwnedCredentialSource::new(v0_credentials);
- let v1_source = OwnedCredentialSource::new(v1_credentials);
- Self { v0_source, v1_source }
- }
-
- /// Creates a new credential source that is empty.
- pub fn new_empty() -> Self {
- Self::new(Vec::new(), Vec::new())
- }
-}
-
-impl<C0, C1> BothCredentialSource<C0, C1> for OwnedBothCredentialSource<C0, C1>
-where
- C0: V0Credential,
- C1: V1Credential,
-{
- type V0Source = OwnedCredentialSource<C0>;
- type V1Source = OwnedCredentialSource<C1>;
-
- fn v0(&self) -> &Self::V0Source {
- &self.v0_source
- }
- fn v1(&self) -> &Self::V1Source {
- &self.v1_source
+ fn iter(&'a self) -> Self::Iterator {
+ self.credentials.iter().map(MatchableCredential::<V, M>::as_pair)
}
}
diff --git a/nearby/presence/np_adv/src/credential/tests.rs b/nearby/presence/np_adv/src/credential/tests.rs
new file mode 100644
index 0000000..297fdb5
--- /dev/null
+++ b/nearby/presence/np_adv/src/credential/tests.rs
@@ -0,0 +1,206 @@
+// Copyright 2023 Google LLC
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Tests of functionality related to credentials, credential-views, and credential suppliers.
+
+use crate::credential::{
+ book::{
+ init_cache_from_source, CachedCredentialSource, PossiblyCachedDiscoveryCryptoMaterialKind,
+ },
+ source::{CredentialSource, SliceCredentialSource},
+ v0::{V0DiscoveryCredential, V0},
+ v1::{
+ SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial, V1DiscoveryCredential,
+ V1DiscoveryCryptoMaterial, V1,
+ },
+ BroadcastCryptoMaterial, EmptyMatchedCredential, KeySeedMatchedCredential, MatchableCredential,
+ MetadataDecryptionError, ProtocolVersion, ReferencedMatchedCredential,
+ SimpleBroadcastCryptoMaterial,
+};
+use crate::legacy::ShortMetadataKey;
+use crate::MetadataKey;
+use alloc::{vec, vec::Vec};
+use crypto_provider_default::CryptoProviderImpl;
+
+fn get_zeroed_v0_discovery_credential() -> V0DiscoveryCredential {
+ V0DiscoveryCredential::new([0u8; 32], [0u8; 32])
+}
+
+fn get_constant_packed_v1_discovery_credential(value: u8) -> V1DiscoveryCredential {
+ let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate();
+ SimpleSignedBroadcastCryptoMaterial::new(
+ [value; 32],
+ MetadataKey([value; 16]),
+ // NOTE: This winds up being unused in these test cases
+ key_pair.private_key(),
+ )
+ .derive_v1_discovery_credential::<CryptoProviderImpl>()
+}
+
+#[test]
+fn cached_credential_source_keeps_same_entries_as_original() {
+ let creds: [MatchableCredential<V1, KeySeedMatchedCredential>; 5] =
+ [0u8, 1, 2, 3, 4].map(|x| {
+ let match_data = KeySeedMatchedCredential::from([x; 32]);
+ MatchableCredential {
+ discovery_credential: get_constant_packed_v1_discovery_credential(x),
+ match_data,
+ }
+ });
+ let supplier = SliceCredentialSource::new(&creds);
+ let cache = init_cache_from_source::<_, _, 3, CryptoProviderImpl>(&supplier);
+ let cached = CachedCredentialSource::new(supplier, cache);
+ let cached_view = &cached;
+ assert_eq!(cached_view.iter().count(), 5);
+ // Now we're going to check that the pairings between the match-data
+ // and the MIC hmac key wind up being the same between the original
+ // creds list and what's provided by the cached source.
+ let expected: Vec<_> = creds
+ .iter()
+ .map(|cred| {
+ (
+ cred.discovery_credential
+ .unsigned_verification_material::<CryptoProviderImpl>()
+ .mic_hmac_key,
+ ReferencedMatchedCredential::from(&cred.match_data),
+ )
+ })
+ .collect();
+ let actual: Vec<_> = cached_view
+ .iter()
+ .map(|(crypto_material, match_data)| {
+ (
+ crypto_material.unsigned_verification_material::<CryptoProviderImpl>().mic_hmac_key,
+ match_data,
+ )
+ })
+ .collect();
+ assert_eq!(actual, expected);
+}
+
+#[test]
+fn cached_credential_source_has_requested_cache_size() {
+ let creds: [MatchableCredential<V0, EmptyMatchedCredential>; 10] =
+ [0u8; 10].map(|_| MatchableCredential {
+ discovery_credential: get_zeroed_v0_discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ });
+ let supplier = SliceCredentialSource::new(&creds);
+ let cache = init_cache_from_source::<_, _, 5, CryptoProviderImpl>(&supplier);
+ let cached = CachedCredentialSource::new(supplier, cache);
+ let cached_view = &cached;
+ assert_eq!(cached_view.iter().count(), 10);
+ for (i, (cred, _)) in cached_view.iter().enumerate() {
+ if i < 5 {
+ // Should be cached
+ if let PossiblyCachedDiscoveryCryptoMaterialKind::Precalculated(_) = cred.wrapped {
+ } else {
+ panic!("Credential #{} was not cached", i);
+ }
+ } else {
+ // Should be discovery credentials
+ if let PossiblyCachedDiscoveryCryptoMaterialKind::Discovery(_) = cred.wrapped {
+ } else {
+ panic!("Credential #{} was not supposed to be cached", i);
+ }
+ }
+ }
+}
+
+#[test]
+fn v0_metadata_decryption_works_same_metadata_key() {
+ let key_seed = [3u8; 32];
+ let metadata_key = ShortMetadataKey([5u8; 14]);
+
+ let metadata = vec![7u8; 42];
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
+
+ let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata);
+
+ let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>();
+
+ let decryption_result = V0::decrypt_metadata::<CryptoProviderImpl>(
+ metadata_nonce,
+ metadata_key,
+ &encrypted_metadata,
+ );
+ assert_eq!(decryption_result, Ok(metadata))
+}
+
+#[test]
+fn v1_metadata_decryption_works_same_metadata_key() {
+ let key_seed = [9u8; 32];
+ let metadata_key = MetadataKey([2u8; 16]);
+
+ let metadata = vec![6u8; 51];
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
+ let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata);
+
+ let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>();
+
+ let decryption_result = V1::decrypt_metadata::<CryptoProviderImpl>(
+ metadata_nonce,
+ metadata_key,
+ &encrypted_metadata,
+ );
+ assert_eq!(decryption_result, Ok(metadata))
+}
+
+#[test]
+fn v0_metadata_decryption_fails_different_metadata_key() {
+ let key_seed = [3u8; 32];
+ let encrypting_metadata_key = ShortMetadataKey([5u8; 14]);
+
+ let metadata = vec![7u8; 42];
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, encrypting_metadata_key);
+
+ let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata);
+
+ let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>();
+
+ let decrypting_metadata_key = ShortMetadataKey([6u8; 14]);
+
+ let decryption_result = V0::decrypt_metadata::<CryptoProviderImpl>(
+ metadata_nonce,
+ decrypting_metadata_key,
+ &encrypted_metadata,
+ );
+ assert_eq!(decryption_result, Err(MetadataDecryptionError))
+}
+
+#[test]
+fn v1_metadata_decryption_fails_different_metadata_key() {
+ let key_seed = [251u8; 32];
+ let encrypting_metadata_key = MetadataKey([127u8; 16]);
+
+ let metadata = vec![255u8; 42];
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, encrypting_metadata_key);
+
+ let encrypted_metadata = broadcast_cm.encrypt_metadata::<CryptoProviderImpl>(&metadata);
+
+ let metadata_nonce = broadcast_cm.metadata_nonce::<CryptoProviderImpl>();
+
+ let decrypting_metadata_key = MetadataKey([249u8; 16]);
+
+ let decryption_result = V1::decrypt_metadata::<CryptoProviderImpl>(
+ metadata_nonce,
+ decrypting_metadata_key,
+ &encrypted_metadata,
+ );
+ assert_eq!(decryption_result, Err(MetadataDecryptionError))
+}
diff --git a/nearby/presence/np_adv/src/credential/v0.rs b/nearby/presence/np_adv/src/credential/v0.rs
index e2d1b5d..c89909d 100644
--- a/nearby/presence/np_adv/src/credential/v0.rs
+++ b/nearby/presence/np_adv/src/credential/v0.rs
@@ -13,32 +13,33 @@
// limitations under the License.
//! Cryptographic materials for v0 advertisement-format credentials.
+use crate::credential::{protocol_version_seal, DiscoveryCryptoMaterial, ProtocolVersion};
+use crate::legacy::ShortMetadataKey;
+use crate::MetadataKey;
+use crypto_provider::{CryptoProvider, CryptoRng};
-use super::*;
-
-/// Cryptographic material for an individual NP credential used to decrypt v0 advertisements.
-// Space-time tradeoffs:
-// - LDT keys (64b) take about 1.4us.
-pub trait V0CryptoMaterial {
- /// Returns an LDT NP advertisement cipher built with the provided `Aes`
- fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>;
-}
-
-/// A [`V0CryptoMaterial`] that minimizes memory footprint at the expense of CPU time when
-/// providing derived key material
-pub struct MinimumFootprintV0CryptoMaterial {
+/// Cryptographic information about a particular V0 discovery credential
+/// necessary to match and decrypt encrypted V0 advertisements.
+#[derive(Clone)]
+pub struct V0DiscoveryCredential {
key_seed: [u8; 32],
legacy_metadata_key_hmac: [u8; 32],
}
-impl MinimumFootprintV0CryptoMaterial {
- /// Construct an [MinimumFootprintV0CryptoMaterial] from the provided identity data.
+impl V0DiscoveryCredential {
+ /// Construct an [V0DiscoveryCredential] from the provided identity data.
pub fn new(key_seed: [u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self {
Self { key_seed, legacy_metadata_key_hmac }
}
}
-impl V0CryptoMaterial for MinimumFootprintV0CryptoMaterial {
+impl DiscoveryCryptoMaterial<V0> for V0DiscoveryCredential {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ V0::metadata_nonce_from_key_seed::<C>(&self.key_seed)
+ }
+}
+
+impl V0DiscoveryCryptoMaterial for V0DiscoveryCredential {
fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
let hkdf = np_hkdf::NpKeySeedHkdf::new(&self.key_seed);
ldt_np_adv::build_np_adv_decrypter(
@@ -49,27 +50,73 @@
}
}
-/// [`V0CryptoMaterial`] that minimizes CPU time when providing key material at
+/// Type-level identifier for the V0 protocol version.
+#[derive(Debug, Clone)]
+pub enum V0 {}
+
+impl protocol_version_seal::ProtocolVersionSeal for V0 {}
+
+impl ProtocolVersion for V0 {
+ type DiscoveryCredential = V0DiscoveryCredential;
+ type MetadataKey = ShortMetadataKey;
+
+ fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12] {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed);
+ hkdf.legacy_metadata_nonce()
+ }
+ fn expand_metadata_key<C: CryptoProvider>(metadata_key: ShortMetadataKey) -> MetadataKey {
+ metadata_key.expand::<C>()
+ }
+ fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> ShortMetadataKey {
+ ShortMetadataKey(rng.gen())
+ }
+}
+
+/// Trait which exists purely to be able to restrict the protocol
+/// version of certain type-bounds to V0.
+pub trait V0ProtocolVersion: ProtocolVersion {}
+
+impl V0ProtocolVersion for V0 {}
+
+/// Cryptographic material for an individual NP credential used to decrypt v0 advertisements.
+/// Unlike [`V0DiscoveryCredential`], derived information about cryptographic materials may
+/// be stored in implementors of this trait.
+// Space-time tradeoffs:
+// - LDT keys (64b) take about 1.4us.
+pub trait V0DiscoveryCryptoMaterial: DiscoveryCryptoMaterial<V0> {
+ /// Returns an LDT NP advertisement cipher built with the provided `Aes`
+ fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>;
+}
+
+/// [`V0DiscoveryCryptoMaterial`] that minimizes CPU time when providing key material at
/// the expense of occupied memory.
-pub struct PrecalculatedV0CryptoMaterial {
+pub struct PrecalculatedV0DiscoveryCryptoMaterial {
pub(crate) legacy_ldt_key: ldt::LdtKey<xts_aes::XtsAes128Key>,
pub(crate) legacy_metadata_key_hmac: [u8; 32],
pub(crate) legacy_metadata_key_hmac_key: [u8; 32],
+ pub(crate) metadata_nonce: [u8; 12],
}
-impl PrecalculatedV0CryptoMaterial {
+impl PrecalculatedV0DiscoveryCryptoMaterial {
/// Construct a new instance from the provided credential material.
- pub fn new<C: CryptoProvider>(key_seed: &[u8; 32], legacy_metadata_key_hmac: [u8; 32]) -> Self {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed);
+ pub(crate) fn new<C: CryptoProvider>(discovery_credential: &V0DiscoveryCredential) -> Self {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&discovery_credential.key_seed);
Self {
legacy_ldt_key: hkdf.legacy_ldt_key(),
- legacy_metadata_key_hmac,
+ legacy_metadata_key_hmac: discovery_credential.legacy_metadata_key_hmac,
legacy_metadata_key_hmac_key: *hkdf.legacy_metadata_key_hmac_key().as_bytes(),
+ metadata_nonce: hkdf.legacy_metadata_nonce(),
}
}
}
-impl V0CryptoMaterial for PrecalculatedV0CryptoMaterial {
+impl DiscoveryCryptoMaterial<V0> for PrecalculatedV0DiscoveryCryptoMaterial {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ self.metadata_nonce
+ }
+}
+
+impl V0DiscoveryCryptoMaterial for PrecalculatedV0DiscoveryCryptoMaterial {
fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
ldt_np_adv::build_np_adv_decrypter(
&self.legacy_ldt_key,
@@ -78,3 +125,30 @@
)
}
}
+
+// Implementations for reference types -- we don't provide a blanket impl for references
+// due to the potential to conflict with downstream crates' implementations.
+
+impl<'a> DiscoveryCryptoMaterial<V0> for &'a V0DiscoveryCredential {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ (*self).metadata_nonce::<C>()
+ }
+}
+
+impl<'a> V0DiscoveryCryptoMaterial for &'a V0DiscoveryCredential {
+ fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
+ (*self).ldt_adv_cipher::<C>()
+ }
+}
+
+impl<'a> DiscoveryCryptoMaterial<V0> for &'a PrecalculatedV0DiscoveryCryptoMaterial {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ (*self).metadata_nonce::<C>()
+ }
+}
+
+impl<'a> V0DiscoveryCryptoMaterial for &'a PrecalculatedV0DiscoveryCryptoMaterial {
+ fn ldt_adv_cipher<C: CryptoProvider>(&self) -> ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C> {
+ (*self).ldt_adv_cipher::<C>()
+ }
+}
diff --git a/nearby/presence/np_adv/src/credential/v1.rs b/nearby/presence/np_adv/src/credential/v1.rs
index 4b134e4..7d060f2 100644
--- a/nearby/presence/np_adv/src/credential/v1.rs
+++ b/nearby/presence/np_adv/src/credential/v1.rs
@@ -14,17 +14,133 @@
//! Cryptographic materials for v1 advertisement-format credentials.
-use core::borrow::Borrow;
-use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider};
+use crate::credential::{
+ protocol_version_seal, BroadcastCryptoMaterial, DiscoveryCryptoMaterial, ProtocolVersion,
+};
+use crate::MetadataKey;
+use crypto_provider::{aes::Aes128Key, ed25519, CryptoProvider, CryptoRng};
use np_hkdf::UnsignedSectionKeys;
+/// Cryptographic information about a particular V1 discovery credential
+/// necessary to match and decrypt encrypted V1 sections.
+#[derive(Clone)]
+pub struct V1DiscoveryCredential {
+ key_seed: [u8; 32],
+ expected_unsigned_metadata_key_hmac: [u8; 32],
+ expected_signed_metadata_key_hmac: [u8; 32],
+ pub_key: ed25519::RawPublicKey,
+}
+impl V1DiscoveryCredential {
+ /// Construct a V1 discovery credential from the provided identity data.
+ pub fn new<C: CryptoProvider>(
+ key_seed: [u8; 32],
+ expected_unsigned_metadata_key_hmac: [u8; 32],
+ expected_signed_metadata_key_hmac: [u8; 32],
+ pub_key: np_ed25519::PublicKey<C>,
+ ) -> Self {
+ Self {
+ key_seed,
+ expected_unsigned_metadata_key_hmac,
+ expected_signed_metadata_key_hmac,
+ pub_key: pub_key.to_bytes(),
+ }
+ }
+
+ /// Constructs pre-calculated crypto material from this discovery credential.
+ pub(crate) fn to_precalculated<C: CryptoProvider>(
+ &self,
+ ) -> PrecalculatedV1DiscoveryCryptoMaterial {
+ let signed_identity_resolution_material = self.signed_identity_resolution_material::<C>();
+ let unsigned_identity_resolution_material =
+ self.unsigned_identity_resolution_material::<C>();
+ let signed_verification_material = self.signed_verification_material::<C>();
+ let unsigned_verification_material = self.unsigned_verification_material::<C>();
+ let metadata_nonce = self.metadata_nonce::<C>();
+ PrecalculatedV1DiscoveryCryptoMaterial {
+ signed_identity_resolution_material,
+ unsigned_identity_resolution_material,
+ signed_verification_material,
+ unsigned_verification_material,
+ metadata_nonce,
+ }
+ }
+}
+
+impl DiscoveryCryptoMaterial<V1> for V1DiscoveryCredential {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ V1::metadata_nonce_from_key_seed::<C>(&self.key_seed)
+ }
+}
+
+impl V1DiscoveryCryptoMaterial for V1DiscoveryCredential {
+ fn signed_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> SignedSectionIdentityResolutionMaterial {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
+ SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
+ &hkdf,
+ self.expected_signed_metadata_key_hmac,
+ )
+ }
+
+ fn unsigned_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionIdentityResolutionMaterial {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
+ UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
+ &hkdf,
+ self.expected_unsigned_metadata_key_hmac,
+ )
+ }
+
+ fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
+ SignedSectionVerificationMaterial { pub_key: self.pub_key }
+ }
+
+ fn unsigned_verification_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionVerificationMaterial {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
+ let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&hkdf).as_bytes();
+ UnsignedSectionVerificationMaterial { mic_hmac_key }
+ }
+}
+
+/// Type-level identifier for the V1 protocol version.
+#[derive(Debug, Clone)]
+pub enum V1 {}
+
+impl protocol_version_seal::ProtocolVersionSeal for V1 {}
+
+impl ProtocolVersion for V1 {
+ type DiscoveryCredential = V1DiscoveryCredential;
+ type MetadataKey = MetadataKey;
+
+ fn metadata_nonce_from_key_seed<C: CryptoProvider>(key_seed: &[u8; 32]) -> [u8; 12] {
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(key_seed);
+ hkdf.extended_metadata_nonce()
+ }
+ fn expand_metadata_key<C: CryptoProvider>(metadata_key: MetadataKey) -> MetadataKey {
+ metadata_key
+ }
+ fn gen_random_metadata_key<R: CryptoRng>(rng: &mut R) -> MetadataKey {
+ MetadataKey(rng.gen())
+ }
+}
+
+/// Trait which exists purely to be able to restrict the protocol
+/// version of certain type-bounds to V1.
+pub trait V1ProtocolVersion: ProtocolVersion {}
+
+impl V1ProtocolVersion for V1 {}
+
/// Cryptographic materials necessary for determining whether or not
/// a given V1 advertisement section matches an identity.
/// Per the construction of the V1 specification, this is also
/// the information necessary to decrypt the raw byte contents
/// of an encrypted V1 section.
#[derive(Clone)]
-pub struct SectionIdentityResolutionMaterial {
+pub(crate) struct SectionIdentityResolutionMaterial {
/// The AES key for decrypting section ciphertext
pub(crate) aes_key: Aes128Key,
/// The metadata key HMAC key for deriving and verifying the identity metadata
@@ -44,9 +160,21 @@
pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self {
Self(raw)
}
+ /// Extracts the underlying section-identity resolution material carried around
+ /// within this wrapper for resolution of signed sections.
+ pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial {
+ self.0
+ }
+ #[cfg(any(test, feature = "devtools"))]
+ /// Gets the underlying section-identity resolution material carried around
+ /// within this wrapper for resolution of signed sections.
pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial {
&self.0
}
+
+ /// Constructs identity-resolution material for a signed section whose
+ /// discovery credential leverages the provided HKDF and has the given
+ /// expected metadata-key HMAC.
pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>(
hkdf: &np_hkdf::NpKeySeedHkdf<C>,
expected_metadata_key_hmac: [u8; 32],
@@ -69,9 +197,20 @@
pub(crate) fn from_raw(raw: SectionIdentityResolutionMaterial) -> Self {
Self(raw)
}
+ /// Extracts the underlying section-identity resolution material carried around
+ /// within this wrapper for resolution of unsigned sections.
+ pub(crate) fn into_raw_resolution_material(self) -> SectionIdentityResolutionMaterial {
+ self.0
+ }
+ /// Gets the underlying section-identity resolution material carried around
+ /// within this wrapper for resolution of unsigned sections.
+ #[cfg(any(test, feature = "devtools"))]
pub(crate) fn as_raw_resolution_material(&self) -> &SectionIdentityResolutionMaterial {
&self.0
}
+ /// Constructs identity-resolution material for an unsigned (MIC) section whose
+ /// discovery credential leverages the provided HKDF and has the given
+ /// expected metadata-key HMAC.
pub(crate) fn from_hkdf_and_expected_metadata_key_hmac<C: CryptoProvider>(
hkdf: &np_hkdf::NpKeySeedHkdf<C>,
expected_metadata_key_hmac: [u8; 32],
@@ -129,88 +268,54 @@
// is only used on the matching identity, not all identities.
/// Cryptographic material for an individual NP credential used to decrypt and verify v1 sections.
-pub trait V1CryptoMaterial {
- /// The return type of `Self::signed_identity_resolution_material`, which is some
- /// data-type which allows borrowing `SignedSectionIdentityResolutionMaterial`
- type SignedIdentityResolverReference<'a>: Borrow<SignedSectionIdentityResolutionMaterial>
- where
- Self: 'a;
- /// The return type of `Self::unsigned_identity_resolution_material`, which is some
- /// data-type which allows borrowing `UnsignedSectionIdentityResolutionMaterial`
- type UnsignedIdentityResolverReference<'a>: Borrow<UnsignedSectionIdentityResolutionMaterial>
- where
- Self: 'a;
-
- /// Constructs or references the identity resolution material for signed sections
+pub trait V1DiscoveryCryptoMaterial: DiscoveryCryptoMaterial<V1> {
+ /// Constructs or copies the identity resolution material for signed sections
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::SignedIdentityResolverReference<'_>;
+ ) -> SignedSectionIdentityResolutionMaterial;
- /// Constructs or references the identity resolution material for unsigned sections
+ /// Constructs or copies the identity resolution material for unsigned sections
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::UnsignedIdentityResolverReference<'_>;
+ ) -> UnsignedSectionIdentityResolutionMaterial;
/// Constructs or copies non-identity-resolution deserialization material for signed
/// sections.
- ///
- /// Note: We mandate "copies" here due to the relatively small size of verification-only crypto
- /// materials (32 bytes).
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial;
/// Constructs or copies non-identity-resolution deserialization material for unsigned
/// sections.
- ///
- /// Note: We mandate "copies" here due to the relatively small size of verification-only crypto
- /// materials (32 bytes).
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial;
-
- /// Constructs pre-calculated crypto material out of this crypto-material.
- fn to_precalculated<C: CryptoProvider>(self) -> PrecalculatedV1CryptoMaterial
- where
- Self: Sized,
- {
- let signed_identity_resolution_material =
- self.signed_identity_resolution_material::<C>().borrow().clone();
- let unsigned_identity_resolution_material =
- self.unsigned_identity_resolution_material::<C>().borrow().clone();
- let signed_verification_material = self.signed_verification_material::<C>();
- let unsigned_verification_material = self.unsigned_verification_material::<C>();
- PrecalculatedV1CryptoMaterial {
- signed_identity_resolution_material,
- unsigned_identity_resolution_material,
- signed_verification_material,
- unsigned_verification_material,
- }
- }
}
-/// [`V1CryptoMaterial`] that minimizes CPU time when providing key material at
+/// V1 [`DiscoveryCryptoMaterial`] that minimizes CPU time when providing key material at
/// the expense of occupied memory
-pub struct PrecalculatedV1CryptoMaterial {
+pub struct PrecalculatedV1DiscoveryCryptoMaterial {
pub(crate) signed_identity_resolution_material: SignedSectionIdentityResolutionMaterial,
pub(crate) unsigned_identity_resolution_material: UnsignedSectionIdentityResolutionMaterial,
pub(crate) signed_verification_material: SignedSectionVerificationMaterial,
pub(crate) unsigned_verification_material: UnsignedSectionVerificationMaterial,
+ pub(crate) metadata_nonce: [u8; 12],
}
-impl V1CryptoMaterial for PrecalculatedV1CryptoMaterial {
- type SignedIdentityResolverReference<'a> = &'a SignedSectionIdentityResolutionMaterial
- where Self: 'a;
- type UnsignedIdentityResolverReference<'a> = &'a UnsignedSectionIdentityResolutionMaterial
- where Self: 'a;
+impl DiscoveryCryptoMaterial<V1> for PrecalculatedV1DiscoveryCryptoMaterial {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ self.metadata_nonce
+ }
+}
+impl V1DiscoveryCryptoMaterial for PrecalculatedV1DiscoveryCryptoMaterial {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::SignedIdentityResolverReference<'_> {
- &self.signed_identity_resolution_material
+ ) -> SignedSectionIdentityResolutionMaterial {
+ self.signed_identity_resolution_material.clone()
}
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::UnsignedIdentityResolverReference<'_> {
- &self.unsigned_identity_resolution_material
+ ) -> UnsignedSectionIdentityResolutionMaterial {
+ self.unsigned_identity_resolution_material.clone()
}
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
self.signed_verification_material.clone()
@@ -222,67 +327,126 @@
}
}
-/// [`V1CryptoMaterial`] that minimizes memory footprint at the expense of CPU
-/// time when providing derived key material.
-pub struct MinimumFootprintV1CryptoMaterial {
- key_seed: [u8; 32],
- expected_unsigned_metadata_key_hmac: [u8; 32],
- expected_signed_metadata_key_hmac: [u8; 32],
- pub_key: ed25519::RawPublicKey,
-}
+// Implementations for reference types -- we don't provide a blanket impl for references
+// due to the potential to conflict with downstream crates' implementations.
-impl MinimumFootprintV1CryptoMaterial {
- /// Construct an [MinimumFootprintV1CryptoMaterial] from the provided identity data.
- pub fn new<C: CryptoProvider>(
- key_seed: [u8; 32],
- expected_unsigned_metadata_key_hmac: [u8; 32],
- expected_signed_metadata_key_hmac: [u8; 32],
- pub_key: np_ed25519::PublicKey<C>,
- ) -> Self {
- Self {
- key_seed,
- expected_unsigned_metadata_key_hmac,
- expected_signed_metadata_key_hmac,
- pub_key: pub_key.to_bytes(),
- }
+impl<'a> DiscoveryCryptoMaterial<V1> for &'a V1DiscoveryCredential {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ (*self).metadata_nonce::<C>()
}
}
-impl V1CryptoMaterial for MinimumFootprintV1CryptoMaterial {
- type SignedIdentityResolverReference<'a> = SignedSectionIdentityResolutionMaterial
- where Self: 'a;
- type UnsignedIdentityResolverReference<'a> = UnsignedSectionIdentityResolutionMaterial
- where Self: 'a;
-
+impl<'a> V1DiscoveryCryptoMaterial for &'a V1DiscoveryCredential {
fn signed_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::SignedIdentityResolverReference<'_> {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
- SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
- &hkdf,
- self.expected_signed_metadata_key_hmac,
- )
+ ) -> SignedSectionIdentityResolutionMaterial {
+ (*self).signed_identity_resolution_material::<C>()
}
-
fn unsigned_identity_resolution_material<C: CryptoProvider>(
&self,
- ) -> Self::UnsignedIdentityResolverReference<'_> {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
- UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac(
- &hkdf,
- self.expected_unsigned_metadata_key_hmac,
- )
+ ) -> UnsignedSectionIdentityResolutionMaterial {
+ (*self).unsigned_identity_resolution_material::<C>()
}
-
fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
- SignedSectionVerificationMaterial { pub_key: self.pub_key }
+ (*self).signed_verification_material::<C>()
}
-
fn unsigned_verification_material<C: CryptoProvider>(
&self,
) -> UnsignedSectionVerificationMaterial {
- let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed);
- let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&hkdf).as_bytes();
- UnsignedSectionVerificationMaterial { mic_hmac_key }
+ (*self).unsigned_verification_material::<C>()
+ }
+}
+
+impl<'a> DiscoveryCryptoMaterial<V1> for &'a PrecalculatedV1DiscoveryCryptoMaterial {
+ fn metadata_nonce<C: CryptoProvider>(&self) -> [u8; 12] {
+ (*self).metadata_nonce::<C>()
+ }
+}
+
+impl<'a> V1DiscoveryCryptoMaterial for &'a PrecalculatedV1DiscoveryCryptoMaterial {
+ fn signed_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> SignedSectionIdentityResolutionMaterial {
+ (*self).signed_identity_resolution_material::<C>()
+ }
+ fn unsigned_identity_resolution_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionIdentityResolutionMaterial {
+ (*self).unsigned_identity_resolution_material::<C>()
+ }
+ fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
+ (*self).signed_verification_material::<C>()
+ }
+ fn unsigned_verification_material<C: CryptoProvider>(
+ &self,
+ ) -> UnsignedSectionVerificationMaterial {
+ (*self).unsigned_verification_material::<C>()
+ }
+}
+
+/// Extension of [`BroadcastCryptoMaterial`] for `V1` to add
+/// crypto-materials which are necessary to sign V1 sections.
+pub trait SignedBroadcastCryptoMaterial: BroadcastCryptoMaterial<V1> {
+ /// Gets the advertisement-signing private key for constructing
+ /// signature-verified V1 sections.
+ ///
+ /// The private key is returned in an opaque, crypto-provider-independent
+ /// form to provide a safeguard against leaking the bytes of the key.
+ fn signing_key(&self) -> ed25519::PrivateKey;
+
+ /// Constructs the V1 discovery credential which may be used to discover
+ /// V1 advertisement sections broadcasted using this broadcast crypto-material
+ fn derive_v1_discovery_credential<C: CryptoProvider>(&self) -> V1DiscoveryCredential {
+ let key_seed = self.key_seed();
+ let metadata_key = self.metadata_key();
+ let pub_key = self.signing_key().derive_public_key::<C::Ed25519>();
+ let pub_key = np_ed25519::PublicKey::<C>::new(pub_key);
+
+ let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
+ let unsigned = hkdf
+ .extended_unsigned_metadata_key_hmac_key()
+ .calculate_hmac(metadata_key.0.as_slice());
+ let signed =
+ hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(metadata_key.0.as_slice());
+ V1DiscoveryCredential::new(key_seed, unsigned, signed, pub_key)
+ }
+}
+
+/// Concrete implementation of a [`SignedBroadcastCryptoMaterial`] which keeps the key
+/// seed, the V1 metadata key, and the signing key contiguous in memory.
+///
+/// For more flexible expression of broadcast
+/// credentials, feel free to directly implement [`SignedBroadcastCryptoMaterial`]
+/// for your own broadcast-credential-storing data-type.
+pub struct SimpleSignedBroadcastCryptoMaterial {
+ key_seed: [u8; 32],
+ metadata_key: MetadataKey,
+ signing_key: ed25519::PrivateKey,
+}
+
+impl SimpleSignedBroadcastCryptoMaterial {
+ /// Builds some simple V1 signed broadcast crypto-materials out of
+ /// the provided key-seed, metadata-key, and signing key.
+ pub fn new(
+ key_seed: [u8; 32],
+ metadata_key: MetadataKey,
+ signing_key: ed25519::PrivateKey,
+ ) -> Self {
+ Self { key_seed, metadata_key, signing_key }
+ }
+}
+
+impl BroadcastCryptoMaterial<V1> for SimpleSignedBroadcastCryptoMaterial {
+ fn key_seed(&self) -> [u8; 32] {
+ self.key_seed
+ }
+ fn metadata_key(&self) -> MetadataKey {
+ self.metadata_key
+ }
+}
+
+impl SignedBroadcastCryptoMaterial for SimpleSignedBroadcastCryptoMaterial {
+ fn signing_key(&self) -> ed25519::PrivateKey {
+ self.signing_key.clone()
}
}
diff --git a/nearby/presence/np_adv/src/de_type.rs b/nearby/presence/np_adv/src/de_type.rs
index 90c86e1..4966ed0 100644
--- a/nearby/presence/np_adv/src/de_type.rs
+++ b/nearby/presence/np_adv/src/de_type.rs
@@ -29,7 +29,7 @@
///
/// Shared between V0 and V1.
///
-/// Must not overlap with [PlainDataElementType].
+/// Must not overlap with [`PlainDataElementType`](crate::legacy::de_type::PlainDataElementType).
#[derive(strum_macros::EnumIter, Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum IdentityDataElementType {
diff --git a/nearby/presence/np_adv/src/deser_v0_tests.rs b/nearby/presence/np_adv/src/deser_v0_tests.rs
index fa94b89..2fb6f38 100644
--- a/nearby/presence/np_adv/src/deser_v0_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v0_tests.rs
@@ -18,28 +18,30 @@
use crate::{
credential::{
- simple::{SimpleMatchedCredential, SimpleV0Credential},
- source::SliceCredentialSource,
- v0::MinimumFootprintV0CryptoMaterial,
+ book::{CredentialBook, CredentialBookBuilder},
+ v0::{V0DiscoveryCredential, V0},
+ EmptyMatchedCredential, MatchableCredential, MatchedCredential,
+ SimpleBroadcastCryptoMaterial,
},
de_type::EncryptedIdentityDataElementType,
- deserialize_v0_advertisement,
+ deserialization_arena,
+ deserialization_arena::DeserializationArena,
+ deserialize_advertisement,
legacy::{
actions::{ActionBits, ActionsDataElement, ToActionElement},
data_elements::DataElement,
deserialize::PlainDataElement,
serialize::{AdvBuilder, Identity, LdtIdentity},
- BLE_ADV_SVC_CONTENT_LEN,
+ ShortMetadataKey, BLE_ADV_SVC_CONTENT_LEN,
},
shared_data::ContextSyncSeqNum,
- CredentialSource, NoIdentity, PlaintextIdentityMode, PublicIdentity, V0AdvContents,
- V0Credential,
+ HasIdentityMatch, PlaintextIdentityMode, PublicIdentity, V0AdvertisementContents,
};
use array_view::ArrayView;
-use core::marker::PhantomData;
+use core::{borrow::BorrowMut, marker::PhantomData};
use crypto_provider::CryptoProvider;
use crypto_provider_default::CryptoProviderImpl;
-use ldt_np_adv::{LdtEncrypterXtsAes128, LegacySalt};
+use ldt_np_adv::LegacySalt;
use std::{prelude::rust_2021::*, vec};
use strum::IntoEnumIterator as _;
@@ -51,10 +53,18 @@
let (adv, adv_config) = adv_random_identity(&mut rng, &identities);
- let creds = identities.iter().map(|i| i.credential()).collect::<Vec<_>>();
- let cred_source = SliceCredentialSource::new(&creds);
+ let creds = identities
+ .iter()
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
+ .collect::<Vec<_>>();
- let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice());
+ let mut arena = deserialization_arena!();
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]);
+ let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
assert_adv_equals(&adv_config, &contents);
}
@@ -68,24 +78,31 @@
let (adv, adv_config) = adv_random_identity(&mut rng, &identities);
- let creds = identities
+ let credentials = identities
.iter()
.filter(|i| {
// remove identity used, if any
!adv_config.identity.map(|sci| sci.key_seed == i.key_seed).unwrap_or(false)
})
- .map(|i| i.credential())
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
.collect::<Vec<_>>();
- let cred_source = SliceCredentialSource::new(&creds);
- let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice());
+ let mut arena = deserialization_arena!();
+ let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
+ &credentials,
+ &[],
+ );
+ let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
match adv_config.identity {
// we ended up generating plaintext, so it's fine
None => assert_adv_equals(&adv_config, &contents),
Some(_) => {
// we generated an encrypted adv, but didn't include the credential
- assert_eq!(V0AdvContents::NoMatchingCredentials, contents);
+ assert_eq!(V0AdvertisementContents::NoMatchingCredentials, contents);
}
}
}
@@ -99,29 +116,34 @@
let (adv, adv_config) = adv_random_identity(&mut rng, &identities);
- let creds = Vec::<SimpleV0Credential<MinimumFootprintV0CryptoMaterial, [u8; 32]>>::new();
- let cred_source = SliceCredentialSource::new(&creds);
+ let creds = Vec::<MatchableCredential<V0, EmptyMatchedCredential>>::new();
- let contents = deser_v0::<_, _, CryptoProviderImpl>(&cred_source, adv.as_slice());
+ let mut arena = deserialization_arena!();
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&creds, &[]);
+ let contents = deser_v0::<_, CryptoProviderImpl>(&mut arena, adv.as_slice(), &cred_book);
match adv_config.identity {
// we ended up generating plaintext, so it's fine
None => assert_adv_equals(&adv_config, &contents),
Some(_) => {
// we generated an encrypted adv, but didn't include the credential
- assert_eq!(V0AdvContents::NoMatchingCredentials, contents);
+ assert_eq!(V0AdvertisementContents::NoMatchingCredentials, contents);
}
}
}
}
-fn assert_adv_equals<'m>(
+/// Short-hand for asserting that the contents of two V0 advertisements
+/// are the same for tests where we only ever have 0-1 broadcasting
+/// identities in play.
+fn assert_adv_equals<M: MatchedCredential + AsRef<EmptyMatchedCredential>>(
adv_config: &AdvConfig,
- adv: &V0AdvContents<'m, SimpleMatchedCredential<'m, [u8; 32]>>,
+ adv: &V0AdvertisementContents<M>,
) {
match adv_config.identity {
None => match adv {
- V0AdvContents::Plaintext(p) => {
+ V0AdvertisementContents::Plaintext(p) => {
let mut action_bits = ActionBits::default();
action_bits.set_action(ContextSyncSeqNum::try_from(3).unwrap());
let de = ActionsDataElement::from(action_bits);
@@ -135,7 +157,7 @@
_ => panic!("should be a plaintext adv"),
},
Some(_) => match adv {
- V0AdvContents::Decrypted(wmc) => {
+ V0AdvertisementContents::Decrypted(wmc) => {
assert!(adv_config.plaintext_mode.is_none());
// different generic type param, so can't re-use the DE from above
@@ -152,7 +174,7 @@
wmc.contents().identity_type()
);
assert_eq!(
- &adv_config.identity.unwrap().legacy_metadata_key,
+ adv_config.identity.unwrap().legacy_metadata_key,
wmc.contents().metadata_key()
);
}
@@ -161,16 +183,19 @@
}
}
-fn deser_v0<'s, C, S, P>(
- cred_source: &'s S,
- adv: &[u8],
-) -> V0AdvContents<'s, SimpleMatchedCredential<'s, [u8; 32]>>
+fn deser_v0<'adv, B, P>(
+ arena: impl BorrowMut<DeserializationArena<'adv>>,
+ adv: &'adv [u8],
+ cred_book: &'adv B,
+) -> V0AdvertisementContents<B::Matched>
where
- C: V0Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's,
- S: CredentialSource<C>,
+ B: CredentialBook<'adv>,
P: CryptoProvider,
{
- deserialize_v0_advertisement::<C, S, P>(adv, cred_source).unwrap()
+ deserialize_advertisement::<_, P>(arena, adv, cred_book)
+ .expect("Should be a valid advertisement")
+ .into_v0()
+ .expect("Should be V0")
}
/// Populate an advertisement with a randomly chosen identity and a DE
@@ -179,38 +204,27 @@
identities: &'a Vec<TestIdentity<CryptoProviderImpl>>,
) -> (ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, AdvConfig<'a>) {
let identity = identities.choose(&mut rng).unwrap();
- match rng.gen_range(0_u8..=2) {
- 0 => {
- let mut adv_builder = AdvBuilder::new(NoIdentity::default());
- add_de(&mut adv_builder);
+ if rng.gen_bool(0.5) {
+ let mut adv_builder = AdvBuilder::new(PublicIdentity);
+ add_de(&mut adv_builder);
- (
- adv_builder.into_advertisement().unwrap(),
- AdvConfig::new(None, Some(PlaintextIdentityMode::None)),
- )
- }
- 1 => {
- let mut adv_builder = AdvBuilder::new(PublicIdentity::default());
- add_de(&mut adv_builder);
+ (
+ adv_builder.into_advertisement().unwrap(),
+ AdvConfig::new(None, Some(PlaintextIdentityMode::Public)),
+ )
+ } else {
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(
+ identity.key_seed,
+ identity.legacy_metadata_key,
+ );
+ let mut adv_builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
+ identity.identity_type,
+ LegacySalt::from(rng.gen::<[u8; 2]>()),
+ &broadcast_cm,
+ ));
+ add_de(&mut adv_builder);
- (
- adv_builder.into_advertisement().unwrap(),
- AdvConfig::new(None, Some(PlaintextIdentityMode::Public)),
- )
- }
- 2 => {
- let mut adv_builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
- identity.identity_type,
- LegacySalt::from(rng.gen::<[u8; 2]>()),
- identity.legacy_metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&identity.hkdf().legacy_ldt_key()),
- ));
- add_de(&mut adv_builder);
-
- (adv_builder.into_advertisement().unwrap(), AdvConfig::new(Some(identity), None))
- }
-
- _ => unreachable!(),
+ (adv_builder.into_advertisement().unwrap(), AdvConfig::new(Some(identity), None))
}
}
@@ -229,7 +243,7 @@
struct TestIdentity<C: CryptoProvider> {
identity_type: EncryptedIdentityDataElementType,
key_seed: [u8; 32],
- legacy_metadata_key: [u8; 14],
+ legacy_metadata_key: ShortMetadataKey,
_marker: PhantomData<C>,
}
@@ -242,20 +256,17 @@
.choose(rng)
.unwrap(),
key_seed: rng.gen(),
- legacy_metadata_key: rng.gen(),
+ legacy_metadata_key: ShortMetadataKey(rng.gen()),
_marker: PhantomData,
}
}
- /// Returns a credential using crypto material from this identity
- fn credential(&self) -> SimpleV0Credential<MinimumFootprintV0CryptoMaterial, [u8; 32]> {
+ /// Returns a discovery-credential using crypto material from this identity
+ fn discovery_credential(&self) -> V0DiscoveryCredential {
let hkdf = self.hkdf();
- SimpleV0Credential::new(
- MinimumFootprintV0CryptoMaterial::new(
- self.key_seed,
- hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&self.legacy_metadata_key),
- ),
+ V0DiscoveryCredential::new(
self.key_seed,
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&self.legacy_metadata_key.0),
)
}
diff --git a/nearby/presence/np_adv/src/deser_v1_tests.rs b/nearby/presence/np_adv/src/deser_v1_tests.rs
index b59b18b..80f646e 100644
--- a/nearby/presence/np_adv/src/deser_v1_tests.rs
+++ b/nearby/presence/np_adv/src/deser_v1_tests.rs
@@ -19,79 +19,133 @@
use crate::{
credential::{
- simple::{SimpleMatchedCredential, SimpleV1Credential},
- source::SliceCredentialSource,
- v1::MinimumFootprintV1CryptoMaterial,
+ book::{CredentialBook, CredentialBookBuilder},
+ v1::{
+ SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial,
+ V1DiscoveryCredential,
+ },
+ EmptyMatchedCredential, MatchableCredential, MatchedCredential,
},
de_type::EncryptedIdentityDataElementType,
- deserialize_v1_advertisement,
+ deserialization_arena,
+ deserialization_arena::DeserializationArena,
+ deserialize_advertisement,
extended::{
data_elements::GenericDataElement,
deserialize::VerificationMode,
serialize::{
- AdvBuilder, EncodedAdvertisement, MicEncrypted, SectionBuilder, SectionIdentity,
- SignedEncrypted,
+ AdvBuilder, AdvertisementType, EncodedAdvertisement, MicEncryptedSectionEncoder,
+ PublicSectionEncoder, SectionBuilder, SectionEncoder, SignedEncryptedSectionEncoder,
},
},
- AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, CredentialSource,
- PlaintextIdentityMode, PublicIdentity, Section, V1AdvContents, V1Credential,
- V1DeserializedSection,
+ AdvDeserializationError, AdvDeserializationErrorDetailsHazmat, HasIdentityMatch, MetadataKey,
+ PlaintextIdentityMode, Section, V1AdvertisementContents, V1DeserializedSection,
};
use crypto_provider::{CryptoProvider, CryptoRng};
use std::{collections, prelude::rust_2021::*, vec};
use strum::IntoEnumIterator as _;
+use crate::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT;
use crypto_provider_default::CryptoProviderImpl;
#[test]
-fn v1_all_identities_resolvable_mix_plaintext_ciphertext() {
+fn v1_plaintext() {
let mut rng = StdRng::from_entropy();
for _ in 0..100 {
let identities = (0..100)
.map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng))
.collect::<Vec<_>>();
- let mut adv_builder = AdvBuilder::new();
- let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder);
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let section_configs: Vec<SectionConfig<CryptoProviderImpl>> =
+ fill_plaintext_adv(&mut rng, &mut adv_builder);
let adv = adv_builder.into_advertisement();
- let creds = identities.iter().map(|i| i.credential()).collect::<Vec<_>>();
- let cred_source = SliceCredentialSource::new(&creds);
- // check if the section header would be 0 or if the section is empty
- match section_configs.is_empty() {
- true => {
- let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv);
- assert_eq!(
- v1_error,
- AdvDeserializationError::ParseError {
- details_hazmat:
- AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
- }
- ); //assert a adv deserialization error
- }
- false => {
- let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv);
- assert_eq!(0, v1_contents.invalid_sections_count());
- assert_eq!(section_configs.len(), v1_contents.sections.len());
- for (section_config, section) in
- section_configs.iter().zip(v1_contents.sections.iter())
- {
- assert_section_equals(section_config, section);
+ let creds = identities
+ .iter()
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
+ .collect::<Vec<_>>();
+
+ let mut arena = deserialization_arena!();
+ // check if the section is empty or there is more than one public section
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
+ if section_configs.is_empty() {
+ let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(
+ v1_error,
+ AdvDeserializationError::ParseError {
+ details_hazmat:
+ AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
}
+ ); //assert a adv deserialization error
+ } else {
+ let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(0, v1_contents.invalid_sections_count());
+ assert_eq!(section_configs.len(), v1_contents.sections.len());
+ for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter())
+ {
+ assert_section_equals(section_config, section);
}
}
}
}
#[test]
-fn v1_only_non_matching_identities_available_mix_plaintext_ciphertext() {
+fn v1_all_identities_resolvable_ciphertext() {
let mut rng = StdRng::from_entropy();
for _ in 0..100 {
let identities = (0..100)
.map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng))
.collect::<Vec<_>>();
- let mut adv_builder = AdvBuilder::new();
- let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder);
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+ let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder);
+ let adv = adv_builder.into_advertisement();
+ let creds = identities
+ .iter()
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
+ .collect::<Vec<_>>();
+ let mut arena = deserialization_arena!();
+ // check if the section header would be 0 or if the section is empty
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
+ if section_configs.is_empty() {
+ let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(
+ v1_error,
+ AdvDeserializationError::ParseError {
+ details_hazmat:
+ AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
+ }
+ ); //assert a adv deserialization error
+ } else {
+ let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(0, v1_contents.invalid_sections_count());
+ assert_eq!(section_configs.len(), v1_contents.sections.len());
+ for (section_config, section) in section_configs.iter().zip(v1_contents.sections.iter())
+ {
+ assert_section_equals(section_config, section);
+ }
+ }
+ }
+}
+
+#[test]
+fn v1_only_non_matching_identities_available_ciphertext() {
+ let mut rng = StdRng::from_entropy();
+ for _ in 0..100 {
+ let identities = (0..100)
+ .map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng))
+ .collect::<Vec<_>>();
+
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+ let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder);
let adv = adv_builder.into_advertisement();
let creds = identities
.iter()
@@ -101,104 +155,103 @@
.iter()
.any(|sc| sc.identity.map(|sci| sci.key_seed == i.key_seed).unwrap_or(false))
})
- .map(|i| i.credential())
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
.collect::<Vec<_>>();
- let cred_source = SliceCredentialSource::new(&creds);
+
+ let mut arena = deserialization_arena!();
// check if the section header would be 0
- match section_configs.is_empty() {
- true => {
- let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv);
- assert_eq!(
- v1_error,
- AdvDeserializationError::ParseError {
- details_hazmat:
- AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
- }
- ); //assert a adv deserialization error
- }
- false => {
- let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv);
- // all encrypted identity sections are invalid
- let encrypted_section_count =
- section_configs.iter().filter(|sc| sc.identity.is_some()).count();
- assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count());
- assert_eq!(
- section_configs.len() - encrypted_section_count,
- v1_contents.sections.len()
- );
- for (section_config, section) in section_configs
- .iter()
- // skip encrypted sections
- .filter(|sc| sc.identity.is_none())
- .zip(v1_contents.sections.iter())
- {
- assert_section_equals(section_config, section);
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
+ if section_configs.is_empty() {
+ let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(
+ v1_error,
+ AdvDeserializationError::ParseError {
+ details_hazmat:
+ AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
}
+ ); //assert a adv deserialization error
+ } else {
+ let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ // all encrypted identity sections are invalid
+ let encrypted_section_count =
+ section_configs.iter().filter(|sc| sc.identity.is_some()).count();
+ assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count());
+ assert_eq!(section_configs.len() - encrypted_section_count, v1_contents.sections.len());
+ for (section_config, section) in section_configs
+ .iter()
+ // skip encrypted sections
+ .filter(|sc| sc.identity.is_none())
+ .zip(v1_contents.sections.iter())
+ {
+ assert_section_equals(section_config, section);
}
}
}
}
#[test]
-fn v1_no_creds_available_mix_plaintext_ciphertext() {
+fn v1_no_creds_available_ciphertext() {
let mut rng = StdRng::from_entropy();
for _ in 0..100 {
let identities = (0..100).map(|_| TestIdentity::random(&mut rng)).collect::<Vec<_>>();
- let mut adv_builder = AdvBuilder::new();
- let section_configs =
- fill_adv::<StdRng, CryptoProviderImpl>(&mut rng, &identities, &mut adv_builder);
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+ let section_configs = fill_encrypted_adv::<StdRng, CryptoProviderImpl>(
+ &mut rng,
+ &identities,
+ &mut adv_builder,
+ );
let adv = adv_builder.into_advertisement();
- let cred_source: SliceCredentialSource<
- '_,
- SimpleV1Credential<MinimumFootprintV1CryptoMaterial, [u8; 32]>,
- > = SliceCredentialSource::new(&[]);
+ let mut arena = deserialization_arena!();
// check if the section header would be 0
- match section_configs.is_empty() {
- true => {
- let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv);
- assert_eq!(
- v1_error,
- AdvDeserializationError::ParseError {
- details_hazmat:
- AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
- }
- ); //assert a adv deserialization error
- }
- false => {
- let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv);
- // all encrypted identity sections are invalid
- let encrypted_section_count =
- section_configs.iter().filter(|sc| sc.identity.is_some()).count();
- assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count());
- assert_eq!(
- section_configs.len() - encrypted_section_count,
- v1_contents.sections.len()
- );
-
- for (section_config, section) in section_configs
- .iter()
- // skip encrypted sections
- .filter(|sc| sc.identity.is_none())
- .zip(v1_contents.sections.iter())
- {
- assert_section_equals(section_config, section);
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+ if section_configs.is_empty() {
+ let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(
+ v1_error,
+ AdvDeserializationError::ParseError {
+ details_hazmat:
+ AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
}
+ ); //assert a adv deserialization error
+ } else {
+ let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ // all encrypted identity sections are invalid
+ let encrypted_section_count =
+ section_configs.iter().filter(|sc| sc.identity.is_some()).count();
+ assert_eq!(encrypted_section_count, v1_contents.invalid_sections_count());
+ assert_eq!(section_configs.len() - encrypted_section_count, v1_contents.sections.len());
+
+ for (section_config, section) in section_configs
+ .iter()
+ // skip encrypted sections
+ .filter(|sc| sc.identity.is_none())
+ .zip(v1_contents.sections.iter())
+ {
+ assert_section_equals(section_config, section);
}
}
}
}
#[test]
-fn v1_only_some_matching_identities_available_mix_plaintext_ciphertext() {
+fn v1_only_some_matching_identities_available_ciphertext() {
let mut rng = StdRng::from_entropy();
for _ in 0..100 {
let identities = (0..100)
.map(|_| TestIdentity::<CryptoProviderImpl>::random(&mut rng))
.collect::<Vec<_>>();
- let mut adv_builder = AdvBuilder::new();
- let section_configs = fill_adv(&mut rng, &identities, &mut adv_builder);
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+ let section_configs = fill_encrypted_adv(&mut rng, &identities, &mut adv_builder);
let adv = adv_builder.into_advertisement();
// identities used in sections, which may be used in multiple sections too
let identities_to_remove: collections::HashSet<_> = identities
@@ -217,59 +270,59 @@
let creds = identities
.iter()
.filter(|i| !identities_to_remove.contains(&i.key_seed))
- .map(|i| i.credential())
+ .map(|i| MatchableCredential {
+ discovery_credential: i.discovery_credential(),
+ match_data: EmptyMatchedCredential,
+ })
.collect::<Vec<_>>();
- let cred_source = SliceCredentialSource::new(&creds);
+
+ let mut arena = deserialization_arena!();
+
+ let cred_book =
+ CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &creds);
+
// check if the section header would be 0
- match section_configs.is_empty() {
- true => {
- let v1_error = deser_v1_error::<_, _, CryptoProviderImpl>(&cred_source, adv);
- assert_eq!(
- v1_error,
- AdvDeserializationError::ParseError {
- details_hazmat:
- AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
- }
- ); //assert a adv deserialization error
- }
- false => {
- let v1_contents = deser_v1::<_, _, CryptoProviderImpl>(&cred_source, adv);
-
- let affected_sections: Vec<_> = section_configs
- .iter()
- .filter(|sc| {
- sc.identity
- .map(|sci| identities_to_remove.iter().any(|ks| &sci.key_seed == ks))
- .unwrap_or(false)
- })
- .collect();
-
- assert_eq!(affected_sections.len(), v1_contents.invalid_sections_count());
- assert_eq!(
- section_configs.len() - affected_sections.len(),
- v1_contents.sections.len()
- );
-
- for (section_config, section) in section_configs
- .iter()
- // skip sections w/ removed identities
- .filter(|sc| {
- sc.identity
- .map(|i| !identities_to_remove.contains(&i.key_seed))
- .unwrap_or(true)
- })
- .zip(v1_contents.sections.iter())
- {
- assert_section_equals(section_config, section);
+ if section_configs.is_empty() {
+ let v1_error = deser_v1_error::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+ assert_eq!(
+ v1_error,
+ AdvDeserializationError::ParseError {
+ details_hazmat:
+ AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
}
+ ); //assert a adv deserialization error
+ } else {
+ let v1_contents = deser_v1::<_, CryptoProviderImpl>(&mut arena, &adv, &cred_book);
+
+ let affected_sections: Vec<_> = section_configs
+ .iter()
+ .filter(|sc| {
+ sc.identity
+ .map(|sci| identities_to_remove.iter().any(|ks| &sci.key_seed == ks))
+ .unwrap_or(false)
+ })
+ .collect();
+
+ assert_eq!(affected_sections.len(), v1_contents.invalid_sections_count());
+ assert_eq!(section_configs.len() - affected_sections.len(), v1_contents.sections.len());
+
+ for (section_config, section) in section_configs
+ .iter()
+ // skip sections w/ removed identities
+ .filter(|sc| {
+ sc.identity.map(|i| !identities_to_remove.contains(&i.key_seed)).unwrap_or(true)
+ })
+ .zip(v1_contents.sections.iter())
+ {
+ assert_section_equals(section_config, section);
}
}
}
}
-fn assert_section_equals<'m, C: CryptoProvider>(
+fn assert_section_equals<M: MatchedCredential, C: CryptoProvider>(
section_config: &SectionConfig<C>,
- section: &V1DeserializedSection<'m, SimpleMatchedCredential<'m, [u8; 32]>>,
+ section: &V1DeserializedSection<M>,
) {
match section_config.identity {
None => match section {
@@ -279,7 +332,7 @@
assert_eq!(section_config.plaintext_mode.unwrap(), p.identity());
assert_eq!(
section_config.data_elements,
- p.data_elements().map(|de| de.into()).collect::<Vec<_>>()
+ p.iter_data_elements().map(|de| (&de.unwrap()).into()).collect::<Vec<_>>()
)
}
V1DeserializedSection::Decrypted(_) => panic!("no id, but decrypted section"),
@@ -291,14 +344,17 @@
assert_eq!(
section_config.data_elements,
- wmc.contents().data_elements().map(|de| de.into()).collect::<Vec<_>>()
+ wmc.contents()
+ .iter_data_elements()
+ .map(|de| (&de.unwrap()).into())
+ .collect::<Vec<GenericDataElement>>()
);
assert_eq!(
section_config.identity.unwrap().identity_type,
wmc.contents().identity_type()
);
assert_eq!(
- §ion_config.identity.unwrap().extended_metadata_key,
+ section_config.identity.unwrap().extended_metadata_key,
wmc.contents().metadata_key()
);
assert_eq!(
@@ -310,35 +366,66 @@
}
}
-fn deser_v1_error<'s, C, S, P>(
- cred_source: &'s S,
- adv: EncodedAdvertisement,
+fn deser_v1_error<'a, B, P>(
+ arena: &'a mut DeserializationArena<'a>,
+ adv: &'a EncodedAdvertisement,
+ cred_book: &'a B,
) -> AdvDeserializationError
where
- C: V1Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's,
- S: CredentialSource<C>,
+ B: CredentialBook<'a>,
P: CryptoProvider,
{
- let v1_contents = match deserialize_v1_advertisement::<C, S, P>(adv.as_slice(), cred_source) {
+ let v1_contents = match deserialize_advertisement::<_, P>(arena, adv.as_slice(), cred_book) {
Err(e) => e,
_ => panic!("Expecting an error!"),
};
v1_contents
}
-fn deser_v1<'s, C, S, P>(
- cred_source: &'s S,
- adv: EncodedAdvertisement,
-) -> V1AdvContents<'s, SimpleMatchedCredential<'s, [u8; 32]>>
+fn deser_v1<'adv, B, P>(
+ arena: &'adv mut DeserializationArena<'adv>,
+ adv: &'adv EncodedAdvertisement,
+ cred_book: &'adv B,
+) -> V1AdvertisementContents<'adv, B::Matched>
where
- C: V1Credential<Matched<'s> = SimpleMatchedCredential<'s, [u8; 32]>> + 's,
- S: CredentialSource<C>,
+ B: CredentialBook<'adv>,
P: CryptoProvider,
{
- deserialize_v1_advertisement::<C, S, P>(adv.as_slice(), cred_source).unwrap()
+ deserialize_advertisement::<_, P>(arena, adv.as_slice(), cred_book)
+ .expect("Should be a valid advertisement")
+ .into_v1()
+ .expect("Should be V1")
}
+
/// Populate a random number of sections with randomly chosen identities and random DEs
-fn fill_adv<'a, R: rand::Rng, C: CryptoProvider>(
+fn fill_plaintext_adv<'a, R: rand::Rng, C: CryptoProvider>(
+ mut rng: &mut R,
+ adv_builder: &mut AdvBuilder,
+) -> Vec<SectionConfig<'a, C>> {
+ let mut expected = Vec::new();
+ // build sections
+ for _ in 0..rng.gen_range(0..=NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT) {
+ let res = adv_builder.section_builder(PublicSectionEncoder::default()).map(|s| {
+ SectionConfig::new(
+ None,
+ Some(PlaintextIdentityMode::Public),
+ None,
+ add_des(s, &mut rng),
+ )
+ });
+ match res {
+ Ok(tuple) => expected.push(tuple),
+ Err(_) => {
+ // couldn't fit that section; maybe another smaller section will fit
+ continue;
+ }
+ }
+ }
+ expected
+}
+
+/// Populate a random number of sections with randomly chosen identities and random DEs
+fn fill_encrypted_adv<'a, R: rand::Rng, C: CryptoProvider>(
mut rng: &mut R,
identities: &'a Vec<TestIdentity<C>>,
adv_builder: &mut AdvBuilder,
@@ -349,21 +436,15 @@
for _ in 0..rng.gen_range(0..=6) {
let chosen_index = rng.gen_range(0..identities.len());
let identity = &identities[chosen_index];
- let res = match rng.gen_range(1_u8..=3) {
- 1 => adv_builder.section_builder(PublicIdentity::default()).map(|s| {
- SectionConfig::new(
- None,
- Some(PlaintextIdentityMode::Public),
- None,
- add_des(s, &mut rng),
- )
- }),
- 2 => adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+
+ let broadcast_cm = identity.broadcast_credential();
+
+ let res = if rng.gen_bool(0.5) {
+ adv_builder
+ .section_builder(MicEncryptedSectionEncoder::<C>::new_random_salt(
&mut salt_rng,
identity.identity_type,
- &identity.extended_metadata_key,
- &identity.hkdf(),
+ &broadcast_cm,
))
.map(|s| {
SectionConfig::new(
@@ -372,14 +453,13 @@
Some(VerificationMode::Mic),
add_des(s, &mut rng),
)
- }),
- 3 => adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ })
+ } else {
+ adv_builder
+ .section_builder(SignedEncryptedSectionEncoder::<C>::new_random_salt(
&mut salt_rng,
identity.identity_type,
- &identity.extended_metadata_key,
- &identity.key_pair,
- &identity.hkdf(),
+ &broadcast_cm,
))
.map(|s| {
SectionConfig::new(
@@ -388,8 +468,7 @@
Some(VerificationMode::Signature),
add_des(s, &mut rng),
)
- }),
- _ => unreachable!(),
+ })
};
match res {
Ok(tuple) => expected.push(tuple),
@@ -401,10 +480,11 @@
}
expected
}
+
struct TestIdentity<C: CryptoProvider> {
identity_type: EncryptedIdentityDataElementType,
key_seed: [u8; 32],
- extended_metadata_key: [u8; 16],
+ extended_metadata_key: MetadataKey,
key_pair: np_ed25519::KeyPair<C>,
marker: PhantomData<C>,
}
@@ -417,32 +497,26 @@
.choose(rng)
.unwrap(),
key_seed: rng.gen(),
- extended_metadata_key: rng.gen(),
+ extended_metadata_key: MetadataKey(rng.gen()),
key_pair: np_ed25519::KeyPair::<C>::generate(),
marker: PhantomData,
}
}
- /// Returns a credential using crypto material from this identity
- fn credential(&self) -> SimpleV1Credential<MinimumFootprintV1CryptoMaterial, [u8; 32]> {
- let hkdf = self.hkdf();
- SimpleV1Credential::new(
- MinimumFootprintV1CryptoMaterial::new(
- self.key_seed,
- hkdf.extended_unsigned_metadata_key_hmac_key()
- .calculate_hmac(&self.extended_metadata_key),
- hkdf.extended_signed_metadata_key_hmac_key()
- .calculate_hmac(&self.extended_metadata_key),
- self.key_pair.public(),
- ),
+ /// Returns a (simple, signed) broadcast credential using crypto material from this identity
+ fn broadcast_credential(&self) -> SimpleSignedBroadcastCryptoMaterial {
+ SimpleSignedBroadcastCryptoMaterial::new(
self.key_seed,
+ self.extended_metadata_key,
+ self.key_pair.private_key(),
)
}
- fn hkdf(&self) -> np_hkdf::NpKeySeedHkdf<C> {
- np_hkdf::NpKeySeedHkdf::<C>::new(&self.key_seed)
+ /// Returns a discovery credential using crypto material from this identity
+ fn discovery_credential(&self) -> V1DiscoveryCredential {
+ self.broadcast_credential().derive_v1_discovery_credential::<C>()
}
}
/// Add several DEs with random types and contents
-fn add_des<I: SectionIdentity, R: rand::Rng>(
+fn add_des<I: SectionEncoder, R: rand::Rng>(
mut sb: SectionBuilder<I>,
rng: &mut R,
) -> Vec<GenericDataElement> {
diff --git a/nearby/presence/np_adv/src/deserialization_arena.rs b/nearby/presence/np_adv/src/deserialization_arena.rs
new file mode 100644
index 0000000..6b5f651
--- /dev/null
+++ b/nearby/presence/np_adv/src/deserialization_arena.rs
@@ -0,0 +1,102 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Types for creating arenas used in deserialization of np_adv. This implementation is purpose-made
+//! for deserializing in `np_adv` and is not intended for general use as an arena.
+
+use crate::extended::BLE_ADV_SVC_CONTENT_LEN;
+
+/// Create a [`DeserializationArena`] suitable for use with deserializing an advertisement.
+#[macro_export]
+macro_rules! deserialization_arena {
+ // Trick borrowed from `pin!`: In an argument to a braced constructor, if we take a reference to
+ // a temporary value, that value will be upgraded to live for the scope of the enclosing block,
+ // avoiding another `let` binding for the buffer which is normally required to keep the buffer
+ // on the stack.
+ // Reference: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension
+ () => {
+ $crate::deserialization_arena::DeserializationArena {
+ slice: &mut $crate::deserialization_arena::DeserializationArena::buffer(),
+ }
+ };
+}
+
+/// A simple allocator that simply keeps splitting the given slice on allocation. Use the
+/// [`deserialization_arena!`][crate::deserialization_arena!] macro to create an instance.
+pub struct DeserializationArena<'a> {
+ #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
+ pub slice: &'a mut [u8],
+}
+
+impl<'a> DeserializationArena<'a> {
+ #[doc(hidden)] // Exposed for use by `deserialization_arena!` only.
+ pub fn buffer() -> [u8; BLE_ADV_SVC_CONTENT_LEN] {
+ [0; BLE_ADV_SVC_CONTENT_LEN]
+ }
+
+ /// Allocates `len` bytes from the slice given upon construction. In the expected use case, the
+ /// allocated slice should be written to with actual data, overwriting what's contained in
+ /// there. While reading from the allocated slice without first writing to it is safe in the
+ /// Rust memory-safety sense, the returned slice contains "garbage" data from the slice given
+ /// during construction.
+ ///
+ /// Returns an error with [`ArenaOutOfSpace`] if the remaining slice is not long enough to
+ /// allocate `len` bytes.
+ pub fn allocate(&mut self, len: u8) -> Result<&'a mut [u8], ArenaOutOfSpace> {
+ if usize::from(len) > self.slice.len() {
+ return Err(ArenaOutOfSpace);
+ }
+ let (allocated, remaining) = core::mem::take(&mut self.slice).split_at_mut(len.into());
+ self.slice = remaining;
+ // Note: the returned data is logically garbage, but in practice it's all zeroes assuming
+ // `deserialization_arena!` was used to create this allocator.
+ Ok(allocated)
+ }
+}
+
+/// Error indicating that the arena has ran out of space, and deserialization cannot proceed. This
+/// should never happen if the arena is created with [`crate::deserialization_arena!`], since the
+/// total size of decrypted sections should be less than the size of the incoming BLE advertisement.
+#[derive(Debug, PartialEq, Eq)]
+pub struct ArenaOutOfSpace;
+
+#[cfg(test)]
+mod test {
+ use crate::{deserialization_arena::ArenaOutOfSpace, extended::BLE_ADV_SVC_CONTENT_LEN};
+
+ #[test]
+ fn test_creation() {
+ assert_eq!(BLE_ADV_SVC_CONTENT_LEN, deserialization_arena!().slice.len());
+ }
+
+ #[test]
+ fn test_allocation() {
+ let mut arena = deserialization_arena!();
+ assert_eq!(Ok(&mut [0_u8; 100][..]), arena.allocate(100));
+ assert_eq!(BLE_ADV_SVC_CONTENT_LEN - 100, arena.slice.len());
+ }
+
+ #[test]
+ fn test_allocation_overflow() {
+ let mut arena = deserialization_arena!();
+ assert_eq!(Err(ArenaOutOfSpace), arena.allocate(u8::MAX));
+ }
+
+ #[test]
+ fn test_allocation_twice_overflow() {
+ let mut arena = deserialization_arena!();
+ assert_eq!(Ok(&mut [0_u8; 128][..]), arena.allocate(128));
+ assert_eq!(Err(ArenaOutOfSpace), arena.allocate(128));
+ }
+}
diff --git a/nearby/presence/np_adv/src/extended/data_elements/mod.rs b/nearby/presence/np_adv/src/extended/data_elements/mod.rs
index 2058be1..4e6e481 100644
--- a/nearby/presence/np_adv/src/extended/data_elements/mod.rs
+++ b/nearby/presence/np_adv/src/extended/data_elements/mod.rs
@@ -67,8 +67,8 @@
}
/// Convert a deserialized DE into one you can serialize
-impl<'a> From<DataElement<'a>> for GenericDataElement {
- fn from(de: DataElement<'a>) -> Self {
+impl<'a> From<&'a DataElement<'a>> for GenericDataElement {
+ fn from(de: &'a DataElement<'a>) -> Self {
Self::try_from(de.de_type(), de.contents())
.expect("Deserialized DE must have a valid length")
}
diff --git a/nearby/presence/np_adv/src/extended/data_elements/tests.rs b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
index a8938b0..a0f9a2f 100644
--- a/nearby/presence/np_adv/src/extended/data_elements/tests.rs
+++ b/nearby/presence/np_adv/src/extended/data_elements/tests.rs
@@ -15,16 +15,16 @@
extern crate std;
use super::*;
+use crate::extended::serialize::AdvertisementType;
use crate::{
- extended::serialize::{section_tests::SectionBuilderExt, AdvBuilder},
+ extended::serialize::{section_tests::SectionBuilderExt, AdvBuilder, PublicSectionEncoder},
shared_data::TxPower,
- PublicIdentity,
};
#[test]
fn serialize_tx_power_de() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder.add_de_res(|_| TxPower::try_from(3_i8).map(TxPowerDataElement::from)).unwrap();
@@ -41,8 +41,8 @@
#[test]
fn serialize_actions_de_empty() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder.add_de_res(|_| ActionsDataElement::try_from_actions(&[])).unwrap();
@@ -58,8 +58,8 @@
#[test]
fn serialize_actions_de_non_empty() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de_res(|_| ActionsDataElement::try_from_actions(&[1, 1, 2, 3, 5, 8]))
@@ -78,8 +78,8 @@
#[test]
fn serialize_context_sync_seq_num_de() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de_res(|_| ContextSyncSeqNum::try_from(3).map(ContextSyncSeqNumDataElement::from))
@@ -98,8 +98,8 @@
#[test]
fn serialize_connectivity_info_de_bluetooth() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder.add_de(|_| ConnectivityInfoDataElement::bluetooth([1; 4], [2; 6])).unwrap();
@@ -118,8 +118,8 @@
#[test]
fn serialize_connectivity_info_de_mdns() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder.add_de(|_| ConnectivityInfoDataElement::mdns([1; 4], 2)).unwrap();
@@ -138,8 +138,8 @@
#[test]
fn serialize_connectivity_info_de_wifi_direct() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de(|_| ConnectivityInfoDataElement::wifi_direct([1; 10], [2; 10], [3; 2], 4))
@@ -162,8 +162,8 @@
#[test]
fn serialize_connectivity_capabilities_de_wifi_direct() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de(|_| ConnectivityCapabilityDataElement::wifi_direct([1; 3], [2; 3]))
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
index d112ead..dfab7ce 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mic_decrypt_tests.rs
@@ -15,20 +15,25 @@
extern crate std;
use super::*;
+use crate::deserialization_arena;
use crate::extended::data_elements::TxPowerDataElement;
-use crate::extended::serialize::{SingleTypeDataElement, WriteDataElement};
+use crate::extended::deserialize::DataElementParseError;
+use crate::extended::serialize::{AdvertisementType, SingleTypeDataElement, WriteDataElement};
use crate::shared_data::TxPower;
use crate::{
- credential::{simple::SimpleV1Credential, v1::MinimumFootprintV1CryptoMaterial},
+ credential::{v1::V1, SimpleBroadcastCryptoMaterial},
de_type::EncryptedIdentityDataElementType,
extended::{
deserialize::{
- encrypted_section::MicEncryptedSectionDeserializationError,
+ encrypted_section::{
+ EncryptedSectionContents, IdentityResolutionOrDeserializationError,
+ MicVerificationError,
+ },
parse_sections,
- test_stubs::{HkdfCryptoMaterial, IntermediateSectionExt},
- CiphertextSection, OffsetDataElement, RawV1Salt,
+ test_stubs::IntermediateSectionExt,
+ CiphertextSection, DataElement, RawV1Salt,
},
- serialize::{AdvBuilder, CapacityLimitedVec, MicEncrypted},
+ serialize::{AdvBuilder, CapacityLimitedVec, MicEncryptedSectionEncoder},
},
parse_adv_header, AdvHeader, Section,
};
@@ -39,20 +44,20 @@
#[test]
fn deserialize_mic_encrypted_correct_keys() {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let raw_salt = RawV1Salt([3; 16]);
let section_salt = V1Salt::<CryptoProviderImpl>::from(raw_salt);
let identity_type = EncryptedIdentityDataElementType::Private;
- let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_for_testing(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
V1Salt::from(*section_salt.as_array_ref()),
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -70,24 +75,38 @@
panic!("incorrect header");
};
- let sections = parse_sections(&adv_header, remaining).unwrap();
+ let sections = parse_sections(adv_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
let enc_section = section.as_ciphertext().unwrap();
+
+ let contents = if let CiphertextSection::MicEncryptedIdentity(contents) = &enc_section {
+ contents
+ } else {
+ panic!("incorrect flavor");
+ };
+
let keypair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate();
// deserializing to Section works
- let credential = SimpleV1Credential::new(
- MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>(
- key_seed,
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- keypair.public(),
- ),
- (),
- );
- let section = enc_section.try_deserialize::<_, CryptoProviderImpl>(&credential).unwrap();
+ let discovery_credential =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, keypair.private_key())
+ .derive_v1_discovery_credential::<CryptoProviderImpl>();
+
+ let identity_resolution_material =
+ discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>();
+ let verification_material =
+ discovery_credential.unsigned_verification_material::<CryptoProviderImpl>();
+
+ let mut arena = deserialization_arena!();
+ let section = contents
+ .try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
+ &mut arena,
+ &identity_resolution_material,
+ &verification_material,
+ )
+ .unwrap();
assert_eq!(
DecryptedSection::new(
@@ -97,37 +116,32 @@
raw_salt,
SectionContents {
section_header: 19 // encryption info de
- + 2 // de header
- + 16 // metadata key
- + 2 // de contents
- + 16, // mic hmac tag
+ + 2 // de header
+ + 16 // metadata key
+ + 2 // de contents
+ + 16, // mic hmac tag
// battery DE
- de_data: ArrayView::try_from_slice(&[5]).unwrap(),
- data_elements: vec![OffsetDataElement {
- offset: 2.into(),
- de_type: 0x05_u8.into(),
- start_of_contents: 0,
- contents_len: 1
- }]
+ data_element_start_offset: 2,
+ de_region_excl_identity: &[txpower_de.de_header().serialize().as_slice(), &[5],]
+ .concat(),
}
),
section
);
+ let data_elements = section.collect_data_elements().unwrap();
+ assert_eq!(
+ data_elements,
+ &[DataElement { offset: 2.into(), de_type: 0x05_u8.into(), contents: &[5] }]
+ );
assert_eq!(
- vec![(DataElementOffset::from(2_usize), TxPowerDataElement::DE_TYPE, vec![5_u8])],
- section
- .data_elements()
+ vec![(DataElementOffset::from(2_u8), TxPowerDataElement::DE_TYPE, vec![5_u8])],
+ data_elements
+ .into_iter()
.map(|de| (de.offset(), de.de_type(), de.contents().to_vec()))
.collect::<Vec<_>>()
);
- let contents = if let CiphertextSection::MicEncryptedIdentity(contents) = &enc_section {
- contents
- } else {
- panic!("incorrect flavor");
- };
-
let mut encryption_info_bytes = [0_u8; 19];
encryption_info_bytes[0..2].copy_from_slice(&[0x91, 0x10]);
encryption_info_bytes[2] = 0x00;
@@ -136,19 +150,21 @@
let ciphertext_end = adv.as_slice().len() - 16;
assert_eq!(
&MicEncryptedSection {
- section_header: 19 // encryption info de
+ contents: EncryptedSectionContents {
+ section_header: 19 // encryption info de
+ 2 // de header
+ 16 // metadata key
+ 2 // de contents
+ 16, // mic hmac tag
- adv_header: &adv_header,
- encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
- identity: EncryptedIdentityMetadata {
- header_bytes: [0x90, 0x1],
- offset: 1.into(),
- identity_type: EncryptedIdentityDataElementType::Private,
+ adv_header,
+ encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
+ identity: EncryptedIdentityMetadata {
+ header_bytes: [0x90, 0x1],
+ offset: 1.into(),
+ identity_type: EncryptedIdentityDataElementType::Private,
+ },
+ all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..ciphertext_end],
},
- all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..ciphertext_end],
mic: SectionMic { mic: adv.as_slice()[ciphertext_end..].try_into().unwrap() }
},
contents
@@ -156,30 +172,26 @@
// plaintext is correct
{
- let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>(
- key_seed,
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- keypair.public(),
- );
- let identity_resolution_material =
- crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>();
- let verification_material =
- crypto_material.unsigned_verification_material::<CryptoProviderImpl>();
-
- let decrypted = contents
- .try_decrypt::<CryptoProviderImpl>(
- &identity_resolution_material,
- &verification_material,
+ let identity_resolution_contents =
+ contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>();
+ let identity_match = identity_resolution_contents
+ .try_match::<CryptoProviderImpl>(
+ &identity_resolution_material.into_raw_resolution_material(),
)
.unwrap();
+ let mut arena = deserialization_arena!();
+ let decrypted = contents
+ .contents
+ .decrypt_ciphertext::<CryptoProviderImpl>(&mut arena, identity_match)
+ .unwrap();
- let mut expected = metadata_key.as_slice().to_vec();
+ let mut expected = Vec::new();
// battery de
expected.extend_from_slice(txpower_de.clone().de_header().serialize().as_slice());
txpower_de.write_de_contents(&mut expected);
- assert_eq!(&expected, decrypted.as_slice());
+ assert_eq!(metadata_key, decrypted.metadata_key_plaintext);
+ assert_eq!(&expected, decrypted.plaintext_contents);
}
}
@@ -187,7 +199,7 @@
fn deserialize_mic_encrypted_incorrect_aes_key_error() {
// bad aes key -> bad metadata key plaintext
do_bad_deserialize_params::<CryptoProviderImpl>(
- MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
Some([0xFF; 16].into()),
None,
None,
@@ -199,7 +211,7 @@
fn deserialize_mic_encrypted_incorrect_metadata_key_hmac_key_error() {
// bad metadata key hmac key -> bad calculated metadata key mac
do_bad_deserialize_params::<CryptoProviderImpl>(
- MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
None,
Some([0xFF; 32].into()),
None,
@@ -211,7 +223,7 @@
fn deserialize_mic_encrypted_incorrect_mic_hmac_key_error() {
// bad mic hmac key -> bad calculated mic
do_bad_deserialize_params::<CryptoProviderImpl>(
- MicEncryptedSectionDeserializationError::MicMismatch,
+ MicVerificationError::MicMismatch.into(),
None,
None,
Some([0xFF; 32].into()),
@@ -223,7 +235,7 @@
fn deserialize_mic_encrypted_incorrect_expected_metadata_key_hmac_error() {
// bad expected metadata key mac
do_bad_deserialize_params::<CryptoProviderImpl>(
- MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
None,
None,
None,
@@ -235,7 +247,9 @@
fn deserialize_mic_encrypted_incorrect_salt_error() {
// bad salt -> bad iv -> bad metadata key plaintext
do_bad_deserialize_tampered(
- MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
+ ),
|_| {},
|adv| adv[23..39].copy_from_slice(&[0xFF; 16]),
);
@@ -245,7 +259,7 @@
fn deserialize_mic_encrypted_de_that_wont_parse() {
// add an extra byte to the section, leading it to try to parse a DE that doesn't exist
do_bad_deserialize_tampered(
- MicEncryptedSectionDeserializationError::DeParseError,
+ DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd),
|sec| sec.try_push(0xFF).unwrap(),
|_| {},
);
@@ -255,7 +269,9 @@
fn deserialize_mic_encrypted_tampered_mic_error() {
// flip the a bit in the first MIC byte
do_bad_deserialize_tampered(
- MicEncryptedSectionDeserializationError::MicMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ MicVerificationError::MicMismatch.into(),
+ ),
|_| {},
|adv| {
let mic_start = adv.len() - 16;
@@ -268,7 +284,9 @@
fn deserialize_mic_encrypted_tampered_payload_error() {
// flip the last payload bit
do_bad_deserialize_tampered(
- MicEncryptedSectionDeserializationError::MicMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ MicVerificationError::MicMismatch.into(),
+ ),
|_| {},
|adv| *adv.last_mut().unwrap() ^= 0x01,
);
@@ -277,26 +295,27 @@
/// Attempt a decryption that will fail when using the provided parameters for decryption only.
/// `None` means use the correct value for that parameter.
fn do_bad_deserialize_params<C: CryptoProvider>(
- error: MicEncryptedSectionDeserializationError,
+ error: IdentityResolutionOrDeserializationError<MicVerificationError>,
aes_key: Option<crypto_provider::aes::Aes128Key>,
metadata_key_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>,
mic_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>,
expected_metadata_key_hmac: Option<[u8; 32]>,
) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let section_salt: V1Salt<C> = [3; 16].into();
let identity_type = EncryptedIdentityDataElementType::Private;
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_for_testing(
+ .section_builder(MicEncryptedSectionEncoder::<C>::new(
identity_type,
section_salt,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -314,7 +333,7 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
@@ -331,7 +350,7 @@
.unwrap_or_else(|| key_seed_hkdf.extended_unsigned_metadata_key_hmac_key())
.as_bytes(),
expected_metadata_key_hmac: expected_metadata_key_hmac.unwrap_or_else(|| {
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key)
+ key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0)
}),
};
let identity_resolution_material =
@@ -346,33 +365,42 @@
assert_eq!(
error,
contents
- .try_deserialize::<C>(&identity_resolution_material, &verification_material)
+ .try_resolve_identity_and_deserialize::<C>(
+ &mut deserialization_arena!(),
+ &identity_resolution_material,
+ &verification_material,
+ )
.unwrap_err()
);
}
-fn do_bad_deserialize_tampered<
- S: Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>),
- A: Fn(&mut Vec<u8>),
->(
- expected_error: MicEncryptedSectionDeserializationError,
- mangle_section: S,
- mangle_adv: A,
+#[derive(Debug, PartialEq)]
+enum DeserializeError {
+ IdentityResolutionOrDeserializationError(
+ IdentityResolutionOrDeserializationError<MicVerificationError>,
+ ),
+ DataElementParseError(DataElementParseError),
+}
+
+fn do_bad_deserialize_tampered(
+ expected_error: DeserializeError,
+ mangle_section: impl Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>),
+ mangle_adv: impl Fn(&mut Vec<u8>),
) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into();
let identity_type = EncryptedIdentityDataElementType::Private;
- let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_for_testing(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
section_salt,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -394,7 +422,7 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
@@ -405,22 +433,35 @@
panic!("incorrect flavor");
};
- // generate a random key pair since we need _some_ public key
+ // generate a random key pair since we need _some_ public key in our discovery
+ // credential, even if it winds up going unused
let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate();
- let crypto_material = HkdfCryptoMaterial::new(&key_seed, &metadata_key, key_pair.public());
- let identity_resolution_material =
- crypto_material.unsigned_identity_resolution_material::<CryptoProviderImpl>();
- let verification_material =
- crypto_material.unsigned_verification_material::<CryptoProviderImpl>();
+ let discovery_credential =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key())
+ .derive_v1_discovery_credential::<CryptoProviderImpl>();
- assert_eq!(
- expected_error,
- contents
- .try_deserialize::<CryptoProviderImpl>(
- &identity_resolution_material,
- &verification_material,
- )
- .unwrap_err()
- );
+ let identity_resolution_material =
+ discovery_credential.unsigned_identity_resolution_material::<CryptoProviderImpl>();
+ let verification_material =
+ discovery_credential.unsigned_verification_material::<CryptoProviderImpl>();
+
+ match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
+ &mut deserialization_arena!(),
+ &identity_resolution_material,
+ &verification_material,
+ ) {
+ Ok(section) => {
+ assert_eq!(
+ expected_error,
+ DeserializeError::DataElementParseError(
+ section.collect_data_elements().unwrap_err()
+ )
+ );
+ }
+ Err(e) => assert_eq!(
+ expected_error,
+ DeserializeError::IdentityResolutionOrDeserializationError(e),
+ ),
+ };
}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
index ad305f5..0afeb08 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/mod.rs
@@ -14,33 +14,124 @@
use crate::{
credential::v1::*,
+ deserialization_arena::DeserializationArena,
extended::{
deserialize::{
- parse_non_identity_des, DecryptedSection, EncryptedIdentityMetadata, EncryptionInfo,
- SectionContents, SectionMic, VerificationMode,
+ DecryptedSection, EncryptedIdentityMetadata, EncryptionInfo, SectionContents,
+ SectionMic, VerificationMode,
},
section_signature_payload::*,
- to_array_view, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN,
+ METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN,
},
- V1Header, NP_SVC_UUID,
+ MetadataKey, V1Header, NP_SVC_UUID,
};
+
+#[cfg(any(feature = "devtools", test))]
+use alloc::vec::Vec;
+#[cfg(feature = "devtools")]
use array_view::ArrayView;
+use core::fmt::Debug;
use crypto_provider::{
- aes::ctr::{AesCtr, AesCtrNonce, NonceAndCounter, AES_CTR_NONCE_LEN},
+ aes::ctr::{AesCtr, AesCtrNonce, NonceAndCounter},
hmac::Hmac,
CryptoProvider,
};
use np_hkdf::v1_salt::V1Salt;
+use super::ArenaOutOfSpace;
+
#[cfg(test)]
mod mic_decrypt_tests;
#[cfg(test)]
mod signature_decrypt_tests;
+/// Represents the contents of an encrypted section
+/// which are directly employed in identity resolution.
+/// This does not incorporate any information about credentials.
#[derive(PartialEq, Eq, Debug)]
-pub(crate) struct SignatureEncryptedSection<'a> {
+pub(crate) struct SectionIdentityResolutionContents {
+ /// The ciphertext for the metadata key
+ pub(crate) metadata_key_ciphertext: [u8; METADATA_KEY_LEN],
+ /// The 12-byte cryptographic nonce which is derived from the encryption info
+ /// and the identity metadata for a particular section.
+ pub(crate) nonce: AesCtrNonce,
+}
+
+impl SectionIdentityResolutionContents {
+ /// Decrypt the contained metadata-key ciphertext buffer whose bytes of plaintext are, maybe, the
+ /// metadata key for an NP identity, as specified via pre-calculated cryptographic materials
+ /// stored in some [`SectionIdentityResolutionMaterial`].
+ ///
+ /// This method does not decrypt an entire section's ciphertext aside from the metadata key,
+ /// and so verification (MIC or signature) needs to be done elsewhere.
+ ///
+ /// Returns `Some` if decrypting the metadata-key ciphertext produces plaintext whose HMAC
+ /// matches the expected MAC. Otherwise, returns `None`.
+ pub(crate) fn try_match<C: CryptoProvider>(
+ &self,
+ identity_resolution_material: &SectionIdentityResolutionMaterial,
+ ) -> Option<IdentityMatch<C>> {
+ let mut decrypt_buf = self.metadata_key_ciphertext;
+ let aes_key = &identity_resolution_material.aes_key;
+ let mut cipher = C::AesCtr128::new(aes_key, NonceAndCounter::from_nonce(self.nonce));
+ cipher.apply_keystream(&mut decrypt_buf[..]);
+
+ let metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C> =
+ identity_resolution_material.metadata_key_hmac_key.into();
+ let expected_metadata_key_hmac = identity_resolution_material.expected_metadata_key_hmac;
+ metadata_key_hmac_key.verify_hmac(&decrypt_buf[..], expected_metadata_key_hmac).ok().map(
+ move |_| IdentityMatch {
+ cipher,
+ metadata_key_plaintext: MetadataKey(decrypt_buf),
+ nonce: self.nonce,
+ },
+ )
+ }
+}
+
+/// Carries data about an identity "match" for a particular section
+/// against some particular V1 identity-resolution crypto-materials.
+pub(crate) struct IdentityMatch<C: CryptoProvider> {
+ /// Decrypted metadata key ciphertext
+ metadata_key_plaintext: MetadataKey,
+ /// The AES-Ctr nonce to be used in section decryption and verification
+ nonce: AesCtrNonce,
+ /// The state of the AES-Ctr cipher after successfully decrypting
+ /// the metadata key ciphertext. May be used to decrypt the remainder
+ /// of the section ciphertext.
+ cipher: C::AesCtr128,
+}
+
+/// Maximum length of a section's contents, after the metadata-key.
+#[allow(unused)]
+const MAX_SECTION_CONTENTS_LEN: usize = NP_ADV_MAX_SECTION_LEN - METADATA_KEY_LEN;
+
+/// Bare, decrypted contents from an encrypted section,
+/// including the decrypted metadata key and the decrypted section ciphertext.
+/// At this point, verification of the plaintext contents has not yet been performed.
+pub(crate) struct RawDecryptedSection<'adv> {
+ pub(crate) metadata_key_plaintext: MetadataKey,
+ pub(crate) nonce: AesCtrNonce,
+ pub(crate) plaintext_contents: &'adv [u8],
+}
+
+#[cfg(feature = "devtools")]
+impl<'adv> RawDecryptedSection<'adv> {
+ pub(crate) fn to_raw_bytes(&self) -> ArrayView<u8, NP_ADV_MAX_SECTION_LEN> {
+ let mut result = Vec::new();
+ result.extend_from_slice(&self.metadata_key_plaintext.0);
+ result.extend_from_slice(self.plaintext_contents);
+ // Won't panic because of the involved lengths
+ ArrayView::try_from_slice(&result).unwrap()
+ }
+}
+
+/// Represents the contents of an encrypted section,
+/// independent of the encryption type.
+#[derive(PartialEq, Eq, Debug)]
+pub(crate) struct EncryptedSectionContents<'a> {
pub(crate) section_header: u8,
- pub(crate) adv_header: &'a V1Header,
+ pub(crate) adv_header: V1Header,
pub(crate) encryption_info: EncryptionInfo,
pub(crate) identity: EncryptedIdentityMetadata,
/// All ciphertext (Contents of identity DE + all DEs)
@@ -48,83 +139,134 @@
pub(crate) all_ciphertext: &'a [u8],
}
-impl<'a> SignatureEncryptedSection<'a> {
- /// Decrypt the ciphertext and check that the first [METADATA_KEY_LEN] bytes match the expected HMAC.
- /// The remaining data is not verified.
+impl<'a> EncryptedSectionContents<'a> {
+ /// Constructs a representation of the contents of an encrypted V1 section
+ /// from the advertisement header, the section header, information about
+ /// the encryption used for identity verification, identity metadata,
+ /// and the entire section contents as an undecrypted ciphertext.
///
- /// Returns `Ok` if the section was an identity match.
- /// Otherwise, returns `Err`.
- pub(crate) fn try_decrypt<P: CryptoProvider>(
- &self,
- identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
- ) -> Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, SignatureEncryptedSectionDeserializationError>
- {
- let salt: V1Salt<P> = self.encryption_info.salt().into();
- let (mut buf, mut cipher) = try_decrypt_metadata_key_prefix::<P>(
- self.all_ciphertext,
- salt.derive(Some(self.identity.offset)).expect("AES-CTR nonce is a valid HKDF size"),
- identity_resolution_material.as_raw_resolution_material(),
- )
- .ok_or(SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch)?;
-
- // decrypt everything else
- cipher.decrypt(&mut buf[METADATA_KEY_LEN..]);
-
- Ok(to_array_view(buf))
+ /// # Panics
+ /// If `all_ciphertext` is greater than `NP_ADV_MAX_SECTION_LEN` bytes,
+ /// or less than `METADATA_KEY_LEN` bytes.
+ pub(crate) fn new(
+ adv_header: V1Header,
+ section_header: u8,
+ encryption_info: EncryptionInfo,
+ identity: EncryptedIdentityMetadata,
+ all_ciphertext: &'a [u8],
+ ) -> Self {
+ assert!(all_ciphertext.len() >= METADATA_KEY_LEN);
+ assert!(all_ciphertext.len() <= NP_ADV_MAX_SECTION_LEN);
+ Self { adv_header, section_header, encryption_info, identity, all_ciphertext }
}
- /// Try deserializing into a [Section].
- ///
- /// Returns an error if the credential is incorrect or if the section data is malformed.
+ /// Gets the salt for this encrypted section
+ pub(crate) fn salt<C: CryptoProvider>(&self) -> V1Salt<C> {
+ self.encryption_info.salt().into()
+ }
+
+ /// Constructs a cryptographic nonce for this encrypted section
+ /// based on the contained salt.
+ pub(crate) fn compute_nonce<C: CryptoProvider>(&self) -> AesCtrNonce {
+ self.salt::<C>()
+ .derive(Some(self.identity.offset))
+ .expect("AES-CTR nonce is a valid HKDF size")
+ }
+
+ /// Constructs some cryptographic contents for section identity-resolution
+ /// out of the entire contents of this encrypted section.
+ pub(crate) fn compute_identity_resolution_contents<C: CryptoProvider>(
+ &self,
+ ) -> SectionIdentityResolutionContents {
+ let nonce = self.compute_nonce::<C>();
+ let metadata_key_ciphertext: [u8; METADATA_KEY_LEN] =
+ self.all_ciphertext[..METADATA_KEY_LEN].try_into().unwrap();
+
+ SectionIdentityResolutionContents { nonce, metadata_key_ciphertext }
+ }
+
+ /// Given an identity-match, decrypts the ciphertext in this encrypted section
+ /// and returns the raw bytes of the decrypted plaintext.
+ pub(crate) fn decrypt_ciphertext<C: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ mut identity_match: IdentityMatch<C>,
+ ) -> Result<RawDecryptedSection<'a>, ArenaOutOfSpace> {
+ // Fill decrypt_buf with the ciphertext after the metadata key
+ let decrypt_buf =
+ arena.allocate(u8::try_from(self.all_ciphertext.len() - METADATA_KEY_LEN).expect(
+ "all_ciphertext.len() must be in [METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]",
+ ))?;
+ decrypt_buf.copy_from_slice(&self.all_ciphertext[METADATA_KEY_LEN..]);
+
+ // Decrypt everything after the metadata key
+ identity_match.cipher.apply_keystream(decrypt_buf);
+
+ Ok(RawDecryptedSection {
+ metadata_key_plaintext: identity_match.metadata_key_plaintext,
+ nonce: identity_match.nonce,
+ plaintext_contents: decrypt_buf,
+ })
+ }
+
+ /// Try decrypting into some raw bytes given some raw identity-resolution material.
+ #[cfg(feature = "devtools")]
+ pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ identity_resolution_material: &SectionIdentityResolutionMaterial,
+ ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
+ let identity_resolution_contents = self.compute_identity_resolution_contents::<P>();
+ identity_resolution_contents.try_match(identity_resolution_material).map(|identity_match| {
+ let decrypted_section = self.decrypt_ciphertext::<P>(arena, identity_match)?;
+ Ok(decrypted_section.to_raw_bytes())
+ })
+ }
+}
+
+/// An encrypted section which is verified using a ed25519 signature
+#[derive(PartialEq, Eq, Debug)]
+pub(crate) struct SignatureEncryptedSection<'a> {
+ pub(crate) contents: EncryptedSectionContents<'a>,
+}
+
+impl<'a> SignatureEncryptedSection<'a> {
+ /// Try deserializing into a [`DecryptedSection`] given an identity-match
+ /// with some paired verification material for the matched identity.
pub(crate) fn try_deserialize<P>(
&self,
- identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
+ arena: &mut DeserializationArena<'a>,
+ identity_match: IdentityMatch<P>,
verification_material: &SignedSectionVerificationMaterial,
- ) -> Result<DecryptedSection, SignatureEncryptedSectionDeserializationError>
+ ) -> Result<DecryptedSection<'a>, DeserializationError<SignatureVerificationError>>
where
P: CryptoProvider,
{
- let raw_salt = self.encryption_info.salt();
- let salt: V1Salt<P> = raw_salt.into();
- let plaintext = self.try_decrypt::<P>(identity_resolution_material)?;
-
- // won't panic: plaintext length is at least METADATA_KEY_LEN
- let (metadata_key, remaining) = plaintext.as_slice().split_at(METADATA_KEY_LEN);
- let metadata_key =
- metadata_key.try_into().expect("slice is the same length as the desired array");
+ let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?;
+ let metadata_key = raw_decrypted.metadata_key_plaintext;
+ let nonce = raw_decrypted.nonce;
+ let remaining = raw_decrypted.plaintext_contents;
if remaining.len() < crypto_provider::ed25519::SIGNATURE_LENGTH {
- return Err(SignatureEncryptedSectionDeserializationError::SignatureMissing);
+ return Err(SignatureVerificationError::SignatureMissing.into());
}
// should not panic due to above check
let (non_identity_des, sig) =
remaining.split_at(remaining.len() - crypto_provider::ed25519::SIGNATURE_LENGTH);
- // de offset 2 because of leading encryption info and identity DEs
- let (_, ref_des) = parse_non_identity_des(2)(non_identity_des)
- .map_err(|_| SignatureEncryptedSectionDeserializationError::DeParseError)?;
-
// All implementations only check for 64 bytes, and this will always result in a 64 byte signature.
let expected_signature =
np_ed25519::Signature::<P>::try_from(sig).expect("Signature is always 64 bytes.");
- let nonce = salt
- .derive::<{ AES_CTR_NONCE_LEN }>(Some(self.identity.offset))
- .expect("AES-CTR nonce is a valid HKDF length");
-
- // all other plaintext, except for the signature.
- // Won't panic because we know we just parsed the sig de from this,
- // and we're using the as-parsed lengths
- let remainder = &plaintext.as_slice()[..plaintext.len() - sig.len()];
-
let section_signature_payload = SectionSignaturePayload::from_deserialized_parts(
- self.adv_header.header_byte,
- self.section_header,
- &self.encryption_info.bytes,
+ self.contents.adv_header.header_byte,
+ self.contents.section_header,
+ &self.contents.encryption_info.bytes,
&nonce,
- self.identity.header_bytes,
- remainder,
+ self.contents.identity.header_bytes,
+ metadata_key,
+ non_identity_des,
);
let public_key = verification_material.signature_verification_public_key();
@@ -133,161 +275,221 @@
// Length of the payload should fit in the signature verification buffer.
debug_assert!(e != np_ed25519::SignatureVerificationError::PayloadTooBig);
- SignatureEncryptedSectionDeserializationError::SignatureMismatch
+ SignatureVerificationError::SignatureMismatch
})?;
+ let salt = self.contents.salt::<P>();
+
Ok(DecryptedSection::new(
- self.identity.identity_type,
+ self.contents.identity.identity_type,
VerificationMode::Signature,
metadata_key,
- raw_salt,
- SectionContents::new(self.section_header, &ref_des),
+ salt.into(),
+ // de offset 2 because of leading encryption info and identity DEs
+ SectionContents::new(self.contents.section_header, non_identity_des, 2),
))
}
+
+ /// Try decrypting into some raw bytes given some raw signed crypto-material.
+ #[cfg(feature = "devtools")]
+ pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
+ ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
+ self.contents.try_resolve_identity_and_decrypt::<P>(
+ arena,
+ identity_resolution_material.as_raw_resolution_material(),
+ )
+ }
+
+ /// Try deserializing into a [Section] given some raw signed crypto-material.
+ #[cfg(test)]
+ pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ identity_resolution_material: &SignedSectionIdentityResolutionMaterial,
+ verification_material: &SignedSectionVerificationMaterial,
+ ) -> Result<
+ DecryptedSection,
+ IdentityResolutionOrDeserializationError<SignatureVerificationError>,
+ > {
+ let section_identity_resolution_contents =
+ self.contents.compute_identity_resolution_contents::<P>();
+ match section_identity_resolution_contents
+ .try_match::<P>(identity_resolution_material.as_raw_resolution_material())
+ {
+ Some(identity_match) => self
+ .try_deserialize(arena, identity_match, verification_material)
+ .map_err(|e| e.into()),
+ None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError),
+ }
+ }
}
-/// Should not be exposed publicly as it's too detailed.
+/// An error when attempting to resolve an identity and then
+/// attempt to deserialize an encrypted advertisement.
+///
+/// This should not be exposed publicly, since it's too
+/// detailed.
+#[cfg(test)]
#[derive(Debug, PartialEq, Eq)]
-pub(crate) enum SignatureEncryptedSectionDeserializationError {
- /// The decrypted metadata key did not have the right MAC
- MetadataKeyMacMismatch,
+pub(crate) enum IdentityResolutionOrDeserializationError<V: VerificationError> {
+ /// Failed to match the encrypted adv to an identity
+ IdentityMatchingError,
+ /// Failed to deserialize the encrypted adv after matching the identity
+ DeserializationError(DeserializationError<V>),
+}
+
+#[cfg(test)]
+impl<V: VerificationError> From<DeserializationError<V>>
+ for IdentityResolutionOrDeserializationError<V>
+{
+ fn from(deserialization_error: DeserializationError<V>) -> Self {
+ Self::DeserializationError(deserialization_error)
+ }
+}
+
+#[cfg(test)]
+impl<V: VerificationError> From<V> for IdentityResolutionOrDeserializationError<V> {
+ fn from(verification_error: V) -> Self {
+ Self::DeserializationError(DeserializationError::VerificationError(verification_error))
+ }
+}
+
+/// An error when attempting to deserialize an encrypted advertisement,
+/// assuming that we already have an identity-match.
+///
+/// This should not be exposed publicly, since it's too
+/// detailed.
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) enum DeserializationError<V: VerificationError> {
+ /// Verification failed
+ VerificationError(V),
+ /// The given arena ran out of space
+ ArenaOutOfSpace,
+}
+
+impl<V: VerificationError> From<ArenaOutOfSpace> for DeserializationError<V> {
+ fn from(_: ArenaOutOfSpace) -> Self {
+ Self::ArenaOutOfSpace
+ }
+}
+
+impl<V: VerificationError> From<V> for DeserializationError<V> {
+ fn from(verification_error: V) -> Self {
+ Self::VerificationError(verification_error)
+ }
+}
+
+/// Common trait bound for errors which arise during
+/// verification of encrypted section contents.
+///
+/// Implementors should not be exposed publicly, since it's too
+/// detailed.
+pub(crate) trait VerificationError: Debug + PartialEq + Eq {}
+
+/// An error when attempting to verify a signature
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) enum SignatureVerificationError {
/// The provided signature did not match the calculated signature
SignatureMismatch,
/// The provided signature is missing
SignatureMissing,
- /// Decrypted DEs could not be parsed, possibly because the ciphertext was tampered with, as
- /// DE parsing has to be done before the signature is verified
- DeParseError,
}
+impl VerificationError for SignatureVerificationError {}
+
+/// An encrypted section whose contents are verified to match a message integrity code (MIC)
#[derive(PartialEq, Eq, Debug)]
pub(crate) struct MicEncryptedSection<'a> {
- pub(crate) section_header: u8,
- pub(crate) adv_header: &'a V1Header,
- pub(crate) encryption_info: EncryptionInfo,
+ pub(crate) contents: EncryptedSectionContents<'a>,
pub(crate) mic: SectionMic,
- pub(crate) identity: EncryptedIdentityMetadata,
- /// All ciphertext (Contents of identity DE + all DEs).
- /// Length must be in `[METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]`.
- pub(crate) all_ciphertext: &'a [u8],
}
impl<'a> MicEncryptedSection<'a> {
- /// Attempt to decrypt and verify the encrypted portion of the section with the provided
- /// precalculated cryptographic material for an MIC-encrypted section.
- ///
- /// If successful, returns a buffer containing the decrypted plaintext.
- pub(crate) fn try_decrypt<P: CryptoProvider>(
- &self,
- identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
- verification_material: &UnsignedSectionVerificationMaterial,
- ) -> Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, MicEncryptedSectionDeserializationError>
- {
- let salt: V1Salt<P> = self.encryption_info.salt().into();
- let nonce =
- salt.derive(Some(self.identity.offset)).expect("AES-CTR nonce is a valid HKDF size");
-
- let (mut buf, mut cipher) = try_decrypt_metadata_key_prefix::<P>(
- self.all_ciphertext,
- nonce,
- identity_resolution_material.as_raw_resolution_material(),
- )
- .ok_or(MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch)?;
-
- // if mic is ok, the section was generated by someone holding at least the shared credential
- let mut mic_hmac = verification_material.mic_hmac_key::<P>().build_hmac();
- mic_hmac.update(&NP_SVC_UUID);
- mic_hmac.update(&[self.adv_header.header_byte]);
- mic_hmac.update(&[self.section_header]);
- mic_hmac.update(&self.encryption_info.bytes);
- mic_hmac.update(&nonce);
- mic_hmac.update(&self.identity.header_bytes);
- mic_hmac.update(self.all_ciphertext);
- mic_hmac
- // adv only contains first 16 bytes of HMAC
- .verify_truncated_left(&self.mic.mic)
- .map_err(|_e| MicEncryptedSectionDeserializationError::MicMismatch)?;
-
- // decrypt everything else
- cipher.decrypt(&mut buf[METADATA_KEY_LEN..]);
-
- Ok(to_array_view(buf))
- }
-
- /// Try deserializing into a [Section].
+ /// Try deserializing into a [`DecryptedSection`].
///
/// Returns an error if the credential is incorrect or if the section data is malformed.
pub(crate) fn try_deserialize<P>(
&self,
- identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
+ arena: &mut DeserializationArena<'a>,
+ identity_match: IdentityMatch<P>,
verification_material: &UnsignedSectionVerificationMaterial,
- ) -> Result<DecryptedSection, MicEncryptedSectionDeserializationError>
+ ) -> Result<DecryptedSection<'a>, DeserializationError<MicVerificationError>>
where
P: CryptoProvider,
{
- let plaintext =
- self.try_decrypt::<P>(identity_resolution_material, verification_material)?;
- // won't panic: plaintext is at least METADATA_KEY_LEN long
- let (metadata_key, remaining_des) = plaintext.as_slice().split_at(METADATA_KEY_LEN);
- let metadata_key =
- metadata_key.try_into().expect("slice is the same length as the desired array");
- // offset 2 for encryption info and identity DEs
- let (_, ref_des) = parse_non_identity_des(2)(remaining_des)
- .map_err(|_| MicEncryptedSectionDeserializationError::DeParseError)?;
+ let raw_decrypted = self.contents.decrypt_ciphertext(arena, identity_match)?;
+ let metadata_key = raw_decrypted.metadata_key_plaintext;
+ let nonce = raw_decrypted.nonce;
+ let remaining_des = raw_decrypted.plaintext_contents;
+
+ // if mic is ok, the section was generated by someone holding at least the shared credential
+ let mut mic_hmac = verification_material.mic_hmac_key::<P>().build_hmac();
+ mic_hmac.update(&NP_SVC_UUID);
+ mic_hmac.update(&[self.contents.adv_header.header_byte]);
+ mic_hmac.update(&[self.contents.section_header]);
+ mic_hmac.update(&self.contents.encryption_info.bytes);
+ mic_hmac.update(&nonce);
+ mic_hmac.update(&self.contents.identity.header_bytes);
+ mic_hmac.update(self.contents.all_ciphertext);
+ mic_hmac
+ // adv only contains first 16 bytes of HMAC
+ .verify_truncated_left(&self.mic.mic)
+ .map_err(|_e| MicVerificationError::MicMismatch)?;
+
+ let salt = self.contents.salt::<P>();
Ok(DecryptedSection::new(
- self.identity.identity_type,
+ self.contents.identity.identity_type,
VerificationMode::Mic,
metadata_key,
- self.encryption_info.salt(),
- SectionContents::new(self.section_header, &ref_des),
+ salt.into(),
+ // offset 2 for encryption info and identity DEs
+ SectionContents::new(self.contents.section_header, remaining_des, 2),
))
}
+
+ /// Try decrypting into some raw bytes given some raw unsigned crypto-material.
+ #[cfg(feature = "devtools")]
+ pub(crate) fn try_resolve_identity_and_decrypt<P: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
+ ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
+ self.contents.try_resolve_identity_and_decrypt::<P>(
+ arena,
+ identity_resolution_material.as_raw_resolution_material(),
+ )
+ }
+
+ /// Try deserializing into a [Section] given some raw unsigned crypto-material.
+ #[cfg(test)]
+ pub(crate) fn try_resolve_identity_and_deserialize<P: CryptoProvider>(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ identity_resolution_material: &UnsignedSectionIdentityResolutionMaterial,
+ verification_material: &UnsignedSectionVerificationMaterial,
+ ) -> Result<DecryptedSection, IdentityResolutionOrDeserializationError<MicVerificationError>>
+ {
+ let section_identity_resolution_contents =
+ self.contents.compute_identity_resolution_contents::<P>();
+ match section_identity_resolution_contents
+ .try_match::<P>(identity_resolution_material.as_raw_resolution_material())
+ {
+ Some(identity_match) => self
+ .try_deserialize(arena, identity_match, verification_material)
+ .map_err(|e| e.into()),
+ None => Err(IdentityResolutionOrDeserializationError::IdentityMatchingError),
+ }
+ }
}
-/// Should not be exposed publicly as it's too detailed.
#[derive(Debug, PartialEq, Eq)]
-pub(crate) enum MicEncryptedSectionDeserializationError {
- /// Plaintext metadata key did not have the expected MAC
- MetadataKeyMacMismatch,
+pub(crate) enum MicVerificationError {
/// Calculated MIC did not match MIC from section
MicMismatch,
- /// Parsing of decrypted DEs failed
- DeParseError,
}
-/// Decrypt a ciphertext buffer whose first [METADATA_KEY_LEN] bytes of plaintext are, maybe, the
-/// metadata key for an NP identity.
-///
-/// Returns `Some` if decrypting the first [METADATA_KEY_LEN] bytes produces plaintext whose HMAC
-/// matches the expected MAC. Only the first [METADATA_KEY_LEN] bytes are decrypted. The cipher is
-/// returned along with the buffer as it is in the appropriate state to continue decrypting the
-/// remaining ciphertext.
-///
-/// Returns `None` if the plaintext does not match the expected HMAC.
-///
-/// # Panics
-///
-/// Panics if `ciphertext`'s length is not in `[METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN]`.
-#[allow(clippy::type_complexity)]
-fn try_decrypt_metadata_key_prefix<C: CryptoProvider>(
- ciphertext: &[u8],
- nonce: AesCtrNonce,
- identity_resolution_material: &SectionIdentityResolutionMaterial,
-) -> Option<(tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>, C::AesCtr128)> {
- assert!(ciphertext.len() <= NP_ADV_MAX_SECTION_LEN);
- let mut decrypt_buf = tinyvec::ArrayVec::<[u8; NP_ADV_MAX_SECTION_LEN]>::new();
- let aes_key = &identity_resolution_material.aes_key;
- let mut cipher = C::AesCtr128::new(aes_key, NonceAndCounter::from_nonce(nonce));
-
- decrypt_buf.extend_from_slice(ciphertext);
- cipher.decrypt(&mut decrypt_buf[..METADATA_KEY_LEN]);
-
- let metadata_key_hmac_key: np_hkdf::NpHmacSha256Key<C> =
- identity_resolution_material.metadata_key_hmac_key.into();
- let expected_metadata_key_hmac = identity_resolution_material.expected_metadata_key_hmac;
- metadata_key_hmac_key
- .verify_hmac(&decrypt_buf[..METADATA_KEY_LEN], expected_metadata_key_hmac)
- .ok()
- .map(|_| (decrypt_buf, cipher))
-}
+impl VerificationError for MicVerificationError {}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
index b78cad9..10fc46b 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/encrypted_section/signature_decrypt_tests.rs
@@ -14,8 +14,12 @@
extern crate std;
+use crate::deserialization_arena;
use crate::extended::data_elements::TxPowerDataElement;
-use crate::extended::deserialize::{DecryptedSection, EncryptionInfo, RawV1Salt};
+use crate::extended::deserialize::{
+ DataElementParseError, DecryptedSection, EncryptionInfo, RawV1Salt, SectionContents,
+};
+use crate::extended::serialize::AdvertisementType;
use crate::shared_data::TxPower;
use crate::{
credential::v1::*,
@@ -23,23 +27,22 @@
extended::{
deserialize::{
encrypted_section::{
- SignatureEncryptedSection, SignatureEncryptedSectionDeserializationError,
+ EncryptedSectionContents, IdentityResolutionOrDeserializationError,
+ SignatureEncryptedSection, SignatureVerificationError,
},
parse_sections,
test_stubs::IntermediateSectionExt,
- CiphertextSection, EncryptedIdentityMetadata, OffsetDataElement, SectionContents,
- VerificationMode,
+ CiphertextSection, DataElement, EncryptedIdentityMetadata, VerificationMode,
},
section_signature_payload::*,
serialize::{
- AdvBuilder, CapacityLimitedVec, SignedEncrypted, SingleTypeDataElement,
+ AdvBuilder, CapacityLimitedVec, SignedEncryptedSectionEncoder, SingleTypeDataElement,
WriteDataElement,
},
NP_ADV_MAX_SECTION_LEN,
},
- parse_adv_header, AdvHeader, Section,
+ parse_adv_header, AdvHeader, MetadataKey, Section,
};
-use array_view::ArrayView;
use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider};
use crypto_provider_default::CryptoProviderImpl;
use np_hkdf::v1_salt;
@@ -51,7 +54,7 @@
#[test]
fn deserialize_signature_encrypted_correct_keys() {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let raw_salt = RawV1Salt([3; 16]);
let section_salt = V1Salt::<CryptoProviderImpl>::from(raw_salt);
@@ -59,15 +62,16 @@
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let key_pair = KeyPair::generate();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
V1Salt::from(*section_salt.as_array_ref()),
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -86,7 +90,7 @@
panic!("incorrect header");
};
- let sections = parse_sections(&adv_header, remaining).unwrap();
+ let sections = parse_sections(adv_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
@@ -105,36 +109,44 @@
let section_len = 19 + 2 + 16 + 2 + 64;
assert_eq!(
&SignatureEncryptedSection {
- section_header: section_len,
- adv_header: &adv_header,
- encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
- identity: EncryptedIdentityMetadata {
- header_bytes: [0x90, 0x1],
- offset: 1.into(),
- identity_type: EncryptedIdentityDataElementType::Private,
+ contents: EncryptedSectionContents {
+ section_header: section_len,
+ adv_header,
+ encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
+ identity: EncryptedIdentityMetadata {
+ header_bytes: [0x90, 0x1],
+ offset: 1.into(),
+ identity_type: EncryptedIdentityDataElementType::Private,
+ },
+ // adv header + salt + section header + encryption info + identity header
+ all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..],
},
- // adv header + salt + section header + encryption info + identity header
- all_ciphertext: &adv.as_slice()[1 + 1 + 19 + 2..],
},
contents
);
// plaintext is correct
{
- let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>(
+ let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
key_seed,
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
+ key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
+ key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
key_pair.public(),
);
let signed_identity_resolution_material =
crypto_material.signed_identity_resolution_material::<CryptoProviderImpl>();
- let decrypted = contents
- .try_decrypt::<CryptoProviderImpl>(&signed_identity_resolution_material)
+ let identity_resolution_contents =
+ contents.contents.compute_identity_resolution_contents::<CryptoProviderImpl>();
+ let identity_match = identity_resolution_contents
+ .try_match::<CryptoProviderImpl>(
+ &signed_identity_resolution_material.into_raw_resolution_material(),
+ )
.unwrap();
- let mut expected = metadata_key.as_slice().to_vec();
- // battery de
+ let mut arena = deserialization_arena!();
+ let decrypted = contents.contents.decrypt_ciphertext(&mut arena, identity_match).unwrap();
+
+ let mut expected = Vec::new();
expected.extend_from_slice(txpower_de.de_header().serialize().as_slice());
txpower_de.write_de_contents(&mut expected);
@@ -151,20 +163,22 @@
&encryption_info,
&nonce,
[0x90, 0x1],
+ metadata_key,
&expected,
);
expected.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes());
-
- assert_eq!(&expected, decrypted.as_slice());
+ assert_eq!(metadata_key, decrypted.metadata_key_plaintext);
+ assert_eq!(nonce, decrypted.nonce);
+ assert_eq!(&expected, decrypted.plaintext_contents);
}
// deserialization to Section works
{
- let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>(
+ let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
key_seed,
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
+ key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
+ key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
key_pair.public(),
);
let signed_identity_resolution_material =
@@ -172,8 +186,10 @@
let signed_verification_material =
crypto_material.signed_verification_material::<CryptoProviderImpl>();
+ let mut arena = deserialization_arena!();
let section = contents
- .try_deserialize::<CryptoProviderImpl>(
+ .try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
+ &mut arena,
&signed_identity_resolution_material,
&signed_verification_material,
)
@@ -187,27 +203,23 @@
raw_salt,
SectionContents {
section_header: 19 + 2 + 16 + 1 + 1 + 64,
- // battery DE
- de_data: ArrayView::try_from_slice(&[7]).unwrap(),
- data_elements: vec![OffsetDataElement {
- offset: 2.into(),
- de_type: 0x05_u8.into(),
- start_of_contents: 0,
- contents_len: 1,
- }],
+ data_element_start_offset: 2,
+ de_region_excl_identity:
+ &[txpower_de.de_header().serialize().as_slice(), &[7],].concat(),
},
),
section
);
+ let data_elements = section.collect_data_elements().unwrap();
+ assert_eq!(
+ data_elements,
+ &[DataElement { offset: 2.into(), de_type: 0x05_u8.into(), contents: &[7] }]
+ );
assert_eq!(
- vec![(
- v1_salt::DataElementOffset::from(2_usize),
- TxPowerDataElement::DE_TYPE,
- vec![7u8]
- )],
- section
- .data_elements()
+ vec![(v1_salt::DataElementOffset::from(2_u8), TxPowerDataElement::DE_TYPE, vec![7u8])],
+ data_elements
+ .into_iter()
.map(|de| (de.offset(), de.de_type(), de.contents().to_vec()))
.collect::<Vec<_>>()
);
@@ -218,7 +230,7 @@
fn deserialize_signature_encrypted_incorrect_aes_key_error() {
// bad aes key -> bad metadata key plaintext
do_bad_deserialize_params::<CryptoProviderImpl>(
- SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
Some([0xFF; 16].into()),
None,
None,
@@ -230,7 +242,7 @@
fn deserialize_signature_encrypted_incorrect_metadata_key_hmac_key_error() {
// bad metadata key hmac key -> bad calculated metadata key mac
do_bad_deserialize_params::<CryptoProviderImpl>(
- SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
None,
Some([0xFF; 32].into()),
None,
@@ -242,7 +254,7 @@
fn deserialize_signature_encrypted_incorrect_expected_metadata_key_hmac_error() {
// bad expected metadata key mac
do_bad_deserialize_params::<CryptoProviderImpl>(
- SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
None,
None,
Some([0xFF; 32]),
@@ -254,7 +266,7 @@
fn deserialize_signature_encrypted_incorrect_pub_key_error() {
// a random pub key will lead to signature mismatch
do_bad_deserialize_params::<CryptoProviderImpl>(
- SignatureEncryptedSectionDeserializationError::SignatureMismatch,
+ SignatureVerificationError::SignatureMismatch.into(),
None,
None,
None,
@@ -266,7 +278,9 @@
fn deserialize_signature_encrypted_incorrect_salt_error() {
// bad salt -> bad iv -> bad metadata key plaintext
do_bad_deserialize_tampered(
- SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ IdentityResolutionOrDeserializationError::IdentityMatchingError,
+ ),
None,
|_| {},
|adv_mut| adv_mut[5..21].copy_from_slice(&[0xFF; 16]),
@@ -276,7 +290,9 @@
#[test]
fn deserialize_signature_encrypted_tampered_signature_error() {
do_bad_deserialize_tampered(
- SignatureEncryptedSectionDeserializationError::SignatureMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ SignatureVerificationError::SignatureMismatch.into(),
+ ),
None,
|_| {},
// flip a bit in the middle of the signature
@@ -290,7 +306,9 @@
#[test]
fn deserialize_signature_encrypted_tampered_ciphertext_error() {
do_bad_deserialize_tampered(
- SignatureEncryptedSectionDeserializationError::SignatureMismatch,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ SignatureVerificationError::SignatureMismatch.into(),
+ ),
None,
|_| {},
// flip a bit outside of the signature
@@ -305,7 +323,9 @@
fn deserialize_signature_encrypted_missing_signature_de_error() {
let section_len = 19 + 2 + 16 + 1 + 1;
do_bad_deserialize_tampered(
- SignatureEncryptedSectionDeserializationError::SignatureMissing,
+ DeserializeError::IdentityResolutionOrDeserializationError(
+ SignatureVerificationError::SignatureMissing.into(),
+ ),
Some(section_len),
|_| {},
|adv_mut| {
@@ -320,7 +340,7 @@
#[test]
fn deserialize_signature_encrypted_des_wont_parse() {
do_bad_deserialize_tampered(
- SignatureEncryptedSectionDeserializationError::DeParseError,
+ DeserializeError::DataElementParseError(DataElementParseError::UnexpectedDataAfterEnd),
Some(19 + 2 + 16 + 1 + 1 + 64 + 1),
// add an impossible DE
|section| section.try_push(0xFF).unwrap(),
@@ -331,28 +351,29 @@
/// Attempt a deserialization that will fail when using the provided parameters for decryption only.
/// `None` means use the correct value for that parameter.
fn do_bad_deserialize_params<C: CryptoProvider>(
- error: SignatureEncryptedSectionDeserializationError,
+ error: IdentityResolutionOrDeserializationError<SignatureVerificationError>,
aes_key: Option<crypto_provider::aes::Aes128Key>,
metadata_key_hmac_key: Option<np_hkdf::NpHmacSha256Key<C>>,
expected_metadata_key_hmac: Option<[u8; 32]>,
pub_key: Option<np_ed25519::PublicKey<C>>,
) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let section_salt: v1_salt::V1Salt<C> = [3; 16].into();
let identity_type = EncryptedIdentityDataElementType::Private;
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed);
let key_pair = np_ed25519::KeyPair::<C>::generate();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new(
+ .section_builder(SignedEncryptedSectionEncoder::new(
identity_type,
section_salt,
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -370,7 +391,7 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
@@ -389,7 +410,9 @@
.unwrap_or_else(|| key_seed_hkdf.extended_signed_metadata_key_hmac_key())
.as_bytes(),
expected_metadata_key_hmac: expected_metadata_key_hmac.unwrap_or_else(|| {
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key)
+ key_seed_hkdf
+ .extended_signed_metadata_key_hmac_key()
+ .calculate_hmac(&metadata_key.0)
}),
});
@@ -400,7 +423,8 @@
assert_eq!(
error,
contents
- .try_deserialize::<C>(
+ .try_resolve_identity_and_deserialize::<C>(
+ &mut deserialization_arena!(),
&signed_identity_resolution_material,
&signed_verification_material,
)
@@ -408,34 +432,40 @@
);
}
+#[derive(Debug, PartialEq)]
+enum DeserializeError {
+ IdentityResolutionOrDeserializationError(
+ IdentityResolutionOrDeserializationError<SignatureVerificationError>,
+ ),
+ DataElementParseError(DataElementParseError),
+}
+
/// Run a test that mangles the advertisement contents before attempting to deserialize.
///
/// Since the advertisement is ciphertext, only changes outside
-fn do_bad_deserialize_tampered<
- A: Fn(&mut Vec<u8>),
- S: Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>),
->(
- expected_error: SignatureEncryptedSectionDeserializationError,
+fn do_bad_deserialize_tampered(
+ expected_error: DeserializeError,
expected_section_len: Option<u8>,
- mangle_section: S,
- mangle_adv_contents: A,
+ mangle_section: impl Fn(&mut CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>),
+ mangle_adv_contents: impl Fn(&mut Vec<u8>),
) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let section_salt: v1_salt::V1Salt<CryptoProviderImpl> = [3; 16].into();
let identity_type = EncryptedIdentityDataElementType::Private;
- let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed);
+ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let key_pair = KeyPair::generate();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new(
+ .section_builder(SignedEncryptedSectionEncoder::new(
identity_type,
section_salt,
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -457,7 +487,7 @@
panic!("incorrect header");
};
- let sections = parse_sections(&adv_header, remaining).unwrap();
+ let sections = parse_sections(adv_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections.into_iter().next().unwrap();
@@ -476,23 +506,25 @@
let section_len = 19 + 2 + 16 + 2 + 64;
assert_eq!(
&SignatureEncryptedSection {
- section_header: expected_section_len.unwrap_or(section_len),
- adv_header: &adv_header,
- encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
- identity: EncryptedIdentityMetadata {
- header_bytes: [0x90, 0x1],
- offset: 1.into(),
- identity_type: EncryptedIdentityDataElementType::Private,
+ contents: EncryptedSectionContents {
+ section_header: expected_section_len.unwrap_or(section_len),
+ adv_header,
+ encryption_info: EncryptionInfo { bytes: encryption_info_bytes },
+ identity: EncryptedIdentityMetadata {
+ header_bytes: [0x90, 0x1],
+ offset: 1.into(),
+ identity_type: EncryptedIdentityDataElementType::Private,
+ },
+ all_ciphertext: &adv_mut[1 + 1 + 19 + 2..],
},
- all_ciphertext: &adv_mut[1 + 1 + 19 + 2..],
},
contents
);
- let crypto_material = MinimumFootprintV1CryptoMaterial::new::<CryptoProviderImpl>(
+ let crypto_material = V1DiscoveryCredential::new::<CryptoProviderImpl>(
key_seed,
- key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
+ key_seed_hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
+ key_seed_hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
key_pair.public(),
);
let identity_resolution_material =
@@ -500,13 +532,22 @@
let verification_material =
crypto_material.signed_verification_material::<CryptoProviderImpl>();
- assert_eq!(
- expected_error,
- contents
- .try_deserialize::<CryptoProviderImpl>(
- &identity_resolution_material,
- &verification_material,
- )
- .unwrap_err()
- );
+ match contents.try_resolve_identity_and_deserialize::<CryptoProviderImpl>(
+ &mut deserialization_arena!(),
+ &identity_resolution_material,
+ &verification_material,
+ ) {
+ Ok(section) => {
+ assert_eq!(
+ expected_error,
+ DeserializeError::DataElementParseError(
+ section.collect_data_elements().unwrap_err()
+ ),
+ );
+ }
+ Err(e) => assert_eq!(
+ expected_error,
+ DeserializeError::IdentityResolutionOrDeserializationError(e),
+ ),
+ };
}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/mod.rs b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
index 1829ad6..d431325 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/mod.rs
@@ -18,9 +18,8 @@
use alloc::vec::Vec;
use core::array::TryFromSliceError;
-use core::borrow::Borrow;
-use core::slice;
+use core::fmt::Debug;
use nom::{branch, bytes, combinator, error, multi, number, sequence};
use strum::IntoEnumIterator as _;
@@ -28,23 +27,30 @@
use crypto_provider::CryptoProvider;
use np_hkdf::v1_salt::{self, V1Salt};
-use crate::extended::NP_V1_ADV_MAX_SECTION_COUNT;
+#[cfg(any(feature = "devtools", test))]
+use crate::credential::v1::V1DiscoveryCryptoMaterial;
+use crate::credential::v1::V1;
+use crate::deserialization_arena::ArenaOutOfSpace;
+#[cfg(any(feature = "devtools", test))]
+use crate::deserialization_arena::DeserializationArena;
+#[cfg(test)]
+use crate::extended::deserialize::encrypted_section::IdentityResolutionOrDeserializationError;
+use crate::extended::{NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT};
use crate::{
- credential::{v1::*, V1Credential},
de_type::{EncryptedIdentityDataElementType, IdentityDataElementType},
extended::{
data_elements::{MIC_ENCRYPTION_SCHEME, SIGNATURE_ENCRYPTION_SCHEME},
de_type::{DeType, ExtendedDataElementType as _},
deserialize::encrypted_section::{
- MicEncryptedSection, MicEncryptedSectionDeserializationError,
- SignatureEncryptedSection, SignatureEncryptedSectionDeserializationError,
+ DeserializationError, EncryptedSectionContents, MicEncryptedSection,
+ MicVerificationError, SignatureEncryptedSection, SignatureVerificationError,
},
- to_array_view, DeLength, ENCRYPTION_INFO_DE_TYPE, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN,
+ DeLength, ENCRYPTION_INFO_DE_TYPE, METADATA_KEY_LEN, NP_ADV_MAX_SECTION_LEN,
},
- PlaintextIdentityMode, V1Header,
+ HasIdentityMatch, MetadataKey, PlaintextIdentityMode, V1Header,
};
-mod encrypted_section;
+pub(crate) mod encrypted_section;
#[cfg(test)]
mod parse_tests;
@@ -57,33 +63,39 @@
/// Parse into [IntermediateSection]s, exposing the underlying parsing errors.
/// Consumes all of `adv_body`.
-pub(crate) fn parse_sections<'a, 'h: 'a>(
- adv_header: &'h V1Header,
- adv_body: &'a [u8],
-) -> Result<Vec<IntermediateSection<'a>>, nom::Err<error::Error<&'a [u8]>>> {
- combinator::all_consuming(multi::many_m_n(
- 1,
- NP_V1_ADV_MAX_SECTION_COUNT,
- IntermediateSection::parser_with_header(adv_header),
- ))(adv_body)
+pub(crate) fn parse_sections(
+ adv_header: V1Header,
+ adv_body: &[u8],
+) -> Result<Vec<IntermediateSection>, nom::Err<error::Error<&[u8]>>> {
+ combinator::all_consuming(branch::alt((
+ // Public advertisement
+ multi::many_m_n(
+ 1,
+ NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT,
+ IntermediateSection::parser_public_section(),
+ ),
+ // Encrypted advertisement
+ multi::many_m_n(
+ 1,
+ NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
+ IntermediateSection::parser_encrypted_with_header(adv_header),
+ ),
+ )))(adv_body)
.map(|(_rem, sections)| sections)
}
/// A partially processed section that hasn't been decrypted (if applicable) yet.
#[derive(PartialEq, Eq, Debug)]
-// sections are large because they have a stack allocated buffer for the whole section
-#[allow(clippy::large_enum_variant)]
pub(crate) enum IntermediateSection<'a> {
/// A section that was not encrypted, e.g. a public identity or no-identity section.
- Plaintext(PlaintextSection),
+ Plaintext(PlaintextSection<'a>),
/// A section whose contents were encrypted, e.g. a private identity section.
Ciphertext(CiphertextSection<'a>),
}
impl<'a> IntermediateSection<'a> {
- fn parser_with_header(
- adv_header: &'a V1Header,
- ) -> impl Fn(&'a [u8]) -> nom::IResult<&[u8], IntermediateSection> {
+ fn parser_public_section(
+ ) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], IntermediateSection<'a>> {
move |adv_body| {
let (remaining, section_contents_len) =
combinator::verify(number::complete::u8, |sec_len| {
@@ -92,29 +104,42 @@
// Section structure possibilities:
// - Public Identity DE, all other DEs
- // - No identity DE, all other DEs (including Salt, excluding identity DEs)
- // - Encryption information, non-public Identity header, ciphertext
- // - MIC, encryption information, non-public Identity header, ciphertext
-
let parse_public_identity = combinator::map(
// 1 starting offset because of public identity before it
- sequence::tuple((PublicIdentity::parse, parse_non_identity_des(1))),
+ sequence::tuple((PublicIdentity::parse, nom::combinator::rest)),
// move closure to copy section_len into scope
- move |(_identity, des)| {
+ move |(_identity, rest)| {
IntermediateSection::Plaintext(PlaintextSection::new(
PlaintextIdentityMode::Public,
- SectionContents::new(/* section_header */ section_contents_len, &des),
+ SectionContents::new(
+ /* section_header */ section_contents_len,
+ rest,
+ 1,
+ ),
))
},
);
+ combinator::map_parser(
+ bytes::complete::take(section_contents_len),
+ // Guarantee we consume all of the section (not all of the adv body)
+ // Note: `all_consuming` should never fail, since we used the `rest` parser above.
+ combinator::all_consuming(parse_public_identity),
+ )(remaining)
+ }
+ }
- // 0 starting offset because there's no identity DE
- let parse_no_identity_de = combinator::map(parse_non_identity_des(0), move |des| {
- IntermediateSection::Plaintext(PlaintextSection::new(
- PlaintextIdentityMode::None,
- SectionContents::new(/* section_header */ section_contents_len, &des),
- ))
- });
+ fn parser_encrypted_with_header(
+ adv_header: V1Header,
+ ) -> impl Fn(&'a [u8]) -> nom::IResult<&[u8], IntermediateSection> {
+ move |adv_body| {
+ let (remaining, section_contents_len) =
+ combinator::verify(number::complete::u8, |sec_len| {
+ *sec_len as usize <= NP_ADV_MAX_SECTION_LEN && *sec_len as usize > 0
+ })(adv_body)?;
+
+ // Section structure possibilities:
+ // - Encryption information, non-public Identity header, ciphertext
+ // - Encryption information, non-public Identity header, ciphertext, MIC
let parse_encrypted_identity = combinator::map(
sequence::tuple((
@@ -139,11 +164,13 @@
let to_skip = identity.header_bytes.len();
IntermediateSection::Ciphertext(CiphertextSection::SignatureEncryptedIdentity(
SignatureEncryptedSection {
- section_header: section_contents_len,
- adv_header,
- encryption_info,
- identity,
- all_ciphertext: &identity_and_ciphertext[to_skip..],
+ contents: EncryptedSectionContents::new(
+ adv_header,
+ section_contents_len,
+ encryption_info,
+ identity,
+ &identity_and_ciphertext[to_skip..],
+ ),
},
))
},
@@ -179,14 +206,16 @@
let to_skip = identity.header_bytes.len();
IntermediateSection::Ciphertext(CiphertextSection::MicEncryptedIdentity(
MicEncryptedSection {
- section_header: section_contents_len,
- adv_header,
+ contents: EncryptedSectionContents::new(
+ adv_header,
+ section_contents_len,
+ encryption_info,
+ identity,
+ &identity_and_ciphertext[to_skip..],
+ ),
mic: mic.try_into().unwrap_or_else(|_| {
panic!("{} is a valid length", SectionMic::CONTENTS_LEN)
}),
- encryption_info,
- identity,
- all_ciphertext: &identity_and_ciphertext[to_skip..],
},
))
},
@@ -198,8 +227,6 @@
combinator::all_consuming(branch::alt((
parse_mic_encrypted_identity,
parse_encrypted_identity,
- parse_public_identity,
- parse_no_identity_de,
))),
)(remaining)
}
@@ -207,61 +234,59 @@
}
#[derive(PartialEq, Eq, Debug)]
-struct SectionContents {
+struct SectionContents<'adv> {
section_header: u8,
- de_data: ArrayView<u8, NP_ADV_MAX_SECTION_LEN>,
- data_elements: Vec<OffsetDataElement>,
+ de_region_excl_identity: &'adv [u8],
+ data_element_start_offset: u8,
}
-impl SectionContents {
- fn new(section_header: u8, data_elements: &[RefDataElement]) -> Self {
- let (data_elements, de_data) = convert_data_elements(data_elements);
-
- Self { section_header, de_data, data_elements }
+impl<'adv> SectionContents<'adv> {
+ fn new(
+ section_header: u8,
+ de_region_excl_identity: &'adv [u8],
+ data_element_start_offset: u8,
+ ) -> Self {
+ Self { section_header, de_region_excl_identity, data_element_start_offset }
}
- fn data_elements(&'_ self) -> DataElements<'_> {
- DataElements { de_iter: self.data_elements.iter(), de_data: &self.de_data }
- }
-}
-
-/// An iterator over data elements in a V1 section
-// A concrete type to make it easy to refer to in return types where opaque types aren't available.
-pub struct DataElements<'a> {
- de_iter: slice::Iter<'a, OffsetDataElement>,
- de_data: &'a ArrayView<u8, NP_ADV_MAX_SECTION_LEN>,
-}
-
-impl<'a> Iterator for DataElements<'a> {
- type Item = DataElement<'a>;
-
- fn next(&mut self) -> Option<Self::Item> {
- self.de_iter.next().map(|de| DataElement { de_data: self.de_data, de })
+ fn iter_data_elements(&self) -> DataElementParsingIterator<'adv> {
+ DataElementParsingIterator {
+ input: self.de_region_excl_identity,
+ offset: self.data_element_start_offset,
+ }
}
}
/// A section deserialized from a V1 advertisement.
-pub trait Section {
+pub trait Section<'adv, E: Debug> {
/// The iterator type used to iterate over data elements
- type Iterator<'a>: Iterator<Item = DataElement<'a>>
- where
- Self: 'a;
+ type Iterator: Iterator<Item = Result<DataElement<'adv>, E>>;
/// Iterator over the data elements in a section, except for any DEs related to resolving the
/// identity or otherwise validating the payload (e.g. MIC, Signature, any identity DEs like
/// Private Identity).
- fn data_elements(&'_ self) -> Self::Iterator<'_>;
+ fn iter_data_elements(&self) -> Self::Iterator;
+
+ /// Collects the data elements into a vector, eagerly catching and resolving any errors during
+ /// parsing.
+ #[cfg(any(test, feature = "alloc"))]
+ fn collect_data_elements(&self) -> Result<Vec<DataElement<'adv>>, E>
+ where
+ Self: Sized,
+ {
+ self.iter_data_elements().collect()
+ }
}
/// A plaintext section deserialized from a V1 advertisement.
#[derive(PartialEq, Eq, Debug)]
-pub struct PlaintextSection {
+pub struct PlaintextSection<'adv> {
identity: PlaintextIdentityMode,
- contents: SectionContents,
+ contents: SectionContents<'adv>,
}
-impl PlaintextSection {
- fn new(identity: PlaintextIdentityMode, contents: SectionContents) -> Self {
+impl<'adv> PlaintextSection<'adv> {
+ fn new(identity: PlaintextIdentityMode, contents: SectionContents<'adv>) -> Self {
Self { identity, contents }
}
@@ -274,11 +299,11 @@
}
}
-impl Section for PlaintextSection {
- type Iterator<'a> = DataElements<'a> where Self: 'a;
+impl<'adv> Section<'adv, DataElementParseError> for PlaintextSection<'adv> {
+ type Iterator = DataElementParsingIterator<'adv>;
- fn data_elements(&'_ self) -> Self::Iterator<'_> {
- self.contents.data_elements()
+ fn iter_data_elements(&self) -> Self::Iterator {
+ self.contents.iter_data_elements()
}
}
@@ -300,23 +325,29 @@
}
}
-/// The decrypted contents of a ciphertext section.
-#[derive(Debug, PartialEq, Eq)]
-pub struct DecryptedSection {
- identity_type: EncryptedIdentityDataElementType,
- verification_mode: VerificationMode,
- metadata_key: [u8; METADATA_KEY_LEN],
- salt: RawV1Salt,
- contents: SectionContents,
+impl<C: CryptoProvider> From<V1Salt<C>> for RawV1Salt {
+ fn from(salt: V1Salt<C>) -> Self {
+ RawV1Salt(salt.into_array())
+ }
}
-impl DecryptedSection {
+/// Fully-parsed and verified decrypted contents from an encrypted section.
+#[derive(Debug, PartialEq, Eq)]
+pub struct DecryptedSection<'adv> {
+ identity_type: EncryptedIdentityDataElementType,
+ verification_mode: VerificationMode,
+ metadata_key: MetadataKey,
+ salt: RawV1Salt,
+ contents: SectionContents<'adv>,
+}
+
+impl<'adv> DecryptedSection<'adv> {
fn new(
identity_type: EncryptedIdentityDataElementType,
verification_mode: VerificationMode,
- metadata_key: [u8; 16],
+ metadata_key: MetadataKey,
salt: RawV1Salt,
- contents: SectionContents,
+ contents: SectionContents<'adv>,
) -> Self {
Self { identity_type, verification_mode, metadata_key, salt, contents }
}
@@ -331,22 +362,24 @@
self.verification_mode
}
- /// The decrypted metadata key from the identity DE.
- pub fn metadata_key(&self) -> &[u8; 16] {
- &self.metadata_key
- }
-
/// The salt used for decryption of this advertisement.
pub fn salt(&self) -> RawV1Salt {
self.salt
}
}
-impl Section for DecryptedSection {
- type Iterator<'a> = DataElements<'a> where Self: 'a;
+impl<'adv> HasIdentityMatch for DecryptedSection<'adv> {
+ type Version = V1;
+ fn metadata_key(&self) -> MetadataKey {
+ self.metadata_key
+ }
+}
- fn data_elements(&'_ self) -> Self::Iterator<'_> {
- self.contents.data_elements()
+impl<'adv> Section<'adv, DataElementParseError> for DecryptedSection<'adv> {
+ type Iterator = DataElementParsingIterator<'adv>;
+
+ fn iter_data_elements(&self) -> Self::Iterator {
+ self.contents.iter_data_elements()
}
}
@@ -357,96 +390,208 @@
}
impl<'a> CiphertextSection<'a> {
- pub(crate) fn try_deserialize<'c, C, P>(
+ /// Tries to match this section's identity using the given crypto-material,
+ /// and if successful, tries to decrypt this section.
+ #[cfg(test)]
+ pub(crate) fn try_resolve_identity_and_deserialize<C, P>(
&'a self,
- credential: &'c C,
- ) -> Result<DecryptedSection, SectionDeserializeError>
+ arena: &mut DeserializationArena<'a>,
+ crypto_material: &C,
+ ) -> Result<DecryptedSection<'a>, SectionDeserializeError>
where
- C: V1Credential,
+ C: V1DiscoveryCryptoMaterial,
P: CryptoProvider,
{
- let crypto_material = credential.crypto_material();
match self {
- Self::SignatureEncryptedIdentity(contents) => {
+ CiphertextSection::SignatureEncryptedIdentity(contents) => {
let identity_resolution_material =
crypto_material.signed_identity_resolution_material::<P>();
let verification_material = crypto_material.signed_verification_material::<P>();
contents
- .try_deserialize::<P>(
- identity_resolution_material.borrow(),
+ .try_resolve_identity_and_deserialize::<P>(
+ arena,
+ &identity_resolution_material,
&verification_material,
)
- .map_err(|e| match e {
- SignatureEncryptedSectionDeserializationError::MetadataKeyMacMismatch
- | SignatureEncryptedSectionDeserializationError::SignatureMismatch => {
- SectionDeserializeError::IncorrectCredential
- }
- SignatureEncryptedSectionDeserializationError::SignatureMissing
- | SignatureEncryptedSectionDeserializationError::DeParseError => {
- SectionDeserializeError::ParseError
- }
- })
+ .map_err(|e| e.into())
}
- Self::MicEncryptedIdentity(contents) => {
+ CiphertextSection::MicEncryptedIdentity(contents) => {
let identity_resolution_material =
crypto_material.unsigned_identity_resolution_material::<P>();
let verification_material = crypto_material.unsigned_verification_material::<P>();
contents
- .try_deserialize::<P>(
- identity_resolution_material.borrow(),
+ .try_resolve_identity_and_deserialize::<P>(
+ arena,
+ &identity_resolution_material,
&verification_material,
)
- .map_err(|e| match e {
- MicEncryptedSectionDeserializationError::MetadataKeyMacMismatch
- | MicEncryptedSectionDeserializationError::MicMismatch => {
- SectionDeserializeError::IncorrectCredential
- }
- MicEncryptedSectionDeserializationError::DeParseError => {
- SectionDeserializeError::ParseError
- }
- })
+ .map_err(|e| e.into())
}
}
}
+
+ /// Try decrypting into some raw bytes given some raw unsigned crypto-material.
+ #[cfg(feature = "devtools")]
+ pub(crate) fn try_resolve_identity_and_decrypt<
+ C: V1DiscoveryCryptoMaterial,
+ P: CryptoProvider,
+ >(
+ &self,
+ arena: &mut DeserializationArena<'a>,
+ crypto_material: &C,
+ ) -> Option<Result<ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, ArenaOutOfSpace>> {
+ match self {
+ CiphertextSection::SignatureEncryptedIdentity(x) => {
+ let identity_resolution_material =
+ crypto_material.signed_identity_resolution_material::<P>();
+ x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material)
+ }
+ CiphertextSection::MicEncryptedIdentity(x) => {
+ let identity_resolution_material =
+ crypto_material.unsigned_identity_resolution_material::<P>();
+ x.try_resolve_identity_and_decrypt::<P>(arena, &identity_resolution_material)
+ }
+ }
+ }
+
+ pub(crate) fn contents(&self) -> &EncryptedSectionContents<'a> {
+ match self {
+ CiphertextSection::SignatureEncryptedIdentity(x) => &x.contents,
+ CiphertextSection::MicEncryptedIdentity(x) => &x.contents,
+ }
+ }
}
-/// Errors that can occur when deserializing an advertisement
+/// Errors that can occur when deserializing a section
+/// of a V1 advertisement.
#[derive(Debug, PartialEq, Eq)]
pub enum SectionDeserializeError {
/// The credential supplied did not match the section's identity data
IncorrectCredential,
/// Section data is malformed
ParseError,
+ /// The given arena is not large enough to hold the decrypted contents
+ ArenaOutOfSpace,
}
-/// Returns a parser function that parses data elements whose type is not one of the known identity
-/// DE types, and whose offsets start from the provided `starting_offset`.
-///
-/// Consumes all input.
-fn parse_non_identity_des(
- starting_offset: usize,
-) -> impl Fn(&[u8]) -> nom::IResult<&[u8], Vec<RefDataElement>> {
- move |input: &[u8]| {
- combinator::map_opt(
- combinator::all_consuming(multi::many0(combinator::verify(
- ProtoDataElement::parse,
- |de| IdentityDataElementType::iter().all(|t| t.type_code() != de.header.de_type),
- ))),
- |proto_des| {
- proto_des
- .into_iter()
- .enumerate()
- .map(|(offset, pde)| starting_offset.checked_add(offset).map(|sum| (sum, pde)))
- .map(|res| {
- res.map(|(offset, pde)| {
- pde.into_data_element(v1_salt::DataElementOffset::from(offset))
- })
- })
- .collect::<Option<Vec<_>>>()
- },
- )(input)
+#[cfg(test)]
+impl From<IdentityResolutionOrDeserializationError<MicVerificationError>>
+ for SectionDeserializeError
+{
+ fn from(error: IdentityResolutionOrDeserializationError<MicVerificationError>) -> Self {
+ match error {
+ IdentityResolutionOrDeserializationError::IdentityMatchingError => {
+ Self::IncorrectCredential
+ }
+ IdentityResolutionOrDeserializationError::DeserializationError(e) => e.into(),
+ }
+ }
+}
+
+#[cfg(test)]
+impl From<IdentityResolutionOrDeserializationError<SignatureVerificationError>>
+ for SectionDeserializeError
+{
+ fn from(error: IdentityResolutionOrDeserializationError<SignatureVerificationError>) -> Self {
+ match error {
+ IdentityResolutionOrDeserializationError::IdentityMatchingError => {
+ Self::IncorrectCredential
+ }
+ IdentityResolutionOrDeserializationError::DeserializationError(e) => e.into(),
+ }
+ }
+}
+
+impl From<DeserializationError<MicVerificationError>> for SectionDeserializeError {
+ fn from(mic_deserialization_error: DeserializationError<MicVerificationError>) -> Self {
+ match mic_deserialization_error {
+ DeserializationError::VerificationError(MicVerificationError::MicMismatch) => {
+ Self::IncorrectCredential
+ }
+ DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace,
+ }
+ }
+}
+
+impl From<DeserializationError<SignatureVerificationError>> for SectionDeserializeError {
+ fn from(
+ signature_deserialization_error: DeserializationError<SignatureVerificationError>,
+ ) -> Self {
+ match signature_deserialization_error {
+ DeserializationError::VerificationError(
+ SignatureVerificationError::SignatureMissing,
+ ) => Self::ParseError,
+ DeserializationError::VerificationError(
+ SignatureVerificationError::SignatureMismatch,
+ ) => Self::IncorrectCredential,
+ DeserializationError::ArenaOutOfSpace => Self::ArenaOutOfSpace,
+ }
+ }
+}
+
+/// An iterator that parses the given data elements iteratively. In environments where memory is
+/// not severely constrained, it is usually safer to collect this into `Result<Vec<DataElement>>`
+/// so the validity of the whole advertisement can be checked before proceeding with further
+/// processing.
+#[derive(Debug)]
+pub struct DataElementParsingIterator<'adv> {
+ input: &'adv [u8],
+ // The index of the data element this is currently at
+ offset: u8,
+}
+
+/// The error that may arise while parsing data elements.
+#[derive(Debug, PartialEq, Eq)]
+pub enum DataElementParseError {
+ /// Only one identity data element is allowed in an advertisement, but a duplicate is found
+ /// while parsing.
+ DuplicateIdentityDataElement,
+ /// Unexpected data found after the end of the data elements portion. This means either the
+ /// parser was fed with additional data (it should only be given the bytes within a section,
+ /// not the whole advertisement), or the length field in the header of the data element is
+ /// malformed.
+ UnexpectedDataAfterEnd,
+ /// There are too many data elements in the advertisement. The maximum number supported by the
+ /// current parsing logic is 255.
+ TooManyDataElements,
+ /// A parse error is returned during nom.
+ NomError(nom::error::ErrorKind),
+}
+
+impl<'adv> Iterator for DataElementParsingIterator<'adv> {
+ type Item = Result<DataElement<'adv>, DataElementParseError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match ProtoDataElement::parse(self.input) {
+ Ok((rem, pde)) => {
+ if !IdentityDataElementType::iter().all(|t| t.type_code() != pde.header.de_type) {
+ return Some(Err(DataElementParseError::DuplicateIdentityDataElement));
+ }
+ self.input = rem;
+ let current_offset = self.offset;
+ self.offset = if let Some(offset) = self.offset.checked_add(1) {
+ offset
+ } else {
+ return Some(Err(DataElementParseError::TooManyDataElements));
+ };
+ Some(Ok(pde.into_data_element(v1_salt::DataElementOffset::from(current_offset))))
+ }
+ Err(nom::Err::Failure(e)) => Some(Err(DataElementParseError::NomError(e.code))),
+ Err(nom::Err::Incomplete(_)) => {
+ panic!("Should always complete since we are parsing using the `nom::complete` APIs")
+ }
+ Err(nom::Err::Error(_)) => {
+ // nom `Error` is recoverable, it usually means we should move on the parsing the
+ // next section. There is nothing after data elements within a section, so we just
+ // check that there is no remaining data.
+ if !self.input.is_empty() {
+ return Some(Err(DataElementParseError::UnexpectedDataAfterEnd));
+ }
+ None
+ }
+ }
}
}
@@ -533,8 +678,8 @@
type_bytes
.iter()
- .fold(Some(0_u32), |accum, b| {
- accum.and_then(|n| n.checked_shl(7)).map(|n| n + ((b & 0x7F) as u32))
+ .try_fold(0_u32, |accum, b| {
+ accum.checked_shl(7).map(|n| n + ((b & 0x7F) as u32))
})
.and_then(|type_code| {
ArrayView::try_from_slice(header_bytes).map(|header_bytes| DeHeader {
@@ -562,56 +707,14 @@
fn parse(input: &[u8]) -> nom::IResult<&[u8], ProtoDataElement> {
let (remaining, header) = DeHeader::parse(input)?;
let len = header.contents_len;
- combinator::map(bytes::complete::take(len.as_usize()), move |slice| {
+ combinator::map(bytes::complete::take(len.as_u8()), move |slice| {
let header_clone = header.clone();
ProtoDataElement { header: header_clone, contents: slice }
})(remaining)
}
- fn into_data_element(self, offset: v1_salt::DataElementOffset) -> RefDataElement<'d> {
- RefDataElement {
- offset,
- header_len: self.header.header_bytes.len().try_into().expect("header is <= 6 bytes"),
- de_type: self.header.de_type,
- contents: self.contents,
- }
- }
-}
-
-/// A data element that holds a slice reference for its contents
-#[derive(Debug, PartialEq, Eq)]
-pub(crate) struct RefDataElement<'d> {
- pub(crate) offset: v1_salt::DataElementOffset,
- pub(crate) header_len: u8,
- pub(crate) de_type: DeType,
- pub(crate) contents: &'d [u8],
-}
-
-/// A deserialized data element in a section.
-///
-/// The DE has been processed to the point of exposing a DE type and its contents as a `&[u8]`, but
-/// no DE-type-specific processing has been performed.
-#[derive(Debug)]
-pub struct DataElement<'a> {
- de_data: &'a ArrayView<u8, NP_ADV_MAX_SECTION_LEN>,
- de: &'a OffsetDataElement,
-}
-
-impl<'a> DataElement<'a> {
- /// The offset of the DE in its containing Section.
- ///
- /// Used with the section salt to derive per-DE salt.
- pub fn offset(&self) -> v1_salt::DataElementOffset {
- self.de.offset
- }
- /// The type of the DE
- pub fn de_type(&self) -> DeType {
- self.de.de_type
- }
- /// The contents of the DE
- pub fn contents(&self) -> &[u8] {
- &self.de_data.as_slice()
- [self.de.start_of_contents..self.de.start_of_contents + self.de.contents_len]
+ fn into_data_element(self, offset: v1_salt::DataElementOffset) -> DataElement<'d> {
+ DataElement { offset, de_type: self.header.de_type, contents: self.contents }
}
}
@@ -635,7 +738,7 @@
pub struct EncryptedSectionIdentity {
identity_type: EncryptedIdentityDataElementType,
validation_mode: VerificationMode,
- metadata_key: [u8; METADATA_KEY_LEN],
+ metadata_key: MetadataKey,
}
impl EncryptedSectionIdentity {
@@ -648,52 +751,37 @@
self.validation_mode
}
/// The decrypted metadata key from the section's identity DE
- pub fn metadata_key(&self) -> &[u8; METADATA_KEY_LEN] {
- &self.metadata_key
+ pub fn metadata_key(&self) -> MetadataKey {
+ self.metadata_key
}
}
-/// A DE that designates its contents via offset and length to avoid self-referential slices.
-#[derive(Debug, PartialEq, Eq)]
-struct OffsetDataElement {
+/// A deserialized data element in a section.
+///
+/// The DE has been processed to the point of exposing a DE type and its contents as a `&[u8]`, but
+/// no DE-type-specific processing has been performed.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct DataElement<'adv> {
offset: v1_salt::DataElementOffset,
de_type: DeType,
- start_of_contents: usize,
- contents_len: usize,
+ contents: &'adv [u8],
}
-/// Convert data elements from holding slices to holding offsets and lengths to avoid
-/// lifetime woes.
-/// This entails some data copying, so if it causes noticeable perf issues we can revisit
-/// it, but it is likely to be much cheaper than decryption.
-///
-/// # Panics
-///
-/// Will panic if handed more data elements than fit in one section. This is only possible if
-/// generating data elements from a source other than parsing a section.
-fn convert_data_elements(
- elements: &[RefDataElement],
-) -> (Vec<OffsetDataElement>, ArrayView<u8, NP_ADV_MAX_SECTION_LEN>) {
- let mut buf = tinyvec::ArrayVec::new();
-
- (
- elements
- .iter()
- .map(|de| {
- let current_len = buf.len();
- // won't overflow because these DEs originally came from a section, and now
- // we're packing only their contents without DE headers
- buf.extend_from_slice(de.contents);
- OffsetDataElement {
- offset: de.offset,
- de_type: de.de_type,
- start_of_contents: current_len,
- contents_len: de.contents.len(),
- }
- })
- .collect(),
- to_array_view(buf),
- )
+impl<'adv> DataElement<'adv> {
+ /// The offset of the DE in its containing Section.
+ ///
+ /// Used with the section salt to derive per-DE salt.
+ pub fn offset(&self) -> v1_salt::DataElementOffset {
+ self.offset
+ }
+ /// The type of the DE
+ pub fn de_type(&self) -> DeType {
+ self.de_type
+ }
+ /// The contents of the DE
+ pub fn contents(&self) -> &'adv [u8] {
+ self.contents
+ }
}
#[derive(PartialEq, Eq, Debug, Clone)]
@@ -787,7 +875,7 @@
combinator::map(
combinator::verify(DeHeader::parse, |deh| {
deh.de_type == IdentityDataElementType::Public.type_code()
- && deh.contents_len.as_usize() == 0
+ && deh.contents_len.as_u8() == 0
}),
|_| PublicIdentity,
)(input)
@@ -809,7 +897,7 @@
// 2-byte header, 16-byte metadata key
pub(crate) const TOTAL_DE_LEN: usize = 18;
- /// Returns a parser function that parses an [EncryptedIdentity] using the provided DE `offset`.
+ /// Returns a parser function that parses an [`EncryptedIdentityMetadata`] using the provided DE `offset`.
fn parser_at_offset(
offset: v1_salt::DataElementOffset,
) -> impl Fn(&[u8]) -> nom::IResult<&[u8], EncryptedIdentityMetadata> {
diff --git a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
index f3b8258..506a127 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/parse_tests.rs
@@ -13,85 +13,17 @@
// limitations under the License.
extern crate std;
-
use super::*;
use crate::extended::deserialize::encrypted_section::{
MicEncryptedSection, SignatureEncryptedSection,
};
+use crate::extended::deserialize::test_stubs::IntermediateSectionExt;
+use rand::rngs::StdRng;
+use rand::seq::SliceRandom;
+use rand::{Rng, SeedableRng};
use std::vec;
#[test]
-fn parse_adv_ext_no_identity() {
- // 2 sections, 2 DEs each
- let mut adv_body = vec![];
- // section
- adv_body.push(9);
- // de 1 byte header, type 5, len 5
- adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]);
- // de 2 byte header, type 6, len 1
- adv_body.extend_from_slice(&[0x81, 0x06, 0x01]);
-
- // section
- adv_body.push(11);
- // de 3 byte header, len 4
- adv_body.extend_from_slice(&[0x84, 0xC3, 0x03, 0x01, 0x02, 0x03, 0x04]);
- // de 1 byte header, type 8, len 3
- adv_body.extend_from_slice(&[0x38, 0x01, 0x02, 0x03]);
-
- assert_eq!(
- Ok(vec![
- PlaintextSection::new(
- PlaintextIdentityMode::None,
- SectionContents::new(
- 9,
- &[
- // 1 byte header, len 5
- RefDataElement {
- offset: 0_usize.into(),
- header_len: 1,
- de_type: 5_u8.into(),
- contents: &[0x01, 0x02, 0x03, 0x04, 0x05]
- },
- // 2 byte header, len 1
- RefDataElement {
- offset: 1_usize.into(),
- header_len: 2,
- de_type: 6_u8.into(),
- contents: &[0x01]
- }
- ]
- )
- )
- .into(),
- PlaintextSection::new(
- PlaintextIdentityMode::None,
- SectionContents::new(
- 11,
- &[
- // 3 byte header, len 4
- RefDataElement {
- offset: 0_usize.into(),
- header_len: 3,
- de_type: 0b0000_0000_0000_0000_0010_0001_1000_0011_u32.into(),
- contents: &[0x01, 0x02, 0x03, 0x04]
- },
- // 1 byte header, len 3
- RefDataElement {
- offset: 1_usize.into(),
- header_len: 1,
- de_type: 8_u8.into(),
- contents: &[0x01, 0x02, 0x03]
- }
- ]
- )
- )
- .into()
- ]),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
- );
-}
-
-#[test]
fn parse_adv_ext_public_identity() {
// 2 sections, 3 DEs each
let mut adv_body = vec![];
@@ -104,65 +36,28 @@
// de 2 byte header, type 6, len 1
adv_body.extend_from_slice(&[0x81, 0x06, 0x01]);
- // section
- adv_body.push(12);
- // public identity
- adv_body.push(0x03);
- // de 3 byte header, len 4
- adv_body.extend_from_slice(&[0x84, 0xC3, 0x03, 0x01, 0x02, 0x03, 0x04]);
- // de 1 byte header, type 8, len 3
- adv_body.extend_from_slice(&[0x38, 0x01, 0x02, 0x03]);
+ let parsed_sections = parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap();
+ assert_eq!(
+ vec![IntermediateSection::from(PlaintextSection::new(
+ PlaintextIdentityMode::Public,
+ SectionContents::new(10, &adv_body[2..], 1)
+ ))],
+ parsed_sections,
+ );
+ let expected_des = [
+ // 1 byte header, len 5
+ DataElement {
+ offset: 1_u8.into(),
+ de_type: 5_u8.into(),
+ contents: &[0x01, 0x02, 0x03, 0x04, 0x05],
+ },
+ // 2 byte header, len 1
+ DataElement { offset: 2_u8.into(), de_type: 6_u8.into(), contents: &[0x01] },
+ ];
assert_eq!(
- Ok(vec![
- PlaintextSection::new(
- PlaintextIdentityMode::Public,
- SectionContents::new(
- 10,
- &[
- // 1 byte header, len 5
- RefDataElement {
- offset: 1_usize.into(),
- header_len: 1,
- de_type: 5_u8.into(),
- contents: &[0x01, 0x02, 0x03, 0x04, 0x05],
- },
- // 2 byte header, len 1
- RefDataElement {
- offset: 2_usize.into(),
- header_len: 2,
- de_type: 6_u8.into(),
- contents: &[0x01],
- },
- ],
- )
- )
- .into(),
- PlaintextSection::new(
- PlaintextIdentityMode::Public,
- SectionContents::new(
- 12,
- &[
- // 3 byte header, len 4
- RefDataElement {
- offset: 1_usize.into(),
- header_len: 3,
- de_type: 0b0000_0000_0000_0000_0010_0001_1000_0011_u32.into(),
- contents: &[0x01, 0x02, 0x03, 0x04],
- },
- // 1 byte header, len 3
- RefDataElement {
- offset: 2_usize.into(),
- header_len: 1,
- de_type: 8_u8.into(),
- contents: &[0x01, 0x02, 0x03],
- },
- ],
- )
- )
- .into(),
- ]),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
+ &expected_des[..],
+ &parsed_sections[0].as_plaintext().unwrap().collect_data_elements().unwrap()
);
}
@@ -230,48 +125,50 @@
0x11, 0x11,
],
};
- assert_eq!(
- Ok(vec![
- SignatureEncryptedSection {
+ let expected_sections = [
+ SignatureEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 47,
- adv_header: &adv_header,
+ adv_header,
encryption_info: encryption_info.clone(),
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x01],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Private,
},
// skip section header + encryption info + identity header -> end of section
all_ciphertext: &adv_body[1 + 19 + 2..48],
- }
- .into(),
- SignatureEncryptedSection {
+ },
+ },
+ SignatureEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 48,
- adv_header: &adv_header,
+ adv_header,
encryption_info: encryption_info.clone(),
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x02],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Trusted,
},
all_ciphertext: &adv_body[48 + 1 + 19 + 2..97],
- }
- .into(),
- SignatureEncryptedSection {
+ },
+ },
+ SignatureEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 49,
- adv_header: &adv_header,
+ adv_header,
encryption_info,
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x04],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Provisioned,
},
all_ciphertext: &adv_body[97 + 1 + 19 + 2..],
- }
- .into()
- ]),
- parse_sections(&adv_header, &adv_body)
- );
+ },
+ },
+ ];
+ let parsed_sections = parse_sections(adv_header, &adv_body).unwrap();
+ assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from));
}
#[test]
@@ -353,145 +250,53 @@
0x11, 0x11,
],
};
- assert_eq!(
- Ok(vec![
- MicEncryptedSection {
+ let expected_sections = [
+ MicEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 63,
- adv_header: &adv_header,
- mic: SectionMic::from([0x33; 16]),
+ adv_header,
encryption_info: encryption_info.clone(),
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x01],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Private,
},
// skip section header + encryption info + identity header -> end of ciphertext
all_ciphertext: &adv_body[1 + 19 + 2..64 - 16],
- }
- .into(),
- MicEncryptedSection {
+ },
+ mic: SectionMic::from([0x33; 16]),
+ },
+ MicEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 64,
- adv_header: &adv_header,
- mic: SectionMic::from([0x66; 16]),
+ adv_header,
encryption_info: encryption_info.clone(),
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x02],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Trusted,
},
all_ciphertext: &adv_body[64 + 1 + 19 + 2..129 - 16],
- }
- .into(),
- MicEncryptedSection {
+ },
+ mic: SectionMic::from([0x66; 16]),
+ },
+ MicEncryptedSection {
+ contents: EncryptedSectionContents {
section_header: 65,
- adv_header: &adv_header,
- mic: SectionMic::from([0x99; 16]),
+ adv_header,
encryption_info,
identity: EncryptedIdentityMetadata {
header_bytes: [0x90, 0x04],
- offset: 1_usize.into(),
+ offset: 1_u8.into(),
identity_type: EncryptedIdentityDataElementType::Provisioned,
},
all_ciphertext: &adv_body[129 + 1 + 19 + 2..195 - 16],
- }
- .into(),
- ]),
- parse_sections(&adv_header, &adv_body)
- );
-}
-
-#[test]
-fn public_identity_not_first_de_error() {
- let mut adv_body = vec![];
-
- // section
- adv_body.push(3 + 1);
- // misc other DE
- adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
- // public identity after another DE
- adv_body.push(0x03);
-
- assert_eq!(
- Err(nom::Err::Error(error::Error {
- input: &adv_body[4..],
- // Eof because all_consuming is used to ensure complete section is parsed
- code: error::ErrorKind::Eof
- })),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
- );
-}
-
-#[test]
-fn public_identity_after_public_identity_error() {
- let mut adv_body = vec![];
-
- // section
- adv_body.push(1 + 3 + 1);
- // public identity after another DE
- adv_body.push(0x03);
- // misc other DE
- adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
- // public identity after another DE
- adv_body.push(0x03);
-
- assert_eq!(
- Err(nom::Err::Error(error::Error {
- // since we use many1, the parser consumes the first byte and returns the remaining in the error
- input: &adv_body[1..],
- // Eof because all_consuming is used to ensure complete section is parsed
- code: error::ErrorKind::Eof
- })),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
- );
-}
-
-#[test]
-fn salt_public_identity_error() {
- let mut adv_body = vec![];
- // section
- adv_body.push(3 + 1 + 3);
- // salt - 1 + 2x 0x22 (invalid: must be first DE)
- adv_body.extend_from_slice(&[0x20, 0x22, 0x22]);
- // public identity
- adv_body.push(0x03);
- // misc other DE
- adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
-
- assert_eq!(
- Err(nom::Err::Error(error::Error {
- input: &adv_body[4..],
- // Eof because all_consuming is used to ensure complete section is parsed
- code: error::ErrorKind::Eof
- })),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
- );
-}
-
-#[test]
-fn salt_mic_public_identity_error() {
- let mut adv_body = vec![];
- // section
- adv_body.push(3 + 18 + 1 + 3);
- // salt - 1 + 2x 0x22 (invalid: must be first DE)
- adv_body.extend_from_slice(&[0x20, 0x22, 0x22]);
- // mic - 2 + 16x 0x33
- adv_body.extend_from_slice(&[
- 0x90, 0x13, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
- 0x33, 0x33, 0x33,
- ]);
- // public identity
- adv_body.push(0x03);
- // misc other DE
- adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
-
- assert_eq!(
- Err(nom::Err::Error(error::Error {
- input: &adv_body[22..],
- // Eof because all_consuming is used to ensure complete section is parsed
- code: error::ErrorKind::Eof
- })),
- parse_sections(&V1Header { header_byte: 0x20 }, &adv_body)
- );
+ },
+ mic: SectionMic::from([0x99; 16]),
+ },
+ ];
+ let parsed_sections = parse_sections(adv_header, &adv_body).unwrap();
+ assert_eq!(parsed_sections, &expected_sections.map(IntermediateSection::from));
}
#[test]
@@ -616,6 +421,191 @@
}
#[test]
+fn public_identity_not_first_de_error() {
+ let mut adv_body = vec![];
+ // section
+ adv_body.push(3 + 1);
+ // misc other DE
+ adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
+ // public identity after another DE
+ adv_body.push(0x03);
+
+ assert_eq!(
+ nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Verify }),
+ parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err()
+ );
+}
+
+#[test]
+fn invalid_public_section() {
+ let mut rng = StdRng::from_entropy();
+ for _ in 0..100 {
+ let mut adv_body = vec![];
+ // Add section length
+ let remove_section_len = rng.gen_bool(0.5);
+ // Add public identity
+ let add_public_identity = rng.gen_bool(0.5);
+ // Add DEs
+ let add_des = rng.gen_bool(0.5);
+ // Shuffle adv
+ let shuffle = rng.gen_bool(0.5);
+
+ adv_body.push(0);
+ if add_public_identity {
+ adv_body[0] += 1;
+ adv_body.push(0x03);
+ }
+ if add_des {
+ adv_body[0] += 1;
+ adv_body.extend_from_slice(&[0x81]);
+ }
+ if remove_section_len {
+ adv_body.remove(0);
+ }
+
+ let ordered_adv = adv_body.clone();
+
+ if shuffle {
+ adv_body.shuffle(&mut rng);
+ }
+ // A V1 public section is invalid if
+ // * section length is missing
+ // * the section is empty
+ // * the section identity is missing
+ // * the identity does not follow the section length
+ // * the section length is 0
+ if remove_section_len || !add_public_identity || (ordered_adv != adv_body) {
+ parse_sections(V1Header { header_byte: 0x20 }, &adv_body)
+ .expect_err("Expected to fail because of missing section length or identity");
+ }
+ }
+}
+
+// There can only be one identity DE
+#[test]
+fn public_identity_after_public_identity_error() {
+ let mut adv_body = vec![];
+ // section
+ adv_body.push(1 + 3 + 1);
+ // public identity after another DE
+ adv_body.push(0x03);
+ // misc other DE
+ adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
+ // public identity after another DE
+ adv_body.push(0x03);
+
+ let sections = parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap();
+ assert_eq!(sections.len(), 1);
+ assert_eq!(
+ DataElementParseError::DuplicateIdentityDataElement,
+ sections[0].as_plaintext().unwrap().collect_data_elements().unwrap_err()
+ );
+}
+
+#[test]
+fn salt_public_identity_error() {
+ let mut adv_body = vec![];
+ // section
+ adv_body.push(3 + 1 + 3);
+ // salt - 1 + 2x 0x22 (invalid: must be first DE)
+ adv_body.extend_from_slice(&[0x20, 0x22, 0x22]);
+ // public identity
+ adv_body.push(0x03);
+ // misc other DE
+ adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
+
+ assert_eq!(
+ nom::Err::Error(error::Error {
+ input: &adv_body[1..],
+ // Eof because all_consuming is used to ensure complete section is parsed
+ code: error::ErrorKind::Verify
+ }),
+ parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err()
+ );
+}
+
+#[test]
+fn salt_mic_public_identity_error() {
+ let mut adv_body = vec![];
+ // section
+ adv_body.push(3 + 18 + 1 + 3);
+ // salt - 1 + 2x 0x22 (invalid: must be first DE)
+ adv_body.extend_from_slice(&[0x20, 0x22, 0x22]);
+ // mic - 2 + 16x 0x33
+ adv_body.extend_from_slice(&[
+ 0x90, 0x13, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
+ 0x33, 0x33, 0x33,
+ ]);
+ // public identity
+ adv_body.push(0x03);
+ // misc other DE
+ adv_body.extend_from_slice(&[0x81, 0x70, 0xFF]);
+
+ assert_eq!(
+ nom::Err::Error(error::Error {
+ input: &adv_body[1..],
+ // Eof because all_consuming is used to ensure complete section is parsed
+ code: error::ErrorKind::Verify
+ }),
+ parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err()
+ );
+}
+
+#[test]
+fn parse_adv_no_identity() {
+ let adv_body = vec![0x55, 0x01, 0x02, 0x03, 0x04, 0x05];
+ assert_eq!(
+ nom::Err::Error(error::Error { input: &adv_body[1..], code: error::ErrorKind::Eof }),
+ parse_sections(V1Header { header_byte: 0x20 }, &adv_body).unwrap_err()
+ );
+}
+
+#[test]
+fn parse_empty_section() {
+ // empty section - should return an EOF error
+ let input = [];
+ assert_eq!(
+ nom::Err::Error(error::Error {
+ // attempted to read section contents
+ input: input.as_slice(),
+ code: error::ErrorKind::Eof
+ }),
+ IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input)
+ .unwrap_err()
+ );
+}
+
+#[test]
+fn parse_de_header_non_canonical_multi_byte() {
+ // length 1, type 1
+ // first byte of type doesn't have any bits in it so it contributes nothing
+ let input = [0b1000_0001, 0b1000_0000, 0b0000_0001];
+ assert_eq!(
+ nom::Err::Error(error::Error {
+ // attempted to read first type byte
+ input: &input.as_slice()[1..],
+ code: error::ErrorKind::Verify
+ }),
+ DeHeader::parse(&input).unwrap_err()
+ );
+}
+
+#[test]
+fn parse_section_length_zero() {
+ // Section length of 0 - should return a verification error
+ let input = [0x00];
+ assert_eq!(
+ nom::Err::Error(error::Error {
+ // attempted to read section contents
+ input: input.as_slice(),
+ code: error::ErrorKind::Verify
+ }),
+ IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input)
+ .unwrap_err()
+ );
+}
+
+#[test]
fn parse_section_length_overrun() {
// section of length 0xF0 - legal but way longer than 3
let input = [0xF0, 0x01, 0x02, 0x03];
@@ -625,7 +615,7 @@
input: &input.as_slice()[1..],
code: error::ErrorKind::Eof
}),
- IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input)
+ IntermediateSection::parser_encrypted_with_header(V1Header { header_byte: 0x20 })(&input)
.unwrap_err()
);
}
@@ -659,47 +649,33 @@
}
#[test]
-fn parse_de_header_non_canonical_multi_byte() {
- // length 1, type 1
- // first byte of type doesn't have any bits in it so it contributes nothing
- let input = [0b1000_0001, 0b1000_0000, 0b0000_0001];
- assert_eq!(
- nom::Err::Error(error::Error {
- // attempted to read first type byte
- input: &input.as_slice()[1..],
- code: error::ErrorKind::Verify
- }),
- DeHeader::parse(&input).unwrap_err()
- );
-}
+fn parse_adv_signature_encrypted_plaintext_mix() {
+ // 2 sections
+ let mut adv_body = vec![];
-#[test]
-fn parse_section_length_zero() {
- // Section length of 0 - should return a verification error
- let input = [0x00];
- assert_eq!(
- nom::Err::Error(error::Error {
- // attempted to read section contents
- input: input.as_slice(),
- code: error::ErrorKind::Verify
- }),
- IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input)
- .unwrap_err()
- );
-}
+ // section 1 - plaintext - 10 bytes
+ adv_body.push(10);
+ // public identity
+ adv_body.push(0x03);
+ // de 1 byte header, type 5, len 5
+ adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]);
+ // de 2 byte header, type 6, len 1
+ adv_body.extend_from_slice(&[0x81, 0x06, 0x01]);
-#[test]
-fn parse_empty_section() {
- // empty section - should return an EOF error
- let input = [];
+ // section 2 - plaintext - 10 bytes
+ adv_body.push(10);
+ // public identity
+ adv_body.push(0x03);
+ // de 1 byte header, type 5, len 5
+ adv_body.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]);
+ // de 2 byte header, type 6, len 1
+ adv_body.extend_from_slice(&[0x81, 0x06, 0x01]);
+
+ let adv_header = V1Header { header_byte: 0x20 };
+
assert_eq!(
- nom::Err::Error(error::Error {
- // attempted to read section contents
- input: input.as_slice(),
- code: error::ErrorKind::Eof
- }),
- IntermediateSection::parser_with_header(&V1Header { header_byte: 0x20 })(&input)
- .unwrap_err()
+ nom::Err::Error(error::Error { input: &adv_body[11..], code: error::ErrorKind::Eof }),
+ parse_sections(adv_header, &adv_body).unwrap_err()
);
}
@@ -717,8 +693,8 @@
}
}
-impl<'a> From<PlaintextSection> for IntermediateSection<'a> {
- fn from(s: PlaintextSection) -> Self {
+impl<'a> From<PlaintextSection<'a>> for IntermediateSection<'a> {
+ fn from(s: PlaintextSection<'a>) -> Self {
IntermediateSection::Plaintext(s)
}
}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
index 7199031..39644ab 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/section_tests.rs
@@ -15,26 +15,29 @@
extern crate std;
use super::*;
+use crate::deserialization_arena;
+use crate::extended::serialize::AdvertisementType;
+use crate::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT;
use crate::{
credential::{
- simple::SimpleV1Credential,
- source::{CredentialSource, SliceCredentialSource},
- MatchableCredential, V1Credential,
+ source::{DiscoveryCredentialSource, SliceCredentialSource},
+ v1::{SignedBroadcastCryptoMaterial, SimpleSignedBroadcastCryptoMaterial, V1},
+ DiscoveryCryptoMaterial, EmptyMatchedCredential, MatchableCredential,
+ MetadataMatchedCredential, SimpleBroadcastCryptoMaterial,
},
extended::{
data_elements::GenericDataElement,
- deserialize::{
- convert_data_elements,
- test_stubs::{HkdfCryptoMaterial, IntermediateSectionExt},
- OffsetDataElement,
- },
+ deserialize::{test_stubs::IntermediateSectionExt, DataElement},
serialize::{
- self, AdvBuilder, MicEncrypted, SectionBuilder, SignedEncrypted, WriteDataElement,
+ self, AdvBuilder, MicEncryptedSectionEncoder, PublicSectionEncoder, SectionBuilder,
+ SignedEncryptedSectionEncoder, WriteDataElement,
},
MAX_DE_LEN,
},
- parse_adv_header, AdvHeader, PublicIdentity,
+ parse_adv_header, AdvHeader, WithMatchedCredential,
};
+use core::borrow::{Borrow, BorrowMut};
+use core::convert::Into;
use crypto_provider::{CryptoProvider, CryptoRng};
use crypto_provider_default::CryptoProviderImpl;
use rand::{seq::SliceRandom as _, Rng as _, SeedableRng as _};
@@ -45,11 +48,10 @@
#[test]
fn deserialize_public_identity_section() {
- do_deserialize_section_unencrypted::<PublicIdentity>(
- PublicIdentity::default(),
+ do_deserialize_section_unencrypted::<PublicSectionEncoder>(
+ PublicSectionEncoder::default(),
PlaintextIdentityMode::Public,
1,
- 1,
);
}
@@ -68,36 +70,52 @@
// share a metadata key to emphasize that we're _only_ using the identity to
// differentiate
let metadata_key: [u8; 16] = rng.gen();
+ let metadata_key = MetadataKey(metadata_key);
let creds = identities
.iter()
- .enumerate()
- .map(|(index, (key_seed, key_pair))| {
- SimpleV1Credential::new(
- HkdfCryptoMaterial::new(key_seed, &metadata_key, key_pair.public()),
- index,
+ .map(|(key_seed, key_pair)| {
+ SimpleSignedBroadcastCryptoMaterial::new(
+ *key_seed,
+ metadata_key,
+ key_pair.private_key(),
)
})
+ .enumerate()
+ .map(|(index, broadcast_cm)| {
+ let match_data = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::<
+ _,
+ _,
+ CryptoProviderImpl,
+ >(&broadcast_cm, &[index as u8]);
+
+ let discovery_credential =
+ broadcast_cm.derive_v1_discovery_credential::<CryptoProviderImpl>();
+
+ MatchableCredential { discovery_credential, match_data }
+ })
.collect::<Vec<_>>();
+
let cred_source = SliceCredentialSource::new(&creds);
let identity_type =
*EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(chosen_key_seed);
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(*chosen_key_seed, metadata_key);
+
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
identity_type,
- &metadata_key,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
- let (expected_de_data, expected_des, orig_des) =
- fill_section_random_des(&mut rng, &mut section_builder, 2);
+ let mut expected_de_data = vec![];
+ let (expected_des, orig_des) =
+ fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2);
section_builder.add_to_advertisement();
@@ -111,31 +129,35 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
- let (section, cred) = try_deserialize_all_creds::<_, _, CryptoProviderImpl>(
- sections[0].as_ciphertext().unwrap(),
- &cred_source,
- )
- .unwrap()
- .unwrap();
- assert_eq!(&chosen_index, cred.matched_data());
+ let arena = deserialization_arena!();
+ let section = sections[0].as_ciphertext().unwrap();
+ let matched_section =
+ try_deserialize_all_creds::<_, CryptoProviderImpl>(arena, section, &cred_source)
+ .unwrap()
+ .unwrap();
+
+ // Verify that the decrypted metadata contains the chosen index
+ let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap();
+ assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata);
+
+ // Verify that the section contents passed through unaltered
+ let section = matched_section.contents();
assert_eq!(section.identity_type(), identity_type);
assert_eq!(section.verification_mode(), VerificationMode::Mic);
- assert_eq!(section.metadata_key(), &metadata_key);
+ assert_eq!(section.metadata_key(), metadata_key);
assert_eq!(
- section.contents,
- SectionContents {
- section_header: (19 + 2 + 16 + total_de_len(&orig_des) + 16) as u8,
- de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(),
- data_elements: expected_des,
- }
+ section.contents.section_header,
+ (19 + 2 + 16 + total_de_len(&orig_des) + 16) as u8
);
+ let data_elements = section.collect_data_elements().unwrap();
+ assert_eq!(data_elements, expected_des);
assert_eq!(
- section
- .data_elements()
+ data_elements
+ .iter()
.map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap())
.collect::<Vec<_>>(),
orig_des
@@ -156,47 +178,55 @@
// share a metadata key to emphasize that we're _only_ using the identity to
// differentiate
let metadata_key: [u8; 16] = rng.gen();
+ let metadata_key = MetadataKey(metadata_key);
let creds = identities
.iter()
- .enumerate()
- .map(|(index, (key_seed, key_pair))| {
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(key_seed);
- let unsigned =
- hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(&metadata_key);
- let signed =
- hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key);
- SimpleV1Credential::new(
- HkdfCryptoMaterial {
- hkdf: *key_seed,
- expected_unsigned_metadata_key_hmac: unsigned,
- expected_signed_metadata_key_hmac: signed,
- pub_key: key_pair.public().to_bytes(),
- },
- index,
+ .map(|(key_seed, key_pair)| {
+ SimpleSignedBroadcastCryptoMaterial::new(
+ *key_seed,
+ metadata_key,
+ key_pair.private_key(),
)
})
+ .enumerate()
+ .map(|(index, broadcast_cm)| {
+ let match_data = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::<
+ _,
+ _,
+ CryptoProviderImpl,
+ >(&broadcast_cm, &[index as u8]);
+
+ let discovery_credential =
+ broadcast_cm.derive_v1_discovery_credential::<CryptoProviderImpl>();
+ MatchableCredential { discovery_credential, match_data }
+ })
.collect::<Vec<_>>();
+
let cred_source = SliceCredentialSource::new(&creds);
let identity_type =
*EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(chosen_key_seed);
+ let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new(
+ *chosen_key_seed,
+ metadata_key,
+ chosen_key_pair.private_key(),
+ );
+
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
identity_type,
- &metadata_key,
- chosen_key_pair,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
- let (expected_de_data, expected_des, orig_des) =
- fill_section_random_des(&mut rng, &mut section_builder, 2);
+ let mut expected_de_data = vec![];
+ let (expected_des, orig_des) =
+ fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2);
section_builder.add_to_advertisement();
@@ -210,31 +240,35 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let arena = deserialization_arena!();
+
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
- let (section, cred) = try_deserialize_all_creds::<_, _, CryptoProviderImpl>(
- sections[0].as_ciphertext().unwrap(),
- &cred_source,
- )
- .unwrap()
- .unwrap();
- assert_eq!(&chosen_index, cred.matched_data());
+ let section = sections[0].as_ciphertext().unwrap();
+ let matched_section =
+ try_deserialize_all_creds::<_, CryptoProviderImpl>(arena, section, &cred_source)
+ .unwrap()
+ .unwrap();
+ // Verify that the decrypted metadata contains the chosen index
+ let decrypted_metadata = matched_section.decrypt_metadata::<CryptoProviderImpl>().unwrap();
+ assert_eq!(&[chosen_index as u8] as &[u8], &decrypted_metadata);
+
+ // Verify that the section contents passed through unaltered
+ let section = matched_section.contents();
assert_eq!(section.identity_type(), identity_type);
assert_eq!(section.verification_mode(), VerificationMode::Signature);
- assert_eq!(section.metadata_key(), &metadata_key);
+ assert_eq!(section.metadata_key(), metadata_key);
assert_eq!(
- section.contents,
- SectionContents {
- section_header: (19 + 2 + 16 + 64 + total_de_len(&orig_des)) as u8,
- de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(),
- data_elements: expected_des,
- }
+ section.contents.section_header,
+ (19 + 2 + 16 + 64 + total_de_len(&orig_des)) as u8
);
+ let data_elements = section.collect_data_elements().unwrap();
+ assert_eq!(data_elements, expected_des);
assert_eq!(
- section
- .data_elements()
+ data_elements
+ .iter()
.map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap())
.collect::<Vec<_>>(),
orig_des
@@ -257,44 +291,60 @@
// share a metadata key to emphasize that we're _only_ using the identity to
// differentiate
let metadata_key: [u8; 16] = rng.gen();
+ let metadata_key = MetadataKey(metadata_key);
let credentials = identities
.iter()
- .enumerate()
- .map(|(index, (key_seed, key_pair))| {
- SimpleV1Credential::new(
- HkdfCryptoMaterial::new(key_seed, &metadata_key, key_pair.public()),
- index,
+ .map(|(key_seed, key_pair)| {
+ SimpleSignedBroadcastCryptoMaterial::new(
+ *key_seed,
+ metadata_key,
+ key_pair.private_key(),
)
+ .derive_v1_discovery_credential::<CryptoProviderImpl>()
+ })
+ .map(|discovery_credential| MatchableCredential {
+ discovery_credential,
+ match_data: EmptyMatchedCredential,
})
.collect::<Vec<_>>();
+
let cred_source = SliceCredentialSource::new(&credentials);
let identity_type =
*EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&chosen_key_seed);
+ let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new(
+ chosen_key_seed,
+ metadata_key,
+ chosen_key_pair.private_key(),
+ );
- // awkward split because SectionIdentity isn't object-safe, so we can't just have a
- // Box<dyn SectionIdentity> and use that in one code path
+ // awkward split because SectionEncoder isn't object-safe, so we can't just have a
+ // Box<dyn SectionEncoder> and use that in one code path
if signed {
- let identity = SignedEncrypted::new_random_salt(
+ let identity = SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
identity_type,
- &metadata_key,
- &chosen_key_pair,
- &hkdf,
+ &broadcast_cm,
);
let mut section_builder = adv_builder.section_builder(identity).unwrap();
- let _ = fill_section_random_des(&mut rng, &mut section_builder, 2);
+ let mut expected_de_data = vec![];
+ let _ =
+ fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2);
section_builder.add_to_advertisement();
} else {
- let identity =
- MicEncrypted::new_random_salt(&mut crypto_rng, identity_type, &metadata_key, &hkdf);
+ let identity = MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
+ &mut crypto_rng,
+ identity_type,
+ &broadcast_cm,
+ );
let mut section_builder = adv_builder.section_builder(identity).unwrap();
- let _ = fill_section_random_des(&mut rng, &mut section_builder, 2);
+ let mut expected_de_data = vec![];
+ let _ =
+ fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, 2);
section_builder.add_to_advertisement();
};
@@ -306,12 +356,13 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
- assert!(try_deserialize_all_creds::<_, _, CryptoProviderImpl>(
+ assert!(try_deserialize_all_creds::<_, CryptoProviderImpl>(
+ deserialization_arena!(),
sections[0].as_ciphertext().unwrap(),
- &cred_source
+ &cred_source,
)
.unwrap()
.is_none());
@@ -319,138 +370,30 @@
}
#[test]
-fn convert_data_elements_empty() {
- let orig_des = vec![];
-
- let (des, data) = convert_data_elements(&orig_des);
-
- assert_eq!(Vec::<OffsetDataElement>::new(), des);
- assert_eq!(&Vec::<u8>::new(), data.as_slice());
-}
-
-#[test]
-fn convert_data_elements_just_fits() {
- // this is actually longer than any real section's worth of DEs could be since we aren't putting
- // DE headers in the array
- let orig_data = vec![0x33; 1000];
-
- let orig_des = vec![
- RefDataElement {
- offset: 2.into(),
- header_len: 2,
- de_type: 100_u32.into(),
- contents: &orig_data[0..10],
- },
- RefDataElement {
- offset: 3.into(),
- header_len: 2,
- de_type: 101_u32.into(),
- contents: &orig_data[10..100],
- },
- RefDataElement {
- offset: 4.into(),
- header_len: 2,
- de_type: 102_u32.into(),
- contents: &orig_data[100..NP_ADV_MAX_SECTION_LEN],
- },
- ];
-
- let (des, data) = convert_data_elements(&orig_des);
-
- assert_eq!(
- &[
- OffsetDataElement {
- offset: 2.into(),
- de_type: 100_u32.into(),
- start_of_contents: 0,
- contents_len: 10
- },
- OffsetDataElement {
- offset: 3.into(),
- de_type: 101_u32.into(),
- start_of_contents: 10,
- contents_len: 90
- },
- OffsetDataElement {
- offset: 4.into(),
- de_type: 102_u32.into(),
- start_of_contents: 100,
- contents_len: NP_ADV_MAX_SECTION_LEN - 100,
- },
- ],
- &des[..]
- );
- assert_eq!(&[0x33; NP_ADV_MAX_SECTION_LEN], data.as_slice());
-}
-
-#[test]
-#[should_panic]
-fn convert_data_elements_doesnt_fit_panic() {
- let orig_data = vec![0x33; 1000];
- let orig_des = vec![
- RefDataElement {
- offset: 2.into(),
- header_len: 2,
- de_type: 100_u32.into(),
- contents: &orig_data[0..10],
- },
- // impossibly large DE
- RefDataElement {
- offset: 3.into(),
- header_len: 2,
- de_type: 101_u32.into(),
- contents: &orig_data[10..500],
- },
- ];
-
- let _ = convert_data_elements(&orig_des);
-}
-
-#[test]
fn section_des_expose_correct_data() {
- let mut orig_data = Vec::new();
- orig_data.resize(130, 0);
- for (index, byte) in orig_data.iter_mut().enumerate() {
- *byte = index as u8;
- }
-
- let orig_des = vec![
- OffsetDataElement {
- offset: 2.into(),
- de_type: 100_u32.into(),
- start_of_contents: 0,
- contents_len: 10,
- },
- OffsetDataElement {
- offset: 3.into(),
- de_type: 101_u32.into(),
- start_of_contents: 10,
- contents_len: 90,
- },
- OffsetDataElement {
- offset: 4.into(),
- de_type: 102_u32.into(),
- start_of_contents: 100,
- contents_len: 30,
- },
- ];
+ // 2 sections, 3 DEs each
+ let mut de_data = vec![];
+ // de 1 byte header, type 5, len 5
+ de_data.extend_from_slice(&[0x55, 0x01, 0x02, 0x03, 0x04, 0x05]);
+ // de 2 byte header, type 16, len 1
+ de_data.extend_from_slice(&[0x81, 0x10, 0x01]);
let section = SectionContents {
section_header: 99,
- de_data: ArrayView::try_from_slice(&orig_data).unwrap(),
- data_elements: orig_des,
+ de_region_excl_identity: &de_data,
+ data_element_start_offset: 2,
};
// extract out the parts of the DE we care about
- let des = section
- .data_elements()
- .map(|de| (de.offset(), de.de_type(), de.contents().to_vec()))
- .collect::<Vec<_>>();
+ let des = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(
vec![
- (2.into(), 100_u32.into(), orig_data[0..10].to_vec()),
- (3.into(), 101_u32.into(), orig_data[10..100].to_vec()),
- (4.into(), 102_u32.into(), orig_data[100..].to_vec())
+ DataElement {
+ offset: 2.into(),
+ de_type: 5_u32.into(),
+ contents: &[0x01, 0x02, 0x03, 0x04, 0x05]
+ },
+ DataElement { offset: 3.into(), de_type: 16_u32.into(), contents: &[0x01] },
],
des
);
@@ -467,12 +410,12 @@
} else {
panic!("incorrect header");
};
- parse_sections(&v1_header, remaining).expect_err("Expected an error");
+ parse_sections(v1_header, remaining).expect_err("Expected an error");
}
#[test]
fn do_deserialize_empty_section() {
- let adv_builder = AdvBuilder::new();
+ let adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
let adv = adv_builder.into_advertisement();
let (remaining, header) = parse_adv_header(adv.as_slice()).unwrap();
let v1_header = if let AdvHeader::V1(h) = header {
@@ -480,12 +423,12 @@
} else {
panic!("incorrect header");
};
- parse_sections(&v1_header, remaining).expect_err("Expected an error");
+ parse_sections(v1_header, remaining).expect_err("Expected an error");
}
#[test]
-fn do_deserialize_max_number_of_sections() {
- let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_SECTION_COUNT);
+fn do_deserialize_max_number_of_public_sections() {
+ let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT);
let adv = adv_builder.into_advertisement();
let (remaining, header) = parse_adv_header(adv.as_slice()).unwrap();
@@ -494,14 +437,13 @@
} else {
panic!("incorrect header");
};
-
- let sections = parse_sections(&v1_header, remaining).unwrap();
- assert_eq!(NP_V1_ADV_MAX_SECTION_COUNT, sections.len());
+ let sections = parse_sections(v1_header, remaining).unwrap();
+ assert_eq!(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT, sections.len());
}
#[test]
-fn try_deserialize_over_max_number_of_sections() {
- let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_SECTION_COUNT);
+fn try_deserialize_over_max_number_of_public_sections() {
+ let adv_builder = build_dummy_advertisement_sections(NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT);
let mut adv = adv_builder.into_advertisement().as_slice().to_vec();
// Push an extra section
@@ -520,8 +462,8 @@
} else {
panic!("incorrect header");
};
- parse_sections(&v1_header, remaining)
- .expect_err("Expected an error because number of sections is over 16");
+ parse_sections(v1_header, remaining)
+ .expect_err("Expected an error because number of sections is over limit");
}
pub(crate) fn random_de<R: rand::Rng>(rng: &mut R) -> GenericDataElement {
@@ -533,19 +475,19 @@
GenericDataElement::try_from(rng.gen_range(20_u32..1000).into(), data.as_slice()).unwrap()
}
-fn do_deserialize_section_unencrypted<I: serialize::SectionIdentity>(
+fn do_deserialize_section_unencrypted<I: serialize::SectionEncoder>(
identity: I,
expected_identity: PlaintextIdentityMode,
- prefix_len: usize,
de_offset: usize,
) {
let mut rng = rand::rngs::StdRng::from_entropy();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
let mut section_builder = adv_builder.section_builder(identity).unwrap();
- let (expected_de_data, expected_des, orig_des) =
- fill_section_random_des(&mut rng, &mut section_builder, de_offset);
+ let mut expected_de_data = vec![];
+ let (expected_des, orig_des) =
+ fill_section_random_des(&mut rng, &mut expected_de_data, &mut section_builder, de_offset);
section_builder.add_to_advertisement();
@@ -559,41 +501,33 @@
panic!("incorrect header");
};
- let sections = parse_sections(&v1_header, remaining).unwrap();
+ let sections = parse_sections(v1_header, remaining).unwrap();
assert_eq!(1, sections.len());
let section = sections[0].as_plaintext().unwrap();
+ assert_eq!(section.identity(), expected_identity);
+ let data_elements = section.collect_data_elements().unwrap();
+ assert_eq!(data_elements, expected_des);
assert_eq!(
- &PlaintextSection {
- identity: expected_identity,
- contents: SectionContents {
- section_header: (prefix_len + total_de_len(&orig_des)) as u8,
- de_data: ArrayView::try_from_slice(expected_de_data.as_slice()).unwrap(),
- data_elements: expected_des,
- }
- },
- section
- );
- assert_eq!(
- section
- .contents
- .data_elements()
+ data_elements
+ .iter()
.map(|de| GenericDataElement::try_from(de.de_type(), de.contents()).unwrap())
.collect::<Vec<_>>(),
orig_des
);
}
-fn fill_section_random_des<R: rand::Rng, I: serialize::SectionIdentity>(
+fn fill_section_random_des<'adv, R: rand::Rng, I: serialize::SectionEncoder>(
mut rng: &mut R,
+ sink: &'adv mut Vec<u8>,
section_builder: &mut SectionBuilder<I>,
de_offset: usize,
-) -> (Vec<u8>, Vec<OffsetDataElement>, Vec<GenericDataElement>) {
- let mut expected_de_data = vec![];
+) -> (Vec<DataElement<'adv>>, Vec<GenericDataElement>) {
let mut expected_des = vec![];
let mut orig_des = vec![];
+ let mut de_ranges = vec![];
- for index in 0..rng.gen_range(1..10) {
+ for _ in 0..rng.gen_range(1..10) {
let de = random_de(&mut rng);
let de_clone = de.clone();
@@ -601,19 +535,21 @@
break;
}
- let orig_len = expected_de_data.len();
- de.write_de_contents(&mut expected_de_data).unwrap();
- let contents_len = expected_de_data.len() - orig_len;
-
- expected_des.push(OffsetDataElement {
- offset: (index as usize + de_offset).into(),
- de_type: de.de_header().de_type,
- contents_len,
- start_of_contents: orig_len,
- });
+ let orig_len = sink.len();
+ de.write_de_contents(sink).unwrap();
+ let contents_len = sink.len() - orig_len;
+ de_ranges.push(orig_len..orig_len + contents_len);
orig_des.push(de);
}
- (expected_de_data, expected_des, orig_des)
+
+ for (index, (de, range)) in orig_des.iter().zip(de_ranges).enumerate() {
+ expected_des.push(DataElement {
+ offset: u8::try_from(index + de_offset).unwrap().into(),
+ de_type: de.de_header().de_type,
+ contents: &sink[range],
+ });
+ }
+ (expected_des, orig_des)
}
fn total_de_len(des: &[GenericDataElement]) -> usize {
@@ -626,29 +562,38 @@
.sum()
}
-type TryDeserOutput<'c, C> = Option<(DecryptedSection, <C as MatchableCredential>::Matched<'c>)>;
+type TryDeserOutput<'adv, M> = Option<WithMatchedCredential<M, DecryptedSection<'adv>>>;
/// Returns:
/// - `Ok(Some)` if a matching credential was found
/// - `Ok(None)` if no matching credential was found, or if `cred_source` provides no credentials
/// - `Err` if an error occurred.
-fn try_deserialize_all_creds<'c, C, S, P>(
- section: &CiphertextSection,
- cred_source: &'c S,
-) -> Result<TryDeserOutput<'c, C>, BatchSectionDeserializeError>
+fn try_deserialize_all_creds<'a, S, P>(
+ mut arena: impl BorrowMut<DeserializationArena<'a>>,
+ section: &'a CiphertextSection,
+ cred_source: &'a S,
+) -> Result<TryDeserOutput<'a, S::Matched>, BatchSectionDeserializeError>
where
- C: V1Credential,
- S: CredentialSource<C>,
+ S: DiscoveryCredentialSource<'a, V1>,
P: CryptoProvider,
{
- for c in cred_source.iter() {
- match section.try_deserialize::<C, P>(c) {
- Ok(s) => return Ok(Some((s, c.matched()))),
+ for (crypto_material, match_data) in cred_source.iter() {
+ match section.try_resolve_identity_and_deserialize::<_, P>(
+ arena.borrow_mut(),
+ crypto_material.borrow(),
+ ) {
+ Ok(s) => {
+ let metadata_nonce = crypto_material.metadata_nonce::<P>();
+ return Ok(Some(WithMatchedCredential::new(match_data, metadata_nonce, s)));
+ }
Err(e) => match e {
SectionDeserializeError::IncorrectCredential => continue,
SectionDeserializeError::ParseError => {
return Err(BatchSectionDeserializeError::ParseError)
}
+ SectionDeserializeError::ArenaOutOfSpace => {
+ return Err(BatchSectionDeserializeError::ArenaOutOfSpace)
+ }
},
}
}
@@ -657,9 +602,9 @@
}
fn build_dummy_advertisement_sections(number_of_sections: usize) -> AdvBuilder {
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
for _ in 0..number_of_sections {
- let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder.add_to_advertisement();
}
adv_builder
@@ -669,4 +614,6 @@
enum BatchSectionDeserializeError {
/// Advertisement data is malformed
ParseError,
+ /// The given arena is not large enough to hold the decrypted data
+ ArenaOutOfSpace,
}
diff --git a/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs b/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs
index 437b997..68b6f5d 100644
--- a/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs
+++ b/nearby/presence/np_adv/src/extended/deserialize/test_stubs.rs
@@ -14,99 +14,29 @@
extern crate std;
-use crypto_provider::{ed25519, CryptoProvider};
-use np_hkdf::{NpKeySeedHkdf, UnsignedSectionKeys};
use std::prelude::rust_2021::*;
use crate::{
- credential::v1::*,
extended::deserialize::{CiphertextSection, PlaintextSection},
IntermediateSection,
};
-pub(crate) struct HkdfCryptoMaterial {
- pub(crate) hkdf: [u8; 32],
- pub(crate) expected_unsigned_metadata_key_hmac: [u8; 32],
- pub(crate) expected_signed_metadata_key_hmac: [u8; 32],
- pub(crate) pub_key: ed25519::RawPublicKey,
-}
-
-impl HkdfCryptoMaterial {
- pub(crate) fn new<C: CryptoProvider>(
- hkdf_key_seed: &[u8; 32],
- metadata_key: &[u8; 16],
- pub_key: np_ed25519::PublicKey<C>,
- ) -> Self {
- let hkdf = NpKeySeedHkdf::<C>::new(hkdf_key_seed);
- let unsigned =
- hkdf.extended_unsigned_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice());
- let signed =
- hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice());
- Self {
- hkdf: *hkdf_key_seed,
- expected_unsigned_metadata_key_hmac: unsigned,
- expected_signed_metadata_key_hmac: signed,
- pub_key: pub_key.to_bytes(),
- }
- }
-}
-
-impl HkdfCryptoMaterial {
- fn hkdf<C: CryptoProvider>(&self) -> NpKeySeedHkdf<C> {
- NpKeySeedHkdf::<C>::new(&self.hkdf)
- }
-}
-
-impl V1CryptoMaterial for HkdfCryptoMaterial {
- type SignedIdentityResolverReference<'a> = SignedSectionIdentityResolutionMaterial
- where Self: 'a;
- type UnsignedIdentityResolverReference<'a> = UnsignedSectionIdentityResolutionMaterial
- where Self: 'a;
-
- fn signed_identity_resolution_material<C: CryptoProvider>(
- &self,
- ) -> Self::SignedIdentityResolverReference<'_> {
- SignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac::<C>(
- &self.hkdf::<C>(),
- self.expected_signed_metadata_key_hmac,
- )
- }
- fn unsigned_identity_resolution_material<C: CryptoProvider>(
- &self,
- ) -> Self::UnsignedIdentityResolverReference<'_> {
- UnsignedSectionIdentityResolutionMaterial::from_hkdf_and_expected_metadata_key_hmac::<C>(
- &self.hkdf::<C>(),
- self.expected_unsigned_metadata_key_hmac,
- )
- }
- fn signed_verification_material<C: CryptoProvider>(&self) -> SignedSectionVerificationMaterial {
- SignedSectionVerificationMaterial { pub_key: self.pub_key }
- }
-
- fn unsigned_verification_material<C: CryptoProvider>(
- &self,
- ) -> UnsignedSectionVerificationMaterial {
- let mic_hmac_key = *UnsignedSectionKeys::hmac_key(&self.hkdf::<C>()).as_bytes();
- UnsignedSectionVerificationMaterial { mic_hmac_key }
- }
-}
-
-pub(crate) trait IntermediateSectionExt {
+pub(crate) trait IntermediateSectionExt<'adv> {
/// Returns `Some` if `self` is `Plaintext`
- fn as_plaintext(&self) -> Option<&PlaintextSection>;
+ fn as_plaintext(&self) -> Option<&PlaintextSection<'adv>>;
/// Returns `Some` if `self` is `Ciphertext`
- fn as_ciphertext(&self) -> Option<&CiphertextSection>;
+ fn as_ciphertext(&self) -> Option<&CiphertextSection<'adv>>;
}
-impl<'a> IntermediateSectionExt for IntermediateSection<'a> {
- fn as_plaintext(&self) -> Option<&PlaintextSection> {
+impl<'adv> IntermediateSectionExt<'adv> for IntermediateSection<'adv> {
+ fn as_plaintext(&self) -> Option<&PlaintextSection<'adv>> {
match self {
IntermediateSection::Plaintext(s) => Some(s),
IntermediateSection::Ciphertext(_) => None,
}
}
- fn as_ciphertext(&self) -> Option<&CiphertextSection> {
+ fn as_ciphertext(&self) -> Option<&CiphertextSection<'adv>> {
match self {
IntermediateSection::Plaintext(_) => None,
IntermediateSection::Ciphertext(s) => Some(s),
diff --git a/nearby/presence/np_adv/src/extended/mod.rs b/nearby/presence/np_adv/src/extended/mod.rs
index c2f485e..68b16c3 100644
--- a/nearby/presence/np_adv/src/extended/mod.rs
+++ b/nearby/presence/np_adv/src/extended/mod.rs
@@ -31,7 +31,10 @@
- 2;
/// Maximum number of sections in an advertisement
-pub const NP_V1_ADV_MAX_SECTION_COUNT: usize = 8;
+pub const NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT: usize = 8;
+
+/// Maximum number of public sections in an advertisement
+pub const NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT: usize = 1;
/// Maximum size of a NP section, including its header byte
pub const NP_ADV_MAX_SECTION_LEN: usize = BLE_ADV_SVC_CONTENT_LEN
@@ -53,8 +56,8 @@
/// A convenient constant for zero length.
pub const ZERO: DeLength = DeLength { len: 0 };
- fn as_usize(&self) -> usize {
- self.len as usize
+ fn as_u8(&self) -> u8 {
+ self.len
}
}
@@ -62,7 +65,7 @@
type Error = DeLengthOutOfRange;
fn try_from(value: u8) -> Result<Self, Self::Error> {
- if value as usize <= MAX_DE_LEN {
+ if usize::from(value) <= MAX_DE_LEN {
Ok(Self { len: value })
} else {
Err(DeLengthOutOfRange {})
diff --git a/nearby/presence/np_adv/src/extended/section_signature_payload.rs b/nearby/presence/np_adv/src/extended/section_signature_payload.rs
index 2b0ebe5..d8ec93c 100644
--- a/nearby/presence/np_adv/src/extended/section_signature_payload.rs
+++ b/nearby/presence/np_adv/src/extended/section_signature_payload.rs
@@ -17,6 +17,7 @@
//! performing signatures and signature verification.
use crate::extended::deserialize::EncryptionInfo;
+use crate::MetadataKey;
use crate::NP_SVC_UUID;
use crypto_provider::{aes::ctr::AesCtrNonce, CryptoProvider};
use sink::{Sink, SinkWriter};
@@ -45,10 +46,10 @@
/// to be included in the signature payload after the derived salt,
/// and before the signature itself.
Raw(&'a [u8]),
- /// References to the plaintext identity DE header and the rest
- /// of the section plaintext after that (includes the plaintext
- /// identity DE payload).
- IdentityHeaderThenRaw([u8; 2], &'a [u8]),
+ /// Plaintext identity DE header followed by the metadata key,
+ /// then the rest of the section plaintext (including
+ /// the plaintext identity DE payload).
+ IdentityHeaderMetadataKeyAndRemainder([u8; 2], MetadataKey, &'a [u8]),
}
const ADV_SIGNATURE_CONTEXT: np_ed25519::SignatureContext = {
@@ -60,6 +61,30 @@
impl<'a> SectionSignaturePayload<'a> {
/// Construct a section signature payload using parts typically found during
+ /// deserialization of advertisements.
+ pub(crate) fn from_deserialized_parts(
+ adv_header_byte: u8,
+ section_header: u8,
+ encryption_info: &'a [u8; EncryptionInfo::TOTAL_DE_LEN],
+ nonce_ref: &'a AesCtrNonce,
+ identity_header: [u8; 2],
+ plaintext_metadata_key: MetadataKey,
+ raw_plaintext_remainder: &'a [u8],
+ ) -> Self {
+ Self {
+ adv_header_byte,
+ section_header,
+ encryption_info,
+ nonce_ref,
+ after_iv_info: AfterIVInfo::IdentityHeaderMetadataKeyAndRemainder(
+ identity_header,
+ plaintext_metadata_key,
+ raw_plaintext_remainder,
+ ),
+ }
+ }
+
+ /// Construct a section signature payload using parts typically found during
/// serialization of advertisements.
pub(crate) fn from_serialized_parts(
adv_header_byte: u8,
@@ -77,28 +102,6 @@
}
}
- /// Construct a section signature payload using parts typically found during
- /// deserialization of advertisements.
- pub(crate) fn from_deserialized_parts(
- adv_header_byte: u8,
- section_header: u8,
- encryption_info: &'a [u8; EncryptionInfo::TOTAL_DE_LEN],
- nonce_ref: &'a AesCtrNonce,
- identity_header: [u8; 2],
- raw_after_identity_header_info: &'a [u8],
- ) -> Self {
- Self {
- adv_header_byte,
- section_header,
- encryption_info,
- nonce_ref,
- after_iv_info: AfterIVInfo::IdentityHeaderThenRaw(
- identity_header,
- raw_after_identity_header_info,
- ),
- }
- }
-
/// Generates a signature for this section signing payload using
/// the given Ed25519 key-pair.
pub(crate) fn sign<C: CryptoProvider>(
@@ -133,9 +136,14 @@
// identity DE and the rest of the DEs except for the suffix
match self.after_iv_info {
AfterIVInfo::Raw(s) => sink.try_extend_from_slice(s),
- AfterIVInfo::IdentityHeaderThenRaw(identity_header, s) => {
+ AfterIVInfo::IdentityHeaderMetadataKeyAndRemainder(
+ identity_header,
+ metadata_key,
+ remainder,
+ ) => {
sink.try_extend_from_slice(&identity_header)?;
- sink.try_extend_from_slice(s)
+ sink.try_extend_from_slice(&metadata_key.0)?;
+ sink.try_extend_from_slice(remainder)
}
}
}
diff --git a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
index 47804ea..36e4e5f 100644
--- a/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/adv_tests.rs
@@ -20,18 +20,10 @@
#[test]
fn adv_encode_no_salt() {
- let mut adv_builder = AdvBuilder::new();
-
- let mut no_identity_section_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
- no_identity_section_builder
- .add_de(|_| DummyDataElement { de_type: 20_u32.into(), data: vec![] })
- .unwrap();
-
- no_identity_section_builder.add_to_advertisement();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
let mut public_identity_section_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
public_identity_section_builder
.add_de(|_| DummyDataElement { de_type: 30_u32.into(), data: vec![] })
.unwrap();
@@ -43,9 +35,6 @@
0x20, // adv header
0x3, // section header
0x3, // public identity
- 0x80, 20, // de header
- 0x3, // section header
- 0x3, // public identity
0x80, 30, // de header
],
adv_builder.into_advertisement().as_slice()
@@ -53,45 +42,12 @@
}
#[test]
-fn adv_encode_with_salt() {
- let mut adv_builder = AdvBuilder::new();
-
- let mut no_identity_section_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
- no_identity_section_builder
- .add_de(|_| DummyDataElement { de_type: 20_u32.into(), data: vec![] })
- .unwrap();
-
- no_identity_section_builder.add_to_advertisement();
-
- let mut public_identity_section_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
- public_identity_section_builder
- .add_de(|_| DummyDataElement { de_type: 30_u32.into(), data: vec![] })
- .unwrap();
-
- public_identity_section_builder.add_to_advertisement();
-
- let mut expected = vec![
- 0x20, // adv header
- ];
- expected.extend_from_slice(&[
- 0x3, // section header
- 0x3, // public identity
- 0x80, 20, // de header
- 0x3, // section header
- 0x3, // public identity
- 0x80, 30, // de header
- ]);
- assert_eq!(expected, adv_builder.into_advertisement().as_slice())
-}
-
-#[test]
fn adding_any_allowed_section_length_always_works_for_single_section() {
// up to section len - 1 to leave room for section header
for section_contents_len in 0..NP_ADV_MAX_SECTION_LEN - 1 {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder =
+ adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
fill_section_builder(section_contents_len, &mut section_builder);
section_builder.add_to_advertisement();
@@ -108,50 +64,17 @@
}
#[test]
-fn adding_any_allowed_section_length_always_works_for_two_sections() {
- // leave room for both section header bytes and the public identities
- for section_1_contents_len in 0..=NP_ADV_MAX_SECTION_LEN - 4 {
- // leave room for both section headers + public identities + section 1 contents
- for section_2_contents_len in 0..=NP_ADV_MAX_SECTION_LEN - section_1_contents_len - 4 {
- let mut adv_builder = AdvBuilder::new();
-
- let mut section_1_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
- fill_section_builder(section_1_contents_len, &mut section_1_builder);
- section_1_builder.add_to_advertisement();
-
- let mut section_2_builder =
- adv_builder.section_builder(PublicIdentity::default()).unwrap();
- fill_section_builder(section_2_contents_len, &mut section_2_builder);
- section_2_builder.add_to_advertisement();
-
- let adv = adv_builder.into_advertisement();
- assert_eq!(
- section_1_contents_len + section_2_contents_len + 1 + 4, // adv and section headers
- adv.as_slice().len(),
- "adv: {:?}\nsection 1 contents len: {}, section 2 contents len: {}",
- adv.as_slice(),
- section_1_contents_len,
- section_2_contents_len,
- );
- }
- }
-}
-
-#[test]
fn building_capacity_0_section_works() {
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
- // leave room for two section headers and the public identities
- fill_section_builder(NP_ADV_MAX_SECTION_LEN - 4, &mut section_builder);
- section_builder.add_to_advertisement();
+ // leave room for section header and the public identity
+ fill_section_builder(NP_ADV_MAX_SECTION_LEN - 2, &mut section_builder);
- let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
- // The section header and the public identity
- assert_eq!(2, section_builder.section.capacity);
- assert_eq!(2, section_builder.section.len());
+ assert_eq!(NP_ADV_MAX_SECTION_LEN, section_builder.section.capacity);
+ assert_eq!(NP_ADV_MAX_SECTION_LEN, section_builder.section.len());
+
section_builder.add_to_advertisement();
assert_eq!(BLE_ADV_SVC_CONTENT_LEN, adv_builder.into_advertisement().as_slice().len());
@@ -161,10 +84,11 @@
#[derive(Default, PartialEq, Eq, Debug)]
struct EnormousIdentity {}
-impl SectionIdentity for EnormousIdentity {
+impl SectionEncoder for EnormousIdentity {
const PREFIX_LEN: usize = 200;
const SUFFIX_LEN: usize = 0;
const INITIAL_DE_OFFSET: DataElementOffset = DataElementOffset::ZERO;
+ const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Plaintext;
fn postprocess(
&mut self,
@@ -176,7 +100,6 @@
}
type DerivedSalt = ();
-
fn de_salt(&self, _de_offset: DataElementOffset) -> Self::DerivedSalt {
panic!("should never be called, just used for its huge prefix")
}
diff --git a/nearby/presence/np_adv/src/extended/serialize/mod.rs b/nearby/presence/np_adv/src/extended/serialize/mod.rs
index 6cedd1e..f736dea 100644
--- a/nearby/presence/np_adv/src/extended/serialize/mod.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/mod.rs
@@ -26,8 +26,8 @@
//! use np_adv::shared_data::TxPower;
//!
//! // no section identities or DEs need salt in this example
-//! let mut adv_builder = AdvBuilder::new();
-//! let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+//! let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+//! let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
//!
//! section_builder.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap();
//!
@@ -54,28 +54,35 @@
//!
//! ```
//! use np_adv::{
+//! credential::{SimpleBroadcastCryptoMaterial, v1::V1},
//! de_type::EncryptedIdentityDataElementType,
//! extended::{data_elements::*, serialize::*, de_type::DeType },
+//! MetadataKey,
//! };
//! use rand::{Rng as _, SeedableRng as _};
//! use crypto_provider::{CryptoProvider, CryptoRng};
//! use crypto_provider_default::CryptoProviderImpl;
//! use np_adv::shared_data::TxPower;
//!
-//! let mut adv_builder = AdvBuilder::new();
+//! let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
//!
//! // these would come from the credential//!
//! let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
//! let metadata_key: [u8; 16] = rng.gen();
+//! let metadata_key = MetadataKey(metadata_key);
//! let key_seed: [u8; 32] = rng.gen();
//! // use your preferred crypto impl
//! let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
//!
-//! let mut section_builder = adv_builder.section_builder(MicEncrypted::new_random_salt(
+//! let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(
+//! key_seed,
+//! metadata_key,
+//! );
+//!
+//! let mut section_builder = adv_builder.section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
//! &mut rng,
//! EncryptedIdentityDataElementType::Private,
-//! &metadata_key,
-//! &key_seed_hkdf,
+//! &broadcast_cm,
//! )).unwrap();
//!
//! section_builder.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap();
@@ -104,8 +111,12 @@
//! .try_into().expect("array sizes match")
//! }
//! ```
-use crate::extended::NP_V1_ADV_MAX_SECTION_COUNT;
+use crate::extended::{NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT, NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT};
use crate::{
+ credential::{
+ v1::{SignedBroadcastCryptoMaterial, V1},
+ BroadcastCryptoMaterial,
+ },
de_type::{EncryptedIdentityDataElementType, IdentityDataElementType},
extended::{
data_elements::EncryptionInfoDataElement,
@@ -114,11 +125,10 @@
section_signature_payload::*,
to_array_view, DeLength, BLE_ADV_SVC_CONTENT_LEN, NP_ADV_MAX_SECTION_LEN,
},
- DeLengthOutOfRange, PublicIdentity, NP_SVC_UUID,
+ DeLengthOutOfRange, MetadataKey, NP_SVC_UUID,
};
use array_view::ArrayView;
-use core::fmt;
-use core::marker::PhantomData;
+use core::fmt::{self, Display};
use crypto_provider::{
aes::{
ctr::{AesCtr, AesCtrNonce, NonceAndCounter},
@@ -145,36 +155,35 @@
pub struct AdvBuilder {
/// Contains the adv header byte
adv: tinyvec::ArrayVec<[u8; BLE_ADV_SVC_CONTENT_LEN]>,
+ /// To track the number of sections that are in the advertisement
section_count: usize,
+ /// Advertisement type: Public or Encrypted
+ advertisement_type: AdvertisementType,
}
impl AdvBuilder {
/// Build an [AdvBuilder].
- pub fn new() -> AdvBuilder {
+ pub fn new(advertisement_type: AdvertisementType) -> Self {
let mut adv = tinyvec::ArrayVec::new();
// version 1, 0bVVVRRRRR
adv.push(0b00100000);
-
- AdvBuilder { adv, section_count: 0 }
+ Self { adv, section_count: 0, advertisement_type }
}
/// Create a section builder.
///
- /// Returns `Err` if there isn't room in the advertisement for the section at its minimum length
- /// with its chosen identity.
- ///
/// The builder will not accept more DEs than can fit given the space already used in the
/// advertisement by previous sections, if any.
///
/// Once the builder is populated, add it to the originating advertisement with
/// [SectionBuilder.add_to_advertisement].
- pub fn section_builder<I: SectionIdentity>(
+ pub fn section_builder<SE: SectionEncoder>(
&mut self,
- identity: I,
- ) -> Result<SectionBuilder<I>, AddSectionError> {
+ section_encoder: SE,
+ ) -> Result<SectionBuilder<SE>, AddSectionError> {
// section header and identity prefix
- let prefix_len = 1 + I::PREFIX_LEN;
- let minimum_section_len = prefix_len + I::SUFFIX_LEN;
+ let prefix_len = 1 + SE::PREFIX_LEN;
+ let minimum_section_len = prefix_len + SE::SUFFIX_LEN;
// the max overall len available to the section
let available_len = self.adv.capacity() - self.adv.len();
@@ -182,11 +191,15 @@
return Err(AddSectionError::InsufficientAdvSpace);
}
- if self.section_count >= NP_V1_ADV_MAX_SECTION_COUNT {
+ if self.section_count >= self.advertisement_type.max_sections() {
return Err(AddSectionError::MaxSectionCountExceeded);
}
- let mut section = tinyvec::ArrayVec::new();
+ if self.advertisement_type != SE::ADVERTISEMENT_TYPE {
+ return Err(AddSectionError::IncompatibleSectionType);
+ }
+
+ let mut section: tinyvec::ArrayVec<[u8; 249]> = tinyvec::ArrayVec::new();
// placeholder for section header and identity prefix
section.resize(prefix_len, 0);
@@ -194,11 +207,11 @@
section: CapacityLimitedVec {
vec: section,
// won't underflow: checked above
- capacity: available_len - I::SUFFIX_LEN,
+ capacity: available_len - SE::SUFFIX_LEN,
},
- identity,
+ section_encoder,
adv_builder: self,
- next_de_offset: I::INITIAL_DE_OFFSET,
+ next_de_offset: SE::INITIAL_DE_OFFSET,
})
}
@@ -221,33 +234,30 @@
}
}
-impl Default for AdvBuilder {
- fn default() -> Self {
- Self::new()
- }
-}
-
/// Errors that can occur when adding a section to an advertisement
#[derive(Debug, PartialEq, Eq)]
pub enum AddSectionError {
/// The advertisement doesn't have enough space to hold the minimum size of the section
InsufficientAdvSpace,
- /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_SECTION_COUNT number of sections
+ /// The advertisement can only hold a maximum of NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT number of sections
MaxSectionCountExceeded,
+ /// An incompatible section trying to be added
+ IncompatibleSectionType,
}
-/// Derived salt for an individual data element.
-pub struct DeSalt<C: CryptoProvider> {
- salt: V1Salt<C>,
- de_offset: DataElementOffset,
-}
-
-impl<C: CryptoProvider> DeSalt<C> {
- /// Derive salt of the requested length.
- ///
- /// The length must be a valid HKDF-SHA256 length.
- pub fn derive<const N: usize>(&self) -> Option<[u8; N]> {
- self.salt.derive(Some(self.de_offset))
+impl Display for AddSectionError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ AddSectionError::InsufficientAdvSpace => {
+ write!(f, "The advertisement (max {BLE_ADV_SVC_CONTENT_LEN} bytes) doesn't have enough remaining space to hold the section")
+ }
+ AddSectionError::MaxSectionCountExceeded => {
+ write!(f, "The advertisement can only hold a maximum of {NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT} number of sections")
+ }
+ AddSectionError::IncompatibleSectionType => {
+ write!(f, "Public and Encrypted sections cannot be mixed in the same advertisement")
+ }
+ }
}
}
@@ -269,22 +279,22 @@
/// Accumulates data elements and encodes them into a section.
#[derive(Debug)]
-pub struct SectionBuilder<'a, I: SectionIdentity> {
+pub struct SectionBuilder<'a, SE: SectionEncoder> {
/// Contains the section header, the identity-specified overhead, and any DEs added
pub(crate) section: CapacityLimitedVec<u8, NP_ADV_MAX_SECTION_LEN>,
- identity: I,
+ section_encoder: SE,
/// mut ref to enforce only one active section builder at a time
adv_builder: &'a mut AdvBuilder,
next_de_offset: DataElementOffset,
}
-impl<'a, I: SectionIdentity> SectionBuilder<'a, I> {
+impl<'a, SE: SectionEncoder> SectionBuilder<'a, SE> {
/// Add this builder to the advertisement that created it.
pub fn add_to_advertisement(self) {
let adv_builder = self.adv_builder;
adv_builder.add_section(Self::build_section(
self.section.into_inner(),
- self.identity,
+ self.section_encoder,
adv_builder,
))
}
@@ -292,11 +302,11 @@
/// Add a data element to the section with a closure that returns a `Result`.
///
/// The provided `build_de` closure will be invoked with the derived salt for this DE.
- pub fn add_de_res<W: WriteDataElement, E, F: FnOnce(I::DerivedSalt) -> Result<W, E>>(
+ pub fn add_de_res<W: WriteDataElement, E, F: FnOnce(SE::DerivedSalt) -> Result<W, E>>(
&mut self,
build_de: F,
) -> Result<(), AddDataElementError<E>> {
- let writer = build_de(self.identity.de_salt(self.next_de_offset))
+ let writer = build_de(self.section_encoder.de_salt(self.next_de_offset))
.map_err(AddDataElementError::BuildDeError)?;
let orig_len = self.section.len();
@@ -320,10 +330,10 @@
e
})?;
- if content_len != de_header.len.as_usize() {
+ if content_len != usize::from(de_header.len.as_u8()) {
panic!(
"Buggy WriteDataElement impl: header len {}, actual written len {}",
- de_header.len.as_usize(),
+ de_header.len.as_u8(),
content_len
);
}
@@ -336,7 +346,7 @@
/// Add a data element to the section with a closure that returns the data element directly.
///
/// The provided `build_de` closure will be invoked with the derived salt for this DE.
- pub fn add_de<W: WriteDataElement, F: FnOnce(I::DerivedSalt) -> W>(
+ pub fn add_de<W: WriteDataElement, F: FnOnce(SE::DerivedSalt) -> W>(
&mut self,
build_de: F,
) -> Result<(), AddDataElementError<()>> {
@@ -348,11 +358,11 @@
/// Implemented without self to avoid partial-move issues.
fn build_section(
mut section_contents: tinyvec::ArrayVec<[u8; NP_ADV_MAX_SECTION_LEN]>,
- mut identity: I,
+ mut section_encoder: SE,
adv_builder: &AdvBuilder,
) -> EncodedSection {
// there is space because the capacity for DEs was restricted to allow it
- section_contents.resize(section_contents.len() + I::SUFFIX_LEN, 0);
+ section_contents.resize(section_contents.len() + SE::SUFFIX_LEN, 0);
section_contents[0] = section_contents
.len()
@@ -361,7 +371,7 @@
.and_then(|len: u8| len.checked_sub(1))
.expect("section length is always <=255 and non-negative");
- identity.postprocess(
+ section_encoder.postprocess(
adv_builder.header_byte(),
section_contents[0],
&mut section_contents[1..],
@@ -380,8 +390,26 @@
InsufficientSectionSpace,
}
-/// The identity used for an individual section.
-pub trait SectionIdentity {
+/// The advertisement type, which dictates what sections can exist
+#[derive(Debug, PartialEq, Eq)]
+pub enum AdvertisementType {
+ /// Plaintext advertisement with only plaintext sections
+ Plaintext,
+ /// Encrypted advertisement with only encrypted sections
+ Encrypted,
+}
+
+impl AdvertisementType {
+ fn max_sections(&self) -> usize {
+ match self {
+ AdvertisementType::Plaintext => NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT,
+ AdvertisementType::Encrypted => NP_V1_ADV_MAX_ENCRYPTED_SECTION_COUNT,
+ }
+ }
+}
+
+/// Everything needed to properly encode a section
+pub trait SectionEncoder {
/// How much space needs to be reserved for this identity's prefix bytes after the section
/// header and before other DEs
const PREFIX_LEN: usize;
@@ -392,6 +420,9 @@
/// The DE offset to use for any DEs added to the section
const INITIAL_DE_OFFSET: DataElementOffset;
+ /// The advertisement type that can support this section
+ const ADVERTISEMENT_TYPE: AdvertisementType;
+
/// Postprocess the contents of the section (the data after the section header byte), which will
/// start with [Self::PREFIX_LEN] bytes set aside for the identity's use, and similarly end with
/// [Self::SUFFIX_LEN] bytes, with DEs (if any) in the middle.
@@ -404,13 +435,16 @@
fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt;
}
-impl SectionIdentity for PublicIdentity {
+/// Public section for plaintext data elements
+#[derive(Default, Debug)]
+pub struct PublicSectionEncoder {}
+impl SectionEncoder for PublicSectionEncoder {
/// 1 byte of public identity DE header
const PREFIX_LEN: usize = 1;
const SUFFIX_LEN: usize = 0;
/// Room for the public DE
const INITIAL_DE_OFFSET: DataElementOffset = DataElementOffset::ZERO.incremented();
-
+ const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Plaintext;
fn postprocess(
&mut self,
_adv_header_byte: u8,
@@ -423,180 +457,50 @@
.as_slice(),
)
}
-
type DerivedSalt = ();
-
fn de_salt(&self, _de_offset: DataElementOffset) -> Self::DerivedSalt {}
}
-/// Encrypts the data elements and protects integrity with a MIC using key material derived from
-/// an NP identity.
-pub struct MicEncrypted<'a, C: CryptoProvider> {
- identity_type: EncryptedIdentityDataElementType,
- salt: V1Salt<C>,
- identity_payload: &'a [u8; 16],
- aes_key: Aes128Key,
- mic_hmac_key: np_hkdf::NpHmacSha256Key<C>,
-}
-
-impl<'a, C: CryptoProvider> MicEncrypted<'a, C> {
- /// Build a [MicEncrypted] from the provided identity info with a random salt.
- pub fn new_random_salt(
- rng: &mut C::CryptoRng,
- identity_type: EncryptedIdentityDataElementType,
- identity_payload: &'a [u8; 16],
- keys: &impl np_hkdf::UnsignedSectionKeys<C>,
- ) -> Self {
- let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into();
- Self::new(identity_type, salt, identity_payload, keys)
- }
-
- /// Build a [MicEncrypted] from the provided identity info.
- fn new(
- identity_type: EncryptedIdentityDataElementType,
- salt: V1Salt<C>,
- identity_payload: &'a [u8; 16],
- keys: &impl np_hkdf::UnsignedSectionKeys<C>,
- ) -> Self {
- MicEncrypted {
- identity_type,
- salt,
- identity_payload,
- aes_key: keys.aes_key(),
- mic_hmac_key: keys.hmac_key(),
- }
- }
-
- /// Build a [MicEncrypted] from the provided identity info. Exposed outside of this crate for
- /// testing purposes only.
- #[cfg(any(test, feature = "testing"))]
- pub fn new_for_testing(
- identity_type: EncryptedIdentityDataElementType,
- salt: V1Salt<C>,
- identity_payload: &'a [u8; 16],
- keys: &impl np_hkdf::UnsignedSectionKeys<C>,
- ) -> Self {
- Self::new(identity_type, salt, identity_payload, keys)
- }
-}
-
-impl<'a, C: CryptoProvider> SectionIdentity for MicEncrypted<'a, C> {
- const PREFIX_LEN: usize =
- EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN;
- /// Length of mic
- const SUFFIX_LEN: usize = SectionMic::CONTENTS_LEN;
- /// Room for the mic, encryption info, and identity DEs
- const INITIAL_DE_OFFSET: DataElementOffset =
- DataElementOffset::ZERO.incremented().incremented();
-
- fn postprocess(
- &mut self,
- adv_header_byte: u8,
- section_header: u8,
- section_contents: &mut [u8],
- ) {
- // prefix byte layout:
- // 0-18: Encryption Info DE (header + scheme + salt)
- // 19-20: Identity DE header
- // 21-36: Identity DE contents (metadata key)
-
- // Encryption Info DE
- let encryption_info_bytes = EncryptionInfoDataElement::mic(
- self.salt.as_slice().try_into().expect("Salt should be 16 bytes"),
- )
- .serialize();
- section_contents[0..19].copy_from_slice(&encryption_info_bytes);
-
- // Identity DE
- let identity_header = identity_de_header(self.identity_type, self.identity_payload);
- section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice());
- section_contents[21..37].copy_from_slice(self.identity_payload);
-
- // DE offset for identity is 1: Encryption Info DE, Identity DE, then other DEs
- let nonce: AesCtrNonce = self
- .de_salt(v1_salt::DataElementOffset::from(1))
- .derive()
- .expect("AES-CTR nonce is a valid HKDF length");
-
- let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce));
-
- let ciphertext_end = section_contents.len() - SectionMic::CONTENTS_LEN;
-
- // encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs
- cipher.encrypt(&mut section_contents[21..ciphertext_end]);
-
- // calculate MAC per the spec
- let mut section_hmac = self.mic_hmac_key.build_hmac();
- // svc uuid
- section_hmac.update(NP_SVC_UUID.as_slice());
- // adv header
- section_hmac.update(&[adv_header_byte]);
- // section header
- section_hmac.update(&[section_header]);
- // encryption info
- section_hmac.update(&encryption_info_bytes);
- // derived salt
- section_hmac.update(&nonce);
- // identity header + ciphertext
- section_hmac.update(§ion_contents[19..ciphertext_end]);
-
- let mic: [u8; 32] = section_hmac.finalize();
-
- // write truncated MIC
- section_contents[ciphertext_end..].copy_from_slice(&mic[..SectionMic::CONTENTS_LEN]);
- }
-
- type DerivedSalt = DeSalt<C>;
-
- fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt {
- DeSalt { salt: V1Salt::from(*self.salt.as_array_ref()), de_offset }
- }
-}
-
/// Encrypts the data elements and protects integrity with an np_ed25519 signature
/// using key material derived from an NP identity.
-pub struct SignedEncrypted<'a, C: CryptoProvider> {
+pub struct SignedEncryptedSectionEncoder<C: CryptoProvider> {
identity_type: EncryptedIdentityDataElementType,
salt: V1Salt<C>,
- metadata_key: &'a [u8; 16],
- key_pair: &'a np_ed25519::KeyPair<C>,
+ metadata_key: MetadataKey,
+ key_pair: np_ed25519::KeyPair<C>,
aes_key: Aes128Key,
- _marker: PhantomData<C>,
}
-impl<'a, C: CryptoProvider> SignedEncrypted<'a, C> {
- /// Build a [SignedEncrypted] from the provided identity material with a random salt.
- pub fn new_random_salt(
+impl<C: CryptoProvider> SignedEncryptedSectionEncoder<C> {
+ /// Build a [SignedEncryptedSectionEncoder] from an identity type,
+ /// some broadcast crypto-material, and with a random salt.
+ pub fn new_random_salt<B: SignedBroadcastCryptoMaterial>(
rng: &mut C::CryptoRng,
identity_type: EncryptedIdentityDataElementType,
- metadata_key: &'a [u8; 16],
- key_pair: &'a np_ed25519::KeyPair<C>,
- key_seed_hkdf: &np_hkdf::NpKeySeedHkdf<C>,
+ crypto_material: &B,
) -> Self {
let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into();
- Self::new(identity_type, salt, metadata_key, key_pair, key_seed_hkdf)
+ Self::new(identity_type, salt, crypto_material)
}
- /// Build a [SignedEncrypted] from the provided identity material.
- pub(crate) fn new(
+ /// Build a [SignedEncryptedSectionEncoder] from an identity type,
+ /// a provided salt, and some broadcast crypto-material.
+ pub(crate) fn new<B: SignedBroadcastCryptoMaterial>(
identity_type: EncryptedIdentityDataElementType,
salt: V1Salt<C>,
- metadata_key: &'a [u8; 16],
- key_pair: &'a np_ed25519::KeyPair<C>,
- key_seed_hkdf: &np_hkdf::NpKeySeedHkdf<C>,
+ crypto_material: &B,
) -> Self {
- Self {
- identity_type,
- salt,
- metadata_key,
- key_pair,
- aes_key: key_seed_hkdf.extended_signed_section_aes_key(),
- _marker: Default::default(),
- }
+ let metadata_key = crypto_material.metadata_key();
+ let key_seed = crypto_material.key_seed();
+ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
+ let private_key = crypto_material.signing_key();
+ let key_pair = np_ed25519::KeyPair::<C>::from_private_key(&private_key);
+ let aes_key = key_seed_hkdf.extended_signed_section_aes_key();
+ Self { identity_type, salt, metadata_key, key_pair, aes_key }
}
}
-impl<'a, C: CryptoProvider> SectionIdentity for SignedEncrypted<'a, C> {
+impl<C: CryptoProvider> SectionEncoder for SignedEncryptedSectionEncoder<C> {
const PREFIX_LEN: usize =
EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN;
/// Ed25519 signature
@@ -604,6 +508,7 @@
/// Room for the encryption info and identity DEs
const INITIAL_DE_OFFSET: DataElementOffset =
DataElementOffset::ZERO.incremented().incremented();
+ const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Encrypted;
fn postprocess(
&mut self,
@@ -619,7 +524,7 @@
let identity_header = identity_de_header(self.identity_type, self.metadata_key);
section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice());
- section_contents[21..37].copy_from_slice(self.metadata_key);
+ section_contents[21..37].copy_from_slice(&self.metadata_key.0);
let nonce: AesCtrNonce = self
.de_salt(v1_salt::DataElementOffset::from(1))
@@ -648,14 +553,14 @@
after_encryption_info,
);
- let signature = section_signature_payload.sign(self.key_pair);
+ let signature = section_signature_payload.sign(&self.key_pair);
sig[0..64].copy_from_slice(&signature.to_bytes());
let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce));
// encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs
- cipher.encrypt(&mut section_contents[21..]);
+ cipher.apply_keystream(&mut section_contents[21..]);
}
type DerivedSalt = DeSalt<C>;
@@ -665,6 +570,135 @@
}
}
+/// Encrypts the data elements and protects integrity with a MIC using key material derived from
+/// an NP identity.
+pub struct MicEncryptedSectionEncoder<C: CryptoProvider> {
+ identity_type: EncryptedIdentityDataElementType,
+ salt: V1Salt<C>,
+ metadata_key: MetadataKey,
+ aes_key: Aes128Key,
+ mic_hmac_key: np_hkdf::NpHmacSha256Key<C>,
+}
+
+impl<C: CryptoProvider> MicEncryptedSectionEncoder<C> {
+ /// Build a [MicEncryptedSectionEncoder] from the provided identity
+ /// info with a random salt.
+ pub fn new_random_salt<B: BroadcastCryptoMaterial<V1>>(
+ rng: &mut C::CryptoRng,
+ identity_type: EncryptedIdentityDataElementType,
+ crypto_material: &B,
+ ) -> Self {
+ let salt: V1Salt<C> = rng.gen::<[u8; 16]>().into();
+ Self::new(identity_type, salt, crypto_material)
+ }
+
+ /// Build a [MicEncryptedSectionEncoder] from the provided identity info.
+ pub(crate) fn new<B: BroadcastCryptoMaterial<V1>>(
+ identity_type: EncryptedIdentityDataElementType,
+ salt: V1Salt<C>,
+ crypto_material: &B,
+ ) -> Self {
+ let metadata_key = crypto_material.metadata_key();
+ let key_seed = crypto_material.key_seed();
+ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
+ let aes_key = np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf);
+ let mic_hmac_key = np_hkdf::UnsignedSectionKeys::hmac_key(&key_seed_hkdf);
+
+ Self { identity_type, salt, metadata_key, aes_key, mic_hmac_key }
+ }
+
+ /// Build a [MicEncrypedSectionEncoder] from the provided identity info.
+ /// Exposed outside of this crate for testing purposes only, since this
+ /// does not handle the generation of random salts.
+ #[cfg(any(test, feature = "testing"))]
+ pub fn new_for_testing<B: BroadcastCryptoMaterial<V1>>(
+ identity_type: EncryptedIdentityDataElementType,
+ salt: V1Salt<C>,
+ crypto_material: &B,
+ ) -> Self {
+ Self::new(identity_type, salt, crypto_material)
+ }
+}
+
+impl<C: CryptoProvider> SectionEncoder for MicEncryptedSectionEncoder<C> {
+ const PREFIX_LEN: usize =
+ EncryptionInfo::TOTAL_DE_LEN + EncryptedIdentityMetadata::TOTAL_DE_LEN;
+ /// Length of mic
+ const SUFFIX_LEN: usize = SectionMic::CONTENTS_LEN;
+ /// Room for the mic, encryption info, and identity DEs
+ const INITIAL_DE_OFFSET: DataElementOffset =
+ DataElementOffset::ZERO.incremented().incremented();
+
+ const ADVERTISEMENT_TYPE: AdvertisementType = AdvertisementType::Encrypted;
+
+ fn postprocess(
+ &mut self,
+ adv_header_byte: u8,
+ section_header: u8,
+ section_contents: &mut [u8],
+ ) {
+ // prefix byte layout:
+ // 0-18: Encryption Info DE (header + scheme + salt)
+ // 19-20: Identity DE header
+ // 21-36: Identity DE contents (metadata key)
+ // Encryption Info DE
+ let encryption_info_bytes = EncryptionInfoDataElement::mic(
+ self.salt.as_slice().try_into().expect("Salt should be 16 bytes"),
+ )
+ .serialize();
+ section_contents[0..19].copy_from_slice(&encryption_info_bytes);
+ // Identity DE
+ let identity_header = identity_de_header(self.identity_type, self.metadata_key);
+ section_contents[19..21].copy_from_slice(identity_header.serialize().as_slice());
+ section_contents[21..37].copy_from_slice(&self.metadata_key.0);
+ // DE offset for identity is 1: Encryption Info DE, Identity DE, then other DEs
+ let nonce: AesCtrNonce = self
+ .de_salt(v1_salt::DataElementOffset::from(1))
+ .derive()
+ .expect("AES-CTR nonce is a valid HKDF length");
+ let mut cipher = C::AesCtr128::new(&self.aes_key, NonceAndCounter::from_nonce(nonce));
+ let ciphertext_end = section_contents.len() - SectionMic::CONTENTS_LEN;
+ // encrypt just the part that should be ciphertext: identity DE contents and subsequent DEs
+ cipher.apply_keystream(&mut section_contents[21..ciphertext_end]);
+ // calculate MAC per the spec
+ let mut section_hmac = self.mic_hmac_key.build_hmac();
+ // svc uuid
+ section_hmac.update(NP_SVC_UUID.as_slice());
+ // adv header
+ section_hmac.update(&[adv_header_byte]);
+ // section header
+ section_hmac.update(&[section_header]);
+ // encryption info
+ section_hmac.update(&encryption_info_bytes);
+ // derived salt
+ section_hmac.update(&nonce);
+ // identity header + ciphertext
+ section_hmac.update(§ion_contents[19..ciphertext_end]);
+ let mic: [u8; 32] = section_hmac.finalize();
+ // write truncated MIC
+ section_contents[ciphertext_end..].copy_from_slice(&mic[..SectionMic::CONTENTS_LEN]);
+ }
+ type DerivedSalt = DeSalt<C>;
+ fn de_salt(&self, de_offset: DataElementOffset) -> Self::DerivedSalt {
+ DeSalt { salt: V1Salt::from(*self.salt.as_array_ref()), de_offset }
+ }
+}
+
+/// Derived salt for an individual data element.
+pub struct DeSalt<C: CryptoProvider> {
+ salt: V1Salt<C>,
+ de_offset: DataElementOffset,
+}
+
+impl<C: CryptoProvider> DeSalt<C> {
+ /// Derive salt of the requested length.
+ ///
+ /// The length must be a valid HKDF-SHA256 length.
+ pub fn derive<const N: usize>(&self) -> Option<[u8; N]> {
+ self.salt.derive(Some(self.de_offset))
+ }
+}
+
/// For DE structs that only implement one DE type, rather than multi-type impls.
pub trait SingleTypeDataElement {
/// The DE type for the DE.
@@ -766,11 +800,12 @@
fn identity_de_header(
id_type: EncryptedIdentityDataElementType,
- metadata_key: &[u8; 16],
+ metadata_key: MetadataKey,
) -> DeHeader {
DeHeader {
de_type: id_type.type_code(),
len: metadata_key
+ .0
.len()
.try_into()
.map_err(|_e| DeLengthOutOfRange)
diff --git a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
index af11076..d418a50 100644
--- a/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/section_tests.rs
@@ -15,11 +15,12 @@
extern crate std;
use super::*;
-use crate::extended::serialize::AddSectionError::MaxSectionCountExceeded;
-use crate::{
- extended::data_elements::{ContextSyncSeqNumDataElement, GenericDataElement},
- shared_data::ContextSyncSeqNum,
+use crate::credential::{
+ v1::{SimpleSignedBroadcastCryptoMaterial, V1},
+ SimpleBroadcastCryptoMaterial,
};
+use crate::extended::data_elements::GenericDataElement;
+use crate::extended::serialize::AddSectionError::MaxSectionCountExceeded;
use crypto_provider::aes::ctr::AES_CTR_NONCE_LEN;
use crypto_provider_default::CryptoProviderImpl;
use np_hkdf::v1_salt::V1Salt;
@@ -32,8 +33,8 @@
#[test]
fn public_identity_section_empty() {
- let mut adv_builder = AdvBuilder::new();
- let section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
assert_eq!(&[1_u8, 0x03], section_builder.into_section().as_slice());
}
@@ -62,23 +63,26 @@
.collect::<Vec<_>>();
let metadata_key = rng.gen();
+ let metadata_key = MetadataKey(metadata_key);
let key_seed = rng.gen();
let identity_type =
*EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap();
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
identity_type,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let section_salt =
- V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let section_salt = V1Salt::<CryptoProviderImpl>::from(
+ *section_builder.section_encoder.salt.as_array_ref(),
+ );
for de in extra_des.iter() {
section_builder.add_de(|_| de).unwrap();
@@ -88,9 +92,7 @@
let section_length = 53
+ extra_des
.iter()
- .map(|de| {
- de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8
- })
+ .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8())
.sum::<u8>();
let encryption_info = [
@@ -123,14 +125,14 @@
&np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf),
NonceAndCounter::from_nonce(nonce),
);
- let mut plaintext = metadata_key.as_slice().to_vec();
+ let mut plaintext = metadata_key.0.as_slice().to_vec();
for de in extra_des {
plaintext.extend_from_slice(de.de_header().serialize().as_slice());
de.write_de_contents(&mut plaintext);
}
- cipher.encrypt(&mut plaintext);
+ cipher.apply_keystream(&mut plaintext);
let ciphertext = plaintext;
hmac_input.extend_from_slice(&ciphertext);
@@ -162,26 +164,31 @@
for _ in 0..1_000 {
let num_des = rng.gen_range(1..=5);
- let metadata_key = rng.gen();
+ let metadata_key = MetadataKey(rng.gen());
let key_seed = rng.gen();
let identity_type =
*EncryptedIdentityDataElementType::iter().collect::<Vec<_>>().choose(&mut rng).unwrap();
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_pair = KeyPair::generate();
+ let broadcast_cm = SimpleSignedBroadcastCryptoMaterial::new(
+ key_seed,
+ metadata_key,
+ key_pair.private_key(),
+ );
+
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
identity_type,
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let section_salt =
- V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let section_salt = V1Salt::<CryptoProviderImpl>::from(
+ *section_builder.section_encoder.salt.as_array_ref(),
+ );
let extra_des = (0..num_des)
.map(|_| {
@@ -201,9 +208,7 @@
+ 64
+ extra_des
.iter()
- .map(|de| {
- de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8
- })
+ .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8())
.sum::<u8>();
let encryption_info = [
@@ -224,11 +229,10 @@
let nonce = section_salt.derive::<{ AES_CTR_NONCE_LEN }>(Some(1.into())).unwrap();
- let mut plaintext = metadata_key.as_slice().to_vec();
-
+ let mut section_body = Vec::new();
for de in extra_des {
- plaintext.extend_from_slice(de.de_header().serialize().as_slice());
- de.write_de_contents(&mut plaintext);
+ section_body.extend_from_slice(de.de_header().serialize().as_slice());
+ de.write_de_contents(&mut section_body);
}
let sig_payload = SectionSignaturePayload::from_deserialized_parts(
@@ -237,16 +241,20 @@
&encryption_info,
&nonce,
identity_de_header,
- &plaintext,
+ metadata_key,
+ §ion_body,
);
+ let mut plaintext = metadata_key.0.as_slice().to_vec();
+ plaintext.extend_from_slice(section_body.as_slice());
+
plaintext.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes());
<CryptoProviderImpl as CryptoProvider>::AesCtr128::new(
&key_seed_hkdf.extended_signed_section_aes_key(),
NonceAndCounter::from_nonce(nonce),
)
- .encrypt(&mut plaintext);
+ .apply_keystream(&mut plaintext);
let ciphertext = plaintext;
let mut expected = vec![section_length];
@@ -262,20 +270,23 @@
fn section_builder_too_full_doesnt_advance_de_index() {
let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_seed = [22; 32];
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let metadata_key = [33; 16];
+ let metadata_key = MetadataKey([33; 16]);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Trusted,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let salt =
+ V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref());
section_builder
.add_de(|de_salt| DummyDataElement {
@@ -306,7 +317,7 @@
let mut expected = vec![];
// metadata key
- expected.extend_from_slice(&metadata_key);
+ expected.extend_from_slice(&metadata_key.0);
// de header
expected.extend_from_slice(&[0x80 + 100, 100]);
// section 0 de 2
@@ -321,7 +332,7 @@
NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()),
);
- cipher.encrypt(&mut expected);
+ cipher.apply_keystream(&mut expected);
let adv_bytes = adv_builder.into_advertisement();
// ignoring the MIC, etc, since that's tested elsewhere
@@ -332,59 +343,47 @@
#[test]
fn section_de_fits_exactly() {
// leave room for initial filler section's header and the identities
- for section_contents_capacity in 1..NP_ADV_MAX_SECTION_LEN - 3 {
- let mut adv_builder = AdvBuilder::new();
+ // for section_contents_capacity in 1..NP_ADV_MAX_SECTION_LEN - 3 {
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
- // fill up space to produce desired capacity
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
- // leave space for adv header, 2 section headers, the section identities, and desired second section capacity
- fill_section_builder(
- BLE_ADV_SVC_CONTENT_LEN - 1 - 2 - 2 - section_contents_capacity,
- &mut section_builder,
- );
- section_builder.add_to_advertisement();
+ // fill up space to produce desired capacity
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
+ // leave space for adv header, 1 section headers, 1 section identity and fill almost full
+ fill_section_builder(BLE_ADV_SVC_CONTENT_LEN - 1 - 1 - 1 - 1, &mut section_builder);
+ assert_eq!(1, section_builder.section.capacity() - section_builder.section.len(), "capacity: ");
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ // can't add a 2 byte DE
+ assert_eq!(
+ Err(AddDataElementError::InsufficientSectionSpace),
+ section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[0xFF])),
+ "capacity: "
+ );
- // fill it almost full
- fill_section_builder(section_contents_capacity - 1, &mut section_builder);
-
- assert_eq!(
- 1,
- section_builder.section.capacity() - section_builder.section.len(),
- "capacity: {section_contents_capacity}"
- );
-
- // can't add a 2 byte DE
- assert_eq!(
- Err(AddDataElementError::InsufficientSectionSpace),
- section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[0xFF])),
- "capacity: {section_contents_capacity}"
- );
-
- // can add a 1 byte DE
- section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[])).unwrap();
- }
+ // can add a 1 byte DE
+ section_builder.add_de_res(|_| GenericDataElement::try_from(1_u32.into(), &[])).unwrap();
}
#[test]
fn section_builder_build_de_error_doesnt_advance_de_index() {
let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_seed = [22; 32];
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let metadata_key = [33; 16];
+ let metadata_key = MetadataKey([33; 16]);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Trusted,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let salt =
+ V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref());
section_builder
.add_de(|de_salt| DummyDataElement {
@@ -409,7 +408,7 @@
let mut expected = vec![];
// metadata key
- expected.extend_from_slice(&metadata_key);
+ expected.extend_from_slice(&metadata_key.0);
// de header
expected.extend_from_slice(&[0x80 + 100, 100]);
// section 0 de 2
@@ -424,7 +423,7 @@
NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()),
);
- cipher.encrypt(&mut expected);
+ cipher.apply_keystream(&mut expected);
let adv_bytes = adv_builder.into_advertisement();
// ignoring the MIC, etc, since that's tested elsewhere
@@ -436,20 +435,23 @@
fn add_multiple_de_correct_de_offsets_mic_encrypted_identity() {
let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_seed = [22; 32];
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let metadata_key = [33; 16];
+ let metadata_key = MetadataKey([33; 16]);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new_random_salt(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Trusted,
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let salt =
+ V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref());
section_builder
.add_de(|de_salt| DummyDataElement {
@@ -468,7 +470,7 @@
let mut expected = vec![];
// metadata key
- expected.extend_from_slice(&metadata_key);
+ expected.extend_from_slice(&metadata_key.0);
// de header
expected.extend_from_slice(&[0x90, 0x40]);
// section 0 de 2
@@ -483,7 +485,7 @@
NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()),
);
- cipher.encrypt(&mut expected);
+ cipher.apply_keystream(&mut expected);
let adv_bytes = adv_builder.into_advertisement();
// ignoring the MIC, etc, since that's tested elsewhere
@@ -495,22 +497,25 @@
fn add_multiple_de_correct_de_offsets_signature_encrypted_identity() {
let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_seed = [22; 32];
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let metadata_key = [33; 16];
+ let metadata_key = MetadataKey([33; 16]);
let key_pair = KeyPair::generate();
+
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
+
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Trusted,
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
- let salt = V1Salt::<CryptoProviderImpl>::from(*section_builder.identity.salt.as_array_ref());
+ let salt =
+ V1Salt::<CryptoProviderImpl>::from(*section_builder.section_encoder.salt.as_array_ref());
section_builder
.add_de(|de_salt| DummyDataElement {
@@ -529,7 +534,7 @@
let mut expected = vec![];
// metadata key
- expected.extend_from_slice(&metadata_key);
+ expected.extend_from_slice(&metadata_key.0);
// de header
expected.extend_from_slice(&[0x90, 0x40]);
// section 0 de 2
@@ -543,7 +548,7 @@
&key_seed_hkdf.extended_signed_section_aes_key(),
NonceAndCounter::from_nonce(salt.derive(Some(1.into())).unwrap()),
)
- .encrypt(&mut expected);
+ .apply_keystream(&mut expected);
let adv_bytes = adv_builder.into_advertisement();
// ignoring the signature since that's tested elsewhere
@@ -558,19 +563,20 @@
fn signature_encrypted_section_de_lengths_allow_room_for_suffix() {
let mut crypto_rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let key_seed = [22; 32];
- let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let metadata_key = [33; 16];
+ let metadata_key = MetadataKey([33; 16]);
let key_pair = KeyPair::generate();
+
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
+
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut crypto_rng,
EncryptedIdentityDataElementType::Trusted,
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -606,35 +612,39 @@
}
#[test]
-fn serialize_max_number_of_sections() {
- let mut adv_builder = AdvBuilder::new();
- for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT {
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
- section_builder.add_de(|_| context_sync_de(1)).unwrap();
+fn serialize_max_number_of_public_sections() {
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT {
+ let mut section_builder =
+ adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
+ section_builder
+ .add_de(|_| DummyDataElement { de_type: 100_u32.into(), data: vec![0; 98] })
+ .unwrap();
section_builder.add_to_advertisement();
}
assert_eq!(
- adv_builder.section_builder(PublicIdentity::default()).unwrap_err(),
+ adv_builder.section_builder(PublicSectionEncoder::default()).unwrap_err(),
MaxSectionCountExceeded
);
}
fn do_mic_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_des: &[W]) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let adv_header_byte = 0b00100000;
let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into();
let identity_type = EncryptedIdentityDataElementType::Private;
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
V1Salt::from(*section_salt.as_array_ref()),
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -646,7 +656,7 @@
let section_length = 53
+ extra_des
.iter()
- .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8)
+ .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8())
.sum::<u8>();
let encryption_info = [
@@ -678,14 +688,14 @@
&np_hkdf::UnsignedSectionKeys::aes_key(&key_seed_hkdf),
NonceAndCounter::from_nonce(nonce),
);
- let mut plaintext = metadata_key.as_slice().to_vec();
+ let mut plaintext = metadata_key.0.as_slice().to_vec();
for de in extra_des {
plaintext.extend_from_slice(de.de_header().serialize().as_slice());
de.write_de_contents(&mut plaintext);
}
- cipher.encrypt(&mut plaintext);
+ cipher.apply_keystream(&mut plaintext);
let ciphertext = plaintext;
hmac_input.extend_from_slice(&ciphertext);
@@ -704,7 +714,7 @@
}
fn do_signature_encrypted_identity_fixed_key_material_test<W: WriteDataElement>(extra_des: &[W]) {
- let metadata_key = [1; 16];
+ let metadata_key = MetadataKey([1; 16]);
let key_seed = [2; 32];
let adv_header_byte = 0b00100000;
let section_salt: V1Salt<CryptoProviderImpl> = [3; 16].into();
@@ -712,15 +722,16 @@
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let key_pair = KeyPair::generate();
- let mut adv_builder = AdvBuilder::new();
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
+
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
V1Salt::from(*section_salt.as_array_ref()),
- &metadata_key,
- &key_pair,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -734,7 +745,7 @@
+ 64
+ extra_des
.iter()
- .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_usize() as u8)
+ .map(|de| de.de_header().serialize().len() as u8 + de.de_header().len.as_u8())
.sum::<u8>();
let encryption_info = [
@@ -751,11 +762,10 @@
DeHeader { len: 16_u8.try_into().unwrap(), de_type: identity_type.type_code() };
let identity_de_header: [u8; 2] = identity_de_header.serialize().as_slice().try_into().unwrap();
- let mut plaintext = metadata_key.as_slice().to_vec();
-
+ let mut section_body = Vec::new();
for de in extra_des {
- plaintext.extend_from_slice(de.de_header().serialize().as_slice());
- de.write_de_contents(&mut plaintext);
+ section_body.extend_from_slice(de.de_header().serialize().as_slice());
+ de.write_de_contents(&mut section_body);
}
let nonce = section_salt.derive(Some(1.into())).unwrap();
@@ -766,16 +776,19 @@
&encryption_info,
&nonce,
identity_de_header,
- &plaintext,
+ metadata_key,
+ §ion_body,
);
+ let mut plaintext = metadata_key.0.as_slice().to_vec();
+ plaintext.extend_from_slice(section_body.as_slice());
plaintext.extend_from_slice(&sig_payload.sign(&key_pair).to_bytes());
<CryptoProviderImpl as CryptoProvider>::AesCtr128::new(
&key_seed_hkdf.extended_signed_section_aes_key(),
NonceAndCounter::from_nonce(nonce),
)
- .encrypt(&mut plaintext);
+ .apply_keystream(&mut plaintext);
let ciphertext = plaintext;
let mut expected = vec![section_length];
@@ -788,7 +801,7 @@
}
/// Write `section_contents_len` bytes of DE and header into `section_builder`
-pub(crate) fn fill_section_builder<I: SectionIdentity>(
+pub(crate) fn fill_section_builder<I: SectionEncoder>(
section_contents_len: usize,
section_builder: &mut SectionBuilder<I>,
) {
@@ -844,17 +857,13 @@
}
}
-fn context_sync_de(num: u8) -> ContextSyncSeqNumDataElement {
- ContextSyncSeqNumDataElement::from(ContextSyncSeqNum::try_from(num).unwrap())
-}
-
pub(crate) trait SectionBuilderExt {
fn into_section(self) -> EncodedSection;
}
-impl<'a, I: SectionIdentity> SectionBuilderExt for SectionBuilder<'a, I> {
+impl<'a, I: SectionEncoder> SectionBuilderExt for SectionBuilder<'a, I> {
/// Convenience method for tests
fn into_section(self) -> EncodedSection {
- Self::build_section(self.section.into_inner(), self.identity, self.adv_builder)
+ Self::build_section(self.section.into_inner(), self.section_encoder, self.adv_builder)
}
}
diff --git a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
index 9f7aa5c..24e6046 100644
--- a/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
+++ b/nearby/presence/np_adv/src/extended/serialize/test_vectors.rs
@@ -14,12 +14,15 @@
extern crate std;
+use crate::extended::serialize::AdvertisementType;
use crate::{
+ credential::{v1::V1, SimpleBroadcastCryptoMaterial},
de_type::EncryptedIdentityDataElementType,
extended::serialize::{
section_tests::{DummyDataElement, SectionBuilderExt},
- AdvBuilder, MicEncrypted,
+ AdvBuilder, MicEncryptedSectionEncoder,
},
+ MetadataKey,
};
use anyhow::anyhow;
use crypto_provider::{aes::ctr::AES_CTR_NONCE_LEN, aes::AesKey};
@@ -49,7 +52,7 @@
for tc in test_cases {
{
let key_seed = extract_key_array::<32>(&tc, "key_seed");
- let metadata_key = extract_key_array::<16>(&tc, "metadata_key");
+ let metadata_key = MetadataKey(extract_key_array::<16>(&tc, "metadata_key"));
let adv_header_byte = extract_key_array::<1>(&tc, "adv_header_byte")[0];
let section_salt = v1_salt::V1Salt::<CryptoProviderImpl>::from(
extract_key_array::<16>(&tc, "section_salt"),
@@ -65,7 +68,7 @@
})
.collect::<Vec<_>>();
- let hkdf = np_hkdf::NpKeySeedHkdf::new(&key_seed);
+ let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
assert_eq!(
extract_key_array::<16>(&tc, "aes_key").as_slice(),
@@ -80,16 +83,17 @@
section_salt.derive::<{ AES_CTR_NONCE_LEN }>(Some(1.into())).unwrap()
);
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
// make an adv builder in the configuration we need
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
assert_eq!(adv_header_byte, adv_builder.header_byte());
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
section_salt,
- &metadata_key,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -127,7 +131,7 @@
})
.collect::<Vec<_>>();
- let metadata_key = rng.gen();
+ let metadata_key = MetadataKey(rng.gen());
let key_seed = rng.gen();
let adv_header_byte = 0b00100000;
let identity_type =
@@ -135,15 +139,16 @@
let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let mut adv_builder = AdvBuilder::new();
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V1>::new(key_seed, metadata_key);
+
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
let section_salt = v1_salt::V1Salt::<CryptoProviderImpl>::from(rng.gen::<[u8; 16]>());
let mut section_builder = adv_builder
- .section_builder(MicEncrypted::new(
+ .section_builder(MicEncryptedSectionEncoder::<CryptoProviderImpl>::new(
identity_type,
V1Salt::from(*section_salt.as_array_ref()),
- &metadata_key,
- &key_seed_hkdf,
+ &broadcast_cm,
))
.unwrap();
@@ -156,7 +161,7 @@
array
.push(json!({
"key_seed": hex::encode_upper(key_seed),
- "metadata_key": hex::encode_upper(metadata_key),
+ "metadata_key": hex::encode_upper(metadata_key.0),
"adv_header_byte": hex::encode_upper([adv_header_byte]),
"section_salt": hex::encode_upper(section_salt.as_slice()),
"identity_type": identity_type_label(identity_type),
diff --git a/nearby/presence/np_adv/src/filter/mod.rs b/nearby/presence/np_adv/src/filter/mod.rs
new file mode 100644
index 0000000..cb020cf
--- /dev/null
+++ b/nearby/presence/np_adv/src/filter/mod.rs
@@ -0,0 +1,291 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provides filtering APIs to determine whether or not a buffer of bytes represents
+//! a valid Nearby Presence advertisement and if so which was its corresponding identity
+//! it matched with. Used as a first pass option to quickly check if a buffer should
+//! further processed.
+use crate::credential::MatchedCredential;
+use crate::legacy::deserialize::DecryptedAdvContents;
+use crate::{
+ credential::{book::CredentialBook, v0::V0DiscoveryCryptoMaterial},
+ legacy,
+ legacy::{
+ actions,
+ actions::ActionsDataElement,
+ deserialize::{
+ DecryptError, EncryptedAdvContents, IntermediateAdvContents, PlainDataElement,
+ },
+ PacketFlavor,
+ },
+ parse_adv_header, AdvHeader, V1Header,
+};
+use array_view::ArrayView;
+use core::fmt::Debug;
+use crypto_provider::CryptoProvider;
+
+#[cfg(test)]
+mod tests;
+
+/// The options to filter for in an advertisement payload.
+pub enum FilterOptions {
+ /// Filter criteria for matching against only v0 advertisements
+ V0FilterOptions(V0Filter),
+ /// Filter criteria for matching against only v1 advertisements
+ V1FilterOptions(V1Filter),
+ /// Criteria to filter for in both v0 and v1 advertisements
+ Either(V0Filter, V1Filter),
+}
+
+/// Error returned when no advertisement matching the criteria was found.
+#[derive(PartialEq, Debug)]
+pub struct NoMatch;
+
+/// Error returned if the provided slice is of invalid length
+#[derive(PartialEq, Debug)]
+pub struct InvalidLength;
+
+/// The criteria of what to filter for in a V0 advertisement.
+pub struct V0Filter {
+ identity: IdentityFilterType,
+ data_elements: V0DataElementsFilter,
+}
+
+/// The type of identity to filter for.
+pub enum IdentityFilterType {
+ /// Filter only on public identity advertisements
+ Public,
+ /// Filter only on private identity advertisements
+ Private,
+ /// Don't filter on any specific identity type
+ Any,
+}
+
+/// Returned by the filter API indicating the status and in the case of private identity the key seed
+/// which matched the decrypted advertisement
+#[derive(Debug, PartialEq)]
+pub enum FilterResult<M: MatchedCredential> {
+ /// A public identity advertisement matching the filter criteria was detected.
+ Public,
+ /// A Private identity advertisement matching the filter criteria was detected and decrypted
+ /// with the contained `KeySeed`.
+ Private(M),
+}
+
+/// The criteria of what data elements to filter for in a V0 advertisement.
+pub struct V0DataElementsFilter {
+ contains_tx_power: Option<bool>,
+ actions_filter: Option<V0ActionsFilter>,
+}
+
+/// The total number of unique boolean action types
+const NUM_ACTIONS: usize = 7;
+
+/// Specify which specific actions bits to filter on, will filter on if any of the specified
+/// actions are matched
+//TODO: do we need a more specific filter criteria, eg: only return if `(A AND B) || C` are found?
+// Potentially this could be pulled out more generally into a V0Filter trait which has many different
+// implementations.
+pub struct V0ActionsFilter {
+ actions: array_view::ArrayView<Option<actions::ActionType>, NUM_ACTIONS>,
+}
+
+impl FilterOptions {
+ /// WARNING: This does not perform signature verification on the advertisement, even if it detects
+ /// a valid advertisement it does not verify it. Verification must occur before further processing
+ /// of the adv.
+ ///
+ /// Returns `Ok` if a advertisement was detected which matched the filter criteria, and `Err` otherwise.
+ pub fn match_advertisement<'a, B, P>(
+ &self,
+ cred_book: &'a B,
+ adv: &[u8],
+ ) -> Result<FilterResult<B::Matched>, NoMatch>
+ where
+ B: CredentialBook<'a>,
+ P: CryptoProvider,
+ {
+ parse_adv_header(adv)
+ .map(|(remaining, header)| match header {
+ AdvHeader::V0 => {
+ let filter = match self {
+ FilterOptions::V0FilterOptions(filter) => filter,
+ FilterOptions::Either(filter, _) => filter,
+ _ => return Err(NoMatch),
+ };
+ filter.match_v0_adv::<B, P>(cred_book, remaining)
+ }
+ AdvHeader::V1(header) => {
+ let filter = match self {
+ FilterOptions::V1FilterOptions(filter) => filter,
+ FilterOptions::Either(_, filter) => filter,
+ _ => return Err(NoMatch),
+ };
+ filter.match_v1_adv::<B, P>(cred_book, remaining, header)
+ }
+ })
+ .map_err(|_| NoMatch)?
+ }
+}
+
+impl V0Filter {
+ /// Filter for the provided criteria returning success if the advertisement bytes successfully
+ /// match the filter criteria
+ fn match_v0_adv<'a, B, P>(
+ &self,
+ cred_book: &'a B,
+ remaining: &[u8],
+ ) -> Result<FilterResult<B::Matched>, NoMatch>
+ where
+ B: CredentialBook<'a>,
+ P: CryptoProvider,
+ {
+ let contents =
+ legacy::deserialize::deserialize_adv_contents::<P>(remaining).map_err(|_| NoMatch)?;
+ match contents {
+ IntermediateAdvContents::Plaintext(p) => match self.identity {
+ IdentityFilterType::Public | IdentityFilterType::Any => self
+ .data_elements
+ .match_v0_legible_adv(p.data_elements())
+ .map(|()| FilterResult::Public),
+ _ => Err(NoMatch),
+ },
+ IntermediateAdvContents::Ciphertext(c) => match self.identity {
+ IdentityFilterType::Private | IdentityFilterType::Any => {
+ let (legible_adv, m) = try_decrypt_and_match::<B, P>(cred_book.v0_iter(), &c)?;
+ self.data_elements
+ .match_v0_legible_adv(legible_adv.data_elements())
+ .map(|()| FilterResult::Private(m))
+ }
+ _ => Err(NoMatch),
+ },
+ }
+ }
+}
+
+fn try_decrypt_and_match<'a, B, P>(
+ v0_creds: B::V0Iterator,
+ adv: &EncryptedAdvContents,
+) -> Result<(DecryptedAdvContents, B::Matched), NoMatch>
+where
+ B: CredentialBook<'a>,
+ P: CryptoProvider,
+{
+ for (crypto_material, m) in v0_creds {
+ let ldt = crypto_material.ldt_adv_cipher::<P>();
+ match adv.try_decrypt(&ldt) {
+ Ok(c) => return Ok((c, m)),
+ Err(e) => match e {
+ DecryptError::DecryptOrVerifyError => continue,
+ DecryptError::DeserializeError(_) => {
+ return Err(NoMatch);
+ }
+ },
+ }
+ }
+ Err(NoMatch)
+}
+
+impl V0DataElementsFilter {
+ /// A legible adv is either plaintext to begin with, or decrypted contents from an encrypted adv
+ fn match_v0_legible_adv<'a, F: PacketFlavor + 'a>(
+ &self,
+ mut data_elements: impl ExactSizeIterator<Item = &'a PlainDataElement<F>>,
+ ) -> Result<(), NoMatch> {
+ match &self.contains_tx_power {
+ None => Ok(()),
+ Some(c) => {
+ if c == &data_elements.any(|de| matches!(de, PlainDataElement::TxPower(_))) {
+ Ok(())
+ } else {
+ Err(NoMatch)
+ }
+ }
+ }?;
+
+ match &self.actions_filter {
+ None => Ok(()),
+ Some(filter) => {
+ // find if an actions DE exists, if so match on the provided action filter
+ let actions = data_elements.find_map(|de| match de {
+ PlainDataElement::Actions(actions) => Some(actions),
+ _ => None,
+ });
+ if let Some(actions) = actions {
+ filter.match_v0_actions(actions)
+ } else {
+ return Err(NoMatch);
+ }
+ }
+ }?;
+
+ Ok(())
+ }
+}
+
+impl V0ActionsFilter {
+ /// Creates a new filter from a slice of Actions to look for. Will succeed if any of the provided
+ /// actions are found. Maximum length of `actions` is 7, as there are 7 unique actions possible to
+ /// filter on for V0.
+ pub fn new_from_slice(actions: &[actions::ActionType]) -> Result<Self, InvalidLength> {
+ if actions.len() > NUM_ACTIONS {
+ return Err(InvalidLength);
+ }
+ let mut filter_actions = [None; NUM_ACTIONS];
+ for (i, action) in actions.iter().map(|action| Some(*action)).enumerate() {
+ // i will always be in bounds because the length is checked above
+ filter_actions[i] = action;
+ }
+ Ok(ArrayView::try_from_array(filter_actions, actions.len())
+ .map(|actions| Self { actions })
+ .expect("Length checked above, so this can't happen"))
+ }
+
+ fn match_v0_actions<F: PacketFlavor>(
+ &self,
+ actions: &ActionsDataElement<F>,
+ ) -> Result<(), NoMatch> {
+ for action in self.actions.as_slice().iter() {
+ if actions
+ .action
+ .has_action(&action.expect("This will always contain Some"))
+ .unwrap_or(false)
+ {
+ return Ok(());
+ }
+ }
+ Err(NoMatch)
+ }
+}
+
+/// The criteria of what to filter for in a V1 advertisement.
+pub struct V1Filter;
+
+impl V1Filter {
+ /// Filter for the provided criteria returning success if the advertisement bytes successfully
+ /// match the filter criteria
+ #[allow(clippy::extra_unused_type_parameters)]
+ fn match_v1_adv<'a, B, P>(
+ &self,
+ _cred_book: &'a B,
+ _remaining: &[u8],
+ _header: V1Header,
+ ) -> Result<FilterResult<B::Matched>, NoMatch>
+ where
+ B: CredentialBook<'a>,
+ P: CryptoProvider,
+ {
+ todo!()
+ }
+}
diff --git a/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
new file mode 100644
index 0000000..946c969
--- /dev/null
+++ b/nearby/presence/np_adv/src/filter/tests/actions_filter_tests.rs
@@ -0,0 +1,136 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::super::*;
+use crate::legacy::actions::{ActionBits, InstantTethering, NearbyShare};
+use crate::legacy::{Ciphertext, Plaintext};
+
+#[test]
+fn new_v0_actions_invalid_length() {
+ let actions = [actions::ActionType::ActiveUnlock; 8];
+ let result = V0ActionsFilter::new_from_slice(&actions);
+ assert!(result.is_err());
+ assert_eq!(result.err().unwrap(), InvalidLength)
+}
+
+#[test]
+fn new_v0_actions() {
+ let actions = [actions::ActionType::ActiveUnlock; 7];
+ let result = V0ActionsFilter::new_from_slice(&actions);
+ assert!(result.is_ok());
+}
+
+#[test]
+fn new_v0_actions_empty_slice() {
+ let result = V0ActionsFilter::new_from_slice(&[]);
+ assert!(result.is_ok());
+}
+
+#[test]
+fn test_actions_filter_single_action_not_present() {
+ // default is all 0 bits
+ let action_bits = ActionBits::<Plaintext>::default();
+
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1])
+ .expect("1 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch))
+}
+
+#[test]
+fn test_actions_filter_all_actions_not_present() {
+ // default is all 0 bits
+ let action_bits = ActionBits::<Plaintext>::default();
+
+ let filter = V0ActionsFilter::new_from_slice(&[
+ actions::ActionType::ActiveUnlock,
+ actions::ActionType::NearbyShare,
+ actions::ActionType::InstantTethering,
+ actions::ActionType::PhoneHub,
+ actions::ActionType::Finder,
+ actions::ActionType::FastPairSass,
+ actions::ActionType::PresenceManager,
+ ])
+ .expect("7 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch))
+}
+
+#[test]
+fn test_actions_filter_single_action_present() {
+ // default is all 0 bits
+ let mut action_bits = ActionBits::<Plaintext>::default();
+ action_bits.set_action(NearbyShare::from(true));
+
+ let filter = V0ActionsFilter::new_from_slice(&[
+ actions::ActionType::ActiveUnlock,
+ actions::ActionType::NearbyShare,
+ actions::ActionType::InstantTethering,
+ actions::ActionType::PhoneHub,
+ actions::ActionType::Finder,
+ actions::ActionType::FastPairSass,
+ actions::ActionType::PresenceManager,
+ ])
+ .expect("7 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(()))
+}
+
+#[test]
+fn test_actions_filter_desired_action_not_present() {
+ // default is all 0 bits
+ let mut action_bits = ActionBits::<Plaintext>::default();
+ action_bits.set_action(NearbyShare::from(true));
+
+ let filter = V0ActionsFilter::new_from_slice(&[
+ actions::ActionType::ActiveUnlock,
+ actions::ActionType::InstantTethering,
+ actions::ActionType::PhoneHub,
+ actions::ActionType::Finder,
+ actions::ActionType::FastPairSass,
+ actions::ActionType::PresenceManager,
+ ])
+ .expect("6 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Err(NoMatch))
+}
+
+#[test]
+fn test_multiple_actions_set() {
+ // default is all 0 bits
+ let mut action_bits = ActionBits::<Ciphertext>::default();
+ action_bits.set_action(NearbyShare::from(true));
+ action_bits.set_action(InstantTethering::from(true));
+
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::InstantTethering])
+ .expect("1 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(()))
+}
+
+#[test]
+fn test_multiple_actions_set_both_present() {
+ // default is all 0 bits
+ let mut action_bits = ActionBits::<Ciphertext>::default();
+ action_bits.set_action(NearbyShare::from(true));
+ action_bits.set_action(InstantTethering::from(true));
+
+ let filter = V0ActionsFilter::new_from_slice(&[
+ actions::ActionType::InstantTethering,
+ actions::ActionType::NearbyShare,
+ ])
+ .expect("7 is a valid length");
+
+ assert_eq!(filter.match_v0_actions(&action_bits.into()), Ok(()))
+}
diff --git a/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
new file mode 100644
index 0000000..cb7c067
--- /dev/null
+++ b/nearby/presence/np_adv/src/filter/tests/data_elements_filter_tests.rs
@@ -0,0 +1,106 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::super::*;
+use crate::legacy::actions::{ActionBits, ActiveUnlock};
+use crate::legacy::data_elements::TxPowerDataElement;
+use crate::legacy::{Ciphertext, Plaintext};
+use crate::shared_data::TxPower;
+
+#[test]
+fn match_contains_tx_power() {
+ let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None };
+
+ let tx_power = TxPower::try_from(5).expect("within range");
+ let tx_power_de = TxPowerDataElement::from(tx_power);
+ let result =
+ filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+ assert_eq!(result, Ok(()))
+}
+
+#[test]
+fn match_not_contains_tx_power() {
+ let filter = V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: None };
+ let result = filter.match_v0_legible_adv::<Plaintext>([].iter());
+ assert_eq!(result, Err(NoMatch))
+}
+
+#[test]
+fn match_not_contains_actions() {
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1])
+ .expect("1 is a valid length");
+ let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) };
+ let tx_power = TxPower::try_from(5).expect("within range");
+ let tx_power_de = TxPowerDataElement::from(tx_power);
+ let result =
+ filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+ assert_eq!(result, Err(NoMatch))
+}
+
+#[test]
+fn match_contains_actions() {
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1])
+ .expect("1 is a valid length");
+ let filter = V0DataElementsFilter { contains_tx_power: None, actions_filter: Some(filter) };
+ let tx_power = TxPower::try_from(5).expect("within range");
+ let tx_power_de = TxPowerDataElement::from(tx_power);
+
+ let mut action_bits = ActionBits::<Ciphertext>::default();
+ action_bits.set_action(ActiveUnlock::from(true));
+
+ let result = filter.match_v0_legible_adv(
+ [
+ PlainDataElement::<Ciphertext>::TxPower(tx_power_de),
+ PlainDataElement::Actions(action_bits.into()),
+ ]
+ .iter(),
+ );
+ assert_eq!(result, Ok(()))
+}
+
+#[test]
+fn match_contains_both() {
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1])
+ .expect("1 is a valid length");
+ let filter =
+ V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) };
+ let tx_power = TxPower::try_from(5).expect("within range");
+ let tx_power_de = TxPowerDataElement::from(tx_power);
+
+ let mut action_bits = ActionBits::<Ciphertext>::default();
+ action_bits.set_action(ActiveUnlock::from(true));
+
+ let result = filter.match_v0_legible_adv(
+ [
+ PlainDataElement::<Ciphertext>::TxPower(tx_power_de),
+ PlainDataElement::Actions(action_bits.into()),
+ ]
+ .iter(),
+ );
+ assert_eq!(result, Ok(()))
+}
+
+#[test]
+fn match_contains_either() {
+ let filter = V0ActionsFilter::new_from_slice(&[actions::ActionType::ActiveUnlock; 1])
+ .expect("1 is a valid length");
+ let filter =
+ V0DataElementsFilter { contains_tx_power: Some(true), actions_filter: Some(filter) };
+ let tx_power = TxPower::try_from(5).expect("within range");
+ let tx_power_de = TxPowerDataElement::from(tx_power);
+
+ let result =
+ filter.match_v0_legible_adv([PlainDataElement::<Ciphertext>::TxPower(tx_power_de)].iter());
+ assert_eq!(result, Err(NoMatch))
+}
diff --git a/nearby/presence/np_adv/src/filter/tests/mod.rs b/nearby/presence/np_adv/src/filter/tests/mod.rs
new file mode 100644
index 0000000..29d3e7c
--- /dev/null
+++ b/nearby/presence/np_adv/src/filter/tests/mod.rs
@@ -0,0 +1,107 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::credential::book::CredentialBookBuilder;
+use crate::credential::KeySeedMatchedCredential;
+use crate::filter::IdentityFilterType::Any;
+use crate::filter::{
+ FilterOptions, FilterResult, NoMatch, V0DataElementsFilter, V0Filter, V1Filter,
+};
+use crypto_provider_default::CryptoProviderImpl;
+
+mod actions_filter_tests;
+mod data_elements_filter_tests;
+mod v0_filter_tests;
+
+#[test]
+fn top_level_match_v0_adv() {
+ let v0_filter = V0Filter {
+ identity: Any,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let empty_cred_book =
+ CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let filter = FilterOptions::V0FilterOptions(v0_filter);
+ let result = filter.match_advertisement::<_, CryptoProviderImpl>(
+ &empty_cred_book,
+ &[
+ 0x0, // adv header
+ 0x03, // public DE
+ 0x16, 0x00, // actions
+ ],
+ );
+
+ assert_eq!(Ok(FilterResult::Public), result);
+}
+
+#[test]
+fn top_level_match_v0_adv_either() {
+ let v0_filter = V0Filter {
+ identity: Any,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let empty_cred_book =
+ CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let filter = FilterOptions::Either(v0_filter, V1Filter {});
+ let result = filter.match_advertisement::<_, CryptoProviderImpl>(
+ &empty_cred_book,
+ &[
+ 0x0, // adv header
+ 0x03, // public DE
+ 0x16, 0x00, // actions
+ ],
+ );
+
+ assert_eq!(Ok(FilterResult::Public), result);
+}
+
+#[test]
+fn top_level_no_match_v0_adv() {
+ let v0_filter = V0Filter {
+ identity: Any,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let empty_cred_book =
+ CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let filter = FilterOptions::V0FilterOptions(v0_filter);
+ let result = filter.match_advertisement::<_, CryptoProviderImpl>(
+ &empty_cred_book,
+ &[
+ 0x20, // V1 Advertisement header
+ 0x03, // Section Header
+ 0x03, // Public Identity DE header
+ 0x15, 0x03, // Length 1 Tx Power DE with value 3
+ ],
+ );
+
+ assert_eq!(Err(NoMatch), result);
+}
diff --git a/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs b/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs
new file mode 100644
index 0000000..971a39e
--- /dev/null
+++ b/nearby/presence/np_adv/src/filter/tests/v0_filter_tests.rs
@@ -0,0 +1,221 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::super::*;
+use crate::credential::book::CredentialBookBuilder;
+use crate::credential::v0::{V0DiscoveryCredential, V0};
+use crate::credential::{
+ KeySeedMatchedCredential, MatchableCredential, ReferencedMatchedCredential,
+};
+use crate::filter::IdentityFilterType::{Any, Private, Public};
+use crypto_provider_default::CryptoProviderImpl;
+use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN;
+
+const METADATA_KEY: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN];
+const KEY_SEED: [u8; 32] = [0x11_u8; 32];
+const PRIVATE_IDENTITY_V0_ADV_CONTENTS: [u8; 19] = [
+ 0x21, // private DE
+ 0x22, 0x22, // salt
+ // ciphertext
+ 0x85, 0xBF, 0xA8, 0x83, 0x58, 0x7C, 0x50, 0xCF, 0x98, 0x38, 0xA7, 0x8A, 0xC0, 0x1C, 0x96, 0xF9,
+];
+
+const PUBLIC_IDENTITY_V0_ADV_CONTENTS: [u8; 3] = [
+ 0x03, // public DE
+ 0x16, 0x00, // actions
+];
+
+#[test]
+fn test_contains_public_identity() {
+ let filter = V0Filter {
+ identity: Public,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Ok(FilterResult::Public));
+}
+
+#[test]
+fn test_not_contains_private() {
+ let filter = V0Filter {
+ identity: Private,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Err(NoMatch));
+}
+
+#[test]
+fn test_contains_any_public() {
+ let filter = V0Filter {
+ identity: Any,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PUBLIC_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Ok(FilterResult::Public));
+}
+
+#[test]
+fn test_not_contains_public_identity() {
+ let filter = V0Filter {
+ identity: Public,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Err(NoMatch));
+}
+
+#[test]
+fn test_contains_private_identity() {
+ let filter = V0Filter {
+ identity: Private,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&KEY_SEED);
+ let metadata_key_hmac: [u8; 32] =
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY);
+ let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, metadata_key_hmac);
+ let match_data: KeySeedMatchedCredential = KEY_SEED.into();
+ let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] =
+ [MatchableCredential { discovery_credential, match_data: match_data.clone() }];
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&v0_creds, &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Ok(FilterResult::Private(ReferencedMatchedCredential::from(&match_data))));
+}
+
+#[test]
+fn test_contains_any_private_identity() {
+ let filter = V0Filter {
+ identity: Any,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&KEY_SEED);
+ let metadata_key_hmac: [u8; 32] =
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY);
+ let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, metadata_key_hmac);
+ let match_data: KeySeedMatchedCredential = KEY_SEED.into();
+ let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] =
+ [MatchableCredential { discovery_credential, match_data: match_data.clone() }];
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&v0_creds, &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Ok(FilterResult::Private(ReferencedMatchedCredential::from(&match_data))));
+}
+
+#[test]
+fn test_contains_private_identity_no_matching_credential() {
+ let filter = V0Filter {
+ identity: Private,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let mut key_seed = [0u8; 32];
+ key_seed.copy_from_slice(&KEY_SEED);
+ // change one byte
+ key_seed[0] = 0x00;
+
+ let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
+ let metadata_key_hmac: [u8; 32] =
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&METADATA_KEY);
+ let discovery_credential = V0DiscoveryCredential::new(key_seed, metadata_key_hmac);
+ let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] =
+ [MatchableCredential { discovery_credential, match_data: KEY_SEED.into() }];
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&v0_creds, &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Err(NoMatch));
+}
+
+#[test]
+fn test_contains_private_identity_invalid_hmac_match() {
+ let filter = V0Filter {
+ identity: Private,
+ data_elements: V0DataElementsFilter { contains_tx_power: None, actions_filter: None },
+ };
+
+ let discovery_credential = V0DiscoveryCredential::new(KEY_SEED, [0u8; 32]);
+ let v0_creds: [MatchableCredential<V0, KeySeedMatchedCredential>; 1] =
+ [MatchableCredential { discovery_credential, match_data: KEY_SEED.into() }];
+
+ let cred_book = CredentialBookBuilder::<KeySeedMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&v0_creds, &[]);
+
+ let result =
+ filter.match_v0_adv::<_, CryptoProviderImpl>(&cred_book, &PRIVATE_IDENTITY_V0_ADV_CONTENTS);
+
+ assert_eq!(result, Err(NoMatch));
+}
diff --git a/nearby/presence/np_adv/src/legacy/actions/macros.rs b/nearby/presence/np_adv/src/legacy/actions/macros.rs
index ecafeb3..8208a57 100644
--- a/nearby/presence/np_adv/src/legacy/actions/macros.rs
+++ b/nearby/presence/np_adv/src/legacy/actions/macros.rs
@@ -44,7 +44,7 @@
};
}
-/// Create a struct w/ From<bool> and ActionElement impls.
+/// Create a struct w/ `From<bool>` and [`ActionElement`](super::ActionElement) impls.
/// Use `plaintext_only`, `ciphertext_only`, or `plaintext_and_ciphertext` to create appropriate
/// impls.
macro_rules! boolean_element {
@@ -108,7 +108,7 @@
};
}
-/// Create a [ToActionElement<Encrypted>] impl with the given index and length 1.
+/// Create a [`ToActionElement<Encrypted>`](super::ToActionElement) impl with the given index and length 1.
macro_rules! boolean_element_to_encrypted_element {
( $type_name:ident) => {
impl $crate::legacy::actions::ToActionElement<$crate::legacy::Ciphertext> for $type_name {
@@ -119,7 +119,7 @@
};
}
-/// Create a [ToActionElement<Plaintext>] impl with the given index and length 1.
+/// Create a [`ToActionElement<Plaintext>`](super::ToActionElement) impl with the given index and length 1.
macro_rules! boolean_element_to_plaintext_element {
( $type_name:ident) => {
impl $crate::legacy::actions::ToActionElement<$crate::legacy::Plaintext> for $type_name {
diff --git a/nearby/presence/np_adv/src/legacy/actions/mod.rs b/nearby/presence/np_adv/src/legacy/actions/mod.rs
index 353848e..90a0186 100644
--- a/nearby/presence/np_adv/src/legacy/actions/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/actions/mod.rs
@@ -42,27 +42,8 @@
/// of 3 bytes occupied by the DE payload.
#[derive(Debug, PartialEq, Eq)]
pub struct ActionsDataElement<F: PacketFlavor> {
- action: ActionBits<F>,
-}
-
-impl<F: PacketFlavor> ActionsDataElement<F> {
- /// Returns the actions bits as a u32. The upper limit of an actions field is 3 bytes,
- /// so the last bytes of this u32 will always be 0
- pub fn as_u32(self) -> u32 {
- self.action.bits
- }
-
- /// Return whether a boolean action type is set in this data element, or `None` if the given
- /// action type does not represent a boolean.
- pub fn has_action(&self, action_type: &ActionType) -> Option<bool> {
- (action_type.bits_len() == 1).then_some(self.action.bits_for_type(action_type) != 0)
- }
-
- /// Return the context sync sequence number.
- pub fn context_sync_seq_num(&self) -> ContextSyncSeqNum {
- ContextSyncSeqNum::try_from(self.action.bits_for_type(&ActionType::ContextSyncSeqNum) as u8)
- .expect("Masking with ActionType::ContextSyncSeqNum should always be in range")
- }
+ /// The action bits
+ pub action: ActionBits<F>,
}
pub(crate) const ACTIONS_MAX_LEN: usize = 3;
@@ -172,6 +153,26 @@
flavor: marker::PhantomData<F>,
}
+impl<F: PacketFlavor> ActionBits<F> {
+ /// Returns the actions bits as a u32. The upper limit of an actions field is 3 bytes,
+ /// so the last bytes of this u32 will always be 0
+ pub fn as_u32(self) -> u32 {
+ self.bits
+ }
+
+ /// Return whether a boolean action type is set in this data element, or `None` if the given
+ /// action type does not represent a boolean.
+ pub fn has_action(&self, action_type: &ActionType) -> Option<bool> {
+ (action_type.bits_len() == 1).then_some(self.bits_for_type(action_type) != 0)
+ }
+
+ /// Return the context sync sequence number.
+ pub fn context_sync_seq_num(&self) -> ContextSyncSeqNum {
+ ContextSyncSeqNum::try_from(self.bits_for_type(&ActionType::ContextSyncSeqNum) as u8)
+ .expect("Masking with ActionType::ContextSyncSeqNum should always be in range")
+ }
+}
+
impl<F: PacketFlavor> Default for ActionBits<F> {
fn default() -> Self {
ActionBits {
diff --git a/nearby/presence/np_adv/src/legacy/actions/tests.rs b/nearby/presence/np_adv/src/legacy/actions/tests.rs
index 2e3fda0..86b7a08 100644
--- a/nearby/presence/np_adv/src/legacy/actions/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/actions/tests.rs
@@ -294,14 +294,14 @@
let mut action_bits = ActionBits::<Plaintext>::default();
action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap());
let action_de = ActionsDataElement::from(action_bits);
- assert_eq!(15, action_de.context_sync_seq_num().as_u8());
+ assert_eq!(15, action_de.action.context_sync_seq_num().as_u8());
}
#[test]
fn context_sync_seq_num_default_zero() {
let action_bits = ActionBits::<Plaintext>::default();
let action_de = ActionsDataElement::from(action_bits);
- assert_eq!(0, action_de.context_sync_seq_num().as_u8());
+ assert_eq!(0, action_de.action.context_sync_seq_num().as_u8());
}
#[test]
@@ -310,9 +310,9 @@
action_bits.set_action(ContextSyncSeqNum::try_from(15).unwrap());
action_bits.set_action(NearbyShare::from(true));
let action_de = ActionsDataElement::from(action_bits);
- assert_eq!(action_de.has_action(&ActionType::NearbyShare), Some(true));
- assert_eq!(action_de.has_action(&ActionType::ActiveUnlock), Some(false));
- assert_eq!(action_de.has_action(&ActionType::PhoneHub), Some(false));
+ assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true));
+ assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(false));
+ assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false));
}
#[test]
@@ -322,10 +322,10 @@
action_bits.set_action(NearbyShare::from(true));
action_bits.set_action(ActiveUnlock::from(true));
let action_de = ActionsDataElement::from(action_bits);
- assert_eq!(action_de.has_action(&ActionType::NearbyShare), Some(true));
- assert_eq!(action_de.has_action(&ActionType::ActiveUnlock), Some(true));
- assert_eq!(action_de.has_action(&ActionType::PhoneHub), Some(false));
- assert_eq!(action_de.has_action(&ActionType::ContextSyncSeqNum), None);
+ assert_eq!(action_de.action.has_action(&ActionType::NearbyShare), Some(true));
+ assert_eq!(action_de.action.has_action(&ActionType::ActiveUnlock), Some(true));
+ assert_eq!(action_de.action.has_action(&ActionType::PhoneHub), Some(false));
+ assert_eq!(action_de.action.has_action(&ActionType::ContextSyncSeqNum), None);
}
// hypothetical action using the last bit
diff --git a/nearby/presence/np_adv/src/legacy/data_elements.rs b/nearby/presence/np_adv/src/legacy/data_elements.rs
index e9c2308..505fdd9 100644
--- a/nearby/presence/np_adv/src/legacy/data_elements.rs
+++ b/nearby/presence/np_adv/src/legacy/data_elements.rs
@@ -58,7 +58,8 @@
/// Data element holding a [TxPower].
#[derive(Debug, PartialEq, Eq)]
pub struct TxPowerDataElement {
- tx_power: TxPower,
+ /// The tx power value
+ pub tx_power: TxPower,
}
impl TxPowerDataElement {
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
index 3119f48..48555be 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/mod.rs
@@ -19,14 +19,15 @@
extern crate alloc;
use crate::{
+ credential::v0::V0,
de_type::EncryptedIdentityDataElementType,
legacy::{
actions,
data_elements::{DataElement, *},
de_type::{DataElementType, DeEncodedLength, DeTypeCode, PlainDataElementType},
- Ciphertext, PacketFlavor, Plaintext, NP_MAX_DE_CONTENT_LEN,
+ Ciphertext, PacketFlavor, Plaintext, ShortMetadataKey, NP_MAX_DE_CONTENT_LEN,
},
- PlaintextIdentityMode,
+ HasIdentityMatch, PlaintextIdentityMode,
};
use alloc::vec::Vec;
use crypto_provider::CryptoProvider;
@@ -42,10 +43,15 @@
input: &[u8],
) -> Result<IntermediateAdvContents, AdvDeserializeError> {
parse_raw_adv_contents::<C>(input).and_then(|raw_adv| match raw_adv {
- RawAdvertisement::Plaintext(parc) => parc
- .try_deserialize()
- .map(IntermediateAdvContents::Plaintext)
- .map_err(AdvDeserializeError::DataElementDeserializeError),
+ RawAdvertisement::Plaintext(parc) => {
+ if parc.data_elements.is_empty() {
+ return Err(AdvDeserializeError::NoPublicDataElements);
+ }
+
+ parc.try_deserialize()
+ .map(IntermediateAdvContents::Plaintext)
+ .map_err(AdvDeserializeError::DataElementDeserializeError)
+ }
RawAdvertisement::Ciphertext(eac) => Ok(IntermediateAdvContents::Ciphertext(eac)),
})
}
@@ -100,12 +106,7 @@
}),
}
} else {
- plain_data_elements(&data_elements).map(|pde| {
- RawAdvertisement::Plaintext(PlaintextAdvRawContents {
- identity_type: PlaintextIdentityMode::None,
- data_elements: pde,
- })
- })
+ Err(AdvDeserializeError::MissingIdentity)
}
}
@@ -120,6 +121,10 @@
TooManyTopLevelDataElements,
/// Must not have an identity DE inside an identity DE
InvalidDataElementHierarchy,
+ /// Missing identity DE
+ MissingIdentity,
+ /// Non-identity DE contents must not be empty
+ NoPublicDataElements,
}
/// Parse an advertisement's contents into raw DEs.
@@ -284,7 +289,6 @@
.map_err(|_e| DecryptError::DecryptOrVerifyError)?;
// plaintext starts with 14 bytes of metadata key, then DEs.
-
let (remaining, metadata_key) = combinator::map_res(
bytes::complete::take(NP_LEGACY_METADATA_KEY_LEN),
|slice: &[u8]| slice.try_into(),
@@ -308,7 +312,7 @@
.map(|data_elements| {
DecryptedAdvContents::new(
self.identity_type,
- metadata_key,
+ ShortMetadataKey(metadata_key),
self.salt,
data_elements,
)
@@ -351,10 +355,12 @@
pub fn identity(&self) -> PlaintextIdentityMode {
self.identity_type
}
- /// Returns the deserialized data elements
- pub fn data_elements(&self) -> impl Iterator<Item = &PlainDataElement<Plaintext>> {
+
+ /// Returns an iterator over the v0 data elements
+ pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Plaintext>> {
self.data_elements.iter()
}
+
/// Destructures this V0 plaintext advertisement
/// into just the contained data elements
pub fn to_data_elements(self) -> Vec<PlainDataElement<Plaintext>> {
@@ -366,7 +372,7 @@
#[derive(Debug, PartialEq, Eq)]
pub struct DecryptedAdvContents {
identity_type: EncryptedIdentityDataElementType,
- metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN],
+ metadata_key: ShortMetadataKey,
salt: LegacySalt,
data_elements: Vec<PlainDataElement<Ciphertext>>,
}
@@ -375,7 +381,7 @@
/// Returns a new DecryptedAdvContents with the provided contents.
fn new(
identity_type: EncryptedIdentityDataElementType,
- metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN],
+ metadata_key: ShortMetadataKey,
salt: LegacySalt,
data_elements: Vec<PlainDataElement<Ciphertext>>,
) -> Self {
@@ -387,15 +393,10 @@
self.identity_type
}
- /// The decrypted metadata key from the identity DE.
- pub fn metadata_key(&self) -> &[u8; 14] {
- &self.metadata_key
- }
-
/// Iterator over the data elements in an advertisement, except for any DEs related to resolving
/// the identity or otherwise validating the payload (e.g. any identity DEs like Private
/// Identity).
- pub fn data_elements(&self) -> impl Iterator<Item = &PlainDataElement<Ciphertext>> {
+ pub fn data_elements(&self) -> impl ExactSizeIterator<Item = &PlainDataElement<Ciphertext>> {
self.data_elements.iter()
}
@@ -405,6 +406,13 @@
}
}
+impl HasIdentityMatch for DecryptedAdvContents {
+ type Version = V0;
+ fn metadata_key(&self) -> ShortMetadataKey {
+ self.metadata_key
+ }
+}
+
/// The contents of an advertisement after plaintext DEs, if any, have been deserialized, but
/// before any decryption is done.
#[derive(Debug, PartialEq, Eq)]
diff --git a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
index b577cd9..4512bf7 100644
--- a/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/deserialize/tests.rs
@@ -18,6 +18,7 @@
use crate::legacy::actions::{ActionBits, ActionsDataElement};
use crate::shared_data::TxPower;
use crate::{
+ credential::{v0::V0, SimpleBroadcastCryptoMaterial},
de_type::IdentityDataElementType,
legacy::{
actions,
@@ -29,7 +30,7 @@
},
PacketFlavorEnum, BLE_ADV_SVC_CONTENT_LEN,
},
- parse_adv_header, shared_data, AdvHeader, NoIdentity, PublicIdentity,
+ parse_adv_header, shared_data, AdvHeader, PublicIdentity,
};
use array_view::ArrayView;
use crypto_provider_default::CryptoProviderImpl;
@@ -42,30 +43,8 @@
#[test]
fn parse_empty_raw_adv() {
- let data_elements = parse_raw_adv_contents::<CryptoProviderImpl>(&[]).unwrap();
- assert_eq!(
- RawAdvertisement::Plaintext(PlaintextAdvRawContents {
- identity_type: PlaintextIdentityMode::None,
- data_elements: Vec::new()
- }),
- data_elements
- );
-}
-
-#[test]
-fn parse_raw_adv_1_de_short_no_identity() {
- // battery uses the header length as is
- let adv = parse_raw_adv_contents::<CryptoProviderImpl>(&[0x36, 0x01, 0x02, 0x03]).unwrap();
- assert_eq!(
- RawAdvertisement::Plaintext(PlaintextAdvRawContents {
- identity_type: PlaintextIdentityMode::None,
- data_elements: vec![RawPlainDataElement {
- de_type: PlainDataElementType::Actions,
- contents: &[0x01, 0x02, 0x03]
- }],
- }),
- adv
- );
+ let adv_data = parse_raw_adv_contents::<CryptoProviderImpl>(&[]);
+ assert_eq!(AdvDeserializeError::MissingIdentity, adv_data.unwrap_err());
}
#[test]
@@ -161,7 +140,7 @@
0x15, 0x03, // tx power de
];
assert_eq!(
- AdvDeserializeError::InvalidDataElementHierarchy,
+ AdvDeserializeError::MissingIdentity,
parse_raw_adv_contents::<CryptoProviderImpl>(input).unwrap_err()
);
}
@@ -373,13 +352,19 @@
}
#[test]
-fn plaintext_random_adv_contents_round_trip_public() {
- plaintext_random_adv_contents_round_trip(PublicIdentity::default, PlaintextIdentityMode::Public)
+fn deserialize_adv_public_identity_empty_des() {
+ let input = &[
+ 0x03, // public identity
+ ];
+ assert_eq!(
+ AdvDeserializeError::NoPublicDataElements,
+ deserialize_adv_contents::<CryptoProviderImpl>(input).unwrap_err()
+ );
}
#[test]
-fn plaintext_random_adv_contents_round_trip_no_identity() {
- plaintext_random_adv_contents_round_trip(NoIdentity::default, PlaintextIdentityMode::None)
+fn plaintext_random_adv_contents_round_trip_public() {
+ plaintext_random_adv_contents_round_trip(PublicIdentity::default, PlaintextIdentityMode::Public)
}
#[test]
@@ -399,15 +384,17 @@
let salt: ldt_np_adv::LegacySalt = rng.gen::<[u8; 2]>().into();
let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = rng.gen();
let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let ldt_key = hkdf.legacy_ldt_key();
let metadata_key_hmac: [u8; 32] =
hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key);
let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac);
+
+ let metadata_key = ShortMetadataKey(metadata_key);
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
+
let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
identity_type,
salt,
- metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key),
+ &broadcast_cm,
));
loop {
@@ -462,15 +449,18 @@
let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN];
let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
- let ldt_key = hkdf.legacy_ldt_key();
let metadata_key_hmac: [u8; 32] =
hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key);
let cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac);
+
+ let metadata_key = ShortMetadataKey(metadata_key);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
+
let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
EncryptedIdentityDataElementType::Private,
salt,
- metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key),
+ &broadcast_cm,
));
let tx = shared_data::TxPower::try_from(3).unwrap();
@@ -524,7 +514,7 @@
#[test]
fn decrypt_and_deserialize_plaintext_adv_canned() {
- let mut builder = AdvBuilder::new(PublicIdentity::default());
+ let mut builder = AdvBuilder::new(PublicIdentity);
let actions = ActionBits::default();
builder.add_data_element(ActionsDataElement::from(actions)).unwrap();
@@ -694,18 +684,19 @@
correct_key_seed: [u8; 32],
) -> (ArrayView<u8, { BLE_ADV_SVC_CONTENT_LEN }>, ldt_np_adv::LdtNpAdvDecrypterXtsAes128<C>) {
let hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&correct_key_seed);
- let ldt_key = hkdf.legacy_ldt_key();
let metadata_key_hmac: [u8; 32] =
hkdf.legacy_metadata_key_hmac_key().calculate_hmac(metadata_key.as_slice());
let correct_cipher = ldt_np_adv::build_np_adv_decrypter_from_key_seed(&hkdf, metadata_key_hmac);
+ let broadcast_cm =
+ SimpleBroadcastCryptoMaterial::<V0>::new(correct_key_seed, ShortMetadataKey(*metadata_key));
+
let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
EncryptedIdentityDataElementType::Private,
salt,
- *metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&ldt_key),
+ &broadcast_cm,
));
builder.add_data_element(TxPowerDataElement::from(TxPower::try_from(3).unwrap())).unwrap();
(builder.into_advertisement().unwrap(), correct_cipher)
diff --git a/nearby/presence/np_adv/src/legacy/mod.rs b/nearby/presence/np_adv/src/legacy/mod.rs
index 0eb035d..59f2308 100644
--- a/nearby/presence/np_adv/src/legacy/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/mod.rs
@@ -14,7 +14,10 @@
//! V0 advertisement support.
+use crate::MetadataKey;
use core::fmt;
+use crypto_provider::CryptoProvider;
+use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN;
pub mod actions;
pub mod data_elements;
@@ -31,6 +34,26 @@
/// Maximum possible DE content: packet size minus 2 for adv header & DE header
const NP_MAX_DE_CONTENT_LEN: usize = BLE_ADV_SVC_CONTENT_LEN - 2;
+/// "Short" 14-byte metadata key type employed for V0, which needs to be
+/// expanded to a regular-size 16-byte metadata key to decrypt metadata.
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+pub struct ShortMetadataKey(pub [u8; NP_LEGACY_METADATA_KEY_LEN]);
+
+impl AsRef<[u8]> for ShortMetadataKey {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+impl ShortMetadataKey {
+ /// Expand this short 14-byte metadata key to a 16-byte metadata key
+ /// which may be used to decrypt metadata.
+ pub fn expand<C: CryptoProvider>(&self) -> MetadataKey {
+ let expanded_bytes = np_hkdf::legacy_metadata_expanded_key::<C>(&self.0);
+ MetadataKey(expanded_bytes)
+ }
+}
+
/// Marker type to allow disambiguating between plaintext and encrypted packets at compile time.
///
/// See also [PacketFlavorEnum] for when runtime flavor checks are more suitable.
diff --git a/nearby/presence/np_adv/src/legacy/serialize/mod.rs b/nearby/presence/np_adv/src/legacy/serialize/mod.rs
index 38e46e1..2920b67 100644
--- a/nearby/presence/np_adv/src/legacy/serialize/mod.rs
+++ b/nearby/presence/np_adv/src/legacy/serialize/mod.rs
@@ -40,24 +40,29 @@
//! Serializing an encrypted advertisement:
//!
//! ```
-//! use np_adv::{shared_data::*, de_type::*, legacy::{de_type::*, data_elements::*, serialize::*}};
+//! use np_adv::{shared_data::*, de_type::*, legacy::{de_type::*, data_elements::*, serialize::*, *}};
+//! use np_adv::credential::{v0::V0, SimpleBroadcastCryptoMaterial};
//! use crypto_provider::CryptoProvider;
//! use crypto_provider_default::CryptoProviderImpl;
//! use ldt_np_adv::{salt_padder, LegacySalt, LdtEncrypterXtsAes128};
//!
//! // Generate these from proper CSPRNGs -- using fixed data here
-//! let metadata_key = [0x33; 14];
+//! let metadata_key = ShortMetadataKey([0x33; 14]);
//! let salt = LegacySalt::from([0x01, 0x02]);
//! let key_seed = [0x44; 32];
//! let ldt_enc = LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(
//! &np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed).legacy_ldt_key()
//! );
//!
+//! let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(
+//! key_seed,
+//! metadata_key,
+//! );
+//!
//! let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
//! EncryptedIdentityDataElementType::Private,
//! salt,
-//! metadata_key,
-//! ldt_enc,
+//! &broadcast_cm,
//! ));
//!
//! builder
@@ -66,18 +71,19 @@
//!
//! let packet = builder.into_advertisement().unwrap();
//! ```
+use crate::credential::{v0::V0, BroadcastCryptoMaterial};
use crate::{
de_type::{EncryptedIdentityDataElementType, IdentityDataElementType},
legacy::{
de_type::{DataElementType, DeActualLength, DeEncodedLength, PlainDataElementType},
- Ciphertext, PacketFlavor, Plaintext, BLE_ADV_SVC_CONTENT_LEN, NP_MAX_DE_CONTENT_LEN,
+ Ciphertext, PacketFlavor, Plaintext, ShortMetadataKey, BLE_ADV_SVC_CONTENT_LEN,
+ NP_MAX_DE_CONTENT_LEN,
},
- DeLengthOutOfRange, NoIdentity, PublicIdentity,
+ DeLengthOutOfRange, PublicIdentity,
};
use array_view::ArrayView;
use core::{convert, fmt, marker};
use crypto_provider::CryptoProvider;
-use ldt_np_adv::NP_LEGACY_METADATA_KEY_LEN;
#[cfg(test)]
mod tests;
@@ -121,26 +127,13 @@
}
}
-impl Identity for NoIdentity {
- type Flavor = Plaintext;
- type Error = convert::Infallible;
- // No DE header
- const OVERHEAD_LEN: usize = 0;
-
- fn postprocess(&self, _buf: &mut [u8]) -> Result<(), Self::Error> {
- Ok(())
- }
-}
-
/// Identity used for encrypted packets (private, trusted, provisioned) that encrypts other DEs
/// (as well as the metadata key).
pub struct LdtIdentity<C: CryptoProvider> {
de_type: EncryptedIdentityDataElementType,
salt: ldt_np_adv::LegacySalt,
- metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN],
+ metadata_key: ShortMetadataKey,
ldt_enc: ldt_np_adv::LdtEncrypterXtsAes128<C>,
- // keep C parameter alive for when A disappears into it
- _crypto_provider: marker::PhantomData<C>,
}
// Exclude sensitive members
@@ -151,14 +144,20 @@
}
impl<C: CryptoProvider> LdtIdentity<C> {
- /// Build an `LdtIdentity` for the provided identity, salt, metadata key, and ldt.
- pub fn new(
+ /// Build an `LdtIdentity` for the provided identity type, salt, and
+ /// broadcast crypto-materials.
+ pub fn new<B: BroadcastCryptoMaterial<V0>>(
de_type: EncryptedIdentityDataElementType,
salt: ldt_np_adv::LegacySalt,
- metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN],
- ldt_enc: ldt_np_adv::LdtEncrypterXtsAes128<C>,
- ) -> LdtIdentity<C> {
- LdtIdentity { de_type, salt, metadata_key, ldt_enc, _crypto_provider: marker::PhantomData }
+ crypto_material: &B,
+ ) -> Self {
+ let metadata_key = crypto_material.metadata_key();
+ let key_seed = crypto_material.key_seed();
+ let key_seed_hkdf = np_hkdf::NpKeySeedHkdf::<C>::new(&key_seed);
+ let ldt_key = key_seed_hkdf.legacy_ldt_key();
+ let ldt_enc = ldt_np_adv::LdtEncrypterXtsAes128::<C>::new(&ldt_key);
+
+ Self { de_type, salt, metadata_key, ldt_enc }
}
}
@@ -180,7 +179,7 @@
buf[0] = encode_de_header_actual_len(id_de_type_as_generic_de_type(de_type), actual_len)
.map_err(|_e| LdtPostprocessError::InvalidLength)?;
buf[1..3].copy_from_slice(self.salt.bytes().as_slice());
- buf[3..17].copy_from_slice(&self.metadata_key);
+ buf[3..17].copy_from_slice(&self.metadata_key.0);
// encrypt everything after DE header and salt
self.ldt_enc.encrypt(&mut buf[3..], &ldt_np_adv::salt_padder::<16, C>(self.salt)).map_err(
diff --git a/nearby/presence/np_adv/src/legacy/serialize/tests.rs b/nearby/presence/np_adv/src/legacy/serialize/tests.rs
index 192b8f4..b4adc9f 100644
--- a/nearby/presence/np_adv/src/legacy/serialize/tests.rs
+++ b/nearby/presence/np_adv/src/legacy/serialize/tests.rs
@@ -17,6 +17,7 @@
use crate::legacy::actions::FastPairSass;
use crate::legacy::actions::NearbyShare;
use crate::{
+ credential::{v0::V0, SimpleBroadcastCryptoMaterial},
de_type::EncryptedIdentityDataElementType,
legacy::{actions::*, data_elements::*, serialize::*},
shared_data::TxPower,
@@ -27,7 +28,7 @@
#[test]
fn public_identity_packet_serialization() {
- let mut builder = AdvBuilder::new(PublicIdentity::default());
+ let mut builder = AdvBuilder::new(PublicIdentity);
let tx_power = TxPower::try_from(3).unwrap();
let mut action = ActionBits::default();
@@ -48,29 +49,8 @@
}
#[test]
-fn no_identity_packet_serialization() {
- let mut builder = AdvBuilder::new(NoIdentity::default());
-
- let tx_power = TxPower::try_from(3).unwrap();
- let mut action = ActionBits::default();
- action.set_action(NearbyShare::from(true));
- builder.add_data_element(TxPowerDataElement::from(tx_power)).unwrap();
- builder.add_data_element(ActionsDataElement::from(action)).unwrap();
-
- let packet = builder.into_advertisement().unwrap();
- assert_eq!(
- &[
- 0x00, // Adv Header
- 0x15, 0x03, // Tx Power DE with value 3
- 0x26, 0x00, 0x40, // Actions DE w/ bit 9
- ],
- packet.as_slice()
- );
-}
-
-#[test]
fn packet_limits_capacity() {
- let mut builder = AdvBuilder::new(PublicIdentity::default());
+ let mut builder = AdvBuilder::new(PublicIdentity);
// 2 + 1 left out of 24 payload bytes
builder.len = 21;
let mut bits = ActionBits::default();
@@ -90,22 +70,24 @@
#[test]
fn ldt_packet_serialization() {
// don't care about the HMAC since we're not decrypting
- let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&[0; 32]);
+ let key_seed = [0; 32];
+ let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let ldt = LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(&hkdf.legacy_ldt_key());
- let metadata_key = [0x33; 14];
+ let metadata_key = ShortMetadataKey([0x33; 14]);
let salt = LegacySalt::from([0x01, 0x02]);
let mut ciphertext = vec![];
- ciphertext.extend_from_slice(&metadata_key);
+ ciphertext.extend_from_slice(&metadata_key.0);
// tx power & action DEs
ciphertext.extend_from_slice(&[0x15, 0x03, 0x26, 0x00, 0x10]);
ldt.encrypt(&mut ciphertext, &salt_padder::<16, CryptoProviderImpl>(salt)).unwrap();
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
+
let mut builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
EncryptedIdentityDataElementType::Private,
salt,
- metadata_key,
- ldt,
+ &broadcast_cm,
));
let tx_power = TxPower::try_from(3).unwrap();
@@ -126,16 +108,16 @@
#[test]
fn ldt_packet_cant_encrypt_without_des() {
- let metadata_key = [0x33; 14];
+ let metadata_key = ShortMetadataKey([0x33; 14]);
let salt = LegacySalt::from([0x01, 0x02]);
+ let key_seed = [0xFE; 32];
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
let builder = AdvBuilder::new(LdtIdentity::<CryptoProviderImpl>::new(
EncryptedIdentityDataElementType::Private,
salt,
- metadata_key,
- LdtEncrypterXtsAes128::<CryptoProviderImpl>::new(
- &np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&[0xFE; 32]).legacy_ldt_key(),
- ),
+ &broadcast_cm,
));
// not enough ciphertext
@@ -144,7 +126,7 @@
#[test]
fn nearby_share_action() {
- let mut builder = AdvBuilder::new(PublicIdentity::default());
+ let mut builder = AdvBuilder::new(PublicIdentity);
let mut action = ActionBits::default();
action.set_action(NearbyShare::from(true));
diff --git a/nearby/presence/np_adv/src/lib.rs b/nearby/presence/np_adv/src/lib.rs
index a6a155e..45207ff 100644
--- a/nearby/presence/np_adv/src/lib.rs
+++ b/nearby/presence/np_adv/src/lib.rs
@@ -25,38 +25,48 @@
extern crate core;
use crate::{
credential::{
- source::{BothCredentialSource, CredentialSource},
- v0::V0CryptoMaterial,
- MatchedCredFromCred, MatchedCredential, V0Credential, V1Credential,
+ book::CredentialBook, v0::V0DiscoveryCryptoMaterial, v1::V1DiscoveryCryptoMaterial,
+ DiscoveryCryptoMaterial, MatchedCredential, ProtocolVersion,
},
+ deserialization_arena::ArenaOutOfSpace,
extended::deserialize::{
- parse_sections, CiphertextSection, DataElements, DecryptedSection, IntermediateSection,
- PlaintextSection, Section, SectionDeserializeError,
+ encrypted_section::*, parse_sections, CiphertextSection, DataElementParseError,
+ DataElementParsingIterator, DecryptedSection, IntermediateSection, PlaintextSection,
+ Section, SectionDeserializeError,
},
legacy::deserialize::{
DecryptError, DecryptedAdvContents, IntermediateAdvContents, PlaintextAdvContents,
},
};
+
use alloc::vec::Vec;
#[cfg(feature = "devtools")]
use array_view::ArrayView;
-use core::{fmt::Debug, marker};
+use core::borrow::BorrowMut;
+use core::fmt::Debug;
use crypto_provider::CryptoProvider;
+use deserialization_arena::DeserializationArena;
+
#[cfg(feature = "devtools")]
use extended::NP_ADV_MAX_SECTION_LEN;
use legacy::{data_elements::DataElementDeserializeError, deserialize::AdvDeserializeError};
use nom::{combinator, number};
+pub use strum;
+
pub mod credential;
pub mod de_type;
#[cfg(test)]
mod deser_v0_tests;
#[cfg(test)]
mod deser_v1_tests;
+pub mod deserialization_arena;
pub mod extended;
+pub mod filter;
#[cfg(test)]
mod header_parse_tests;
pub mod legacy;
pub mod shared_data;
+
/// Canonical form of NP's service UUID.
///
/// Note that UUIDs are encoded in BT frames in little-endian order, so these bytes may need to be
@@ -65,73 +75,40 @@
/// Parse, deserialize, decrypt, and validate a complete NP advertisement (the entire contents of
/// the service data for the NP UUID).
-pub fn deserialize_advertisement<'s, C0, C1, M, S, P>(
- adv: &[u8],
- cred_source: &'s S,
-) -> Result<DeserializedAdvertisement<'s, M>, AdvDeserializationError>
+pub fn deserialize_advertisement<'adv, 'cred, B, P>(
+ mut arena: impl BorrowMut<DeserializationArena<'adv>>,
+ adv: &'adv [u8],
+ cred_book: &'cred B,
+) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
where
- C0: V0Credential<Matched<'s> = M> + 's,
- C1: V1Credential<Matched<'s> = M> + 's,
- M: MatchedCredential<'s>,
- S: BothCredentialSource<C0, C1>,
+ B: CredentialBook<'cred>,
+ P: CryptoProvider,
+{
+ // Turn `impl BorrowMut` into the concrete `&mut` reference so that the compiler does not need
+ // to duplicate the function code for different concrete types.
+ deserialize_advertisement_impl::<B, P>(arena.borrow_mut(), adv, cred_book)
+}
+
+fn deserialize_advertisement_impl<'adv, 'cred, B, P>(
+ arena: &mut DeserializationArena<'adv>,
+ adv: &'adv [u8],
+ cred_book: &'cred B,
+) -> Result<DeserializedAdvertisement<'adv, B::Matched>, AdvDeserializationError>
+where
+ B: CredentialBook<'cred>,
P: CryptoProvider,
{
let (remaining, header) =
parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?;
match header {
- AdvHeader::V1(header) => {
- deser_decrypt_v1::<C1, S::V1Source, P>(cred_source.v1(), remaining, header)
- .map(DeserializedAdvertisement::V1)
+ AdvHeader::V0 => {
+ deser_decrypt_v0::<B, P>(cred_book, remaining).map(DeserializedAdvertisement::V0)
}
- AdvHeader::V0 => deser_decrypt_v0::<C0, S::V0Source, P>(cred_source.v0(), remaining)
- .map(DeserializedAdvertisement::V0),
+ AdvHeader::V1(header) => deser_decrypt_v1::<B, P>(arena, cred_book, remaining, header)
+ .map(DeserializedAdvertisement::V1),
}
}
-/// Parse, deserialize, decrypt, and validate a complete V0 NP advertisement (the entire contents
-/// of the service data for the NP UUID). If the advertisement version header does not match V0,
-/// this method will return an [`AdvDeserializationError::HeaderParseError`]
-pub fn deserialize_v0_advertisement<'s, C, S, P>(
- adv: &[u8],
- cred_source: &'s S,
-) -> Result<V0AdvertisementContents<'s, C>, AdvDeserializationError>
-where
- C: V0Credential,
- S: CredentialSource<C>,
- P: CryptoProvider,
-{
- let (remaining, header) =
- parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?;
-
- match header {
- AdvHeader::V0 => deser_decrypt_v0::<C, S, P>(cred_source, remaining),
- AdvHeader::V1(_) => Err(AdvDeserializationError::HeaderParseError),
- }
-}
-
-/// Parse, deserialize, decrypt, and validate a complete V1 NP advertisement (the entire contents
-/// of the service data for the NP UUID). If the advertisement version header does not match V1,
-/// this method will return an [`AdvDeserializationError::HeaderParseError`]
-pub fn deserialize_v1_advertisement<'s, C, S, P>(
- adv: &[u8],
- cred_source: &'s S,
-) -> Result<V1AdvertisementContents<'s, C>, AdvDeserializationError>
-where
- C: V1Credential,
- S: CredentialSource<C>,
- P: CryptoProvider,
-{
- let (remaining, header) =
- parse_adv_header(adv).map_err(|_e| AdvDeserializationError::HeaderParseError)?;
-
- match header {
- AdvHeader::V0 => Err(AdvDeserializationError::HeaderParseError),
- AdvHeader::V1(header) => deser_decrypt_v1::<C, S, P>(cred_source, remaining, header),
- }
-}
-
-type V1AdvertisementContents<'s, C> = V1AdvContents<'s, MatchedCredFromCred<'s, C>>;
-
/// The encryption scheme used for a V1 advertisement.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum V1EncryptionScheme {
@@ -151,157 +128,282 @@
ParseError,
/// No suitable credential found to decrypt the given section.
NoMatchingCredentials,
+ /// Given Arena is not large enough to hold the decrypted data.
+ ArenaOutOfSpace,
+}
+
+#[cfg(feature = "devtools")]
+impl From<ArenaOutOfSpace> for AdvDecryptionError {
+ fn from(_: ArenaOutOfSpace) -> Self {
+ Self::ArenaOutOfSpace
+ }
}
/// Decrypt, but do not further deserialize the v1 bytes, intended for developer tooling uses only.
-/// Production uses should use [deserialize_v1_advertisement] instead, which deserializes to a
+/// Production uses should use [deserialize_advertisement] instead, which deserializes to a
/// structured format and provides extra type safety.
#[cfg(feature = "devtools")]
-pub fn deser_decrypt_v1_section_bytes_for_dev_tools<S, V1, P>(
- cred_source: &S,
+pub fn deser_decrypt_v1_section_bytes_for_dev_tools<'adv, 'cred, B, P>(
+ arena: &mut DeserializationArena<'adv>,
+ cred_book: &'cred B,
header_byte: u8,
- section_bytes: &[u8],
+ section_bytes: &'adv [u8],
) -> Result<(ArrayView<u8, NP_ADV_MAX_SECTION_LEN>, V1EncryptionScheme), AdvDecryptionError>
where
- S: CredentialSource<V1>,
- V1: V1Credential,
+ B: CredentialBook<'cred>,
P: CryptoProvider,
{
let header = V1Header { header_byte };
let int_sections =
- parse_sections(&header, section_bytes).map_err(|_| AdvDecryptionError::ParseError)?;
+ parse_sections(header, section_bytes).map_err(|_| AdvDecryptionError::ParseError)?;
let cipher_section = match &int_sections[0] {
IntermediateSection::Plaintext(_) => Err(AdvDecryptionError::InputNotEncrypted)?,
IntermediateSection::Ciphertext(section) => section,
};
- use crate::credential::v1::V1CryptoMaterial;
- use core::borrow::Borrow;
-
- for cred in cred_source.iter() {
- let crypto_material = cred.crypto_material();
-
- match cipher_section {
- CiphertextSection::SignatureEncryptedIdentity(encrypted_section) => {
- let identity_resolution_material =
- crypto_material.signed_identity_resolution_material::<P>();
- match encrypted_section.try_decrypt::<P>(identity_resolution_material.borrow()) {
- Ok(plaintext) => return Ok((plaintext, V1EncryptionScheme::Signature)),
- Err(_) => continue,
- }
- }
- CiphertextSection::MicEncryptedIdentity(encrypted_section) => {
- let identity_resolution_material =
- crypto_material.unsigned_identity_resolution_material::<P>();
- let verification_material = crypto_material.unsigned_verification_material::<P>();
- match encrypted_section
- .try_decrypt::<P>(identity_resolution_material.borrow(), &verification_material)
- {
- Ok(plaintext) => return Ok((plaintext, V1EncryptionScheme::Mic)),
- Err(_) => continue,
- }
- }
+ for (crypto_material, _) in cred_book.v1_iter() {
+ if let Some(plaintext) = cipher_section
+ .try_resolve_identity_and_decrypt::<_, P>(arena.borrow_mut(), &crypto_material)
+ {
+ return plaintext
+ .map(|pt| {
+ let encryption_scheme = match cipher_section {
+ CiphertextSection::SignatureEncryptedIdentity(_) => {
+ V1EncryptionScheme::Signature
+ }
+ CiphertextSection::MicEncryptedIdentity(_) => V1EncryptionScheme::Mic,
+ };
+ (pt, encryption_scheme)
+ })
+ .map_err(|e| e.into());
}
}
Err(AdvDecryptionError::NoMatchingCredentials)
}
-/// Deserialize and decrypt the contents of a v1 adv after the version header
-fn deser_decrypt_v1<'s, C, S, P>(
- cred_source: &'s S,
- remaining: &[u8],
- header: V1Header,
-) -> Result<V1AdvertisementContents<'s, C>, AdvDeserializationError>
-where
- C: V1Credential,
- S: CredentialSource<C>,
- P: CryptoProvider,
-{
- let int_sections =
- parse_sections(&header, remaining).map_err(|_| AdvDeserializationError::ParseError {
- details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
- })?;
- let mut sections = Vec::new();
- let mut to_decrypt = Vec::new();
- // keep track of ordering for later sorting
- for (idx, s) in int_sections.into_iter().enumerate() {
- match s {
- IntermediateSection::Plaintext(p) => {
- sections.push((idx, V1DeserializedSection::Plaintext(p)))
+/// A ciphertext section which has not yet been
+/// resolved to an identity, but for which some
+/// `SectionIdentityResolutionContents` have been
+/// pre-computed for speedy identity-resolution.
+struct ResolvableCiphertextSection<'a> {
+ identity_resolution_contents: SectionIdentityResolutionContents,
+ ciphertext_section: CiphertextSection<'a>,
+}
+
+/// A collection of possibly-deserialized sections which are separated according
+/// to whether/not they're intermediate encrypted sections (of either type)
+/// or fully-deserialized, with a running count of the number of malformed sections.
+/// Each potentially-valid section is tagged with a 0-based index derived from the original
+/// section ordering as they appeared within the original advertisement to ensure
+/// that the fully-deserialized advertisement may be correctly reconstructed.
+struct SectionsInProcessing<'adv, M: MatchedCredential> {
+ deserialized_sections: Vec<(usize, V1DeserializedSection<'adv, M>)>,
+ encrypted_sections: Vec<(usize, ResolvableCiphertextSection<'adv>)>,
+ malformed_sections_count: usize,
+}
+
+impl<'adv, M: MatchedCredential> SectionsInProcessing<'adv, M> {
+ /// Attempts to parse a V1 advertisement's contents after the version header
+ /// into a collection of not-yet-fully-deserialized sections which may
+ /// require credentials to be decrypted.
+ fn from_advertisement_contents<C: CryptoProvider>(
+ header: V1Header,
+ remaining: &'adv [u8],
+ ) -> Result<Self, AdvDeserializationError> {
+ let int_sections =
+ parse_sections(header, remaining).map_err(|_| AdvDeserializationError::ParseError {
+ details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError,
+ })?;
+ let mut deserialized_sections = Vec::new();
+ let mut encrypted_sections = Vec::new();
+ // keep track of ordering for later sorting during `self.finished_with_decryption_attempts()`.
+ for (idx, s) in int_sections.into_iter().enumerate() {
+ match s {
+ IntermediateSection::Plaintext(p) => {
+ deserialized_sections.push((idx, V1DeserializedSection::Plaintext(p)))
+ }
+ IntermediateSection::Ciphertext(ciphertext_section) => {
+ let identity_resolution_contents =
+ ciphertext_section.contents().compute_identity_resolution_contents::<C>();
+ let resolvable_ciphertext_section = ResolvableCiphertextSection {
+ identity_resolution_contents,
+ ciphertext_section,
+ };
+ encrypted_sections.push((idx, resolvable_ciphertext_section));
+ }
}
- IntermediateSection::Ciphertext(c) => to_decrypt.push((idx, c)),
}
+ Ok(Self { deserialized_sections, encrypted_sections, malformed_sections_count: 0 })
}
- let mut invalid_sections = 0;
- // Hot loop
- // We assume that iterating credentials is more expensive than iterating sections
- for cred in cred_source.iter() {
+
+ /// Returns true iff we have resolved all sections to identities.
+ fn resolved_all_identities(&self) -> bool {
+ self.encrypted_sections.is_empty()
+ }
+
+ /// Runs through all of the encrypted sections in processing, and attempts
+ /// to use the given credential to decrypt them. Suitable for situations
+ /// where iterating over credentials is relatively slow compared to
+ /// the cost of iterating over sections-in-memory.
+ fn try_decrypt_with_credential<C: V1DiscoveryCryptoMaterial, P: CryptoProvider>(
+ &mut self,
+ arena: &mut DeserializationArena<'adv>,
+ crypto_material: C,
+ match_data: M,
+ ) -> Result<(), ArenaOutOfSpace> {
let mut i = 0;
- while i < to_decrypt.len() {
- let (section_idx, c): &(usize, CiphertextSection) = &to_decrypt[i];
- match c.try_deserialize::<C, P>(cred) {
- Ok(s) => {
- sections.push((
- *section_idx,
- V1DeserializedSection::Decrypted(WithMatchedCredential::new(
- cred.matched(),
- s,
- )),
- ));
- // we don't care about maintaining order, so use O(1) remove
- to_decrypt.swap_remove(i);
+ while i < self.encrypted_sections.len() {
+ let (section_idx, section): &(usize, ResolvableCiphertextSection) =
+ &self.encrypted_sections[i];
+ // Fast-path: Check for an identity match, ignore if there's no identity match.
+ let identity_resolution_contents = §ion.identity_resolution_contents;
+ let identity_resolution_material = match §ion.ciphertext_section {
+ CiphertextSection::MicEncryptedIdentity(_) => crypto_material
+ .unsigned_identity_resolution_material::<P>()
+ .into_raw_resolution_material(),
+ CiphertextSection::SignatureEncryptedIdentity(_) => crypto_material
+ .signed_identity_resolution_material::<P>()
+ .into_raw_resolution_material(),
+ };
+ match identity_resolution_contents.try_match::<P>(&identity_resolution_material) {
+ None => {
+ // Try again with another section
+ i += 1;
+ continue;
+ }
+ Some(identity_match) => {
+ // The identity matched, so now we need to more closely scrutinize
+ // the provided ciphertext. Try to decrypt and parse the section.
+ let metadata_nonce = crypto_material.metadata_nonce::<P>();
+ let deserialization_result = match §ion.ciphertext_section {
+ CiphertextSection::SignatureEncryptedIdentity(c) => c
+ .try_deserialize(
+ arena.borrow_mut(),
+ identity_match,
+ &crypto_material.signed_verification_material::<P>(),
+ )
+ .map_err(SectionDeserializeError::from),
+ CiphertextSection::MicEncryptedIdentity(c) => c
+ .try_deserialize(
+ arena.borrow_mut(),
+ identity_match,
+ &crypto_material.unsigned_verification_material::<P>(),
+ )
+ .map_err(SectionDeserializeError::from),
+ };
+ match deserialization_result {
+ Ok(s) => {
+ self.deserialized_sections.push((
+ *section_idx,
+ V1DeserializedSection::Decrypted(WithMatchedCredential::new(
+ match_data.clone(),
+ metadata_nonce,
+ s,
+ )),
+ ));
+ }
+ Err(e) => match e {
+ SectionDeserializeError::IncorrectCredential => {
+ // keep it around to try with another credential
+ i += 1;
+ continue;
+ }
+ SectionDeserializeError::ParseError => {
+ // the credential worked, but the section itself was bogus
+ self.malformed_sections_count += 1;
+ }
+ SectionDeserializeError::ArenaOutOfSpace => {
+ return Err(ArenaOutOfSpace)
+ }
+ },
+ }
+ // By default, if we have an identity match, assume that decrypting the section worked,
+ // or that the section was somehow invalid.
+ // We don't care about maintaining order, so use O(1) remove
+ self.encrypted_sections.swap_remove(i);
// don't advance i -- it now points to a new element
}
- Err(e) => match e {
- SectionDeserializeError::IncorrectCredential => {
- // keep it around to try with another credential
- i += 1;
- }
- SectionDeserializeError::ParseError => {
- // the credential worked, but the section itself was bogus, so drop
- // it
- invalid_sections += 1;
- to_decrypt.swap_remove(i);
- }
- },
}
}
- if to_decrypt.is_empty() {
- // no need to consider the remaining credentials
+ Ok(())
+ }
+
+ /// Packages the current state of the deserialization process into a
+ /// `V1AdvertisementContents` representing a fully-deserialized V1 advertisement.
+ ///
+ /// This method should only be called after all sections were either successfully
+ /// decrypted or have had all relevant credentials checked against
+ /// them without obtaining a successful identity-match and/or subsequent
+ /// cryptographic verification of the section contents.
+ fn finished_with_decryption_attempts(mut self) -> V1AdvertisementContents<'adv, M> {
+ // Invalid sections = malformed sections + number of encrypted sections
+ // which we could not manage to decrypt with any of our credentials
+ let invalid_sections_count = self.malformed_sections_count + self.encrypted_sections.len();
+
+ // Put the deserialized sections back into the original ordering for
+ // the returned `V1AdvertisementContents`
+ // (Note: idx is unique, so unstable sort is ok)
+ self.deserialized_sections.sort_unstable_by_key(|(idx, _section)| *idx);
+ let ordered_sections = self.deserialized_sections.into_iter().map(|(_idx, s)| s).collect();
+ V1AdvertisementContents::new(ordered_sections, invalid_sections_count)
+ }
+}
+
+/// Deserialize and decrypt the contents of a v1 adv after the version header
+fn deser_decrypt_v1<'adv, 'cred, B, P>(
+ arena: &mut DeserializationArena<'adv>,
+ cred_book: &'cred B,
+ remaining: &'adv [u8],
+ header: V1Header,
+) -> Result<V1AdvertisementContents<'adv, B::Matched>, AdvDeserializationError>
+where
+ B: CredentialBook<'cred>,
+ P: CryptoProvider,
+{
+ let mut sections_in_processing =
+ SectionsInProcessing::<'_, B::Matched>::from_advertisement_contents::<P>(
+ header, remaining,
+ )?;
+
+ // Hot loop
+ // We assume that iterating credentials is more expensive than iterating sections
+ for (crypto_material, match_data) in cred_book.v1_iter() {
+ sections_in_processing.try_decrypt_with_credential::<_, P>(
+ arena.borrow_mut(),
+ crypto_material,
+ match_data,
+ )?;
+ if sections_in_processing.resolved_all_identities() {
+ // No need to consider the other credentials
break;
}
}
- invalid_sections += to_decrypt.len();
- // decryption may produce sections out of order
- sections.sort_by_key(|(idx, _section)| *idx);
- Ok(V1AdvContents::new(sections.into_iter().map(|(_idx, s)| s).collect(), invalid_sections))
+ Ok(sections_in_processing.finished_with_decryption_attempts())
}
-type V0AdvertisementContents<'s, C> = V0AdvContents<'s, MatchedCredFromCred<'s, C>>;
-
/// Deserialize and decrypt the contents of a v0 adv after the version header
-fn deser_decrypt_v0<'s, C, S, P>(
- cred_source: &'s S,
+fn deser_decrypt_v0<'a, B, P>(
+ cred_book: &'a B,
remaining: &[u8],
-) -> Result<V0AdvertisementContents<'s, C>, AdvDeserializationError>
+) -> Result<V0AdvertisementContents<B::Matched>, AdvDeserializationError>
where
- C: V0Credential,
- S: CredentialSource<C>,
+ B: CredentialBook<'a>,
P: CryptoProvider,
{
let contents = legacy::deserialize::deserialize_adv_contents::<P>(remaining)?;
- return match contents {
- IntermediateAdvContents::Plaintext(p) => Ok(V0AdvContents::Plaintext(p)),
+ match contents {
+ IntermediateAdvContents::Plaintext(p) => Ok(V0AdvertisementContents::Plaintext(p)),
IntermediateAdvContents::Ciphertext(c) => {
- for cred in cred_source.iter() {
- let cm = cred.crypto_material();
- let ldt = cm.ldt_adv_cipher::<P>();
+ for (crypto_material, matched) in cred_book.v0_iter() {
+ let ldt = crypto_material.ldt_adv_cipher::<P>();
match c.try_decrypt(&ldt) {
Ok(c) => {
- return Ok(V0AdvContents::Decrypted(WithMatchedCredential::new(
- cred.matched(),
+ let metadata_nonce = crypto_material.metadata_nonce::<P>();
+ return Ok(V0AdvertisementContents::Decrypted(WithMatchedCredential::new(
+ matched,
+ metadata_nonce,
c,
- )))
+ )));
}
Err(e) => match e {
DecryptError::DecryptOrVerifyError => continue,
@@ -311,10 +413,11 @@
},
}
}
- Ok(V0AdvContents::NoMatchingCredentials)
+ Ok(V0AdvertisementContents::NoMatchingCredentials)
}
- };
+ }
}
+
/// Parse a NP advertisement header.
///
/// This can be used on all versions of advertisements since it's the header that determines the
@@ -338,88 +441,157 @@
_ => unreachable!(),
}
}
+
#[derive(Debug, PartialEq, Eq, Clone)]
pub(crate) enum AdvHeader {
V0,
V1(V1Header),
}
+
/// An NP advertisement with its header parsed.
#[derive(Debug, PartialEq, Eq)]
-pub enum DeserializedAdvertisement<'m, M: MatchedCredential<'m>> {
+pub enum DeserializedAdvertisement<'adv, M: MatchedCredential> {
/// V0 header has all reserved bits, so there is no data to represent other than the version
/// itself.
- V0(V0AdvContents<'m, M>),
+ V0(V0AdvertisementContents<M>),
/// V1 advertisement
- V1(V1AdvContents<'m, M>),
+ V1(V1AdvertisementContents<'adv, M>),
}
+
+impl<'adv, M: MatchedCredential> DeserializedAdvertisement<'adv, M> {
+ /// Attempts to cast this deserialized advertisement into the `V0AdvertisementContents`
+ /// variant. If the underlying advertisement is not V0, this will instead return `None`.
+ pub fn into_v0(self) -> Option<V0AdvertisementContents<M>> {
+ match self {
+ Self::V0(x) => Some(x),
+ _ => None,
+ }
+ }
+ /// Attempts to cast this deserialized advertisement into the `V1AdvertisementContents`
+ /// variant. If the underlying advertisement is not V1, this will instead return `None`.
+ pub fn into_v1(self) -> Option<V1AdvertisementContents<'adv, M>> {
+ match self {
+ Self::V1(x) => Some(x),
+ _ => None,
+ }
+ }
+}
+
/// The contents of a deserialized and decrypted V1 advertisement.
#[derive(Debug, PartialEq, Eq)]
-pub struct V1AdvContents<'m, M: MatchedCredential<'m>> {
- sections: Vec<V1DeserializedSection<'m, M>>,
+pub struct V1AdvertisementContents<'adv, M: MatchedCredential> {
+ sections: Vec<V1DeserializedSection<'adv, M>>,
invalid_sections: usize,
}
-impl<'m, M: MatchedCredential<'m>> V1AdvContents<'m, M> {
- fn new(sections: Vec<V1DeserializedSection<'m, M>>, invalid_sections: usize) -> Self {
+
+impl<'adv, M: MatchedCredential> V1AdvertisementContents<'adv, M> {
+ fn new(sections: Vec<V1DeserializedSection<'adv, M>>, invalid_sections: usize) -> Self {
Self { sections, invalid_sections }
}
+
/// Destructures this V1 advertisement into just the sections
/// which could be successfully deserialized and decrypted
- pub fn into_valid_sections(self) -> Vec<V1DeserializedSection<'m, M>> {
+ pub fn into_sections(self) -> Vec<V1DeserializedSection<'adv, M>> {
self.sections
}
+
/// The sections that could be successfully deserialized and decrypted
- pub fn sections(&self) -> impl Iterator<Item = &V1DeserializedSection<M>> {
+ pub fn sections(&self) -> impl ExactSizeIterator<Item = &V1DeserializedSection<M>> {
self.sections.iter()
}
+
/// The number of sections that could not be parsed or decrypted.
pub fn invalid_sections_count(&self) -> usize {
self.invalid_sections
}
}
+
/// Advertisement content that was either already plaintext or has been decrypted.
#[derive(Debug, PartialEq, Eq)]
-pub enum V0AdvContents<'m, M: MatchedCredential<'m>> {
+pub enum V0AdvertisementContents<M: MatchedCredential> {
/// Contents of an originally plaintext advertisement
Plaintext(PlaintextAdvContents),
/// Contents that was ciphertext in the original advertisement, and has been decrypted
/// with the credential in the [MatchedCredential]
- Decrypted(WithMatchedCredential<'m, M, DecryptedAdvContents>),
+ Decrypted(WithMatchedCredential<M, DecryptedAdvContents>),
/// The advertisement was encrypted, but no credentials matched
NoMatchingCredentials,
}
+
/// Advertisement content that was either already plaintext or has been decrypted.
#[derive(Debug, PartialEq, Eq)]
-pub enum V1DeserializedSection<'m, M: MatchedCredential<'m>> {
+pub enum V1DeserializedSection<'adv, M: MatchedCredential> {
/// Section that was plaintext in the original advertisement
- Plaintext(PlaintextSection),
+ Plaintext(PlaintextSection<'adv>),
/// Section that was ciphertext in the original advertisement, and has been decrypted
/// with the credential in the [MatchedCredential]
- Decrypted(WithMatchedCredential<'m, M, DecryptedSection>),
+ Decrypted(WithMatchedCredential<M, DecryptedSection<'adv>>),
}
-impl<'m, M> Section for V1DeserializedSection<'m, M>
+
+impl<'adv, M> Section<'adv, DataElementParseError> for V1DeserializedSection<'adv, M>
where
- M: MatchedCredential<'m>,
+ M: MatchedCredential,
{
- type Iterator<'d> = DataElements<'d> where Self: 'd;
- fn data_elements(&'_ self) -> Self::Iterator<'_> {
+ type Iterator = DataElementParsingIterator<'adv>;
+
+ fn iter_data_elements(&self) -> Self::Iterator {
match self {
- V1DeserializedSection::Plaintext(p) => p.data_elements(),
- V1DeserializedSection::Decrypted(d) => d.contents.data_elements(),
+ V1DeserializedSection::Plaintext(p) => p.iter_data_elements(),
+ V1DeserializedSection::Decrypted(d) => d.contents.iter_data_elements(),
}
}
}
-/// Decrypted advertisement content with the [MatchedCredential] from the credential that decrypted
-/// it.
-#[derive(Debug, PartialEq, Eq)]
-pub struct WithMatchedCredential<'m, M: MatchedCredential<'m>, T> {
- matched: M,
- contents: T,
- // the compiler sees 'm as unused
- marker: marker::PhantomData<&'m ()>,
+
+/// 16-byte metadata keys, as employed for metadata decryption.
+#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
+pub struct MetadataKey(pub [u8; 16]);
+
+impl AsRef<[u8]> for MetadataKey {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
}
-impl<'m, M: MatchedCredential<'m>, T> WithMatchedCredential<'m, M, T> {
- fn new(matched: M, contents: T) -> Self {
- Self { matched, contents, marker: marker::PhantomData }
+
+/// Common trait to deserialized, decrypted V0 advs and V1 sections which
+/// exposes relevant data about matched identities.
+pub trait HasIdentityMatch {
+ /// The protocol version for which this advertisement
+ /// content has an identity-match.
+ type Version: ProtocolVersion;
+
+ /// Gets the decrypted plaintext version-specific
+ /// metadata key for the associated identity.
+ fn metadata_key(&self) -> <Self::Version as ProtocolVersion>::MetadataKey;
+}
+
+#[cfg(any(test, feature = "alloc"))]
+/// Type for errors from [`WithMatchedCredential#decrypt_metadata`]
+#[derive(Debug)]
+pub enum MatchedMetadataDecryptionError<M: MatchedCredential> {
+ /// Retrieving the encrypted metadata failed for one reason
+ /// or another, so we didn't get a chance to try decryption.
+ RetrievalFailed(<M as MatchedCredential>::EncryptedMetadataFetchError),
+ /// The encrypted metadata could be retrieved, but it did
+ /// not successfully decrypt against the matched identity.
+ /// This could be an indication of data corruption or
+ /// of malformed crypto on the sender-side.
+ DecryptionFailed,
+}
+
+/// Decrypted advertisement content with the [MatchedCredential] from the credential that decrypted
+/// it, along with any other information which is relevant to the identity-match.
+#[derive(Debug, PartialEq, Eq)]
+pub struct WithMatchedCredential<M: MatchedCredential, T: HasIdentityMatch> {
+ matched: M,
+ /// The 12-byte metadata nonce as derived from the key-seed HKDF
+ /// to be used for decrypting the encrypted metadata in the attached
+ /// matched-credential.
+ metadata_nonce: [u8; 12],
+ contents: T,
+}
+impl<M: MatchedCredential, T: HasIdentityMatch> WithMatchedCredential<M, T> {
+ fn new(matched: M, metadata_nonce: [u8; 12], contents: T) -> Self {
+ Self { matched, metadata_nonce, contents }
}
/// Credential data for the credential that decrypted the content.
pub fn matched_credential(&self) -> &M {
@@ -429,12 +601,41 @@
pub fn contents(&self) -> &T {
&self.contents
}
+
+ #[cfg(any(test, feature = "alloc"))]
+ fn decrypt_metadata_from_fetch<C: CryptoProvider>(
+ &self,
+ encrypted_metadata: &[u8],
+ ) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> {
+ let metadata_key = self.contents.metadata_key();
+ <<T as HasIdentityMatch>::Version as ProtocolVersion>::decrypt_metadata::<C>(
+ self.metadata_nonce,
+ metadata_key,
+ encrypted_metadata,
+ )
+ .map_err(|_| MatchedMetadataDecryptionError::DecryptionFailed)
+ }
+
+ #[cfg(any(test, feature = "alloc"))]
+ /// Attempts to decrypt the encrypted metadata
+ /// associated with the matched credential
+ /// based on the details of the identity-match.
+ pub fn decrypt_metadata<C: CryptoProvider>(
+ &self,
+ ) -> Result<Vec<u8>, MatchedMetadataDecryptionError<M>> {
+ self.matched
+ .fetch_encrypted_metadata()
+ .map_err(|e| MatchedMetadataDecryptionError::RetrievalFailed(e))
+ .and_then(|x| Self::decrypt_metadata_from_fetch::<C>(self, x.as_ref()))
+ }
}
+
/// Data in a V1 advertisement header.
-#[derive(Debug, PartialEq, Eq, Clone)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) struct V1Header {
header_byte: u8,
}
+
const PROTOCOL_VERSION_LEGACY: u8 = 0;
const PROTOCOL_VERSION_EXTENDED: u8 = 1;
@@ -449,6 +650,14 @@
/// [AdvDeserializationErrorDetailsHazmat] before using this field.
details_hazmat: AdvDeserializationErrorDetailsHazmat,
},
+ /// The given arena is not large enough to hold the decrypted data.
+ ArenaOutOfSpace,
+}
+
+impl From<ArenaOutOfSpace> for AdvDeserializationError {
+ fn from(_: ArenaOutOfSpace) -> Self {
+ Self::ArenaOutOfSpace
+ }
}
impl Debug for AdvDeserializationError {
@@ -456,6 +665,7 @@
match self {
AdvDeserializationError::HeaderParseError => write!(f, "HeaderParseError"),
AdvDeserializationError::ParseError { .. } => write!(f, "ParseError"),
+ AdvDeserializationError::ArenaOutOfSpace => write!(f, "ArenaOutOfSpace"),
}
}
}
@@ -474,6 +684,10 @@
TooManyTopLevelDataElements,
/// Must not have an identity DE inside an identity DE
InvalidDataElementHierarchy,
+ /// Must have an identity DE
+ MissingIdentity,
+ /// Non-identity DE contents must not be empty
+ NoPublicDataElements,
}
impl From<AdvDeserializeError> for AdvDeserializationError {
@@ -503,6 +717,12 @@
AdvDeserializationErrorDetailsHazmat::InvalidDataElementHierarchy,
}
}
+ AdvDeserializeError::MissingIdentity => AdvDeserializationError::ParseError {
+ details_hazmat: AdvDeserializationErrorDetailsHazmat::MissingIdentity,
+ },
+ AdvDeserializeError::NoPublicDataElements => AdvDeserializationError::ParseError {
+ details_hazmat: AdvDeserializationErrorDetailsHazmat::NoPublicDataElements,
+ },
}
}
}
@@ -515,8 +735,6 @@
/// The identity mode for a deserialized plaintext section or advertisement.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum PlaintextIdentityMode {
- /// No identity DE was present in the section
- None,
/// A "Public Identity" DE was present in the section
Public,
}
@@ -525,10 +743,4 @@
///
/// Used when serializing V0 advertisements or V1 sections.
#[derive(Default, Debug)]
-pub struct PublicIdentity {}
-
-/// The lack of any identity information whatsoever, which is distinct from [PublicIdentity].
-///
-/// Used when serializing V0 advertisements or V1 sections.
-#[derive(Default, Debug)]
-pub struct NoIdentity {}
+pub struct PublicIdentity;
diff --git a/nearby/presence/np_adv/tests/examples_v0.rs b/nearby/presence/np_adv/tests/examples_v0.rs
index e30e12f..27ea7c5 100644
--- a/nearby/presence/np_adv/tests/examples_v0.rs
+++ b/nearby/presence/np_adv/tests/examples_v0.rs
@@ -1,5 +1,4 @@
// Copyright 2023 Google LLC
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -17,32 +16,40 @@
use np_adv::legacy::data_elements::TxPowerDataElement;
use np_adv::{
credential::{
- simple::SimpleV0Credential, source::SliceCredentialSource,
- v0::MinimumFootprintV0CryptoMaterial,
+ book::CredentialBookBuilder,
+ v0::{V0DiscoveryCredential, V0},
+ EmptyMatchedCredential, MatchableCredential, MetadataMatchedCredential,
+ SimpleBroadcastCryptoMaterial,
},
de_type::*,
- deserialize_v0_advertisement,
- legacy::deserialize::*,
+ legacy::{deserialize::*, ShortMetadataKey},
shared_data::*,
*,
};
+use serde::{Deserialize, Serialize};
#[test]
fn v0_deser_plaintext() {
- let creds =
- SliceCredentialSource::<SimpleV0Credential<MinimumFootprintV0CryptoMaterial, ()>>::new(&[]);
- let adv = deserialize_v0_advertisement::<_, _, CryptoProviderImpl>(
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+ let adv = deserialize_advertisement::<_, CryptoProviderImpl>(
+ deserialization_arena!(),
&[
0x00, // adv header
0x03, // public identity
0x15, 0x03, // Length 1 Tx Power DE with value 3
],
- &creds,
+ &cred_book,
)
- .unwrap();
+ .expect("Should be a valid advertisement")
+ .into_v0()
+ .expect("Should be V0");
match adv {
- V0AdvContents::Plaintext(p) => {
+ V0AdvertisementContents::Plaintext(p) => {
assert_eq!(PlaintextIdentityMode::Public, p.identity());
assert_eq!(
vec![&PlainDataElement::TxPower(TxPowerDataElement::from(
@@ -55,14 +62,49 @@
}
}
+/// Sample contents for some encrypted identity metadata
+/// which includes just a name and an e-mail address.
+#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
+struct IdentityMetadata {
+ name: String,
+ email: String,
+}
+
+impl IdentityMetadata {
+ /// Serialize this identity metadata to a JSON byte-string.
+ fn to_bytes(&self) -> Vec<u8> {
+ serde_json::to_vec(&self).expect("serialization should always succeed")
+ }
+ /// Attempt to deserialize identity metadata from a JSON byte-string.
+ fn try_from_bytes(serialized: &[u8]) -> Option<Self> {
+ serde_json::from_slice(serialized).ok()
+ }
+}
+
#[test]
fn v0_deser_ciphertext() {
+ // These are kept fixed in this example for reproducibility.
+ // In practice, these should instead be derived from a cryptographically-secure
+ // random number generator.
let key_seed = [0x11_u8; 32];
let metadata_key: [u8; NP_LEGACY_METADATA_KEY_LEN] = [0x33; NP_LEGACY_METADATA_KEY_LEN];
+ let metadata_key = ShortMetadataKey(metadata_key);
+
+ let broadcast_cm = SimpleBroadcastCryptoMaterial::<V0>::new(key_seed, metadata_key);
let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
let metadata_key_hmac: [u8; 32] =
- hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key);
+ hkdf.legacy_metadata_key_hmac_key().calculate_hmac(&metadata_key.0);
+
+ // Serialize and encrypt some identity metadata (sender-side)
+ let sender_metadata =
+ IdentityMetadata { name: "Alice".to_string(), email: "alice@gmail.com".to_string() };
+ let sender_metadata_bytes = sender_metadata.to_bytes();
+ let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::<
+ _,
+ _,
+ CryptoProviderImpl,
+ >(&broadcast_cm, &sender_metadata_bytes);
// output of building a packet using AdvBuilder
let adv = &[
@@ -74,25 +116,42 @@
0xF9,
];
- let credentials: [SimpleV0Credential<_, [u8; 32]>; 1] = [SimpleV0Credential::new(
- MinimumFootprintV0CryptoMaterial::new(key_seed, metadata_key_hmac),
- key_seed,
- )];
- let cred_source = SliceCredentialSource::new(credentials.as_slice());
+ let discovery_credential = V0DiscoveryCredential::new(key_seed, metadata_key_hmac);
- let matched = match deserialize_v0_advertisement::<_, _, CryptoProviderImpl>(adv, &cred_source)
- .unwrap()
+ let credentials: [MatchableCredential<V0, MetadataMatchedCredential<_>>; 1] =
+ [MatchableCredential { discovery_credential, match_data: encrypted_sender_metadata }];
+
+ let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
+ &credentials,
+ &[],
+ );
+
+ let matched = match deserialize_advertisement::<_, CryptoProviderImpl>(
+ deserialization_arena!(),
+ adv,
+ &cred_book,
+ )
+ .expect("Should be a valid advertisement")
+ .into_v0()
+ .expect("Should be V0")
{
- V0AdvContents::Decrypted(c) => c,
+ V0AdvertisementContents::Decrypted(c) => c,
_ => panic!("this examples is ciphertext"),
};
- assert_eq!(&key_seed, matched.matched_credential().matched_data());
+ let decrypted_metadata_bytes = matched
+ .decrypt_metadata::<CryptoProviderImpl>()
+ .expect("Sender metadata should be decryptable");
+ let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes)
+ .expect("Sender metadata should be deserializable");
+
+ assert_eq!(sender_metadata, decrypted_metadata);
+
let decrypted = matched.contents();
assert_eq!(EncryptedIdentityDataElementType::Private, decrypted.identity_type());
- assert_eq!(&metadata_key, decrypted.metadata_key());
+ assert_eq!(metadata_key, decrypted.metadata_key());
assert_eq!(
vec![&PlainDataElement::TxPower(TxPowerDataElement::from(TxPower::try_from(3).unwrap())),],
diff --git a/nearby/presence/np_adv/tests/examples_v1.rs b/nearby/presence/np_adv/tests/examples_v1.rs
index fbda763..bf633e5 100644
--- a/nearby/presence/np_adv/tests/examples_v1.rs
+++ b/nearby/presence/np_adv/tests/examples_v1.rs
@@ -15,38 +15,47 @@
use crypto_provider::{CryptoProvider, CryptoRng};
use crypto_provider_default::CryptoProviderImpl;
use np_adv::extended::data_elements::TxPowerDataElement;
-use np_adv::extended::serialize::SingleTypeDataElement;
+use np_adv::extended::serialize::{AdvertisementType, PublicSectionEncoder, SingleTypeDataElement};
+use np_adv::extended::NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT;
use np_adv::shared_data::TxPower;
use np_adv::{
credential::{
- simple::SimpleV1Credential, source::SliceCredentialSource,
- v1::MinimumFootprintV1CryptoMaterial,
+ book::CredentialBookBuilder,
+ v1::{SimpleSignedBroadcastCryptoMaterial, V1DiscoveryCredential, V1},
+ EmptyMatchedCredential, MatchableCredential, MetadataMatchedCredential,
},
de_type::*,
- deserialize_v1_advertisement,
extended::{
deserialize::{Section, VerificationMode},
- serialize::{AdvBuilder, SignedEncrypted},
- NP_V1_ADV_MAX_SECTION_COUNT,
+ serialize::{AdvBuilder, SignedEncryptedSectionEncoder},
},
PlaintextIdentityMode, *,
};
use np_hkdf::v1_salt;
+use serde::{Deserialize, Serialize};
#[test]
fn v1_deser_plaintext() {
- let mut adv_builder = AdvBuilder::new();
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ let mut section_builder = adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(6).unwrap()))
.unwrap();
section_builder.add_to_advertisement();
let adv = adv_builder.into_advertisement();
- let creds =
- SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]);
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+
+ let arena = deserialization_arena!();
let contents =
- deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds).unwrap();
+ deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
+ .expect("Should be a valid advertisemement")
+ .into_v1()
+ .expect("Should be V1");
assert_eq!(0, contents.invalid_sections_count());
@@ -59,7 +68,7 @@
_ => panic!("this is a plaintext adv"),
};
assert_eq!(PlaintextIdentityMode::Public, section.identity());
- let data_elements = section.data_elements().collect::<Vec<_>>();
+ let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(1, data_elements.len());
let de = &data_elements[0];
@@ -68,24 +77,61 @@
assert_eq!(&[6], de.contents());
}
+/// Sample contents for some encrypted identity metadata
+/// which consists of a UUID together with a display name
+/// and a general location.
+#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
+struct IdentityMetadata {
+ uuid: String,
+ display_name: String,
+ location: String,
+}
+
+impl IdentityMetadata {
+ /// Serialize this identity metadata to a MsgPack byte-string.
+ fn to_bytes(&self) -> Vec<u8> {
+ rmp_serde::to_vec(&self).expect("serialization should always succeed")
+ }
+ /// Attempt to deserialize identity metadata from a MsgPack byte-string.
+ fn try_from_bytes(serialized: &[u8]) -> Option<Self> {
+ rmp_serde::from_slice(serialized).ok()
+ }
+}
+
#[test]
fn v1_deser_ciphertext() {
// identity material
let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
let metadata_key: [u8; 16] = rng.gen();
+ let metadata_key = MetadataKey(metadata_key);
let key_pair = np_ed25519::KeyPair::<CryptoProviderImpl>::generate();
let key_seed = rng.gen();
let hkdf = np_hkdf::NpKeySeedHkdf::<CryptoProviderImpl>::new(&key_seed);
+ let broadcast_cm =
+ SimpleSignedBroadcastCryptoMaterial::new(key_seed, metadata_key, key_pair.private_key());
+
+ // Serialize and encrypt some identity metadata (sender-side)
+ let sender_metadata = IdentityMetadata {
+ uuid: "378845e1-2616-420d-86f5-674177a7504d".to_string(),
+ display_name: "Alice".to_string(),
+ location: "Wonderland".to_string(),
+ };
+ let sender_metadata_bytes = sender_metadata.to_bytes();
+ let encrypted_sender_metadata = MetadataMatchedCredential::<Vec<u8>>::encrypt_from_plaintext::<
+ _,
+ _,
+ CryptoProviderImpl,
+ >(&broadcast_cm, &sender_metadata_bytes);
+
// prepare advertisement
- let mut adv_builder = AdvBuilder::new();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Encrypted);
+
let mut section_builder = adv_builder
- .section_builder(SignedEncrypted::new_random_salt(
+ .section_builder(SignedEncryptedSectionEncoder::<CryptoProviderImpl>::new_random_salt(
&mut rng,
EncryptedIdentityDataElementType::Private,
- &metadata_key,
- &key_pair,
- &hkdf,
+ &broadcast_cm,
))
.unwrap();
section_builder
@@ -94,37 +140,50 @@
section_builder.add_to_advertisement();
let adv = adv_builder.into_advertisement();
- let cred_array: [SimpleV1Credential<_, [u8; 32]>; 1] = [SimpleV1Credential::new(
- MinimumFootprintV1CryptoMaterial::new(
- key_seed,
- [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here.
- hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key),
- key_pair.public(),
- ),
+ let discovery_credential = V1DiscoveryCredential::new(
key_seed,
- )];
- let creds = SliceCredentialSource::new(&cred_array);
+ [0; 32], // Zeroing out MIC HMAC, since it's unused in examples here.
+ hkdf.extended_signed_metadata_key_hmac_key().calculate_hmac(&metadata_key.0),
+ key_pair.public(),
+ );
+
+ let credentials: [MatchableCredential<V1, MetadataMatchedCredential<_>>; 1] =
+ [MatchableCredential { discovery_credential, match_data: encrypted_sender_metadata }];
+ let cred_book = CredentialBookBuilder::build_cached_slice_book::<0, 0, CryptoProviderImpl>(
+ &[],
+ &credentials,
+ );
+ let arena = deserialization_arena!();
let contents =
- deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds).unwrap();
+ deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
+ .expect("Should be a valid advertisement")
+ .into_v1()
+ .expect("Should be V1");
assert_eq!(0, contents.invalid_sections_count());
let sections = contents.sections().collect::<Vec<_>>();
assert_eq!(1, sections.len());
- let matched_credential = match §ions[0] {
+ let matched: &WithMatchedCredential<_, _> = match §ions[0] {
V1DeserializedSection::Decrypted(d) => d,
_ => panic!("this is a ciphertext adv"),
};
- assert_eq!(&key_seed, matched_credential.matched_credential().matched_data());
- let section = matched_credential.contents();
+ let decrypted_metadata_bytes = matched
+ .decrypt_metadata::<CryptoProviderImpl>()
+ .expect("Sender metadata should be decryptable");
+ let decrypted_metadata = IdentityMetadata::try_from_bytes(&decrypted_metadata_bytes)
+ .expect("Sender metadata should be deserializable");
+ assert_eq!(sender_metadata, decrypted_metadata);
+
+ let section = matched.contents();
assert_eq!(EncryptedIdentityDataElementType::Private, section.identity_type());
assert_eq!(VerificationMode::Signature, section.verification_mode());
- assert_eq!(&metadata_key, section.metadata_key());
+ assert_eq!(metadata_key, section.metadata_key());
- let data_elements = section.data_elements().collect::<Vec<_>>();
+ let data_elements = section.iter_data_elements().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(1, data_elements.len());
let de = &data_elements[0];
@@ -135,12 +194,16 @@
#[test]
fn v1_deser_no_section() {
- let adv_builder = AdvBuilder::new();
+ let adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
let adv = adv_builder.into_advertisement();
- let creds =
- SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]);
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+ let arena = deserialization_arena!();
let v1_deserialize_error =
- deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds)
+ deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
.expect_err(" Expected an error");
assert_eq!(
v1_deserialize_error,
@@ -152,9 +215,10 @@
#[test]
fn v1_deser_plaintext_over_max_sections() {
- let mut adv_builder = AdvBuilder::new();
- for _ in 0..NP_V1_ADV_MAX_SECTION_COUNT {
- let mut section_builder = adv_builder.section_builder(PublicIdentity::default()).unwrap();
+ let mut adv_builder = AdvBuilder::new(AdvertisementType::Plaintext);
+ for _ in 0..NP_V1_ADV_MAX_PUBLIC_SECTION_COUNT {
+ let mut section_builder =
+ adv_builder.section_builder(PublicSectionEncoder::default()).unwrap();
section_builder
.add_de(|_salt| TxPowerDataElement::from(TxPower::try_from(7).unwrap()))
.unwrap();
@@ -169,10 +233,14 @@
]
.as_slice(),
);
- let creds =
- SliceCredentialSource::<SimpleV1Credential<MinimumFootprintV1CryptoMaterial, ()>>::new(&[]);
+ let cred_book = CredentialBookBuilder::<EmptyMatchedCredential>::build_cached_slice_book::<
+ 0,
+ 0,
+ CryptoProviderImpl,
+ >(&[], &[]);
+ let arena = deserialization_arena!();
assert_eq!(
- deserialize_v1_advertisement::<_, _, CryptoProviderImpl>(adv.as_slice(), &creds)
+ deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv.as_slice(), &cred_book)
.unwrap_err(),
AdvDeserializationError::ParseError {
details_hazmat: AdvDeserializationErrorDetailsHazmat::AdvertisementDeserializeError
diff --git a/nearby/presence/np_c_ffi/Cargo.lock b/nearby/presence/np_c_ffi/Cargo.lock
index ca23800..f55cf29 100644
--- a/nearby/presence/np_c_ffi/Cargo.lock
+++ b/nearby/presence/np_c_ffi/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -40,23 +54,6 @@
]
[[package]]
-name = "ahash"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
-]
-
-[[package]]
-name = "allocator-api2"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
-
-[[package]]
name = "array_ref"
version = "0.1.0"
@@ -70,7 +67,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
- "hermit-abi 0.1.19",
+ "hermit-abi",
"libc",
"winapi",
]
@@ -88,12 +85,24 @@
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+name = "bitflags"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+
+[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -112,10 +121,21 @@
]
[[package]]
+name = "bssl-crypto"
+version = "0.1.0"
+dependencies = [
+ "bssl-sys",
+]
+
+[[package]]
+name = "bssl-sys"
+version = "0.1.0"
+
+[[package]]
name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cbc"
@@ -146,12 +166,6 @@
]
[[package]]
-name = "cc"
-version = "1.0.79"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
-
-[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -174,7 +188,7 @@
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
- "bitflags",
+ "bitflags 1.3.2",
"clap_lex",
"indexmap",
"strsim",
@@ -193,24 +207,24 @@
[[package]]
name = "const-oid"
-version = "0.9.3"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc"
+checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
[[package]]
name = "cpufeatures"
-version = "0.2.9"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
[[package]]
name = "crypto-bigint"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15"
+checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124"
dependencies = [
"generic-array",
"rand_core",
@@ -232,6 +246,17 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "crypto_provider_boringssl"
+version = "0.1.0"
+dependencies = [
+ "bssl-crypto",
+ "crypto_provider",
+]
[[package]]
name = "crypto_provider_default"
@@ -239,6 +264,7 @@
dependencies = [
"cfg-if",
"crypto_provider",
+ "crypto_provider_boringssl",
"crypto_provider_rustcrypto",
]
@@ -248,6 +274,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -277,9 +304,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -289,24 +316,25 @@
"platforms",
"rustc_version",
"subtle",
+ "zeroize",
]
[[package]]
name = "curve25519-dalek-derive"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.38",
]
[[package]]
name = "der"
-version = "0.7.7"
+version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946"
+checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
dependencies = [
"const-oid",
"zeroize",
@@ -325,30 +353,33 @@
[[package]]
name = "ed25519"
-version = "2.2.1"
+version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
+ "pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand_core",
+ "serde",
"sha2",
+ "zeroize",
]
[[package]]
name = "elliptic-curve"
-version = "0.13.5"
+version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
+checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914"
dependencies = [
"base16ct",
"crypto-bigint",
@@ -365,33 +396,19 @@
[[package]]
name = "errno"
-version = "0.3.1"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
- "errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
-
-[[package]]
name = "fastrand"
-version = "1.9.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "ff"
@@ -405,9 +422,9 @@
[[package]]
name = "fiat-crypto"
-version = "0.1.20"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
+checksum = "a481586acf778f1b1455424c343f71124b048ffa5f4fc3f8f6ae9dc432dcb3c7"
[[package]]
name = "generic-array"
@@ -432,6 +449,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -447,10 +474,7 @@
version = "0.1.0"
dependencies = [
"crypto_provider",
- "hashbrown 0.14.0",
- "lock_api",
- "portable-atomic",
- "spin 0.9.8",
+ "lock_adapter",
]
[[package]]
@@ -460,16 +484,6 @@
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
-name = "hashbrown"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
-dependencies = [
- "ahash",
- "allocator-api2",
-]
-
-[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -485,12 +499,6 @@
]
[[package]]
-name = "hermit-abi"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
-
-[[package]]
name = "hkdf"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -515,7 +523,7 @@
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
- "hashbrown 0.12.3",
+ "hashbrown",
]
[[package]]
@@ -529,30 +537,10 @@
]
[[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
-dependencies = [
- "hermit-abi 0.3.2",
- "libc",
- "windows-sys",
-]
-
-[[package]]
name = "itoa"
-version = "1.0.8"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "lazy_static"
@@ -560,7 +548,7 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
- "spin 0.5.2",
+ "spin",
]
[[package]]
@@ -592,37 +580,31 @@
[[package]]
name = "libc"
-version = "0.2.147"
+version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "linux-raw-sys"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
-[[package]]
-name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+
+[[package]]
+name = "lock_adapter"
+version = "0.1.0"
[[package]]
name = "log"
-version = "0.4.19"
+version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
-version = "2.5.0"
+version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "minimal-lexical"
@@ -652,7 +634,6 @@
"nom",
"np_ed25519",
"np_hkdf",
- "rand",
"sink",
"strum",
"strum_macros",
@@ -665,9 +646,8 @@
version = "0.1.0"
dependencies = [
"cbindgen",
- "crypto_provider_default",
+ "lock_adapter",
"np_ffi_core",
- "spin 0.9.8",
]
[[package]]
@@ -688,8 +668,9 @@
"crypto_provider",
"crypto_provider_default",
"handle_map",
+ "lazy_static",
+ "lock_adapter",
"np_adv",
- "spin 0.9.8",
]
[[package]]
@@ -702,12 +683,6 @@
]
[[package]]
-name = "once_cell"
-version = "1.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
-
-[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -715,9 +690,9 @@
[[package]]
name = "os_str_bytes"
-version = "6.5.1"
+version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
[[package]]
name = "p256"
@@ -730,10 +705,20 @@
]
[[package]]
-name = "platforms"
-version = "3.0.2"
+name = "pkcs8"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "platforms"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
[[package]]
name = "polyval"
@@ -748,12 +733,6 @@
]
[[package]]
-name = "portable-atomic"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794"
-
-[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -770,18 +749,18 @@
[[package]]
name = "proc-macro2"
-version = "1.0.63"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.29"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
@@ -792,6 +771,8 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
+ "libc",
+ "rand_chacha",
"rand_core",
]
@@ -816,11 +797,11 @@
[[package]]
name = "redox_syscall"
-version = "0.3.5"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
- "bitflags",
+ "bitflags 1.3.2",
]
[[package]]
@@ -834,13 +815,12 @@
[[package]]
name = "rustix"
-version = "0.37.23"
+version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
- "bitflags",
+ "bitflags 2.4.1",
"errno",
- "io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
@@ -848,27 +828,21 @@
[[package]]
name = "rustversion"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "ryu"
-version = "1.0.14"
+version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "sec1"
-version = "0.7.2"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
@@ -879,35 +853,35 @@
[[package]]
name = "semver"
-version = "1.0.17"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
-version = "1.0.166"
+version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
+checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.166"
+version = "1.0.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
+checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.23",
+ "syn 2.0.38",
]
[[package]]
name = "serde_json"
-version = "1.0.100"
+version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
@@ -916,9 +890,9 @@
[[package]]
name = "sha2"
-version = "0.10.7"
+version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -945,12 +919,13 @@
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
-name = "spin"
-version = "0.9.8"
+name = "spki"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
dependencies = [
- "lock_api",
+ "base64ct",
+ "der",
]
[[package]]
@@ -961,21 +936,21 @@
[[package]]
name = "strum"
-version = "0.24.1"
+version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
+checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
-version = "0.24.3"
+version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
+checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
- "syn 1.0.109",
+ "syn 2.0.38",
]
[[package]]
@@ -997,9 +972,9 @@
[[package]]
name = "syn"
-version = "2.0.23"
+version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@@ -1008,11 +983,10 @@
[[package]]
name = "tempfile"
-version = "3.6.0"
+version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
- "autocfg",
"cfg-if",
"fastrand",
"redox_syscall",
@@ -1022,9 +996,9 @@
[[package]]
name = "termcolor"
-version = "1.2.0"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
dependencies = [
"winapi-util",
]
@@ -1052,15 +1026,15 @@
[[package]]
name = "typenum"
-version = "1.16.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
-version = "1.0.10"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "universal-hash"
@@ -1102,9 +1076,9 @@
[[package]]
name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
@@ -1126,9 +1100,9 @@
[[package]]
name = "windows-targets"
-version = "0.48.1"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@@ -1141,51 +1115,51 @@
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.0"
+version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/presence/np_c_ffi/Cargo.toml b/nearby/presence/np_c_ffi/Cargo.toml
index 1dd9f32..86bbd53 100644
--- a/nearby/presence/np_c_ffi/Cargo.toml
+++ b/nearby/presence/np_c_ffi/Cargo.toml
@@ -5,14 +5,27 @@
publish = false
[dependencies]
-# TODO: We need to make this configurable for this crate and for np_ffi below it.
-crypto_provider_default = {path = "../../crypto/crypto_provider_default", features = ["rustcrypto"]}
-np_ffi_core = {path = "../np_ffi_core"}
-spin = "0.9.8"
+np_ffi_core = { path = "../np_ffi_core" }
+lock_adapter = {path = "../../util/lock_adapter"}
[build-dependencies]
cbindgen = "0.24.5"
+[features]
+default = ["rustcrypto"]
+rustcrypto = ["np_ffi_core/rustcrypto"]
+boringssl = ["np_ffi_core/boringssl"]
+
[lib]
# boringssl and bssl-sys are built as a static lib, so we need to as well
-crate-type = ["staticlib"]
\ No newline at end of file
+crate-type = ["staticlib"]
+
+# build profile optimized for size
+[profile.release-min-size]
+inherits = "release"
+panic = "abort"
+codegen-units = 1
+lto = true
+# z optimizes for size
+opt-level = "z"
+strip = true
\ No newline at end of file
diff --git a/nearby/presence/np_c_ffi/deny.toml b/nearby/presence/np_c_ffi/deny.toml
new file mode 100644
index 0000000..ca51e11
--- /dev/null
+++ b/nearby/presence/np_c_ffi/deny.toml
@@ -0,0 +1,213 @@
+# This template contains all of the possible sections and their default values
+
+# Note that all fields that take a lint level have these possible values:
+# * deny - An error will be produced and the check will fail
+# * warn - A warning will be produced, but the check will not fail
+# * allow - No warning or error will be produced, though in some cases a note
+# will be
+
+# The values provided in this template are the default values that will be used
+# when any section or field is not specified in your own configuration
+
+# If 1 or more target triples (and optionally, target_features) are specified,
+# only the specified targets will be checked when running `cargo deny check`.
+# This means, if a particular package is only ever used as a target specific
+# dependency, such as, for example, the `nix` crate only being used via the
+# `target_family = "unix"` configuration, that only having windows targets in
+# this list would mean the nix crate, as well as any of its exclusive
+# dependencies not shared by any other crates, would be ignored, as the target
+# list here is effectively saying which targets you are building for.
+targets = [
+ # The triple can be any string, but only the target triples built in to
+ # rustc (as of 1.40) can be checked against actual config expressions
+ #{ triple = "x86_64-unknown-linux-musl" },
+ # You can also specify which target_features you promise are enabled for a
+ # particular target. target_features are currently not validated against
+ # the actual valid features supported by the target architecture.
+ #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
+]
+
+# This section is considered when running `cargo deny check advisories`
+# More documentation for the advisories section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
+[advisories]
+# The path where the advisory database is cloned/fetched into
+db-path = "~/.cargo/advisory-db"
+# The url(s) of the advisory databases to use
+db-urls = ["https://github.com/rustsec/advisory-db"]
+# The lint level for security vulnerabilities
+vulnerability = "deny"
+# The lint level for unmaintained crates
+unmaintained = "warn"
+# The lint level for crates that have been yanked from their source registry
+yanked = "warn"
+# The lint level for crates with security notices. Note that as of
+# 2019-12-17 there are no security notice advisories in
+# https://github.com/rustsec/advisory-db
+notice = "warn"
+# A list of advisory IDs to ignore. Note that ignored advisories will still
+# output a note when they are encountered.
+ignore = [
+ #"RUSTSEC-0000-0000",
+]
+# Threshold for security vulnerabilities, any vulnerability with a CVSS score
+# lower than the range specified will be ignored. Note that ignored advisories
+# will still output a note when they are encountered.
+# * None - CVSS Score 0.0
+# * Low - CVSS Score 0.1 - 3.9
+# * Medium - CVSS Score 4.0 - 6.9
+# * High - CVSS Score 7.0 - 8.9
+# * Critical - CVSS Score 9.0 - 10.0
+#severity-threshold =
+
+# If this is true, then cargo deny will use the git executable to fetch advisory database.
+# If this is false, then it uses a built-in git library.
+# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
+# See Git Authentication for more information about setting up git authentication.
+#git-fetch-with-cli = true
+
+# This section is considered when running `cargo deny check licenses`
+# More documentation for the licenses section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
+[licenses]
+# The lint level for crates which do not have a detectable license
+unlicensed = "deny"
+# List of explicitly allowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+allow = [
+ "MIT",
+ "Apache-2.0",
+ "BSD-3-Clause",
+ "Unicode-DFS-2016",
+]
+# List of explicitly disallowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+deny = [
+ #"Nokia",
+]
+# Lint level for licenses considered copyleft
+copyleft = "warn"
+# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
+# * both - The license will be approved if it is both OSI-approved *AND* FSF
+# * either - The license will be approved if it is either OSI-approved *OR* FSF
+# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
+# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
+# * neither - This predicate is ignored and the default lint level is used
+allow-osi-fsf-free = "neither"
+# Lint level used when no other predicates are matched
+# 1. License isn't in the allow or deny lists
+# 2. License isn't copyleft
+# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
+default = "deny"
+# The confidence threshold for detecting a license from license text.
+# The higher the value, the more closely the license text must be to the
+# canonical license text of a valid SPDX license file.
+# [possible values: any between 0.0 and 1.0].
+confidence-threshold = 0.8
+# Allow 1 or more licenses on a per-crate basis, so that particular licenses
+# aren't accepted for every possible crate as with the normal allow list
+exceptions = [
+ # Each entry is the crate and version constraint, and its specific allow
+ # list
+ #{ allow = ["Zlib"], name = "adler32", version = "*" },
+]
+
+# Some crates don't have (easily) machine readable licensing information,
+# adding a clarification entry for it allows you to manually specify the
+# licensing information
+#[[licenses.clarify]]
+# The name of the crate the clarification applies to
+#name = "ring"
+# The optional version constraint for the crate
+#version = "*"
+# The SPDX expression for the license requirements of the crate
+#expression = "MIT AND ISC AND OpenSSL"
+# One or more files in the crate's source used as the "source of truth" for
+# the license expression. If the contents match, the clarification will be used
+# when running the license check, otherwise the clarification will be ignored
+# and the crate will be checked normally, which may produce warnings or errors
+# depending on the rest of your configuration
+#license-files = [
+ # Each entry is a crate relative path, and the (opaque) hash of its contents
+ #{ path = "LICENSE", hash = 0xbd0eed23 }
+#]
+
+[[licenses.clarify]]
+name = "ring"
+version = "*"
+expression = "MIT AND ISC AND OpenSSL"
+license-files = [
+ # Each entry is a crate relative path, and the (opaque) hash of its contents
+ { path = "LICENSE", hash = 0xbd0eed23 }
+]
+
+[licenses.private]
+# If true, ignores workspace crates that aren't published, or are only
+# published to private registries.
+# To see how to mark a crate as unpublished (to the official registry),
+# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
+ignore = true
+# One or more private registries that you might publish crates to, if a crate
+# is only published to private registries, and ignore is true, the crate will
+# not have its license(s) checked
+registries = [
+ #"https://sekretz.com/registry
+]
+
+# This section is considered when running `cargo deny check bans`.
+# More documentation about the 'bans' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
+[bans]
+# Lint level for when multiple versions of the same crate are detected
+multiple-versions = "allow"
+# Lint level for when a crate version requirement is `*`
+wildcards = "allow"
+# The graph highlighting used when creating dotgraphs for crates
+# with multiple versions
+# * lowest-version - The path to the lowest versioned duplicate is highlighted
+# * simplest-path - The path to the version with the fewest edges is highlighted
+# * all - Both lowest-version and simplest-path are used
+highlight = "all"
+# List of crates that are allowed. Use with care!
+allow = [
+ #{ name = "ansi_term", version = "=0.11.0" },
+]
+# List of crates to deny
+deny = [
+ # Each entry the name of a crate and a version range. If version is
+ # not specified, all versions will be matched.
+ #{ name = "ansi_term", version = "=0.11.0" },
+ #
+ # Wrapper crates can optionally be specified to allow the crate when it
+ # is a direct dependency of the otherwise banned crate
+ #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+]
+# Certain crates/versions that will be skipped when doing duplicate detection.
+skip = [
+ #{ name = "ansi_term", version = "=0.11.0" },
+]
+# Similarly to `skip` allows you to skip certain crates during duplicate
+# detection. Unlike skip, it also includes the entire tree of transitive
+# dependencies starting at the specified crate, up to a certain depth, which is
+# by default infinite
+skip-tree = [
+ #{ name = "ansi_term", version = "=0.11.0", depth = 20 },
+]
+
+# This section is considered when running `cargo deny check sources`.
+# More documentation about the 'sources' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
+[sources]
+# Lint level for what to happen when a crate from a crate registry that is not
+# in the allow list is encountered
+unknown-registry = "warn"
+# Lint level for what to happen when a crate from a git repository that is not
+# in the allow list is encountered
+unknown-git = "warn"
+# List of URLs for allowed crate registries. Defaults to the crates.io index
+# if not specified. If it is specified but empty, no registries are allowed.
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+# List of URLs for allowed Git repositories
+allow-git = []
\ No newline at end of file
diff --git a/nearby/presence/np_c_ffi/src/deserialize/v0.rs b/nearby/presence/np_c_ffi/src/deserialize/v0.rs
index 877cee5..24c4c2a 100644
--- a/nearby/presence/np_c_ffi/src/deserialize/v0.rs
+++ b/nearby/presence/np_c_ffi/src/deserialize/v0.rs
@@ -49,7 +49,7 @@
pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_payload(
adv: LegibleDeserializedV0Advertisement,
) -> V0Payload {
- adv.into_payload()
+ adv.payload()
}
/// Gets just the identity information associated with a `LegibleDeserializedV0Advertisement`.
@@ -57,7 +57,7 @@
pub extern "C" fn np_ffi_LegibleDeserializedV0Advertisement_into_identity(
adv: LegibleDeserializedV0Advertisement,
) -> DeserializedV0Identity {
- adv.into_identity()
+ adv.identity()
}
/// Deallocates any internal data of a `LegibleDeserializedV0Advertisement`
diff --git a/nearby/presence/np_c_ffi/src/lib.rs b/nearby/presence/np_c_ffi/src/lib.rs
index bd475ab..0dd6d33 100644
--- a/nearby/presence/np_c_ffi/src/lib.rs
+++ b/nearby/presence/np_c_ffi/src/lib.rs
@@ -14,17 +14,15 @@
//! NP Rust C FFI
-#![cfg_attr(not(test), no_std)]
-#![allow(dead_code)]
-extern crate alloc;
-extern crate core;
-
pub mod credentials;
pub mod deserialize;
+use lock_adapter::std::RwLock;
+use lock_adapter::RwLock as _;
+
/// Structure for categorized reasons for why a NP C FFI call may
/// be panicking.
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum PanicReason {
/// Some enum cast to a variant failed. Utilized
@@ -45,7 +43,6 @@
/// Structure which maintains information about the panic-handler
/// for panicking calls in the NP C FFI.
-#[derive(Debug)]
struct PanicHandler {
/// Optional function-pointer to client-specified panic behavior.
handler: Option<unsafe extern "C" fn(PanicReason) -> ()>,
@@ -81,27 +78,18 @@
}
Self::system_handler(panic_reason)
}
- #[cfg(feature = "std")]
+
fn system_handler(panic_reason: PanicReason) -> ! {
- eprintln!("NP FFI Panicked: {:?}", panic_reason);
+ std::eprintln!("NP FFI Panicked: {:?}", panic_reason);
let backtrace = std::backtrace::Backtrace::capture();
- eprintln!("Stack trace: {}", backtrace);
- std::process::abort!();
- }
- #[cfg(not(feature = "std"))]
- #[allow(clippy::empty_loop)]
- fn system_handler(_: PanicReason) -> ! {
- // Looping is the only platform-independent thing
- // that we can really do in this scenario.
- // (Even clippy's explanation for the empty-loop
- // lint mentions platform-specific intrinsics
- // as being the only true way to avoid this
- // in a no_std environment.)
- loop {}
+ std::eprintln!("Stack trace: {}", backtrace);
+ std::process::abort()
}
}
-static PANIC_HANDLER: spin::RwLock<PanicHandler> = spin::RwLock::new(PanicHandler::new());
+//TODO: use a std library RwLock if we have that available, spin is only needed for no_std and
+// won't be as performant
+static PANIC_HANDLER: RwLock<PanicHandler> = RwLock::new(PanicHandler::new());
pub(crate) fn panic(reason: PanicReason) -> ! {
PANIC_HANDLER.read().panic(reason)
diff --git a/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt b/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt
index 17f2f1f..0a1c088 100644
--- a/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt
+++ b/nearby/presence/np_cpp_ffi/fuzz/CMakeLists.txt
@@ -16,6 +16,7 @@
# libfuzzer needs clang
SET(CMAKE_C_COMPILER "clang")
+SET(CMAKE_CXX_COMPILER "clang++")
add_compile_definitions("CORPUS_DIR=${CMAKE_CURRENT_SOURCE_DIR}/corpus")
file(COPY corpus DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
diff --git a/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt b/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt
index da33258..3987edd 100644
--- a/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt
+++ b/nearby/presence/np_cpp_ffi/sample/CMakeLists.txt
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-add_executable(np_cpp_sample main.cpp)
+add_executable(np_cpp_sample main.cc)
target_link_libraries(np_cpp_sample nearby_protocol)
diff --git a/nearby/presence/np_cpp_ffi/sample/main.cpp b/nearby/presence/np_cpp_ffi/sample/main.cc
similarity index 98%
rename from nearby/presence/np_cpp_ffi/sample/main.cpp
rename to nearby/presence/np_cpp_ffi/sample/main.cc
index 114ed64..a18b996 100644
--- a/nearby/presence/np_cpp_ffi/sample/main.cpp
+++ b/nearby/presence/np_cpp_ffi/sample/main.cc
@@ -132,7 +132,7 @@
std::cout << "AssertFailed \n";
break;
}
- case np_ffi::internal::PanicReason::InvalidActionBits: {
+ case nearby_protocol::PanicReason::InvalidActionBits: {
std::cout << "InvalidActionBits \n";
break;
}
diff --git a/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc b/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc
index 27e98a6..67ca828 100644
--- a/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc
+++ b/nearby/presence/np_cpp_ffi/shared/shared_test_util.cc
@@ -14,6 +14,9 @@
#include "nearby_protocol.h"
+#include <cstdlib>
+#include <cstddef> // IWYU pragma: keep
+
std::string PanicReasonToString(nearby_protocol::PanicReason reason) {
switch (reason) {
case nearby_protocol::PanicReason::EnumCastFailed: {
@@ -22,7 +25,7 @@
case nearby_protocol::PanicReason::AssertFailed: {
return "AssertFailed";
}
- case np_ffi::internal::PanicReason::InvalidActionBits: {
+ case nearby_protocol::PanicReason::InvalidActionBits: {
return "InvalidActionBits";
}
}
diff --git a/nearby/presence/np_cpp_ffi/shared/shared_test_util.h b/nearby/presence/np_cpp_ffi/shared/shared_test_util.h
index 52c5363..22d1bb3 100644
--- a/nearby/presence/np_cpp_ffi/shared/shared_test_util.h
+++ b/nearby/presence/np_cpp_ffi/shared/shared_test_util.h
@@ -18,7 +18,7 @@
#include "nearby_protocol.h"
inline nearby_protocol::RawAdvertisementPayload
- V0AdvEmpty(nearby_protocol::ByteBuffer<255>({1, {0x00}}));
+ V0AdvEmpty(nearby_protocol::ByteBuffer<255>({2, {0x00, 0x03}}));
inline nearby_protocol::RawAdvertisementPayload
V0AdvSimple(nearby_protocol::ByteBuffer<255>({
@@ -37,20 +37,6 @@
0x03, // Public Identity DE header
0x15, 0x03 // Length 1 Tx Power DE with value 3
}}));
-
-inline nearby_protocol::RawAdvertisementPayload
- V1AdvMultipleSections(nearby_protocol::ByteBuffer<255>(
- {10,
- {
- 0x20, // V1 Advertisement header
- 0x04, // Section Header
- 0x03, // Public Identity DE header
- 0x26, 0x00, 0x46, // Length 2 Actions DE
- 0x03, // Section Header
- 0x03, // Public Identity DE header
- 0x15, 0x03 // Length 1 Tx Power DE with value 3
- }}));
-
void test_panic_handler(nearby_protocol::PanicReason reason);
std::string generate_hex_string(size_t length);
diff --git a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
index 5eb9ce2..53218ee 100644
--- a/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/credential_book_tests.cc
@@ -20,7 +20,7 @@
TEST(NpFfiCredentialBookTests, TestMoveConstructor) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto deserialize_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -28,7 +28,7 @@
// still result in success
nearby_protocol::CredentialBook next_book(std::move(book));
auto deserialize_result_moved =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty,
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
next_book);
ASSERT_EQ(deserialize_result_moved.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -36,21 +36,21 @@
// The old object should now lead to use after moved assert failure
ASSERT_DEATH([[maybe_unused]] auto failure =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book), // NOLINT(bugprone-use-after-move)
+ V0AdvSimple, book), // NOLINT(bugprone-use-after-move)
"");
// moving again should still lead to a use after moved assert failure
nearby_protocol::CredentialBook another_moved_book(std::move(book));
ASSERT_DEATH([[maybe_unused]] auto failure =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, another_moved_book),
+ V0AdvSimple, another_moved_book),
"");
}
TEST(NpFfiCredentialBookTests, TestMoveAssignment) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto deserialize_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -60,7 +60,7 @@
// new credential book should still be successful
auto deserialize_result_other =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty,
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
other_book);
ASSERT_EQ(deserialize_result_other.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -68,13 +68,13 @@
// The old object should now lead to use after moved assert failure
ASSERT_DEATH([[maybe_unused]] auto failure =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book), // NOLINT(bugprone-use-after-move)
+ V0AdvSimple, book), // NOLINT(bugprone-use-after-move)
"");
// moving again should still lead to a use after moved assert failure
auto another_moved_book = std::move(book);
ASSERT_DEATH([[maybe_unused]] auto failure =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, another_moved_book),
+ V0AdvSimple, another_moved_book),
"");
}
\ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
index e3a44d5..54872c6 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_result_tests.cc
@@ -21,7 +21,7 @@
TEST(NpFfiDeserializeResultTests, TestResultMoveConstructor) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -89,13 +89,13 @@
TEST(NpFfiDeserializeResultTests, TestResultMoveAssignment) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
// create a second result
auto another_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(another_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -165,7 +165,7 @@
ASSERT_TRUE(maybe_credential_book.ok());
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, maybe_credential_book.value());
+ V0AdvSimple, maybe_credential_book.value());
ASSERT_EQ(deserialize_result.GetKind(),
nearby_protocol::DeserializeAdvertisementResultKind::V0);
@@ -179,7 +179,7 @@
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -215,7 +215,7 @@
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -253,7 +253,7 @@
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
index 9781720..cd95c48 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v0_tests.cc
@@ -207,29 +207,13 @@
V0AdvEmpty, maybe_credential_book.value());
ASSERT_EQ(deserialize_result.GetKind(),
- nearby_protocol::DeserializeAdvertisementResultKind::V0);
- auto v0_adv = deserialize_result.IntoV0();
-
- ASSERT_EQ(v0_adv.GetKind(),
- nearby_protocol::DeserializedV0AdvertisementKind::Legible);
- auto legible_adv = v0_adv.IntoLegible();
- auto identity = legible_adv.GetIdentity();
- ASSERT_EQ(identity.GetKind(),
- nearby_protocol::DeserializedV0IdentityKind::Plaintext);
-
- auto num_des = legible_adv.GetNumberOfDataElements();
- ASSERT_EQ(num_des, 0);
- auto payload = legible_adv.IntoPayload();
-
- auto result = payload.TryGetDataElement(0);
- ASSERT_FALSE(result.ok());
- ASSERT_TRUE(absl::IsOutOfRange(result.status()));
+ nearby_protocol::DeserializeAdvertisementResultKind::Error);
}
TEST(NpFfiDeserializeV0Tests, TestV0AdvMoveConstructor) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto adv = result.IntoV0();
@@ -256,14 +240,14 @@
TEST(NpFfiDeserializeResultTests, TestV0AdvMoveAssignment) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto adv = result.IntoV0();
// create a second result
auto another_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(another_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto adv2 = another_result.IntoV0();
@@ -293,14 +277,14 @@
{
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
// Going over max amount should result in error
auto deserialize_result2 =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result2.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::Error);
@@ -313,7 +297,7 @@
// will create room for one more to be created.
auto deserialize_result3 =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result3.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
}
@@ -341,14 +325,14 @@
TEST(NpFfiDeserializeV0Tests, TestLegibleAdvMoveConstructor) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto legible = result.IntoV0().IntoLegible();
// Now move the adv into a new value, and make sure its still valid
nearby_protocol::LegibleDeserializedV0Advertisement moved(std::move(legible));
- ASSERT_EQ(moved.GetNumberOfDataElements(), 0);
+ ASSERT_EQ(moved.GetNumberOfDataElements(), 1);
ASSERT_EQ(moved.GetIdentity().GetKind(),
np_ffi::internal::DeserializedV0IdentityKind::Plaintext);
@@ -375,14 +359,14 @@
TEST(NpFfiDeserializeResultTests, TestLegibleAdvMoveAssignment) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto legible = result.IntoV0().IntoLegible();
// create a second result
auto another_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(another_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto legible2 = another_result.IntoV0().IntoLegible();
diff --git a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
index 48b881b..115ad14 100644
--- a/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/deserialize_v1_tests.cc
@@ -55,50 +55,6 @@
ASSERT_EQ(vec, expected);
}
-TEST(NpFfiDeserializeV1Tests, V1MultipleSections) {
- auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
- ASSERT_TRUE(maybe_credential_book.ok());
-
- auto deserialize_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(
- V1AdvMultipleSections, maybe_credential_book.value());
- ASSERT_EQ(deserialize_result.GetKind(),
- nearby_protocol::DeserializeAdvertisementResultKind::V1);
-
- auto v1_adv = deserialize_result.IntoV1();
- ASSERT_EQ(v1_adv.GetNumLegibleSections(), 2);
- ASSERT_EQ(v1_adv.GetNumUndecryptableSections(), 0);
-
- auto invalid = v1_adv.TryGetSection(2);
- ASSERT_FALSE(invalid.ok());
- ASSERT_TRUE(absl::IsOutOfRange(invalid.status()));
-
- auto section = v1_adv.TryGetSection(0);
- ASSERT_TRUE(section.ok());
- ASSERT_EQ(section.value().GetIdentityKind(),
- nearby_protocol::DeserializedV1IdentityKind::Plaintext);
- ASSERT_EQ(section.value().NumberOfDataElements(), 1);
-
- auto invalid_de = section.value().TryGetDataElement(1);
- ASSERT_FALSE(invalid_de.ok());
- ASSERT_TRUE(absl::IsOutOfRange(invalid_de.status()));
-
- auto de = section.value().TryGetDataElement(0);
- ASSERT_TRUE(de.ok());
- ASSERT_EQ(de.value().GetDataElementTypeCode(), 6);
-
- auto payload = de.value().GetPayload();
- auto vec = payload.ToVector();
- std::vector<uint8_t> expected{0x00, 0x46};
- ASSERT_EQ(vec, expected);
-
- auto section2 = v1_adv.TryGetSection(1);
- ASSERT_TRUE(section2.ok());
- ASSERT_EQ(section2.value().GetIdentityKind(),
- nearby_protocol::DeserializedV1IdentityKind::Plaintext);
- ASSERT_EQ(section2.value().NumberOfDataElements(), 1);
-}
-
TEST(NpFfiDeserializeV1Tests, TestV1AdvMoveConstructor) {
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto result = nearby_protocol::Deserializer::DeserializeAdvertisement(
@@ -216,39 +172,50 @@
ASSERT_TRUE(TryDeserializeNewV1Adv(maybe_credential_book.value()));
}
-void NestedGetSectionsPart2(nearby_protocol::DeserializedV1Advertisement &adv,
- nearby_protocol::CredentialBook &book) {
- { auto section = adv.TryGetSection(1); }
- assert(!TryDeserializeNewV1Adv(book));
-}
-
-nearby_protocol::DeserializedV1Section
-NestedGetSections(nearby_protocol::CredentialBook &book) {
- auto v1_adv = nearby_protocol::Deserializer::DeserializeAdvertisement(
- V1AdvMultipleSections, book)
- .IntoV1();
- auto section = v1_adv.TryGetSection(0);
- NestedGetSectionsPart2(v1_adv, book);
- return section.value();
-}
-
-// Make sure handle stays in scope until all of the sections and adv have gone
-// out of scope
-TEST(NpFfiDeserializeV1Tests, TestSectionOwnershipMultipleSections) {
- // Capping the max number so we can verify that de-allocations are happening
- nearby_protocol::GlobalConfig::SetMaxNumDeserializedV1Advertisements(1);
+/*
+ * Multiple sections are not supported in plaintext advertisements
+ * TODO Update the below test to use encrypted sections
+TEST(NpFfiDeserializeV1Tests, V1MultipleSections) {
auto maybe_credential_book = nearby_protocol::CredentialBook::TryCreate();
ASSERT_TRUE(maybe_credential_book.ok());
- {
- auto section = NestedGetSections(maybe_credential_book.value());
- ASSERT_EQ(section.GetIdentityKind(),
- nearby_protocol::DeserializedV1IdentityKind::Plaintext);
- ASSERT_EQ(section.NumberOfDataElements(), 1);
- ASSERT_FALSE(TryDeserializeNewV1Adv(maybe_credential_book.value()));
- }
+ auto deserialize_result =
+ nearby_protocol::Deserializer::DeserializeAdvertisement(
+ V1AdvMultipleSections, maybe_credential_book.value());
+ ASSERT_EQ(deserialize_result.GetKind(),
+ nearby_protocol::DeserializeAdvertisementResultKind::V1);
- // now that the section has gone out of scope, deserializing a new adv should
- // succeed
- ASSERT_TRUE(TryDeserializeNewV1Adv(maybe_credential_book.value()));
-}
\ No newline at end of file
+ auto v1_adv = deserialize_result.IntoV1();
+ ASSERT_EQ(v1_adv.GetNumLegibleSections(), 2);
+ ASSERT_EQ(v1_adv.GetNumUndecryptableSections(), 0);
+
+ auto invalid = v1_adv.TryGetSection(2);
+ ASSERT_FALSE(invalid.ok());
+ ASSERT_TRUE(absl::IsOutOfRange(invalid.status()));
+
+ auto section = v1_adv.TryGetSection(0);
+ ASSERT_TRUE(section.ok());
+ ASSERT_EQ(section.value().GetIdentityKind(),
+ nearby_protocol::DeserializedV1IdentityKind::Plaintext);
+ ASSERT_EQ(section.value().NumberOfDataElements(), 1);
+
+ auto invalid_de = section.value().TryGetDataElement(1);
+ ASSERT_FALSE(invalid_de.ok());
+ ASSERT_TRUE(absl::IsOutOfRange(invalid_de.status()));
+
+ auto de = section.value().TryGetDataElement(0);
+ ASSERT_TRUE(de.ok());
+ ASSERT_EQ(de.value().GetDataElementTypeCode(), 6);
+
+ auto payload = de.value().GetPayload();
+ auto vec = payload.ToVector();
+ std::vector<uint8_t> expected{0x00, 0x46};
+ ASSERT_EQ(vec, expected);
+
+ auto section2 = v1_adv.TryGetSection(1);
+ ASSERT_TRUE(section2.ok());
+ ASSERT_EQ(section2.value().GetIdentityKind(),
+ nearby_protocol::DeserializedV1IdentityKind::Plaintext);
+ ASSERT_EQ(section2.value().NumberOfDataElements(), 1);
+}
+*/
\ No newline at end of file
diff --git a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc b/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
index e120aee..6936f23 100644
--- a/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
+++ b/nearby/presence/np_cpp_ffi/tests/global_config_tests.cc
@@ -24,7 +24,7 @@
nearby_protocol::GlobalConfig::SetPanicHandler(test_panic_handler));
auto book = nearby_protocol::CredentialBook::TryCreate().value();
auto deserialize_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -54,7 +54,7 @@
auto book2 = nearby_protocol::CredentialBook::TryCreate().value();
auto book3 = nearby_protocol::CredentialBook::TryCreate().value();
auto deserialize_result =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
// Should still work
ASSERT_EQ(deserialize_result.GetKind(),
@@ -64,12 +64,12 @@
// still work.
nearby_protocol::GlobalConfig::SetNumShards(1);
auto deserialize_result_2 =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty,
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
book2);
ASSERT_EQ(deserialize_result_2.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto deserialize_result_3 =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty,
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple,
book3);
ASSERT_EQ(deserialize_result_3.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
@@ -81,11 +81,11 @@
// should still be able to parse 2 payloads with only one shard
auto deserialize_result1 =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(deserialize_result1.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
auto deserialize_result2 =
- nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvEmpty, book);
+ nearby_protocol::Deserializer::DeserializeAdvertisement(V0AdvSimple, book);
ASSERT_EQ(deserialize_result2.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
}
@@ -119,14 +119,14 @@
{
auto deserialize_result =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
// Going over max amount should result in error
auto deserialize_result2 =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result2.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::Error);
}
@@ -135,7 +135,7 @@
// will create room for one more to be created.
auto deserialize_result3 =
nearby_protocol::Deserializer::DeserializeAdvertisement(
- V0AdvEmpty, book_result.value());
+ V0AdvSimple, book_result.value());
ASSERT_EQ(deserialize_result3.GetKind(),
np_ffi::internal::DeserializeAdvertisementResultKind::V0);
}
diff --git a/nearby/presence/np_ed25519/Cargo.toml b/nearby/presence/np_ed25519/Cargo.toml
index b4bca66..601fc82 100644
--- a/nearby/presence/np_ed25519/Cargo.toml
+++ b/nearby/presence/np_ed25519/Cargo.toml
@@ -6,7 +6,7 @@
[dependencies]
array_view.workspace = true
-crypto_provider.workspace = true
+crypto_provider = {workspace = true, features = ["raw_private_key_permit"]}
sink.workspace = true
tinyvec.workspace = true
diff --git a/nearby/presence/np_ed25519/src/lib.rs b/nearby/presence/np_ed25519/src/lib.rs
index 60c8302..81a1a10 100644
--- a/nearby/presence/np_ed25519/src/lib.rs
+++ b/nearby/presence/np_ed25519/src/lib.rs
@@ -26,8 +26,8 @@
use array_view::ArrayView;
use crypto_provider::ed25519::{
- Ed25519Provider, KeyPair as _, PublicKey as _, RawPrivateKey, RawPublicKey, RawSignature,
- Signature as _, SignatureError,
+ Ed25519Provider, KeyPair as _, PrivateKey, PublicKey as _, RawPrivateKey, RawPrivateKeyPermit,
+ RawPublicKey, RawSignature, Signature as _, SignatureError,
};
use crypto_provider::CryptoProvider;
use sink::{Sink, SinkWriter};
@@ -55,15 +55,30 @@
pub struct KeyPair<C: CryptoProvider>(CpKeyPair<C>);
impl<C: CryptoProvider> KeyPair<C> {
- /// Returns the `KeyPair`'s private key bytes. This method should only ever be called by code
- /// which securely stores private credentials.
- pub fn private_key(&self) -> RawPrivateKey {
+ /// Returns the `KeyPair`'s private key bytes.
+ /// This method is only usable in situations where
+ /// the caller has permission to handle the raw bytes
+ /// of a private key.
+ pub fn raw_private_key(&self, permit: &RawPrivateKeyPermit) -> RawPrivateKey {
+ self.0.raw_private_key(permit)
+ }
+
+ /// Builds this key-pair from an array of its private key bytes in the format
+ /// yielded by `private_key`.
+ /// This method is only usable in situations where
+ /// the caller has permission to handle the raw bytes
+ /// of a private key.
+ pub fn from_raw_private_key(private_key: &RawPrivateKey, permit: &RawPrivateKeyPermit) -> Self {
+ Self(CpKeyPair::<C>::from_raw_private_key(private_key, permit))
+ }
+
+ /// Returns the private key of this key-pair.
+ pub fn private_key(&self) -> PrivateKey {
self.0.private_key()
}
- /// Builds this key-pair from an array of its private key bytes in the yielded by `private_key`.
- /// This method should only ever be called by code which securely stores private credentials.
- pub fn from_private_key(private_key: &RawPrivateKey) -> Self {
+ /// Builds this key-pair from a private key.
+ pub fn from_private_key(private_key: &PrivateKey) -> Self {
Self(CpKeyPair::<C>::from_private_key(private_key))
}
@@ -127,6 +142,11 @@
}
impl<C: CryptoProvider> PublicKey<C> {
+ /// Constructs a public key for NP adv signature verification
+ /// from a public key under the given crypto-provider
+ pub fn new(public_key: CpPublicKey<C>) -> Self {
+ Self { public_key }
+ }
/// Succeeds if the signature was a valid signature created via the corresponding
/// keypair to this public key using the given [`SignatureContext`] on the given
/// message payload. The message payload is represented
diff --git a/nearby/presence/np_ffi_core/Cargo.toml b/nearby/presence/np_ffi_core/Cargo.toml
index 5be03a6..cdcc7d3 100644
--- a/nearby/presence/np_ffi_core/Cargo.toml
+++ b/nearby/presence/np_ffi_core/Cargo.toml
@@ -9,9 +9,12 @@
np_adv.workspace = true
handle_map.workspace = true
crypto_provider.workspace = true
-#TODO: Allow this to be configurable!
-crypto_provider_default = {workspace = true, features = ["rustcrypto"]}
-spin.workspace = true
+crypto_provider_default = {workspace = true, default-features = false}
+lock_adapter.workspace = true
+lazy_static.workspace = true
[features]
-std = []
+default = ["rustcrypto"]
+rustcrypto = ["crypto_provider_default/rustcrypto", "crypto_provider_default/std"]
+opensslbssl = ["crypto_provider_default/opensslbssl"]
+boringssl = ["crypto_provider_default/boringssl"]
diff --git a/nearby/presence/np_ffi_core/src/common.rs b/nearby/presence/np_ffi_core/src/common.rs
index f00c358..1683b68 100644
--- a/nearby/presence/np_ffi_core/src/common.rs
+++ b/nearby/presence/np_ffi_core/src/common.rs
@@ -14,9 +14,11 @@
//! Common externally-accessible FFI constructs which are needed
//! in order to define the interfaces in this crate's various modules.
-use alloc::string::String;
use array_view::ArrayView;
use handle_map::HandleNotPresentError;
+use lock_adapter::std::RwLock;
+use lock_adapter::RwLock as _;
+use std::string::String;
const DEFAULT_MAX_HANDLES: u32 = u32::MAX - 1;
@@ -133,7 +135,7 @@
}
}
-static COMMON_CONFIG: spin::RwLock<CommonConfig> = spin::RwLock::new(CommonConfig::new());
+static COMMON_CONFIG: RwLock<CommonConfig> = RwLock::new(CommonConfig::new());
pub(crate) fn global_num_shards() -> u8 {
COMMON_CONFIG.read().num_shards()
diff --git a/nearby/presence/np_ffi_core/src/credentials.rs b/nearby/presence/np_ffi_core/src/credentials.rs
index 412ebc1..5392417 100644
--- a/nearby/presence/np_ffi_core/src/credentials.rs
+++ b/nearby/presence/np_ffi_core/src/credentials.rs
@@ -19,7 +19,7 @@
/// Internal, Rust-side implementation of a credential-book.
/// See [`CredentialBook`] for the FFI-side handles.
-// TODO(b/283249542): Give this a real definition!
+// TODO: Give this a real definition!
pub struct CredentialBookInternals;
fn get_credential_book_handle_map_dimensions() -> HandleMapDimensions {
@@ -29,8 +29,13 @@
}
}
-declare_handle_map! {credential_book, CredentialBook, super::CredentialBookInternals, super::get_credential_book_handle_map_dimensions()}
-use credential_book::CredentialBook;
+declare_handle_map! {
+ mod credential_book {
+ #[dimensions = super::get_credential_book_handle_map_dimensions()]
+ type CredentialBook: HandleLike<Object = super::CredentialBookInternals>;
+ }
+}
+pub use credential_book::CredentialBook;
/// Discriminant for `CreateCredentialBookResult`
#[repr(u8)]
diff --git a/nearby/presence/np_ffi_core/src/deserialize/mod.rs b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
index a651bcf..9c20fde 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/mod.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/mod.rs
@@ -20,6 +20,7 @@
use crate::utils::FfiEnum;
use crypto_provider_default::CryptoProviderImpl;
use handle_map::{HandleLike, HandleMapFullError, HandleNotPresentError};
+use np_adv::deserialization_arena;
pub mod v0;
pub mod v1;
@@ -106,17 +107,6 @@
}
}
-type DefaultV0Credential = np_adv::credential::simple::SimpleV0Credential<
- np_adv::credential::v0::MinimumFootprintV0CryptoMaterial,
- (),
->;
-type DefaultV1Credential = np_adv::credential::simple::SimpleV1Credential<
- np_adv::credential::v1::MinimumFootprintV1CryptoMaterial,
- (),
->;
-type DefaultBothCredentialSource =
- np_adv::credential::source::OwnedBothCredentialSource<DefaultV0Credential, DefaultV1Credential>;
-
fn deserialize_advertisement_from_slice_internal(
adv_payload: &[u8],
credential_book: CredentialBook,
@@ -124,14 +114,14 @@
// Deadlock Safety: Credential-book locks always live longer than deserialized advs.
let _credential_book_read_guard = credential_book.get()?;
- //TODO(b/283249542): Use an actual credential source
- let cred_source: DefaultBothCredentialSource = DefaultBothCredentialSource::new_empty();
+ //TODO: Use an actual credential source
+ let cred_book = np_adv::credential::book::CredentialBookBuilder::<
+ np_adv::credential::EmptyMatchedCredential,
+ >::build_cached_slice_book::<0, 0, CryptoProviderImpl>(&[], &[]);
+ let arena = deserialization_arena!();
let deserialized_advertisement =
- np_adv::deserialize_advertisement::<_, _, _, _, CryptoProviderImpl>(
- adv_payload,
- &cred_source,
- )?;
+ np_adv::deserialize_advertisement::<_, CryptoProviderImpl>(arena, adv_payload, &cred_book)?;
match deserialized_advertisement {
np_adv::DeserializedAdvertisement::V0(adv_contents) => {
let adv_handle = DeserializedV0Advertisement::allocate_with_contents(adv_contents)?;
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v0.rs b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
index 0f4b24b..7b9ee75 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v0.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v0.rs
@@ -16,10 +16,10 @@
use crate::common::*;
use crate::credentials::credential_book::CredentialBook;
use crate::utils::{FfiEnum, LocksLongerThan};
-use alloc::vec::Vec;
use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError};
use np_adv::legacy::actions::ActionsDataElement;
use np_adv::legacy::{data_elements as np_adv_de, Ciphertext, PacketFlavorEnum, Plaintext};
+use std::vec::Vec;
/// Discriminant for possible results of V0 advertisement deserialization
#[derive(Clone, Copy)]
@@ -66,20 +66,22 @@
}
}
- pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>(
- contents: np_adv::V0AdvContents<'m, M>,
+ pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
+ contents: np_adv::V0AdvertisementContents<M>,
) -> Result<Self, HandleMapFullError> {
match contents {
- np_adv::V0AdvContents::Plaintext(plaintext_contents) => {
+ np_adv::V0AdvertisementContents::Plaintext(plaintext_contents) => {
let adv = LegibleDeserializedV0Advertisement::allocate_with_plaintext_contents(
plaintext_contents,
)?;
Ok(Self::Legible(adv))
}
- np_adv::V0AdvContents::Decrypted(_) => {
+ np_adv::V0AdvertisementContents::Decrypted(_) => {
unimplemented!();
}
- np_adv::V0AdvContents::NoMatchingCredentials => Ok(Self::NoMatchingCredentials),
+ np_adv::V0AdvertisementContents::NoMatchingCredentials => {
+ Ok(Self::NoMatchingCredentials)
+ }
}
}
@@ -109,11 +111,11 @@
self.num_des
}
/// Destructures this legible advertisement into just the payload
- pub fn into_payload(self) -> V0Payload {
+ pub fn payload(&self) -> V0Payload {
self.payload
}
/// Destructures this legible advertisement into just the identity information
- pub fn into_identity(self) -> DeserializedV0Identity {
+ pub fn identity(&self) -> DeserializedV0Identity {
self.identity
}
/// Deallocates the underlying handle of the payload
@@ -136,6 +138,7 @@
/// by a deserialized V0 advertisement
#[repr(C)]
#[allow(missing_docs)]
+#[derive(Copy, Clone, Eq, PartialEq)]
pub enum DeserializedV0Identity {
Plaintext,
// TODO: This gets a payload once we support creds
@@ -176,7 +179,12 @@
}
}
-declare_handle_map! {v0_payload, V0Payload, super::V0PayloadInternals, super::get_v0_payload_handle_map_dimensions() }
+declare_handle_map! {
+ mod v0_payload {
+ #[dimensions = super::get_v0_payload_handle_map_dimensions()]
+ type V0Payload: HandleLike<Object = super::V0PayloadInternals>;
+ }
+}
use v0_payload::V0Payload;
impl LocksLongerThan<V0Payload> for CredentialBook {}
@@ -327,10 +335,10 @@
fn from(value: ActionsDataElement<F>) -> Self {
match F::ENUM_VARIANT {
PacketFlavorEnum::Plaintext => {
- Self::Plaintext(V0ActionBits { bitfield: value.as_u32() })
+ Self::Plaintext(V0ActionBits { bitfield: value.action.as_u32() })
}
PacketFlavorEnum::Ciphertext => {
- Self::Encrypted(V0ActionBits { bitfield: value.as_u32() })
+ Self::Encrypted(V0ActionBits { bitfield: value.action.as_u32() })
}
}
}
@@ -402,6 +410,7 @@
let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits);
Ok(actions_de
+ .action
.has_action(&action_type.into())
.expect("BooleanActionType only has one bit"))
}
@@ -413,6 +422,7 @@
let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits);
Ok(actions_de
+ .action
.has_action(&action_type.into())
.expect("BooleanActionType only has one bit"))
}
@@ -430,7 +440,7 @@
.map_err(|_| InvalidActionBits)?;
let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits);
- Ok(actions_de.context_sync_seq_num().as_u8())
+ Ok(actions_de.action.context_sync_seq_num().as_u8())
}
V0Actions::Encrypted(action_bits) => {
let bits = np_adv::legacy::actions::ActionBits::<Ciphertext>::try_from(
@@ -438,7 +448,7 @@
)
.map_err(|_| InvalidActionBits)?;
let actions_de = np_adv::legacy::actions::ActionsDataElement::from(bits);
- Ok(actions_de.context_sync_seq_num().as_u8())
+ Ok(actions_de.action.context_sync_seq_num().as_u8())
}
}
}
diff --git a/nearby/presence/np_ffi_core/src/deserialize/v1.rs b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
index 9b4a3f4..be44fb7 100644
--- a/nearby/presence/np_ffi_core/src/deserialize/v1.rs
+++ b/nearby/presence/np_ffi_core/src/deserialize/v1.rs
@@ -16,9 +16,10 @@
use crate::common::*;
use crate::credentials::credential_book::CredentialBook;
use crate::utils::*;
-use alloc::vec::Vec;
use array_view::ArrayView;
use handle_map::{declare_handle_map, HandleLike, HandleMapDimensions, HandleMapFullError};
+use np_adv::extended::deserialize::DataElementParseError;
+use std::vec::Vec;
/// Representation of a deserialized V1 advertisement
#[repr(C)]
@@ -56,12 +57,12 @@
self.legible_sections.deallocate().map(|_| ()).into()
}
- pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>(
- contents: np_adv::V1AdvContents<'m, M>,
+ pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
+ contents: np_adv::V1AdvertisementContents<M>,
) -> Result<Self, HandleMapFullError> {
// 16-section limit enforced by np_adv
let num_undecryptable_sections = contents.invalid_sections_count() as u8;
- let legible_sections = contents.into_valid_sections();
+ let legible_sections = contents.into_sections();
let num_legible_sections = legible_sections.len() as u8;
let legible_sections = LegibleV1Sections::allocate_with_contents(legible_sections)?;
Ok(Self { num_undecryptable_sections, num_legible_sections, legible_sections })
@@ -105,11 +106,14 @@
}
}
-impl<'m, M: np_adv::credential::MatchedCredential<'m>>
- From<Vec<np_adv::V1DeserializedSection<'m, M>>> for LegibleV1SectionsInternals
+impl<'adv, M: np_adv::credential::MatchedCredential>
+ From<Vec<np_adv::V1DeserializedSection<'adv, M>>> for LegibleV1SectionsInternals
{
- fn from(contents: Vec<np_adv::V1DeserializedSection<'m, M>>) -> Self {
- let sections = contents.into_iter().map(DeserializedV1SectionInternals::from).collect();
+ fn from(contents: Vec<np_adv::V1DeserializedSection<'adv, M>>) -> Self {
+ let sections = contents
+ .into_iter()
+ .filter_map(|s| DeserializedV1SectionInternals::try_from(s).ok())
+ .collect();
Self { sections }
}
}
@@ -121,14 +125,19 @@
}
}
-declare_handle_map! {legible_v1_sections, LegibleV1Sections, super::LegibleV1SectionsInternals, super::get_legible_v1_sections_handle_map_dimensions() }
+declare_handle_map! {
+ mod legible_v1_sections {
+ #[dimensions = super::get_legible_v1_sections_handle_map_dimensions()]
+ type LegibleV1Sections: HandleLike<Object = super::LegibleV1SectionsInternals>;
+ }
+}
use legible_v1_sections::LegibleV1Sections;
impl LocksLongerThan<LegibleV1Sections> for CredentialBook {}
impl LegibleV1Sections {
- pub(crate) fn allocate_with_contents<'m, M: np_adv::credential::MatchedCredential<'m>>(
- contents: Vec<np_adv::V1DeserializedSection<'m, M>>,
+ pub(crate) fn allocate_with_contents<M: np_adv::credential::MatchedCredential>(
+ contents: Vec<np_adv::V1DeserializedSection<M>>,
) -> Result<Self, HandleMapFullError> {
Self::allocate(move || LegibleV1SectionsInternals::from(contents))
}
@@ -197,17 +206,22 @@
}
}
-impl<'m, M: np_adv::credential::MatchedCredential<'m>> From<np_adv::V1DeserializedSection<'m, M>>
+impl<'adv, M: np_adv::credential::MatchedCredential> TryFrom<np_adv::V1DeserializedSection<'adv, M>>
for DeserializedV1SectionInternals
{
- fn from(section: np_adv::V1DeserializedSection<'m, M>) -> Self {
+ type Error = DataElementParseError;
+
+ fn try_from(section: np_adv::V1DeserializedSection<M>) -> Result<Self, Self::Error> {
use np_adv::extended::deserialize::Section;
use np_adv::V1DeserializedSection;
match section {
V1DeserializedSection::Plaintext(section) => {
- let des = section.data_elements().map(V1DataElement::from).collect();
+ let des = section
+ .iter_data_elements()
+ .map(|r| r.map(|de| V1DataElement::from(&de)))
+ .collect::<Result<Vec<_>, _>>()?;
let identity = DeserializedV1Identity::Plaintext;
- Self { des, identity }
+ Ok(Self { des, identity })
}
V1DeserializedSection::Decrypted(_) => {
unimplemented!();
@@ -341,8 +355,8 @@
}
}
-impl<'a> From<np_adv::extended::deserialize::DataElement<'a>> for V1DataElement {
- fn from(de: np_adv::extended::deserialize::DataElement<'a>) -> Self {
+impl<'a> From<&'a np_adv::extended::deserialize::DataElement<'a>> for V1DataElement {
+ fn from(de: &'a np_adv::extended::deserialize::DataElement<'a>) -> Self {
let de_type = V1DEType::from(de.de_type());
let contents_as_slice = de.contents();
//Guaranteed not to panic due DE size limit.
diff --git a/nearby/presence/np_ffi_core/src/lib.rs b/nearby/presence/np_ffi_core/src/lib.rs
index dd9e49e..bba33e2 100644
--- a/nearby/presence/np_ffi_core/src/lib.rs
+++ b/nearby/presence/np_ffi_core/src/lib.rs
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Core functionality common to all NP Rust FFI layers
-#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![deny(
missing_docs,
@@ -22,8 +21,8 @@
clippy::expect_used
)]
-extern crate alloc;
-extern crate core;
+#[macro_use]
+extern crate lazy_static;
#[macro_use]
pub mod utils;
diff --git a/nearby/presence/np_hkdf/src/v1_salt.rs b/nearby/presence/np_hkdf/src/v1_salt.rs
index b139e84..ae327db 100644
--- a/nearby/presence/np_hkdf/src/v1_salt.rs
+++ b/nearby/presence/np_hkdf/src/v1_salt.rs
@@ -60,6 +60,11 @@
self.data.as_slice()
}
+ /// Returns the salt bytes as an array
+ pub fn into_array(self) -> [u8; 16] {
+ self.data
+ }
+
/// Returns the salt bytes as a reference to an array
pub fn as_array_ref(&self) -> &[u8; 16] {
&self.data
@@ -91,7 +96,7 @@
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct DataElementOffset {
/// 0-based offset of the DE in the advertisement
- offset: usize,
+ offset: u8,
}
impl DataElementOffset {
@@ -99,7 +104,7 @@
pub const ZERO: DataElementOffset = Self { offset: 0 };
/// Returns the offset as a usize
- pub fn as_usize(&self) -> usize {
+ pub fn as_u8(&self) -> u8 {
self.offset
}
@@ -111,8 +116,8 @@
}
}
-impl From<usize> for DataElementOffset {
- fn from(num: usize) -> Self {
+impl From<u8> for DataElementOffset {
+ fn from(num: u8) -> Self {
Self { offset: num }
}
}
diff --git a/nearby/presence/xts_aes/Cargo.toml b/nearby/presence/xts_aes/Cargo.toml
index d0b88b6..ba7aadb 100644
--- a/nearby/presence/xts_aes/Cargo.toml
+++ b/nearby/presence/xts_aes/Cargo.toml
@@ -14,9 +14,10 @@
ldt_tbc.workspace = true
[dev-dependencies]
-crypto_provider_default.workspace = true
+crypto_provider_default = {workspace = true, features = ["rustcrypto"]}
rand_ext.workspace = true
test_helper.workspace = true
+wycheproof.workspace = true
anyhow.workspace = true
base64.workspace = true
diff --git a/nearby/presence/xts_aes/fuzz/Cargo.lock b/nearby/presence/xts_aes/fuzz/Cargo.lock
index f0b7f56..1844bd3 100644
--- a/nearby/presence/xts_aes/fuzz/Cargo.lock
+++ b/nearby/presence/xts_aes/fuzz/Cargo.lock
@@ -25,6 +25,20 @@
]
[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
name = "aes-gcm-siv"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -157,6 +171,9 @@
[[package]]
name = "crypto_provider"
version = "0.1.0"
+dependencies = [
+ "tinyvec",
+]
[[package]]
name = "crypto_provider_rustcrypto"
@@ -164,6 +181,7 @@
dependencies = [
"aead",
"aes",
+ "aes-gcm",
"aes-gcm-siv",
"cbc",
"cfg-if",
@@ -193,9 +211,9 @@
[[package]]
name = "curve25519-dalek"
-version = "4.0.0-rc.3"
+version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7"
+checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -261,9 +279,9 @@
[[package]]
name = "ed25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faa8e9049d5d72bfc12acbc05914731b5322f79b5e2f195e9f2d705fca22ab4c"
+checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -329,6 +347,16 @@
]
[[package]]
+name = "ghash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -564,6 +592,12 @@
]
[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+
+[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -599,9 +633,9 @@
[[package]]
name = "x25519-dalek"
-version = "2.0.0-rc.3"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec7fae07da688e17059d5886712c933bb0520f15eff2e09cfa18e30968f4e63a"
+checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96"
dependencies = [
"curve25519-dalek",
"rand_core",
diff --git a/nearby/presence/xts_aes/src/lib.rs b/nearby/presence/xts_aes/src/lib.rs
index ff68d76..ac2cf15 100644
--- a/nearby/presence/xts_aes/src/lib.rs
+++ b/nearby/presence/xts_aes/src/lib.rs
@@ -34,7 +34,7 @@
use core::fmt;
use core::marker::PhantomData;
-use crypto_provider::aes::{Aes, AesCipher, AesDecryptCipher, AesEncryptCipher};
+use crypto_provider::aes::{Aes, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher};
use crypto_provider::{
aes::{AesKey, BLOCK_SIZE},
CryptoProvider,
@@ -58,7 +58,7 @@
/// little endian bytes (e.g. with `u128::to_le_bytes()`).
///
/// Inside each data unit, there are one or more 16-byte blocks. The length of a data unit SHOULD
-/// not to exceed 2^20 blocks (16GiB) in order to maintain security properties; see
+/// not to exceed 2^20 blocks (16MB) in order to maintain security properties; see
/// [appendix D](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf).
/// The last block (if there is more than one block) may have fewer than 16 bytes, in which case
/// ciphertext stealing will be applied.
@@ -104,7 +104,6 @@
}
#[allow(clippy::expect_used)]
-#[allow(clippy::indexing_slicing)]
impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> {
/// Encrypt a data unit in place, using sequential block numbers for each block.
/// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than
@@ -118,20 +117,24 @@
standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| {
// Until array_chunks is stabilized, using a macro to get array refs out of slice chunks
// Won't panic because we are only processing complete blocks
+ #[allow(clippy::indexing_slicing)]
tweaked_xts.encrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE));
tweaked_xts.advance_to_next_block_num();
});
if partial_last_block.is_empty() {
+ #[allow(clippy::indexing_slicing)]
tweaked_xts.encrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE));
// nothing to do for the last block since it's empty
} else {
+ // This implements ciphertext stealing for the last two blocks which allows processing
+ // of a message length not divisible by block size without any expansion or padding.
// b is in bytes not bits; we do not consider partial bytes
let b = partial_last_block.len();
// Per spec: CC = encrypt P_{m-1} (the last complete block)
let cc = {
- let mut last_complete_block_plaintext: crypto_provider::aes::AesBlock =
+ let mut last_complete_block_plaintext: AesBlock =
last_complete_block.try_into().expect("complete block");
tweaked_xts.encrypt_block(&mut last_complete_block_plaintext);
tweaked_xts.advance_to_next_block_num();
@@ -139,7 +142,7 @@
};
// Copy b bytes of P_m before we overwrite them with C_m
- let mut partial_last_block_plaintext: crypto_provider::aes::AesBlock = [0; BLOCK_SIZE];
+ let mut partial_last_block_plaintext: AesBlock = [0; BLOCK_SIZE];
partial_last_block_plaintext
.get_mut(0..b)
.expect("this should never be hit, since a partial block is always smaller than a complete block")
@@ -148,7 +151,7 @@
// C_m = first b bytes of CC
partial_last_block.copy_from_slice(
cc.get(0..b)
- .expect("partial block len should always be less than a complete block"),
+ .expect("b is the len of partial_last_block so it will always be in bounds"),
);
// PP = P_m || last (16 - b) bytes of CC
@@ -173,7 +176,7 @@
Ok(())
}
- /// Returns an [XtsTweaked] configured with the specified tweak and a block number of 0.
+ /// Returns an [`XtsEncrypterTweaked`] configured with the specified tweak and a block number of 0.
fn tweaked(&self, tweak: Tweak) -> XtsEncrypterTweaked<A> {
let mut bytes = tweak.bytes;
self.tweak_encryption_cipher.encrypt(&mut bytes);
@@ -186,7 +189,6 @@
}
#[allow(clippy::expect_used)]
-#[allow(clippy::indexing_slicing)]
impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> {
/// Decrypt a data unit in place, using sequential block numbers for each block.
/// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than
@@ -198,11 +200,13 @@
let mut tweaked_xts = self.tweaked(tweak);
standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| {
+ #[allow(clippy::indexing_slicing)]
tweaked_xts.decrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE));
tweaked_xts.advance_to_next_block_num();
});
if partial_last_block.is_empty() {
+ #[allow(clippy::indexing_slicing)]
tweaked_xts.decrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE));
} else {
let b = partial_last_block.len();
@@ -215,7 +219,7 @@
tweaked_xts.advance_to_next_block_num();
// Block num is now at m
// We need C_m later, so make a copy to avoid overwriting
- let mut last_complete_block_ciphertext: crypto_provider::aes::AesBlock =
+ let mut last_complete_block_ciphertext: AesBlock =
last_complete_block.try_into().expect("complete block");
tweaked_xts.decrypt_block(&mut last_complete_block_ciphertext);
tweaked_xts.set_tweak(tweak_state_m_1);
@@ -223,28 +227,47 @@
};
// Copy b bytes of C_m before we overwrite them with P_m
- let mut partial_last_block_ciphertext: crypto_provider::aes::AesBlock = [0; BLOCK_SIZE];
- partial_last_block_ciphertext[0..b].copy_from_slice(partial_last_block);
+ let mut partial_last_block_ciphertext: AesBlock = [0; BLOCK_SIZE];
+ partial_last_block_ciphertext
+ .get_mut(0..b)
+ .expect("this should never be hit, since a partial block is always smaller than a complete block")
+ .copy_from_slice(partial_last_block);
// P_m = first b bytes of PP
- partial_last_block.copy_from_slice(&pp[0..b]);
+ partial_last_block.copy_from_slice(
+ pp.get(0..b)
+ .expect("b is the len of partial_last_block so it will always be in bounds"),
+ );
// CC = C_m | CP (last 16-b bytes of PP)
let cc = {
- let cp = &pp[b..];
- last_complete_block[0..b].copy_from_slice(&partial_last_block_ciphertext[0..b]);
- last_complete_block[b..].copy_from_slice(cp);
+ let cp = pp.get(b..).expect(
+ "b is in bounds since a partial block is always smaller than a complete block",
+ );
+ last_complete_block
+ .get_mut(0..b)
+ .expect("partial block length within bounds of complete block")
+ .copy_from_slice(
+ partial_last_block_ciphertext
+ .get(0..b)
+ .expect("partial block length within bounds of complete block"),
+ );
+ last_complete_block
+ .get_mut(b..)
+ .expect("partial block length within bounds of complete block")
+ .copy_from_slice(cp);
last_complete_block
};
// decrypting at block num = m -1
+ #[allow(clippy::indexing_slicing)]
tweaked_xts.decrypt_block(array_mut_ref!(cc, 0, BLOCK_SIZE));
}
Ok(())
}
- /// Returns an [XtsTweaked] configured with the specified tweak and a block number of 0.
+ /// Returns an [`XtsDecrypterTweaked`] configured with the specified tweak and a block number of 0.
fn tweaked(&self, tweak: Tweak) -> XtsDecrypterTweaked<A> {
let mut bytes = tweak.bytes;
self.tweak_encryption_cipher.encrypt(&mut bytes);
@@ -289,10 +312,10 @@
}
#[allow(clippy::expect_used)]
- fn encrypt(&self, tweak: Self::Tweak, block: &mut [u8; 16]) {
- // we're encrypting precisely one block, so the block number won't advance, and ciphertext
- // stealing will not be applied.
- self.encrypt_data_unit(tweak, block).expect("One block is a valid size");
+ fn encrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) {
+ // We're encrypting precisely one block, so the block number won't advance, and there is no
+ // need to apply ciphertext stealing
+ self.tweaked(tweak).encrypt_block(block)
}
}
@@ -311,8 +334,9 @@
}
#[allow(clippy::expect_used)]
- fn decrypt(&self, tweak: Self::Tweak, block: &mut [u8; 16]) {
- self.decrypt_data_unit(tweak, block).expect("One block is a valid size");
+ fn decrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) {
+ // We don't need the ciphertext stealing here since the input size is always exactly one block
+ self.tweaked(tweak).decrypt_block(block)
}
}
@@ -341,6 +365,8 @@
fn key_2(&self) -> &Self::BlockCipherKey;
}
+const AES_128_KEY_SIZE: usize = 16;
+
/// An XTS-AES-128 key.
pub struct XtsAes128Key {
key_1: <Self as XtsKey>::BlockCipherKey,
@@ -364,7 +390,7 @@
type Error = XtsKeyTryFromSliceError;
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
- try_split_concat_key::<16>(slice)
+ try_split_concat_key::<AES_128_KEY_SIZE>(slice)
.map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() })
.ok_or_else(XtsKeyTryFromSliceError::new)
}
@@ -373,8 +399,10 @@
#[allow(clippy::expect_used)]
impl From<&[u8; 32]> for XtsAes128Key {
fn from(array: &[u8; 32]) -> Self {
- let arr1: [u8; 16] = array[..16].try_into().expect("array is correctly sized");
- let arr2: [u8; 16] = array[16..].try_into().expect("array is correctly sized");
+ let arr1: [u8; AES_128_KEY_SIZE] =
+ array[..AES_128_KEY_SIZE].try_into().expect("array is correctly sized");
+ let arr2: [u8; AES_128_KEY_SIZE] =
+ array[AES_128_KEY_SIZE..].try_into().expect("array is correctly sized");
Self {
key_1: crypto_provider::aes::Aes128Key::from(arr1),
key_2: crypto_provider::aes::Aes128Key::from(arr2),
@@ -402,6 +430,8 @@
}
}
+const AES_256_KEY_SIZE: usize = 32;
+
/// An XTS-AES-256 key.
pub struct XtsAes256Key {
key_1: <Self as XtsKey>::BlockCipherKey,
@@ -425,7 +455,7 @@
type Error = XtsKeyTryFromSliceError;
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
- try_split_concat_key::<32>(slice)
+ try_split_concat_key::<AES_256_KEY_SIZE>(slice)
.map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() })
.ok_or_else(XtsKeyTryFromSliceError::new)
}
@@ -434,8 +464,10 @@
#[allow(clippy::expect_used)]
impl From<&[u8; 64]> for XtsAes256Key {
fn from(array: &[u8; 64]) -> Self {
- let arr1: [u8; 32] = array[..32].try_into().expect("array is correctly sized");
- let arr2: [u8; 32] = array[32..].try_into().expect("array is correctly sized");
+ let arr1: [u8; AES_256_KEY_SIZE] =
+ array[..AES_256_KEY_SIZE].try_into().expect("array is correctly sized");
+ let arr2: [u8; AES_256_KEY_SIZE] =
+ array[AES_256_KEY_SIZE..].try_into().expect("array is correctly sized");
Self {
key_1: crypto_provider::aes::Aes256Key::from(arr1),
key_2: crypto_provider::aes::Aes256Key::from(arr2),
@@ -478,18 +510,18 @@
/// The tweak for an XTS-AES cipher.
#[derive(Clone)]
pub struct Tweak {
- bytes: crypto_provider::aes::AesBlock,
+ bytes: AesBlock,
}
impl Tweak {
/// Little-endian content of the tweak.
- pub fn le_bytes(&self) -> crypto_provider::aes::AesBlock {
+ pub fn le_bytes(&self) -> AesBlock {
self.bytes
}
}
-impl From<crypto_provider::aes::AesBlock> for Tweak {
- fn from(bytes: crypto_provider::aes::AesBlock) -> Self {
+impl From<AesBlock> for Tweak {
+ fn from(bytes: AesBlock) -> Self {
Self { bytes }
}
}
@@ -506,15 +538,31 @@
/// The block number inside the data unit. Should not exceed 2^20.
block_num: u32,
/// Original tweak multiplied by the primitive polynomial `block_num` times as per section 5.2
- tweak: crypto_provider::aes::AesBlock,
+ tweak: AesBlock,
}
impl TweakState {
/// Create a TweakState from the provided state with block_num = 0.
- fn new(tweak: [u8; 16]) -> TweakState {
+ fn new(tweak: [u8; BLOCK_SIZE]) -> TweakState {
TweakState { block_num: 0, tweak }
}
+ /// Advance the tweak state in the data unit to the next block without encrypting
+ /// or decrypting the intermediate blocks.
+ #[allow(clippy::indexing_slicing)]
+ fn advance_to_next_block(&mut self) {
+ // Conceptual left shift across the bytes.
+ // Most significant byte: if shift would carry, XOR in the coefficients of primitive
+ // polynomial in F_2^128 (x^128 = x^7 + x^2 + x + 1) => 0b1000_0111 in binary.
+ let mut target = [0_u8; BLOCK_SIZE];
+ target[0] = (self.tweak[0] << 1) ^ ((self.tweak[BLOCK_SIZE - 1] >> 7) * 0b1000_0111);
+ for (j, byte) in target.iter_mut().enumerate().skip(1) {
+ *byte = (self.tweak[j] << 1) ^ (self.tweak[j - 1] >> 7);
+ }
+ self.tweak = target;
+ self.block_num += 1;
+ }
+
/// Advance the tweak state in the data unit to the `block_num`'th block without encrypting
/// or decrypting the intermediate blocks.
///
@@ -522,39 +570,17 @@
///
/// # Panics
/// - If `block_num` is less than the current block num
+ #[cfg(test)]
fn advance_to_block(&mut self, block_num: u32) {
// It's a programmer error; nothing to recover from
assert!(self.block_num <= block_num);
- let mut target = [0_u8; BLOCK_SIZE];
-
// Multiply by the primitive polynomial as many times as needed, as per section 5.2
// of IEEE spec
- #[allow(clippy::expect_used)]
+ #[allow(clippy::indexing_slicing)]
for _ in 0..(block_num - self.block_num) {
- // Conceptual left shift across the bytes.
- // Most significant byte: if shift would carry, XOR in the coefficients of primitive
- // polynomial in F_2^128 (x^128 = x^7 + x^2 + x + 1 = 0) = 135 decimal.
- // % 128 is compiled as & !128 (i.e. fast).
- target[0] = (2
- * (self.tweak.first().expect("aes block must have non zero length") % 128))
- ^ (135
- * select_hi_bit(
- *self.tweak.get(15).expect("15 is a valid index in an aes block"),
- ));
- // Remaining bytes
- for (j, byte) in target.iter_mut().enumerate().skip(1) {
- *byte = (2
- * (self.tweak.get(j).expect("j is always in range of block size") % 128))
- ^ select_hi_bit(
- *self.tweak.get(j - 1).expect("j > 0 always because of the .skip(1)"),
- );
- }
- self.tweak = target;
- // no need to zero target as it will be overwritten completely next iteration
+ self.advance_to_next_block()
}
-
- self.block_num = block_num;
}
}
@@ -570,11 +596,11 @@
impl<'a, A: Aes> XtsEncrypterTweaked<'a, A> {
fn advance_to_next_block_num(&mut self) {
- self.tweak_state.advance_to_block(self.tweak_state.block_num + 1)
+ self.tweak_state.advance_to_next_block()
}
/// Encrypt a block in place using the configured tweak and current block number.
- fn encrypt_block(&self, block: &mut crypto_provider::aes::AesBlock) {
+ fn encrypt_block(&self, block: &mut AesBlock) {
array_xor(block, &self.tweak_state.tweak);
self.enc_cipher.encrypt(block);
array_xor(block, &self.tweak_state.tweak);
@@ -593,7 +619,7 @@
impl<'a, A: Aes> XtsDecrypterTweaked<'a, A> {
fn advance_to_next_block_num(&mut self) {
- self.tweak_state.advance_to_block(self.tweak_state.block_num + 1)
+ self.tweak_state.advance_to_next_block()
}
/// Get the current tweak state -- useful if needed to reset to an earlier block num.
@@ -601,11 +627,11 @@
self.tweak_state.clone()
}
- /// Set the tweak to a state captured via [current_tweak].
+ /// Set the tweak to a state captured via [`current_tweak`](XtsDecrypterTweaked::current_tweak).
fn set_tweak(&mut self, tweak_state: TweakState) {
self.tweak_state = tweak_state;
}
- fn decrypt_block(&self, block: &mut crypto_provider::aes::AesBlock) {
+ fn decrypt_block(&self, block: &mut AesBlock) {
// CC = C ^ T
array_xor(block, &self.tweak_state.tweak);
// PP = decrypt CC
@@ -617,7 +643,7 @@
/// Calculate `base = base ^ rhs` for each byte.
#[allow(clippy::expect_used)]
-fn array_xor(base: &mut crypto_provider::aes::AesBlock, rhs: &crypto_provider::aes::AesBlock) {
+fn array_xor(base: &mut AesBlock, rhs: &AesBlock) {
// hopefully this gets done smartly by the compiler (intel pxor, arm veorq, or equivalent).
// This seems to happen in practice at opt level 3: https://gcc.godbolt.org/z/qvjE8joMv
for i in 0..BLOCK_SIZE {
@@ -626,12 +652,6 @@
}
}
-/// 1 if hi bit set, 0 if not.
-fn select_hi_bit(byte: u8) -> u8 {
- // compiled as shr 7: https://gcc.godbolt.org/z/1rzvfshnx
- byte / 128
-}
-
fn try_split_concat_key<const N: usize>(slice: &[u8]) -> Option<([u8; N], [u8; N])> {
slice.get(0..N).and_then(|slice| slice.try_into().ok()).and_then(|k1: [u8; N]| {
slice.get(N..).and_then(|slice| slice.try_into().ok()).map(|k2: [u8; N]| (k1, k2))
diff --git a/nearby/presence/xts_aes/tests/tests.rs b/nearby/presence/xts_aes/tests/tests.rs
new file mode 100644
index 0000000..0452f06
--- /dev/null
+++ b/nearby/presence/xts_aes/tests/tests.rs
@@ -0,0 +1,117 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crypto_provider::aes::{Aes, BLOCK_SIZE};
+use crypto_provider::CryptoProvider;
+use crypto_provider_default::CryptoProviderImpl;
+use ldt_tbc::{TweakableBlockCipherDecrypter, TweakableBlockCipherEncrypter};
+use xts_aes::{XtsAes128Key, XtsAes256Key, XtsDecrypter, XtsEncrypter, XtsError, XtsKey};
+
+const MAX_XTS_SIZE: usize = (1 << 20) * BLOCK_SIZE;
+
+#[test]
+fn too_small_payload() {
+ let key = [0_u8; 32];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>(
+ &XtsAes128Key::from(&key),
+ );
+ let tweak = [0u8; 16];
+ let mut payload = [0u8; BLOCK_SIZE - 1];
+ assert_eq!(enc.encrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort));
+ assert_eq!(dec.decrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort));
+
+ let key = [0u8; 64];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>(
+ &XtsAes256Key::from(&key),
+ );
+ assert_eq!(enc.encrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort));
+ assert_eq!(dec.decrypt_data_unit(tweak.into(), &mut payload), Err(XtsError::DataTooShort));
+}
+
+#[test]
+fn block_size_payload() {
+ let key = [0_u8; 32];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>(
+ &XtsAes128Key::from(&key),
+ );
+ let tweak = [0u8; 16];
+ let mut payload = [0u8; BLOCK_SIZE];
+ assert!(enc.encrypt_data_unit(tweak.into(), &mut payload).is_ok());
+ assert!(dec.decrypt_data_unit(tweak.into(), &mut payload).is_ok());
+
+ let key = [0u8; 64];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>(
+ &XtsAes256Key::from(&key),
+ );
+ assert!(enc.encrypt_data_unit(tweak.into(), &mut payload).is_ok());
+ assert!(dec.decrypt_data_unit(tweak.into(), &mut payload).is_ok());
+}
+
+#[test]
+fn max_xts_sized_payload() {
+ let key = [0_u8; 32];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>(
+ &XtsAes128Key::from(&key),
+ );
+ let tweak = [0u8; 16];
+ let mut payload = vec![0u8; MAX_XTS_SIZE];
+ assert!(enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok());
+ assert!(dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok());
+
+ let key = [0u8; 64];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>(
+ &XtsAes256Key::from(&key),
+ );
+ assert!(enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok());
+ assert!(dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()).is_ok());
+}
+
+#[test]
+fn too_large_payload() {
+ let key = [0_u8; 32];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes128, _>(
+ &XtsAes128Key::from(&key),
+ );
+ let tweak = [0u8; 16];
+ let mut payload = vec![0u8; MAX_XTS_SIZE + 1];
+ assert_eq!(
+ enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()),
+ Err(XtsError::DataTooLong)
+ );
+ assert_eq!(
+ dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()),
+ Err(XtsError::DataTooLong)
+ );
+
+ let key = [0u8; 64];
+ let (enc, dec) = build_ciphers::<<CryptoProviderImpl as CryptoProvider>::Aes256, _>(
+ &XtsAes256Key::from(&key),
+ );
+ assert_eq!(
+ enc.encrypt_data_unit(tweak.into(), payload.as_mut_slice()),
+ Err(XtsError::DataTooLong)
+ );
+ assert_eq!(
+ dec.decrypt_data_unit(tweak.into(), payload.as_mut_slice()),
+ Err(XtsError::DataTooLong)
+ );
+}
+
+fn build_ciphers<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + ldt_tbc::TweakableBlockCipherKey>(
+ key: &K,
+) -> (XtsEncrypter<A, K>, XtsDecrypter<A, K>) {
+ let enc = XtsEncrypter::<A, _>::new(key);
+ let dec = XtsDecrypter::<A, _>::new(key);
+ (enc, dec)
+}
diff --git a/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
new file mode 100644
index 0000000..724e3bd
--- /dev/null
+++ b/nearby/presence/xts_aes/tests/wycheproof_test_vectors.rs
@@ -0,0 +1,77 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate core;
+
+use crypto_provider::CryptoProvider;
+use crypto_provider_default::CryptoProviderImpl;
+use ldt_tbc::TweakableBlockCipherDecrypter;
+use ldt_tbc::TweakableBlockCipherEncrypter;
+use ldt_tbc::TweakableBlockCipherKey;
+use wycheproof::cipher::TestGroup;
+use xts_aes::{self, XtsAes128Key, XtsAes256Key, XtsDecrypter, XtsEncrypter, XtsKey};
+
+#[test]
+fn run_wycheproof_vectors() {
+ run_test_vectors();
+}
+
+fn run_test_vectors() {
+ let test_set = wycheproof::cipher::TestSet::load(wycheproof::cipher::TestName::AesXts)
+ .expect("Should be able to load test set");
+
+ for test_group in test_set.test_groups {
+ match test_group.key_size {
+ 256 => run_tests::<XtsAes128Key, <CryptoProviderImpl as CryptoProvider>::Aes128>(
+ test_group,
+ ),
+ 512 => run_tests::<XtsAes256Key, <CryptoProviderImpl as CryptoProvider>::Aes256>(
+ test_group,
+ ),
+ _ => {}
+ }
+ }
+}
+
+fn run_tests<K, A>(test_group: TestGroup)
+where
+ K: XtsKey + TweakableBlockCipherKey,
+ A: crypto_provider::aes::Aes<Key = K::BlockCipherKey>,
+{
+ let mut buf = Vec::new();
+ for test in test_group.tests {
+ buf.clear();
+ let key = test.key.as_slice();
+ let iv = test.nonce.as_slice();
+ let msg = test.pt.as_slice();
+ let ct = test.ct.as_slice();
+
+ let mut block = [0u8; 16];
+ block[..iv.len()].copy_from_slice(iv);
+ let tweak = xts_aes::Tweak::from(block);
+
+ // test encryption
+ let xts_enc = XtsEncrypter::<A, _>::new(&K::try_from(key).unwrap());
+ buf.extend_from_slice(msg);
+ xts_enc.encrypt_data_unit(tweak.clone(), &mut buf).unwrap();
+ assert_eq!(ct, buf, "Test case id: {}", test.tc_id);
+
+ // test decryption
+ buf.clear();
+ let xts_dec = XtsDecrypter::<A, _>::new(&K::try_from(key).unwrap());
+ buf.extend_from_slice(ct);
+ xts_dec.decrypt_data_unit(tweak, &mut buf).unwrap();
+ assert_eq!(msg, buf, "Test case id: {}", test.tc_id);
+ }
+}
diff --git a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch b/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch
deleted file mode 100644
index be6ca09..0000000
--- a/nearby/scripts/openssl-patches/0001-Apply-Android-changes.patch
+++ /dev/null
@@ -1,2712 +0,0 @@
-From 66e1daa9b4a345617bf9a090d56fa09ccd9b1d35 Mon Sep 17 00:00:00 2001
-From: Maurice Lam <yukl@google.com>
-Date: Wed, 26 Apr 2023 02:21:36 +0000
-Subject: [PATCH] Apply Android patches
-
----
- openssl/src/bio.rs | 6 +-
- openssl/src/bn.rs | 2 +-
- openssl/src/cipher.rs | 4 +
- openssl/src/dh.rs | 2 +-
- openssl/src/ec.rs | 20 +
- openssl/src/encrypt.rs | 4 +-
- openssl/src/hkdf.rs | 89 ++
- openssl/src/hmac.rs | 217 ++++
- openssl/src/lib.rs | 12 +
- openssl/src/pkey.rs | 20 +-
- openssl/src/sign.rs | 10 +-
- openssl/src/symm.rs | 7 +-
- openssl/src/x509/mod.rs | 52 +-
- openssl/src/x509/mod.rs.orig | 1879 ++++++++++++++++++++++++++++++++++
- 14 files changed, 2292 insertions(+), 32 deletions(-)
- create mode 100644 openssl/src/hkdf.rs
- create mode 100644 openssl/src/hmac.rs
- create mode 100644 openssl/src/x509/mod.rs.orig
-
-diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs
-index 6a72552a..03242188 100644
---- a/openssl/src/bio.rs
-+++ b/openssl/src/bio.rs
-@@ -4,7 +4,7 @@ use std::marker::PhantomData;
- use std::ptr;
- use std::slice;
-
--use crate::cvt_p;
-+use crate::{cvt_p, SignedLenType};
- use crate::error::ErrorStack;
-
- pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
-@@ -25,7 +25,7 @@ impl<'a> MemBioSlice<'a> {
- let bio = unsafe {
- cvt_p(BIO_new_mem_buf(
- buf.as_ptr() as *const _,
-- buf.len() as c_int,
-+ buf.len() as SignedLenType,
- ))?
- };
-
-@@ -78,7 +78,7 @@ cfg_if! {
- use ffi::BIO_new_mem_buf;
- } else {
- #[allow(bad_style)]
-- unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO {
-+ unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO {
- ffi::BIO_new_mem_buf(buf as *mut _, len)
- }
- }
-diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs
-index 0328730a..fe820a2c 100644
---- a/openssl/src/bn.rs
-+++ b/openssl/src/bn.rs
-@@ -814,7 +814,7 @@ impl BigNumRef {
- /// assert_eq!(&bn_vec, &[0, 0, 0x45, 0x43]);
- /// ```
- #[corresponds(BN_bn2binpad)]
-- #[cfg(ossl110)]
-+ #[cfg(any(boringssl, ossl110))]
- pub fn to_vec_padded(&self, pad_to: i32) -> Result<Vec<u8>, ErrorStack> {
- let mut v = Vec::with_capacity(pad_to as usize);
- unsafe {
-diff --git a/openssl/src/cipher.rs b/openssl/src/cipher.rs
-index aeedf459..570794ca 100644
---- a/openssl/src/cipher.rs
-+++ b/openssl/src/cipher.rs
-@@ -208,6 +208,7 @@ impl Cipher {
- unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb1() as *mut _) }
- }
-
-+ #[cfg(not(boringssl))]
- pub fn aes_192_cfb128() -> &'static CipherRef {
- unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb128() as *mut _) }
- }
-@@ -253,6 +254,7 @@ impl Cipher {
- unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb1() as *mut _) }
- }
-
-+ #[cfg(not(boringssl))]
- pub fn aes_256_cfb128() -> &'static CipherRef {
- unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb128() as *mut _) }
- }
-@@ -282,11 +284,13 @@ impl Cipher {
- }
-
- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
-+ #[cfg(not(boringssl))]
- pub fn bf_cbc() -> &'static CipherRef {
- unsafe { CipherRef::from_ptr(ffi::EVP_bf_cbc() as *mut _) }
- }
-
- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
-+ #[cfg(not(boringssl))]
- pub fn bf_ecb() -> &'static CipherRef {
- unsafe { CipherRef::from_ptr(ffi::EVP_bf_ecb() as *mut _) }
- }
-diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs
-index 12170b99..e781543e 100644
---- a/openssl/src/dh.rs
-+++ b/openssl/src/dh.rs
-@@ -239,7 +239,7 @@ where
- }
-
- cfg_if! {
-- if #[cfg(any(ossl110, libressl270))] {
-+ if #[cfg(any(ossl110, libressl270, boringssl))] {
- use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key};
- } else {
- #[allow(bad_style)]
-diff --git a/openssl/src/ec.rs b/openssl/src/ec.rs
-index 248ced3e..c56f5da7 100644
---- a/openssl/src/ec.rs
-+++ b/openssl/src/ec.rs
-@@ -954,6 +954,26 @@ impl EcKey<Private> {
- EcKey<Private>,
- ffi::d2i_ECPrivateKey
- }
-+
-+ /// Decodes a DER-encoded elliptic curve private key structure for the specified curve.
-+ #[corresponds(EC_KEY_parse_private_key)]
-+ #[cfg(boringssl)]
-+ pub fn private_key_from_der_for_group(
-+ der: &[u8],
-+ group: &EcGroupRef,
-+ ) -> Result<EcKey<Private>, ErrorStack> {
-+ unsafe {
-+ let mut cbs = ffi::CBS {
-+ data: der.as_ptr(),
-+ len: der.len(),
-+ };
-+ cvt_p(ffi::EC_KEY_parse_private_key(
-+ &mut cbs as *mut ffi::CBS,
-+ group.as_ptr(),
-+ ))
-+ .map(|p| EcKey::from_ptr(p))
-+ }
-+ }
- }
-
- impl<T> Clone for EcKey<T> {
-diff --git a/openssl/src/encrypt.rs b/openssl/src/encrypt.rs
-index d3db0fd4..19a9a459 100644
---- a/openssl/src/encrypt.rs
-+++ b/openssl/src/encrypt.rs
-@@ -148,7 +148,7 @@ impl<'a> Encrypter<'a> {
- /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`].
- ///
- /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html
-- #[cfg(any(ossl102, libressl310))]
-+ #[cfg(any(ossl102, libressl310, boringssl))]
- pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
- unsafe {
- cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md(
-@@ -352,7 +352,7 @@ impl<'a> Decrypter<'a> {
- /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`].
- ///
- /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html
-- #[cfg(any(ossl102, libressl310))]
-+ #[cfg(any(ossl102, libressl310, boringssl))]
- pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
- unsafe {
- cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md(
-diff --git a/openssl/src/hkdf.rs b/openssl/src/hkdf.rs
-new file mode 100644
-index 00000000..cc7e5b3a
---- /dev/null
-+++ b/openssl/src/hkdf.rs
-@@ -0,0 +1,89 @@
-+use crate::cvt;
-+use crate::error::ErrorStack;
-+use crate::md::MdRef;
-+use foreign_types::ForeignTypeRef;
-+use openssl_macros::corresponds;
-+
-+/// Computes HKDF (as specified by RFC 5869).
-+///
-+/// HKDF is an Extract-and-Expand algorithm. It does not do any key stretching,
-+/// and as such, is not suited to be used alone to generate a key from a
-+/// password.
-+#[corresponds(HKDF)]
-+#[inline]
-+pub fn hkdf(
-+ out_key: &mut [u8],
-+ md: &MdRef,
-+ secret: &[u8],
-+ salt: &[u8],
-+ info: &[u8],
-+) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::HKDF(
-+ out_key.as_mut_ptr(),
-+ out_key.len(),
-+ md.as_ptr(),
-+ secret.as_ptr(),
-+ secret.len(),
-+ salt.as_ptr(),
-+ salt.len(),
-+ info.as_ptr(),
-+ info.len(),
-+ ))?;
-+ }
-+
-+ Ok(())
-+}
-+
-+/// Computes a HKDF PRK (as specified by RFC 5869).
-+///
-+/// WARNING: This function orders the inputs differently from RFC 5869
-+/// specification. Double-check which parameter is the secret/IKM and which is
-+/// the salt when using.
-+#[corresponds(HKDF_extract)]
-+#[inline]
-+pub fn hkdf_extract<'a>(
-+ out_key: &'a mut [u8],
-+ md: &MdRef,
-+ secret: &[u8],
-+ salt: &[u8],
-+) -> Result<&'a [u8], ErrorStack> {
-+ let mut out_len = out_key.len();
-+ unsafe {
-+ cvt(ffi::HKDF_extract(
-+ out_key.as_mut_ptr(),
-+ &mut out_len,
-+ md.as_ptr(),
-+ secret.as_ptr(),
-+ secret.len(),
-+ salt.as_ptr(),
-+ salt.len(),
-+ ))?;
-+ }
-+
-+ Ok(&out_key[..out_len])
-+}
-+
-+/// Computes a HKDF OKM (as specified by RFC 5869).
-+#[corresponds(HKDF_expand)]
-+#[inline]
-+pub fn hkdf_expand(
-+ out_key: &mut [u8],
-+ md: &MdRef,
-+ prk: &[u8],
-+ info: &[u8],
-+) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::HKDF_expand(
-+ out_key.as_mut_ptr(),
-+ out_key.len(),
-+ md.as_ptr(),
-+ prk.as_ptr(),
-+ prk.len(),
-+ info.as_ptr(),
-+ info.len(),
-+ ))?;
-+ }
-+
-+ Ok(())
-+}
-diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs
-new file mode 100644
-index 00000000..465781e2
---- /dev/null
-+++ b/openssl/src/hmac.rs
-@@ -0,0 +1,217 @@
-+use crate::error::ErrorStack;
-+use crate::md::MdRef;
-+use crate::{cvt, cvt_p};
-+use ffi::HMAC_CTX;
-+use foreign_types::ForeignTypeRef;
-+use libc::{c_uint, c_void};
-+use openssl_macros::corresponds;
-+use std::convert::TryFrom;
-+use std::ptr;
-+
-+/// Computes the HMAC as a one-shot operation.
-+///
-+/// Calculates the HMAC of data, using the given |key|
-+/// and hash function |md|, and returns the result re-using the space from
-+/// buffer |out|. On entry, |out| must contain at least |EVP_MD_size| bytes
-+/// of space. The actual length of the result is used to resize the returned
-+/// slice. An output size of |EVP_MAX_MD_SIZE| will always be large enough.
-+/// It returns a resized |out| or ErrorStack on error.
-+#[corresponds(HMAC)]
-+#[inline]
-+pub fn hmac<'a>(
-+ md: &MdRef,
-+ key: &[u8],
-+ data: &[u8],
-+ out: &'a mut [u8],
-+) -> Result<&'a [u8], ErrorStack> {
-+ assert!(out.len() >= md.size());
-+ let mut out_len = c_uint::try_from(out.len()).unwrap();
-+ unsafe {
-+ cvt_p(ffi::HMAC(
-+ md.as_ptr(),
-+ key.as_ptr() as *const c_void,
-+ key.len(),
-+ data.as_ptr(),
-+ data.len(),
-+ out.as_mut_ptr(),
-+ &mut out_len,
-+ ))?;
-+ }
-+ Ok(&out[..out_len as usize])
-+}
-+
-+/// A context object used to perform HMAC operations.
-+///
-+/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message
-+/// authentication, which is based on a hash function.
-+///
-+/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead.
-+#[cfg(boringssl)]
-+pub struct HmacCtx {
-+ ctx: *mut HMAC_CTX,
-+ output_size: usize,
-+}
-+
-+#[cfg(boringssl)]
-+impl HmacCtx {
-+ /// Creates a new [HmacCtx] to use the hash function `md` and key `key`.
-+ #[corresponds(HMAC_Init_ex)]
-+ pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> {
-+ unsafe {
-+ // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into
-+ // ErrorStack in the returned result by `cvt_p`.
-+ let ctx = cvt_p(ffi::HMAC_CTX_new())?;
-+ // Safety:
-+ // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new,
-+ // which is the line above.
-+ // - HMAC_Init_ex may return an error if key is null but the md is different from
-+ // before. This is avoided here since key is guaranteed to be non-null.
-+ cvt(ffi::HMAC_Init_ex(
-+ ctx,
-+ key.as_ptr() as *const c_void,
-+ key.len(),
-+ md.as_ptr(),
-+ ptr::null_mut(),
-+ ))?;
-+ Ok(Self {
-+ ctx,
-+ output_size: md.size(),
-+ })
-+ }
-+ }
-+
-+ /// `update` can be called repeatedly with chunks of the message `data` to be authenticated.
-+ #[corresponds(HMAC_Update)]
-+ pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
-+ unsafe {
-+ // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the
-+ // returned result by `cvt`.
-+ cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ())
-+ }
-+ }
-+
-+ /// Finishes the HMAC process, and places the message authentication code in `output`.
-+ /// The number of bytes written to `output` is returned.
-+ ///
-+ /// # Panics
-+ ///
-+ /// Panics if the `output` is smaller than the required size. The output size is indicated by
-+ /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will
-+ /// always be large enough.
-+ #[corresponds(HMAC_Final)]
-+ pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
-+ assert!(output.len() >= self.output_size);
-+ unsafe {
-+ // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer
-+ // than the length of `output`.
-+ let mut size: c_uint = 0;
-+ cvt(ffi::HMAC_Final(
-+ self.ctx,
-+ output.as_mut_ptr(),
-+ &mut size as *mut c_uint,
-+ ))
-+ .map(|_| size as usize)
-+ }
-+ }
-+}
-+
-+impl Drop for HmacCtx {
-+ #[corresponds(HMAC_CTX_free)]
-+ fn drop(&mut self) {
-+ unsafe {
-+ ffi::HMAC_CTX_free(self.ctx);
-+ }
-+ }
-+}
-+
-+#[cfg(test)]
-+mod tests {
-+ use super::*;
-+ use crate::md::Md;
-+
-+ const SHA_256_DIGEST_SIZE: usize = 32;
-+
-+ #[test]
-+ fn hmac_sha256_test() {
-+ let expected_hmac = [
-+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+ 0x2e, 0x32, 0xcf, 0xf7,
-+ ];
-+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let data = b"Hi There";
-+ let hmac_result =
-+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+ assert_eq!(&hmac_result, &expected_hmac);
-+ }
-+
-+ #[test]
-+ #[should_panic]
-+ fn hmac_sha256_output_too_short() {
-+ let mut out = vec![0_u8; 1];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let data = b"Hi There";
-+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+ }
-+
-+ #[test]
-+ fn hmac_sha256_test_big_buffer() {
-+ let expected_hmac = [
-+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+ 0x2e, 0x32, 0xcf, 0xf7,
-+ ];
-+ let mut out: [u8; 100] = [0; 100];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let data = b"Hi There";
-+ let hmac_result =
-+ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
-+ assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
-+ assert_eq!(&hmac_result, &expected_hmac);
-+ }
-+
-+ #[test]
-+ fn hmac_sha256_update_test() {
-+ let expected_hmac = [
-+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+ 0x2e, 0x32, 0xcf, 0xf7,
-+ ];
-+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let data = b"Hi There";
-+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
-+ hmac_ctx.update(data).unwrap();
-+ let size = hmac_ctx.finalize(&mut out).unwrap();
-+ assert_eq!(&out, &expected_hmac);
-+ assert_eq!(size, SHA_256_DIGEST_SIZE);
-+ }
-+
-+ #[test]
-+ fn hmac_sha256_update_chunks_test() {
-+ let expected_hmac = [
-+ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
-+ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
-+ 0x2e, 0x32, 0xcf, 0xf7,
-+ ];
-+ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
-+ hmac_ctx.update(b"Hi").unwrap();
-+ hmac_ctx.update(b" There").unwrap();
-+ let size = hmac_ctx.finalize(&mut out).unwrap();
-+ assert_eq!(&out, &expected_hmac);
-+ assert_eq!(size, SHA_256_DIGEST_SIZE);
-+ }
-+
-+ #[test]
-+ #[should_panic]
-+ fn hmac_sha256_update_output_too_short() {
-+ let mut out = vec![0_u8; 1];
-+ let key: [u8; 20] = [0x0b; 20];
-+ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
-+ hmac_ctx.update(b"Hi There").unwrap();
-+ hmac_ctx.finalize(&mut out).unwrap();
-+ }
-+}
-diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs
-index 035c90c6..02a51dbb 100644
---- a/openssl/src/lib.rs
-+++ b/openssl/src/lib.rs
-@@ -120,6 +120,9 @@
- #![doc(html_root_url = "https://docs.rs/openssl/0.10")]
- #![warn(rust_2018_idioms)]
-
-+#[cfg(all(soong, boringssl))]
-+extern crate bssl_ffi as ffi;
-+
- #[doc(inline)]
- pub use ffi::init;
-
-@@ -155,6 +158,10 @@ pub mod ex_data;
- #[cfg(not(any(libressl, ossl300)))]
- pub mod fips;
- pub mod hash;
-+#[cfg(boringssl)]
-+pub mod hkdf;
-+#[cfg(boringssl)]
-+pub mod hmac;
- #[cfg(ossl300)]
- pub mod lib_ctx;
- pub mod md;
-@@ -189,6 +196,11 @@ type LenType = libc::size_t;
- #[cfg(not(boringssl))]
- type LenType = libc::c_int;
-
-+#[cfg(boringssl)]
-+type SignedLenType = libc::ssize_t;
-+#[cfg(not(boringssl))]
-+type SignedLenType = libc::c_int;
-+
- #[inline]
- fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
- if r.is_null() {
-diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs
-index 780bd637..92daa882 100644
---- a/openssl/src/pkey.rs
-+++ b/openssl/src/pkey.rs
-@@ -47,7 +47,7 @@ use crate::dh::Dh;
- use crate::dsa::Dsa;
- use crate::ec::EcKey;
- use crate::error::ErrorStack;
--#[cfg(ossl110)]
-+#[cfg(any(boringssl, ossl110))]
- use crate::pkey_ctx::PkeyCtx;
- use crate::rsa::Rsa;
- use crate::symm::Cipher;
-@@ -89,11 +89,11 @@ impl Id {
- #[cfg(ossl110)]
- pub const HKDF: Id = Id(ffi::EVP_PKEY_HKDF);
-
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub const ED25519: Id = Id(ffi::EVP_PKEY_ED25519);
- #[cfg(ossl111)]
- pub const ED448: Id = Id(ffi::EVP_PKEY_ED448);
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub const X25519: Id = Id(ffi::EVP_PKEY_X25519);
- #[cfg(ossl111)]
- pub const X448: Id = Id(ffi::EVP_PKEY_X448);
-@@ -252,7 +252,7 @@ where
- /// This function only works for algorithms that support raw public keys.
- /// Currently this is: [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`].
- #[corresponds(EVP_PKEY_get_raw_public_key)]
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn raw_public_key(&self) -> Result<Vec<u8>, ErrorStack> {
- unsafe {
- let mut len = 0;
-@@ -303,7 +303,7 @@ where
- /// This function only works for algorithms that support raw private keys.
- /// Currently this is: [`Id::HMAC`], [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`].
- #[corresponds(EVP_PKEY_get_raw_private_key)]
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn raw_private_key(&self) -> Result<Vec<u8>, ErrorStack> {
- unsafe {
- let mut len = 0;
-@@ -503,7 +503,7 @@ impl PKey<Private> {
- ctx.keygen()
- }
-
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- fn generate_eddsa(id: Id) -> Result<PKey<Private>, ErrorStack> {
- let mut ctx = PkeyCtx::new_id(id)?;
- ctx.keygen_init()?;
-@@ -533,7 +533,7 @@ impl PKey<Private> {
- /// assert_eq!(secret.len(), 32);
- /// # Ok(()) }
- /// ```
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn generate_x25519() -> Result<PKey<Private>, ErrorStack> {
- PKey::generate_eddsa(Id::X25519)
- }
-@@ -587,7 +587,7 @@ impl PKey<Private> {
- /// assert_eq!(signature.len(), 64);
- /// # Ok(()) }
- /// ```
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn generate_ed25519() -> Result<PKey<Private>, ErrorStack> {
- PKey::generate_eddsa(Id::ED25519)
- }
-@@ -737,7 +737,7 @@ impl PKey<Private> {
- ///
- /// Algorithm types that support raw private keys are HMAC, X25519, ED25519, X448 or ED448
- #[corresponds(EVP_PKEY_new_raw_private_key)]
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn private_key_from_raw_bytes(
- bytes: &[u8],
- key_type: Id,
-@@ -778,7 +778,7 @@ impl PKey<Public> {
- ///
- /// Algorithm types that support raw public keys are X25519, ED25519, X448 or ED448
- #[corresponds(EVP_PKEY_new_raw_public_key)]
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn public_key_from_raw_bytes(
- bytes: &[u8],
- key_type: Id,
-diff --git a/openssl/src/sign.rs b/openssl/src/sign.rs
-index 9cfda481..e5b17a87 100644
---- a/openssl/src/sign.rs
-+++ b/openssl/src/sign.rs
-@@ -290,7 +290,7 @@ impl<'a> Signer<'a> {
- self.len_intern()
- }
-
-- #[cfg(not(ossl111))]
-+ #[cfg(not(any(boringssl, ossl111)))]
- fn len_intern(&self) -> Result<usize, ErrorStack> {
- unsafe {
- let mut len = 0;
-@@ -303,7 +303,7 @@ impl<'a> Signer<'a> {
- }
- }
-
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- fn len_intern(&self) -> Result<usize, ErrorStack> {
- unsafe {
- let mut len = 0;
-@@ -360,7 +360,7 @@ impl<'a> Signer<'a> {
- /// OpenSSL documentation at [`EVP_DigestSign`].
- ///
- /// [`EVP_DigestSign`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestSign.html
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn sign_oneshot(
- &mut self,
- sig_buf: &mut [u8],
-@@ -382,7 +382,7 @@ impl<'a> Signer<'a> {
- /// Returns the signature.
- ///
- /// This is a simple convenience wrapper over `len` and `sign_oneshot`.
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn sign_oneshot_to_vec(&mut self, data_buf: &[u8]) -> Result<Vec<u8>, ErrorStack> {
- let mut sig_buf = vec![0; self.len()?];
- let len = self.sign_oneshot(&mut sig_buf, data_buf)?;
-@@ -596,7 +596,7 @@ impl<'a> Verifier<'a> {
- /// OpenSSL documentation at [`EVP_DigestVerify`].
- ///
- /// [`EVP_DigestVerify`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestVerify.html
-- #[cfg(ossl111)]
-+ #[cfg(any(boringssl, ossl111))]
- pub fn verify_oneshot(&mut self, signature: &[u8], buf: &[u8]) -> Result<bool, ErrorStack> {
- unsafe {
- let r = ffi::EVP_DigestVerify(
-diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs
-index 911a7ab2..75b0365c 100644
---- a/openssl/src/symm.rs
-+++ b/openssl/src/symm.rs
-@@ -119,6 +119,7 @@ impl Cipher {
- unsafe { Cipher(ffi::EVP_aes_128_cfb1()) }
- }
-
-+ #[cfg(not(boringssl))]
- pub fn aes_128_cfb128() -> Cipher {
- unsafe { Cipher(ffi::EVP_aes_128_cfb128()) }
- }
-@@ -164,6 +165,7 @@ impl Cipher {
- unsafe { Cipher(ffi::EVP_aes_192_cfb1()) }
- }
-
-+ #[cfg(not(boringssl))]
- pub fn aes_192_cfb128() -> Cipher {
- unsafe { Cipher(ffi::EVP_aes_192_cfb128()) }
- }
-@@ -214,6 +216,7 @@ impl Cipher {
- unsafe { Cipher(ffi::EVP_aes_256_cfb1()) }
- }
-
-+ #[cfg(not(boringssl))]
- pub fn aes_256_cfb128() -> Cipher {
- unsafe { Cipher(ffi::EVP_aes_256_cfb128()) }
- }
-@@ -242,12 +245,12 @@ impl Cipher {
- unsafe { Cipher(ffi::EVP_aes_256_ocb()) }
- }
-
-- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
-+ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))]
- pub fn bf_cbc() -> Cipher {
- unsafe { Cipher(ffi::EVP_bf_cbc()) }
- }
-
-- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
-+ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))]
- pub fn bf_ecb() -> Cipher {
- unsafe { Cipher(ffi::EVP_bf_ecb()) }
- }
-diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs
-index 940c8c9c..f9477e4a 100644
---- a/openssl/src/x509/mod.rs
-+++ b/openssl/src/x509/mod.rs
-@@ -356,6 +356,19 @@ impl X509Builder {
- unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) }
- }
-
-+ /// Signs the certificate with a private key but without a digest.
-+ ///
-+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
-+ /// message digest.
-+ #[cfg(boringssl)]
-+ #[corresponds(X509_sign)]
-+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) }
-+ }
-+
- /// Consumes the builder, returning the certificate.
- pub fn build(self) -> X509 {
- self.0
-@@ -898,13 +911,13 @@ impl X509NameBuilder {
- pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> {
- unsafe {
- let field = CString::new(field).unwrap();
-- assert!(value.len() <= c_int::max_value() as usize);
-+ assert!(value.len() <= isize::max_value() as usize);
- cvt(ffi::X509_NAME_add_entry_by_txt(
- self.0.as_ptr(),
- field.as_ptr() as *mut _,
- ffi::MBSTRING_UTF8,
- value.as_ptr(),
-- value.len() as c_int,
-+ value.len() as isize,
- -1,
- 0,
- ))
-@@ -925,13 +938,13 @@ impl X509NameBuilder {
- ) -> Result<(), ErrorStack> {
- unsafe {
- let field = CString::new(field).unwrap();
-- assert!(value.len() <= c_int::max_value() as usize);
-+ assert!(value.len() <= isize::max_value() as usize);
- cvt(ffi::X509_NAME_add_entry_by_txt(
- self.0.as_ptr(),
- field.as_ptr() as *mut _,
- ty.as_raw(),
- value.as_ptr(),
-- value.len() as c_int,
-+ value.len() as isize,
- -1,
- 0,
- ))
-@@ -946,13 +959,13 @@ impl X509NameBuilder {
- /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html
- pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> {
- unsafe {
-- assert!(value.len() <= c_int::max_value() as usize);
-+ assert!(value.len() <= isize::max_value() as usize);
- cvt(ffi::X509_NAME_add_entry_by_NID(
- self.0.as_ptr(),
- field.as_raw(),
- ffi::MBSTRING_UTF8,
- value.as_ptr() as *mut _,
-- value.len() as c_int,
-+ value.len() as isize,
- -1,
- 0,
- ))
-@@ -972,13 +985,13 @@ impl X509NameBuilder {
- ty: Asn1Type,
- ) -> Result<(), ErrorStack> {
- unsafe {
-- assert!(value.len() <= c_int::max_value() as usize);
-+ assert!(value.len() <= isize::max_value() as usize);
- cvt(ffi::X509_NAME_add_entry_by_NID(
- self.0.as_ptr(),
- field.as_raw(),
- ty.as_raw(),
- value.as_ptr() as *mut _,
-- value.len() as c_int,
-+ value.len() as isize,
- -1,
- 0,
- ))
-@@ -1286,6 +1299,29 @@ impl X509ReqBuilder {
- }
- }
-
-+ /// Sign the request using a private key without a digest.
-+ ///
-+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
-+ /// message digest.
-+ ///
-+ /// This corresponds to [`X509_REQ_sign`].
-+ ///
-+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html
-+ #[cfg(boringssl)]
-+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe {
-+ cvt(ffi::X509_REQ_sign(
-+ self.0.as_ptr(),
-+ key.as_ptr(),
-+ ptr::null(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
- /// Returns the `X509Req`.
- pub fn build(self) -> X509Req {
- self.0
-diff --git a/openssl/src/x509/mod.rs.orig b/openssl/src/x509/mod.rs.orig
-new file mode 100644
-index 00000000..34b86c10
---- /dev/null
-+++ b/openssl/src/x509/mod.rs.orig
-@@ -0,0 +1,1879 @@
-+//! The standard defining the format of public key certificates.
-+//!
-+//! An `X509` certificate binds an identity to a public key, and is either
-+//! signed by a certificate authority (CA) or self-signed. An entity that gets
-+//! a hold of a certificate can both verify your identity (via a CA) and encrypt
-+//! data with the included public key. `X509` certificates are used in many
-+//! Internet protocols, including SSL/TLS, which is the basis for HTTPS,
-+//! the secure protocol for browsing the web.
-+
-+use cfg_if::cfg_if;
-+use foreign_types::{ForeignType, ForeignTypeRef, Opaque};
-+use libc::{c_int, c_long, c_uint};
-+use std::cmp::{self, Ordering};
-+use std::convert::TryFrom;
-+use std::error::Error;
-+use std::ffi::{CStr, CString};
-+use std::fmt;
-+use std::marker::PhantomData;
-+use std::mem;
-+use std::net::IpAddr;
-+use std::path::Path;
-+use std::ptr;
-+use std::slice;
-+use std::str;
-+
-+use crate::asn1::{
-+ Asn1BitStringRef, Asn1IntegerRef, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef, Asn1Type,
-+};
-+use crate::bio::MemBioSlice;
-+use crate::conf::ConfRef;
-+use crate::error::ErrorStack;
-+use crate::ex_data::Index;
-+use crate::hash::{DigestBytes, MessageDigest};
-+use crate::nid::Nid;
-+use crate::pkey::{HasPrivate, HasPublic, PKey, PKeyRef, Public};
-+use crate::ssl::SslRef;
-+use crate::stack::{Stack, StackRef, Stackable};
-+use crate::string::OpensslString;
-+use crate::util::{ForeignTypeExt, ForeignTypeRefExt};
-+use crate::{cvt, cvt_n, cvt_p};
-+use openssl_macros::corresponds;
-+
-+#[cfg(any(ossl102, libressl261))]
-+pub mod verify;
-+
-+pub mod extension;
-+pub mod store;
-+
-+#[cfg(test)]
-+mod tests;
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_STORE_CTX;
-+ fn drop = ffi::X509_STORE_CTX_free;
-+
-+ /// An `X509` certificate store context.
-+ pub struct X509StoreContext;
-+
-+ /// A reference to an [`X509StoreContext`].
-+ pub struct X509StoreContextRef;
-+}
-+
-+impl X509StoreContext {
-+ /// Returns the index which can be used to obtain a reference to the `Ssl` associated with a
-+ /// context.
-+ #[corresponds(SSL_get_ex_data_X509_STORE_CTX_idx)]
-+ pub fn ssl_idx() -> Result<Index<X509StoreContext, SslRef>, ErrorStack> {
-+ unsafe { cvt_n(ffi::SSL_get_ex_data_X509_STORE_CTX_idx()).map(|idx| Index::from_raw(idx)) }
-+ }
-+
-+ /// Creates a new `X509StoreContext` instance.
-+ #[corresponds(X509_STORE_CTX_new)]
-+ pub fn new() -> Result<X509StoreContext, ErrorStack> {
-+ unsafe {
-+ ffi::init();
-+ cvt_p(ffi::X509_STORE_CTX_new()).map(X509StoreContext)
-+ }
-+ }
-+}
-+
-+impl X509StoreContextRef {
-+ /// Returns application data pertaining to an `X509` store context.
-+ #[corresponds(X509_STORE_CTX_get_ex_data)]
-+ pub fn ex_data<T>(&self, index: Index<X509StoreContext, T>) -> Option<&T> {
-+ unsafe {
-+ let data = ffi::X509_STORE_CTX_get_ex_data(self.as_ptr(), index.as_raw());
-+ if data.is_null() {
-+ None
-+ } else {
-+ Some(&*(data as *const T))
-+ }
-+ }
-+ }
-+
-+ /// Returns the error code of the context.
-+ #[corresponds(X509_STORE_CTX_get_error)]
-+ pub fn error(&self) -> X509VerifyResult {
-+ unsafe { X509VerifyResult::from_raw(ffi::X509_STORE_CTX_get_error(self.as_ptr())) }
-+ }
-+
-+ /// Initializes this context with the given certificate, certificates chain and certificate
-+ /// store. After initializing the context, the `with_context` closure is called with the prepared
-+ /// context. As long as the closure is running, the context stays initialized and can be used
-+ /// to e.g. verify a certificate. The context will be cleaned up, after the closure finished.
-+ ///
-+ /// * `trust` - The certificate store with the trusted certificates.
-+ /// * `cert` - The certificate that should be verified.
-+ /// * `cert_chain` - The certificates chain.
-+ /// * `with_context` - The closure that is called with the initialized context.
-+ ///
-+ /// This corresponds to [`X509_STORE_CTX_init`] before calling `with_context` and to
-+ /// [`X509_STORE_CTX_cleanup`] after calling `with_context`.
-+ ///
-+ /// [`X509_STORE_CTX_init`]: https://www.openssl.org/docs/manmaster/crypto/X509_STORE_CTX_init.html
-+ /// [`X509_STORE_CTX_cleanup`]: https://www.openssl.org/docs/manmaster/crypto/X509_STORE_CTX_cleanup.html
-+ pub fn init<F, T>(
-+ &mut self,
-+ trust: &store::X509StoreRef,
-+ cert: &X509Ref,
-+ cert_chain: &StackRef<X509>,
-+ with_context: F,
-+ ) -> Result<T, ErrorStack>
-+ where
-+ F: FnOnce(&mut X509StoreContextRef) -> Result<T, ErrorStack>,
-+ {
-+ struct Cleanup<'a>(&'a mut X509StoreContextRef);
-+
-+ impl<'a> Drop for Cleanup<'a> {
-+ fn drop(&mut self) {
-+ unsafe {
-+ ffi::X509_STORE_CTX_cleanup(self.0.as_ptr());
-+ }
-+ }
-+ }
-+
-+ unsafe {
-+ cvt(ffi::X509_STORE_CTX_init(
-+ self.as_ptr(),
-+ trust.as_ptr(),
-+ cert.as_ptr(),
-+ cert_chain.as_ptr(),
-+ ))?;
-+
-+ let cleanup = Cleanup(self);
-+ with_context(cleanup.0)
-+ }
-+ }
-+
-+ /// Verifies the stored certificate.
-+ ///
-+ /// Returns `true` if verification succeeds. The `error` method will return the specific
-+ /// validation error if the certificate was not valid.
-+ ///
-+ /// This will only work inside of a call to `init`.
-+ #[corresponds(X509_verify_cert)]
-+ pub fn verify_cert(&mut self) -> Result<bool, ErrorStack> {
-+ unsafe { cvt_n(ffi::X509_verify_cert(self.as_ptr())).map(|n| n != 0) }
-+ }
-+
-+ /// Set the error code of the context.
-+ #[corresponds(X509_STORE_CTX_set_error)]
-+ pub fn set_error(&mut self, result: X509VerifyResult) {
-+ unsafe {
-+ ffi::X509_STORE_CTX_set_error(self.as_ptr(), result.as_raw());
-+ }
-+ }
-+
-+ /// Returns a reference to the certificate which caused the error or None if
-+ /// no certificate is relevant to the error.
-+ #[corresponds(X509_STORE_CTX_get_current_cert)]
-+ pub fn current_cert(&self) -> Option<&X509Ref> {
-+ unsafe {
-+ let ptr = ffi::X509_STORE_CTX_get_current_cert(self.as_ptr());
-+ X509Ref::from_const_ptr_opt(ptr)
-+ }
-+ }
-+
-+ /// Returns a non-negative integer representing the depth in the certificate
-+ /// chain where the error occurred. If it is zero it occurred in the end
-+ /// entity certificate, one if it is the certificate which signed the end
-+ /// entity certificate and so on.
-+ #[corresponds(X509_STORE_CTX_get_error_depth)]
-+ pub fn error_depth(&self) -> u32 {
-+ unsafe { ffi::X509_STORE_CTX_get_error_depth(self.as_ptr()) as u32 }
-+ }
-+
-+ /// Returns a reference to a complete valid `X509` certificate chain.
-+ #[corresponds(X509_STORE_CTX_get0_chain)]
-+ pub fn chain(&self) -> Option<&StackRef<X509>> {
-+ unsafe {
-+ let chain = X509_STORE_CTX_get0_chain(self.as_ptr());
-+
-+ if chain.is_null() {
-+ None
-+ } else {
-+ Some(StackRef::from_ptr(chain))
-+ }
-+ }
-+ }
-+}
-+
-+/// A builder used to construct an `X509`.
-+pub struct X509Builder(X509);
-+
-+impl X509Builder {
-+ /// Creates a new builder.
-+ #[corresponds(X509_new)]
-+ pub fn new() -> Result<X509Builder, ErrorStack> {
-+ unsafe {
-+ ffi::init();
-+ cvt_p(ffi::X509_new()).map(|p| X509Builder(X509(p)))
-+ }
-+ }
-+
-+ /// Sets the notAfter constraint on the certificate.
-+ #[corresponds(X509_set1_notAfter)]
-+ pub fn set_not_after(&mut self, not_after: &Asn1TimeRef) -> Result<(), ErrorStack> {
-+ unsafe { cvt(X509_set1_notAfter(self.0.as_ptr(), not_after.as_ptr())).map(|_| ()) }
-+ }
-+
-+ /// Sets the notBefore constraint on the certificate.
-+ #[corresponds(X509_set1_notBefore)]
-+ pub fn set_not_before(&mut self, not_before: &Asn1TimeRef) -> Result<(), ErrorStack> {
-+ unsafe { cvt(X509_set1_notBefore(self.0.as_ptr(), not_before.as_ptr())).map(|_| ()) }
-+ }
-+
-+ /// Sets the version of the certificate.
-+ ///
-+ /// Note that the version is zero-indexed; that is, a certificate corresponding to version 3 of
-+ /// the X.509 standard should pass `2` to this method.
-+ #[corresponds(X509_set_version)]
-+ #[allow(clippy::useless_conversion)]
-+ pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
-+ unsafe { cvt(ffi::X509_set_version(self.0.as_ptr(), version as c_long)).map(|_| ()) }
-+ }
-+
-+ /// Sets the serial number of the certificate.
-+ #[corresponds(X509_set_serialNumber)]
-+ pub fn set_serial_number(&mut self, serial_number: &Asn1IntegerRef) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_set_serialNumber(
-+ self.0.as_ptr(),
-+ serial_number.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Sets the issuer name of the certificate.
-+ #[corresponds(X509_set_issuer_name)]
-+ pub fn set_issuer_name(&mut self, issuer_name: &X509NameRef) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_set_issuer_name(
-+ self.0.as_ptr(),
-+ issuer_name.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Sets the subject name of the certificate.
-+ ///
-+ /// When building certificates, the `C`, `ST`, and `O` options are common when using the openssl command line tools.
-+ /// The `CN` field is used for the common name, such as a DNS name.
-+ ///
-+ /// ```
-+ /// use openssl::x509::{X509, X509NameBuilder};
-+ ///
-+ /// let mut x509_name = openssl::x509::X509NameBuilder::new().unwrap();
-+ /// x509_name.append_entry_by_text("C", "US").unwrap();
-+ /// x509_name.append_entry_by_text("ST", "CA").unwrap();
-+ /// x509_name.append_entry_by_text("O", "Some organization").unwrap();
-+ /// x509_name.append_entry_by_text("CN", "www.example.com").unwrap();
-+ /// let x509_name = x509_name.build();
-+ ///
-+ /// let mut x509 = openssl::x509::X509::builder().unwrap();
-+ /// x509.set_subject_name(&x509_name).unwrap();
-+ /// ```
-+ #[corresponds(X509_set_subject_name)]
-+ pub fn set_subject_name(&mut self, subject_name: &X509NameRef) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_set_subject_name(
-+ self.0.as_ptr(),
-+ subject_name.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Sets the public key associated with the certificate.
-+ #[corresponds(X509_set_pubkey)]
-+ pub fn set_pubkey<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPublic,
-+ {
-+ unsafe { cvt(ffi::X509_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) }
-+ }
-+
-+ /// Returns a context object which is needed to create certain X509 extension values.
-+ ///
-+ /// Set `issuer` to `None` if the certificate will be self-signed.
-+ #[corresponds(X509V3_set_ctx)]
-+ pub fn x509v3_context<'a>(
-+ &'a self,
-+ issuer: Option<&'a X509Ref>,
-+ conf: Option<&'a ConfRef>,
-+ ) -> X509v3Context<'a> {
-+ unsafe {
-+ let mut ctx = mem::zeroed();
-+
-+ let issuer = match issuer {
-+ Some(issuer) => issuer.as_ptr(),
-+ None => self.0.as_ptr(),
-+ };
-+ let subject = self.0.as_ptr();
-+ ffi::X509V3_set_ctx(
-+ &mut ctx,
-+ issuer,
-+ subject,
-+ ptr::null_mut(),
-+ ptr::null_mut(),
-+ 0,
-+ );
-+
-+ // nodb case taken care of since we zeroed ctx above
-+ if let Some(conf) = conf {
-+ ffi::X509V3_set_nconf(&mut ctx, conf.as_ptr());
-+ }
-+
-+ X509v3Context(ctx, PhantomData)
-+ }
-+ }
-+
-+ /// Adds an X509 extension value to the certificate.
-+ ///
-+ /// This works just as `append_extension` except it takes ownership of the `X509Extension`.
-+ pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> {
-+ self.append_extension2(&extension)
-+ }
-+
-+ /// Adds an X509 extension value to the certificate.
-+ #[corresponds(X509_add_ext)]
-+ pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?;
-+ Ok(())
-+ }
-+ }
-+
-+ /// Signs the certificate with a private key.
-+ #[corresponds(X509_sign)]
-+ pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) }
-+ }
-+
-+ /// Signs the certificate with a private key but without a digest.
-+ ///
-+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
-+ /// message digest.
-+ #[cfg(boringssl)]
-+ #[corresponds(X509_sign)]
-+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) }
-+ }
-+
-+ /// Consumes the builder, returning the certificate.
-+ pub fn build(self) -> X509 {
-+ self.0
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509;
-+ fn drop = ffi::X509_free;
-+
-+ /// An `X509` public key certificate.
-+ pub struct X509;
-+ /// Reference to `X509`.
-+ pub struct X509Ref;
-+}
-+
-+#[cfg(boringssl)]
-+type X509LenTy = c_uint;
-+#[cfg(not(boringssl))]
-+type X509LenTy = c_int;
-+
-+impl X509Ref {
-+ /// Returns this certificate's subject name.
-+ #[corresponds(X509_get_subject_name)]
-+ pub fn subject_name(&self) -> &X509NameRef {
-+ unsafe {
-+ let name = ffi::X509_get_subject_name(self.as_ptr());
-+ X509NameRef::from_const_ptr_opt(name).expect("subject name must not be null")
-+ }
-+ }
-+
-+ /// Returns the hash of the certificates subject
-+ #[corresponds(X509_subject_name_hash)]
-+ pub fn subject_name_hash(&self) -> u32 {
-+ unsafe { ffi::X509_subject_name_hash(self.as_ptr()) as u32 }
-+ }
-+
-+ /// Returns this certificate's issuer name.
-+ #[corresponds(X509_get_issuer_name)]
-+ pub fn issuer_name(&self) -> &X509NameRef {
-+ unsafe {
-+ let name = ffi::X509_get_issuer_name(self.as_ptr());
-+ X509NameRef::from_const_ptr_opt(name).expect("issuer name must not be null")
-+ }
-+ }
-+
-+ /// Returns the hash of the certificates issuer
-+ #[corresponds(X509_issuer_name_hash)]
-+ pub fn issuer_name_hash(&self) -> u32 {
-+ unsafe { ffi::X509_issuer_name_hash(self.as_ptr()) as u32 }
-+ }
-+
-+ /// Returns this certificate's subject alternative name entries, if they exist.
-+ #[corresponds(X509_get_ext_d2i)]
-+ pub fn subject_alt_names(&self) -> Option<Stack<GeneralName>> {
-+ unsafe {
-+ let stack = ffi::X509_get_ext_d2i(
-+ self.as_ptr(),
-+ ffi::NID_subject_alt_name,
-+ ptr::null_mut(),
-+ ptr::null_mut(),
-+ );
-+ Stack::from_ptr_opt(stack as *mut _)
-+ }
-+ }
-+
-+ /// Returns this certificate's issuer alternative name entries, if they exist.
-+ #[corresponds(X509_get_ext_d2i)]
-+ pub fn issuer_alt_names(&self) -> Option<Stack<GeneralName>> {
-+ unsafe {
-+ let stack = ffi::X509_get_ext_d2i(
-+ self.as_ptr(),
-+ ffi::NID_issuer_alt_name,
-+ ptr::null_mut(),
-+ ptr::null_mut(),
-+ );
-+ Stack::from_ptr_opt(stack as *mut _)
-+ }
-+ }
-+
-+ /// Returns this certificate's [`authority information access`] entries, if they exist.
-+ ///
-+ /// [`authority information access`]: https://tools.ietf.org/html/rfc5280#section-4.2.2.1
-+ #[corresponds(X509_get_ext_d2i)]
-+ pub fn authority_info(&self) -> Option<Stack<AccessDescription>> {
-+ unsafe {
-+ let stack = ffi::X509_get_ext_d2i(
-+ self.as_ptr(),
-+ ffi::NID_info_access,
-+ ptr::null_mut(),
-+ ptr::null_mut(),
-+ );
-+ Stack::from_ptr_opt(stack as *mut _)
-+ }
-+ }
-+
-+ #[corresponds(X509_get_pubkey)]
-+ pub fn public_key(&self) -> Result<PKey<Public>, ErrorStack> {
-+ unsafe {
-+ let pkey = cvt_p(ffi::X509_get_pubkey(self.as_ptr()))?;
-+ Ok(PKey::from_ptr(pkey))
-+ }
-+ }
-+
-+ /// Returns a digest of the DER representation of the certificate.
-+ #[corresponds(X509_digest)]
-+ pub fn digest(&self, hash_type: MessageDigest) -> Result<DigestBytes, ErrorStack> {
-+ unsafe {
-+ let mut digest = DigestBytes {
-+ buf: [0; ffi::EVP_MAX_MD_SIZE as usize],
-+ len: ffi::EVP_MAX_MD_SIZE as usize,
-+ };
-+ let mut len = ffi::EVP_MAX_MD_SIZE as c_uint;
-+ cvt(ffi::X509_digest(
-+ self.as_ptr(),
-+ hash_type.as_ptr(),
-+ digest.buf.as_mut_ptr() as *mut _,
-+ &mut len,
-+ ))?;
-+ digest.len = len as usize;
-+
-+ Ok(digest)
-+ }
-+ }
-+
-+ #[deprecated(since = "0.10.9", note = "renamed to digest")]
-+ pub fn fingerprint(&self, hash_type: MessageDigest) -> Result<Vec<u8>, ErrorStack> {
-+ self.digest(hash_type).map(|b| b.to_vec())
-+ }
-+
-+ /// Returns the certificate's Not After validity period.
-+ #[corresponds(X509_getm_notAfter)]
-+ pub fn not_after(&self) -> &Asn1TimeRef {
-+ unsafe {
-+ let date = X509_getm_notAfter(self.as_ptr());
-+ Asn1TimeRef::from_const_ptr_opt(date).expect("not_after must not be null")
-+ }
-+ }
-+
-+ /// Returns the certificate's Not Before validity period.
-+ #[corresponds(X509_getm_notBefore)]
-+ pub fn not_before(&self) -> &Asn1TimeRef {
-+ unsafe {
-+ let date = X509_getm_notBefore(self.as_ptr());
-+ Asn1TimeRef::from_const_ptr_opt(date).expect("not_before must not be null")
-+ }
-+ }
-+
-+ /// Returns the certificate's signature
-+ #[corresponds(X509_get0_signature)]
-+ pub fn signature(&self) -> &Asn1BitStringRef {
-+ unsafe {
-+ let mut signature = ptr::null();
-+ X509_get0_signature(&mut signature, ptr::null_mut(), self.as_ptr());
-+ Asn1BitStringRef::from_const_ptr_opt(signature).expect("signature must not be null")
-+ }
-+ }
-+
-+ /// Returns the certificate's signature algorithm.
-+ #[corresponds(X509_get0_signature)]
-+ pub fn signature_algorithm(&self) -> &X509AlgorithmRef {
-+ unsafe {
-+ let mut algor = ptr::null();
-+ X509_get0_signature(ptr::null_mut(), &mut algor, self.as_ptr());
-+ X509AlgorithmRef::from_const_ptr_opt(algor)
-+ .expect("signature algorithm must not be null")
-+ }
-+ }
-+
-+ /// Returns the list of OCSP responder URLs specified in the certificate's Authority Information
-+ /// Access field.
-+ #[corresponds(X509_get1_ocsp)]
-+ pub fn ocsp_responders(&self) -> Result<Stack<OpensslString>, ErrorStack> {
-+ unsafe { cvt_p(ffi::X509_get1_ocsp(self.as_ptr())).map(|p| Stack::from_ptr(p)) }
-+ }
-+
-+ /// Checks that this certificate issued `subject`.
-+ #[corresponds(X509_check_issued)]
-+ pub fn issued(&self, subject: &X509Ref) -> X509VerifyResult {
-+ unsafe {
-+ let r = ffi::X509_check_issued(self.as_ptr(), subject.as_ptr());
-+ X509VerifyResult::from_raw(r)
-+ }
-+ }
-+
-+ /// Returns certificate version. If this certificate has no explicit version set, it defaults to
-+ /// version 1.
-+ ///
-+ /// Note that `0` return value stands for version 1, `1` for version 2 and so on.
-+ #[corresponds(X509_get_version)]
-+ #[cfg(ossl110)]
-+ pub fn version(&self) -> i32 {
-+ unsafe { ffi::X509_get_version(self.as_ptr()) as i32 }
-+ }
-+
-+ /// Check if the certificate is signed using the given public key.
-+ ///
-+ /// Only the signature is checked: no other checks (such as certificate chain validity)
-+ /// are performed.
-+ ///
-+ /// Returns `true` if verification succeeds.
-+ #[corresponds(X509_verify)]
-+ pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack>
-+ where
-+ T: HasPublic,
-+ {
-+ unsafe { cvt_n(ffi::X509_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) }
-+ }
-+
-+ /// Returns this certificate's serial number.
-+ #[corresponds(X509_get_serialNumber)]
-+ pub fn serial_number(&self) -> &Asn1IntegerRef {
-+ unsafe {
-+ let r = ffi::X509_get_serialNumber(self.as_ptr());
-+ Asn1IntegerRef::from_const_ptr_opt(r).expect("serial number must not be null")
-+ }
-+ }
-+
-+ to_pem! {
-+ /// Serializes the certificate into a PEM-encoded X509 structure.
-+ ///
-+ /// The output will have a header of `-----BEGIN CERTIFICATE-----`.
-+ #[corresponds(PEM_write_bio_X509)]
-+ to_pem,
-+ ffi::PEM_write_bio_X509
-+ }
-+
-+ to_der! {
-+ /// Serializes the certificate into a DER-encoded X509 structure.
-+ #[corresponds(i2d_X509)]
-+ to_der,
-+ ffi::i2d_X509
-+ }
-+
-+ to_pem! {
-+ /// Converts the certificate to human readable text.
-+ #[corresponds(X509_print)]
-+ to_text,
-+ ffi::X509_print
-+ }
-+}
-+
-+impl ToOwned for X509Ref {
-+ type Owned = X509;
-+
-+ fn to_owned(&self) -> X509 {
-+ unsafe {
-+ X509_up_ref(self.as_ptr());
-+ X509::from_ptr(self.as_ptr())
-+ }
-+ }
-+}
-+
-+impl Ord for X509Ref {
-+ fn cmp(&self, other: &Self) -> cmp::Ordering {
-+ // X509_cmp returns a number <0 for less than, 0 for equal and >0 for greater than.
-+ // It can't fail if both pointers are valid, which we know is true.
-+ let cmp = unsafe { ffi::X509_cmp(self.as_ptr(), other.as_ptr()) };
-+ cmp.cmp(&0)
-+ }
-+}
-+
-+impl PartialOrd for X509Ref {
-+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-+ Some(self.cmp(other))
-+ }
-+}
-+
-+impl PartialOrd<X509> for X509Ref {
-+ fn partial_cmp(&self, other: &X509) -> Option<cmp::Ordering> {
-+ <X509Ref as PartialOrd<X509Ref>>::partial_cmp(self, other)
-+ }
-+}
-+
-+impl PartialEq for X509Ref {
-+ fn eq(&self, other: &Self) -> bool {
-+ self.cmp(other) == cmp::Ordering::Equal
-+ }
-+}
-+
-+impl PartialEq<X509> for X509Ref {
-+ fn eq(&self, other: &X509) -> bool {
-+ <X509Ref as PartialEq<X509Ref>>::eq(self, other)
-+ }
-+}
-+
-+impl Eq for X509Ref {}
-+
-+impl X509 {
-+ /// Returns a new builder.
-+ pub fn builder() -> Result<X509Builder, ErrorStack> {
-+ X509Builder::new()
-+ }
-+
-+ from_pem! {
-+ /// Deserializes a PEM-encoded X509 structure.
-+ ///
-+ /// The input should have a header of `-----BEGIN CERTIFICATE-----`.
-+ #[corresponds(PEM_read_bio_X509)]
-+ from_pem,
-+ X509,
-+ ffi::PEM_read_bio_X509
-+ }
-+
-+ from_der! {
-+ /// Deserializes a DER-encoded X509 structure.
-+ #[corresponds(d2i_X509)]
-+ from_der,
-+ X509,
-+ ffi::d2i_X509
-+ }
-+
-+ /// Deserializes a list of PEM-formatted certificates.
-+ #[corresponds(PEM_read_bio_X509)]
-+ pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509>, ErrorStack> {
-+ unsafe {
-+ ffi::init();
-+ let bio = MemBioSlice::new(pem)?;
-+
-+ let mut certs = vec![];
-+ loop {
-+ let r =
-+ ffi::PEM_read_bio_X509(bio.as_ptr(), ptr::null_mut(), None, ptr::null_mut());
-+ if r.is_null() {
-+ let err = ffi::ERR_peek_last_error();
-+ if ffi::ERR_GET_LIB(err) as X509LenTy == ffi::ERR_LIB_PEM
-+ && ffi::ERR_GET_REASON(err) == ffi::PEM_R_NO_START_LINE
-+ {
-+ ffi::ERR_clear_error();
-+ break;
-+ }
-+
-+ return Err(ErrorStack::get());
-+ } else {
-+ certs.push(X509(r));
-+ }
-+ }
-+
-+ Ok(certs)
-+ }
-+ }
-+}
-+
-+impl Clone for X509 {
-+ fn clone(&self) -> X509 {
-+ X509Ref::to_owned(self)
-+ }
-+}
-+
-+impl fmt::Debug for X509 {
-+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ let serial = match &self.serial_number().to_bn() {
-+ Ok(bn) => match bn.to_hex_str() {
-+ Ok(hex) => hex.to_string(),
-+ Err(_) => "".to_string(),
-+ },
-+ Err(_) => "".to_string(),
-+ };
-+ let mut debug_struct = formatter.debug_struct("X509");
-+ debug_struct.field("serial_number", &serial);
-+ debug_struct.field("signature_algorithm", &self.signature_algorithm().object());
-+ debug_struct.field("issuer", &self.issuer_name());
-+ debug_struct.field("subject", &self.subject_name());
-+ if let Some(subject_alt_names) = &self.subject_alt_names() {
-+ debug_struct.field("subject_alt_names", subject_alt_names);
-+ }
-+ debug_struct.field("not_before", &self.not_before());
-+ debug_struct.field("not_after", &self.not_after());
-+
-+ if let Ok(public_key) = &self.public_key() {
-+ debug_struct.field("public_key", public_key);
-+ };
-+ // TODO: Print extensions once they are supported on the X509 struct.
-+
-+ debug_struct.finish()
-+ }
-+}
-+
-+impl AsRef<X509Ref> for X509Ref {
-+ fn as_ref(&self) -> &X509Ref {
-+ self
-+ }
-+}
-+
-+impl Stackable for X509 {
-+ type StackType = ffi::stack_st_X509;
-+}
-+
-+impl Ord for X509 {
-+ fn cmp(&self, other: &Self) -> cmp::Ordering {
-+ X509Ref::cmp(self, other)
-+ }
-+}
-+
-+impl PartialOrd for X509 {
-+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
-+ X509Ref::partial_cmp(self, other)
-+ }
-+}
-+
-+impl PartialOrd<X509Ref> for X509 {
-+ fn partial_cmp(&self, other: &X509Ref) -> Option<cmp::Ordering> {
-+ X509Ref::partial_cmp(self, other)
-+ }
-+}
-+
-+impl PartialEq for X509 {
-+ fn eq(&self, other: &Self) -> bool {
-+ X509Ref::eq(self, other)
-+ }
-+}
-+
-+impl PartialEq<X509Ref> for X509 {
-+ fn eq(&self, other: &X509Ref) -> bool {
-+ X509Ref::eq(self, other)
-+ }
-+}
-+
-+impl Eq for X509 {}
-+
-+/// A context object required to construct certain `X509` extension values.
-+pub struct X509v3Context<'a>(ffi::X509V3_CTX, PhantomData<(&'a X509Ref, &'a ConfRef)>);
-+
-+impl<'a> X509v3Context<'a> {
-+ pub fn as_ptr(&self) -> *mut ffi::X509V3_CTX {
-+ &self.0 as *const _ as *mut _
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_EXTENSION;
-+ fn drop = ffi::X509_EXTENSION_free;
-+
-+ /// Permit additional fields to be added to an `X509` v3 certificate.
-+ pub struct X509Extension;
-+ /// Reference to `X509Extension`.
-+ pub struct X509ExtensionRef;
-+}
-+
-+impl Stackable for X509Extension {
-+ type StackType = ffi::stack_st_X509_EXTENSION;
-+}
-+
-+impl X509Extension {
-+ /// Constructs an X509 extension value. See `man x509v3_config` for information on supported
-+ /// names and their value formats.
-+ ///
-+ /// Some extension types, such as `subjectAlternativeName`, require an `X509v3Context` to be
-+ /// provided.
-+ ///
-+ /// See the extension module for builder types which will construct certain common extensions.
-+ pub fn new(
-+ conf: Option<&ConfRef>,
-+ context: Option<&X509v3Context<'_>>,
-+ name: &str,
-+ value: &str,
-+ ) -> Result<X509Extension, ErrorStack> {
-+ let name = CString::new(name).unwrap();
-+ let value = CString::new(value).unwrap();
-+ unsafe {
-+ ffi::init();
-+ let conf = conf.map_or(ptr::null_mut(), ConfRef::as_ptr);
-+ let context = context.map_or(ptr::null_mut(), X509v3Context::as_ptr);
-+ let name = name.as_ptr() as *mut _;
-+ let value = value.as_ptr() as *mut _;
-+
-+ cvt_p(ffi::X509V3_EXT_nconf(conf, context, name, value)).map(X509Extension)
-+ }
-+ }
-+
-+ /// Constructs an X509 extension value. See `man x509v3_config` for information on supported
-+ /// extensions and their value formats.
-+ ///
-+ /// Some extension types, such as `nid::SUBJECT_ALTERNATIVE_NAME`, require an `X509v3Context` to
-+ /// be provided.
-+ ///
-+ /// See the extension module for builder types which will construct certain common extensions.
-+ pub fn new_nid(
-+ conf: Option<&ConfRef>,
-+ context: Option<&X509v3Context<'_>>,
-+ name: Nid,
-+ value: &str,
-+ ) -> Result<X509Extension, ErrorStack> {
-+ let value = CString::new(value).unwrap();
-+ unsafe {
-+ ffi::init();
-+ let conf = conf.map_or(ptr::null_mut(), ConfRef::as_ptr);
-+ let context = context.map_or(ptr::null_mut(), X509v3Context::as_ptr);
-+ let name = name.as_raw();
-+ let value = value.as_ptr() as *mut _;
-+
-+ cvt_p(ffi::X509V3_EXT_nconf_nid(conf, context, name, value)).map(X509Extension)
-+ }
-+ }
-+
-+ /// Adds an alias for an extension
-+ ///
-+ /// # Safety
-+ ///
-+ /// This method modifies global state without locking and therefore is not thread safe
-+ #[corresponds(X509V3_EXT_add_alias)]
-+ pub unsafe fn add_alias(to: Nid, from: Nid) -> Result<(), ErrorStack> {
-+ ffi::init();
-+ cvt(ffi::X509V3_EXT_add_alias(to.as_raw(), from.as_raw())).map(|_| ())
-+ }
-+}
-+
-+/// A builder used to construct an `X509Name`.
-+pub struct X509NameBuilder(X509Name);
-+
-+impl X509NameBuilder {
-+ /// Creates a new builder.
-+ pub fn new() -> Result<X509NameBuilder, ErrorStack> {
-+ unsafe {
-+ ffi::init();
-+ cvt_p(ffi::X509_NAME_new()).map(|p| X509NameBuilder(X509Name(p)))
-+ }
-+ }
-+
-+ /// Add a name entry
-+ #[corresponds(X509_NAME_add_entry)]
-+ #[cfg(any(ossl101, libressl350))]
-+ pub fn append_entry(&mut self, ne: &X509NameEntryRef) -> std::result::Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_NAME_add_entry(
-+ self.0.as_ptr(),
-+ ne.as_ptr(),
-+ -1,
-+ 0,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Add a field entry by str.
-+ ///
-+ /// This corresponds to [`X509_NAME_add_entry_by_txt`].
-+ ///
-+ /// [`X509_NAME_add_entry_by_txt`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_txt.html
-+ pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> {
-+ unsafe {
-+ let field = CString::new(field).unwrap();
-+ assert!(value.len() <= c_int::max_value() as usize);
-+ cvt(ffi::X509_NAME_add_entry_by_txt(
-+ self.0.as_ptr(),
-+ field.as_ptr() as *mut _,
-+ ffi::MBSTRING_UTF8,
-+ value.as_ptr(),
-+ value.len() as c_int,
-+ -1,
-+ 0,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Add a field entry by str with a specific type.
-+ ///
-+ /// This corresponds to [`X509_NAME_add_entry_by_txt`].
-+ ///
-+ /// [`X509_NAME_add_entry_by_txt`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_txt.html
-+ pub fn append_entry_by_text_with_type(
-+ &mut self,
-+ field: &str,
-+ value: &str,
-+ ty: Asn1Type,
-+ ) -> Result<(), ErrorStack> {
-+ unsafe {
-+ let field = CString::new(field).unwrap();
-+ assert!(value.len() <= c_int::max_value() as usize);
-+ cvt(ffi::X509_NAME_add_entry_by_txt(
-+ self.0.as_ptr(),
-+ field.as_ptr() as *mut _,
-+ ty.as_raw(),
-+ value.as_ptr(),
-+ value.len() as c_int,
-+ -1,
-+ 0,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Add a field entry by NID.
-+ ///
-+ /// This corresponds to [`X509_NAME_add_entry_by_NID`].
-+ ///
-+ /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html
-+ pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> {
-+ unsafe {
-+ assert!(value.len() <= c_int::max_value() as usize);
-+ cvt(ffi::X509_NAME_add_entry_by_NID(
-+ self.0.as_ptr(),
-+ field.as_raw(),
-+ ffi::MBSTRING_UTF8,
-+ value.as_ptr() as *mut _,
-+ value.len() as c_int,
-+ -1,
-+ 0,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Add a field entry by NID with a specific type.
-+ ///
-+ /// This corresponds to [`X509_NAME_add_entry_by_NID`].
-+ ///
-+ /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_add_entry_by_NID.html
-+ pub fn append_entry_by_nid_with_type(
-+ &mut self,
-+ field: Nid,
-+ value: &str,
-+ ty: Asn1Type,
-+ ) -> Result<(), ErrorStack> {
-+ unsafe {
-+ assert!(value.len() <= c_int::max_value() as usize);
-+ cvt(ffi::X509_NAME_add_entry_by_NID(
-+ self.0.as_ptr(),
-+ field.as_raw(),
-+ ty.as_raw(),
-+ value.as_ptr() as *mut _,
-+ value.len() as c_int,
-+ -1,
-+ 0,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Return an `X509Name`.
-+ pub fn build(self) -> X509Name {
-+ self.0
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_NAME;
-+ fn drop = ffi::X509_NAME_free;
-+
-+ /// The names of an `X509` certificate.
-+ pub struct X509Name;
-+ /// Reference to `X509Name`.
-+ pub struct X509NameRef;
-+}
-+
-+impl X509Name {
-+ /// Returns a new builder.
-+ pub fn builder() -> Result<X509NameBuilder, ErrorStack> {
-+ X509NameBuilder::new()
-+ }
-+
-+ /// Loads subject names from a file containing PEM-formatted certificates.
-+ ///
-+ /// This is commonly used in conjunction with `SslContextBuilder::set_client_ca_list`.
-+ pub fn load_client_ca_file<P: AsRef<Path>>(file: P) -> Result<Stack<X509Name>, ErrorStack> {
-+ let file = CString::new(file.as_ref().as_os_str().to_str().unwrap()).unwrap();
-+ unsafe { cvt_p(ffi::SSL_load_client_CA_file(file.as_ptr())).map(|p| Stack::from_ptr(p)) }
-+ }
-+
-+ from_der! {
-+ /// Deserializes a DER-encoded X509 name structure.
-+ ///
-+ /// This corresponds to [`d2i_X509_NAME`].
-+ ///
-+ /// [`d2i_X509_NAME`]: https://www.openssl.org/docs/manmaster/man3/d2i_X509_NAME.html
-+ from_der,
-+ X509Name,
-+ ffi::d2i_X509_NAME
-+ }
-+}
-+
-+impl Stackable for X509Name {
-+ type StackType = ffi::stack_st_X509_NAME;
-+}
-+
-+impl X509NameRef {
-+ /// Returns the name entries by the nid.
-+ pub fn entries_by_nid(&self, nid: Nid) -> X509NameEntries<'_> {
-+ X509NameEntries {
-+ name: self,
-+ nid: Some(nid),
-+ loc: -1,
-+ }
-+ }
-+
-+ /// Returns an iterator over all `X509NameEntry` values
-+ pub fn entries(&self) -> X509NameEntries<'_> {
-+ X509NameEntries {
-+ name: self,
-+ nid: None,
-+ loc: -1,
-+ }
-+ }
-+
-+ /// Compare two names, like [`Ord`] but it may fail.
-+ ///
-+ /// With OpenSSL versions from 3.0.0 this may return an error if the underlying `X509_NAME_cmp`
-+ /// call fails.
-+ /// For OpenSSL versions before 3.0.0 it will never return an error, but due to a bug it may
-+ /// spuriously return `Ordering::Less` if the `X509_NAME_cmp` call fails.
-+ #[corresponds(X509_NAME_cmp)]
-+ pub fn try_cmp(&self, other: &X509NameRef) -> Result<Ordering, ErrorStack> {
-+ let cmp = unsafe { ffi::X509_NAME_cmp(self.as_ptr(), other.as_ptr()) };
-+ if cfg!(ossl300) && cmp == -2 {
-+ return Err(ErrorStack::get());
-+ }
-+ Ok(cmp.cmp(&0))
-+ }
-+
-+ /// Copies the name to a new `X509Name`.
-+ #[corresponds(X509_NAME_dup)]
-+ #[cfg(any(boringssl, ossl110, libressl270))]
-+ pub fn to_owned(&self) -> Result<X509Name, ErrorStack> {
-+ unsafe { cvt_p(ffi::X509_NAME_dup(self.as_ptr())).map(|n| X509Name::from_ptr(n)) }
-+ }
-+
-+ to_der! {
-+ /// Serializes the certificate into a DER-encoded X509 name structure.
-+ ///
-+ /// This corresponds to [`i2d_X509_NAME`].
-+ ///
-+ /// [`i2d_X509_NAME`]: https://www.openssl.org/docs/manmaster/crypto/i2d_X509_NAME.html
-+ to_der,
-+ ffi::i2d_X509_NAME
-+ }
-+}
-+
-+impl fmt::Debug for X509NameRef {
-+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ formatter.debug_list().entries(self.entries()).finish()
-+ }
-+}
-+
-+/// A type to destructure and examine an `X509Name`.
-+pub struct X509NameEntries<'a> {
-+ name: &'a X509NameRef,
-+ nid: Option<Nid>,
-+ loc: c_int,
-+}
-+
-+impl<'a> Iterator for X509NameEntries<'a> {
-+ type Item = &'a X509NameEntryRef;
-+
-+ fn next(&mut self) -> Option<&'a X509NameEntryRef> {
-+ unsafe {
-+ match self.nid {
-+ Some(nid) => {
-+ // There is a `Nid` specified to search for
-+ self.loc =
-+ ffi::X509_NAME_get_index_by_NID(self.name.as_ptr(), nid.as_raw(), self.loc);
-+ if self.loc == -1 {
-+ return None;
-+ }
-+ }
-+ None => {
-+ // Iterate over all `Nid`s
-+ self.loc += 1;
-+ if self.loc >= ffi::X509_NAME_entry_count(self.name.as_ptr()) {
-+ return None;
-+ }
-+ }
-+ }
-+
-+ let entry = ffi::X509_NAME_get_entry(self.name.as_ptr(), self.loc);
-+
-+ Some(X509NameEntryRef::from_const_ptr_opt(entry).expect("entry must not be null"))
-+ }
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_NAME_ENTRY;
-+ fn drop = ffi::X509_NAME_ENTRY_free;
-+
-+ /// A name entry associated with a `X509Name`.
-+ pub struct X509NameEntry;
-+ /// Reference to `X509NameEntry`.
-+ pub struct X509NameEntryRef;
-+}
-+
-+impl X509NameEntryRef {
-+ /// Returns the field value of an `X509NameEntry`.
-+ ///
-+ /// This corresponds to [`X509_NAME_ENTRY_get_data`].
-+ ///
-+ /// [`X509_NAME_ENTRY_get_data`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_ENTRY_get_data.html
-+ pub fn data(&self) -> &Asn1StringRef {
-+ unsafe {
-+ let data = ffi::X509_NAME_ENTRY_get_data(self.as_ptr());
-+ Asn1StringRef::from_ptr(data)
-+ }
-+ }
-+
-+ /// Returns the `Asn1Object` value of an `X509NameEntry`.
-+ /// This is useful for finding out about the actual `Nid` when iterating over all `X509NameEntries`.
-+ ///
-+ /// This corresponds to [`X509_NAME_ENTRY_get_object`].
-+ ///
-+ /// [`X509_NAME_ENTRY_get_object`]: https://www.openssl.org/docs/manmaster/crypto/X509_NAME_ENTRY_get_object.html
-+ pub fn object(&self) -> &Asn1ObjectRef {
-+ unsafe {
-+ let object = ffi::X509_NAME_ENTRY_get_object(self.as_ptr());
-+ Asn1ObjectRef::from_ptr(object)
-+ }
-+ }
-+}
-+
-+impl fmt::Debug for X509NameEntryRef {
-+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ formatter.write_fmt(format_args!("{:?} = {:?}", self.object(), self.data()))
-+ }
-+}
-+
-+/// A builder used to construct an `X509Req`.
-+pub struct X509ReqBuilder(X509Req);
-+
-+impl X509ReqBuilder {
-+ /// Returns a builder for a certificate request.
-+ ///
-+ /// This corresponds to [`X509_REQ_new`].
-+ ///
-+ ///[`X509_REQ_new`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_new.html
-+ pub fn new() -> Result<X509ReqBuilder, ErrorStack> {
-+ unsafe {
-+ ffi::init();
-+ cvt_p(ffi::X509_REQ_new()).map(|p| X509ReqBuilder(X509Req(p)))
-+ }
-+ }
-+
-+ /// Set the numerical value of the version field.
-+ ///
-+ /// This corresponds to [`X509_REQ_set_version`].
-+ ///
-+ ///[`X509_REQ_set_version`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_version.html
-+ #[allow(clippy::useless_conversion)]
-+ pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_REQ_set_version(
-+ self.0.as_ptr(),
-+ version as c_long,
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Set the issuer name.
-+ ///
-+ /// This corresponds to [`X509_REQ_set_subject_name`].
-+ ///
-+ /// [`X509_REQ_set_subject_name`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_subject_name.html
-+ pub fn set_subject_name(&mut self, subject_name: &X509NameRef) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_REQ_set_subject_name(
-+ self.0.as_ptr(),
-+ subject_name.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Set the public key.
-+ ///
-+ /// This corresponds to [`X509_REQ_set_pubkey`].
-+ ///
-+ /// [`X509_REQ_set_pubkey`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_set_pubkey.html
-+ pub fn set_pubkey<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPublic,
-+ {
-+ unsafe { cvt(ffi::X509_REQ_set_pubkey(self.0.as_ptr(), key.as_ptr())).map(|_| ()) }
-+ }
-+
-+ /// Return an `X509v3Context`. This context object can be used to construct
-+ /// certain `X509` extensions.
-+ pub fn x509v3_context<'a>(&'a self, conf: Option<&'a ConfRef>) -> X509v3Context<'a> {
-+ unsafe {
-+ let mut ctx = mem::zeroed();
-+
-+ ffi::X509V3_set_ctx(
-+ &mut ctx,
-+ ptr::null_mut(),
-+ ptr::null_mut(),
-+ self.0.as_ptr(),
-+ ptr::null_mut(),
-+ 0,
-+ );
-+
-+ // nodb case taken care of since we zeroed ctx above
-+ if let Some(conf) = conf {
-+ ffi::X509V3_set_nconf(&mut ctx, conf.as_ptr());
-+ }
-+
-+ X509v3Context(ctx, PhantomData)
-+ }
-+ }
-+
-+ /// Permits any number of extension fields to be added to the certificate.
-+ pub fn add_extensions(
-+ &mut self,
-+ extensions: &StackRef<X509Extension>,
-+ ) -> Result<(), ErrorStack> {
-+ unsafe {
-+ cvt(ffi::X509_REQ_add_extensions(
-+ self.0.as_ptr(),
-+ extensions.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Sign the request using a private key.
-+ ///
-+ /// This corresponds to [`X509_REQ_sign`].
-+ ///
-+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_sign.html
-+ pub fn sign<T>(&mut self, key: &PKeyRef<T>, hash: MessageDigest) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe {
-+ cvt(ffi::X509_REQ_sign(
-+ self.0.as_ptr(),
-+ key.as_ptr(),
-+ hash.as_ptr(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Sign the request using a private key without a digest.
-+ ///
-+ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
-+ /// message digest.
-+ ///
-+ /// This corresponds to [`X509_REQ_sign`].
-+ ///
-+ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html
-+ #[cfg(boringssl)]
-+ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
-+ where
-+ T: HasPrivate,
-+ {
-+ unsafe {
-+ cvt(ffi::X509_REQ_sign(
-+ self.0.as_ptr(),
-+ key.as_ptr(),
-+ ptr::null(),
-+ ))
-+ .map(|_| ())
-+ }
-+ }
-+
-+ /// Returns the `X509Req`.
-+ pub fn build(self) -> X509Req {
-+ self.0
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_REQ;
-+ fn drop = ffi::X509_REQ_free;
-+
-+ /// An `X509` certificate request.
-+ pub struct X509Req;
-+ /// Reference to `X509Req`.
-+ pub struct X509ReqRef;
-+}
-+
-+impl X509Req {
-+ /// A builder for `X509Req`.
-+ pub fn builder() -> Result<X509ReqBuilder, ErrorStack> {
-+ X509ReqBuilder::new()
-+ }
-+
-+ from_pem! {
-+ /// Deserializes a PEM-encoded PKCS#10 certificate request structure.
-+ ///
-+ /// The input should have a header of `-----BEGIN CERTIFICATE REQUEST-----`.
-+ ///
-+ /// This corresponds to [`PEM_read_bio_X509_REQ`].
-+ ///
-+ /// [`PEM_read_bio_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/PEM_read_bio_X509_REQ.html
-+ from_pem,
-+ X509Req,
-+ ffi::PEM_read_bio_X509_REQ
-+ }
-+
-+ from_der! {
-+ /// Deserializes a DER-encoded PKCS#10 certificate request structure.
-+ ///
-+ /// This corresponds to [`d2i_X509_REQ`].
-+ ///
-+ /// [`d2i_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/d2i_X509_REQ.html
-+ from_der,
-+ X509Req,
-+ ffi::d2i_X509_REQ
-+ }
-+}
-+
-+impl X509ReqRef {
-+ to_pem! {
-+ /// Serializes the certificate request to a PEM-encoded PKCS#10 structure.
-+ ///
-+ /// The output will have a header of `-----BEGIN CERTIFICATE REQUEST-----`.
-+ ///
-+ /// This corresponds to [`PEM_write_bio_X509_REQ`].
-+ ///
-+ /// [`PEM_write_bio_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/PEM_write_bio_X509_REQ.html
-+ to_pem,
-+ ffi::PEM_write_bio_X509_REQ
-+ }
-+
-+ to_der! {
-+ /// Serializes the certificate request to a DER-encoded PKCS#10 structure.
-+ ///
-+ /// This corresponds to [`i2d_X509_REQ`].
-+ ///
-+ /// [`i2d_X509_REQ`]: https://www.openssl.org/docs/manmaster/crypto/i2d_X509_REQ.html
-+ to_der,
-+ ffi::i2d_X509_REQ
-+ }
-+
-+ to_pem! {
-+ /// Converts the request to human readable text.
-+ #[corresponds(X509_Req_print)]
-+ to_text,
-+ ffi::X509_REQ_print
-+ }
-+
-+ /// Returns the numerical value of the version field of the certificate request.
-+ ///
-+ /// This corresponds to [`X509_REQ_get_version`]
-+ ///
-+ /// [`X509_REQ_get_version`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_version.html
-+ pub fn version(&self) -> i32 {
-+ unsafe { X509_REQ_get_version(self.as_ptr()) as i32 }
-+ }
-+
-+ /// Returns the subject name of the certificate request.
-+ ///
-+ /// This corresponds to [`X509_REQ_get_subject_name`]
-+ ///
-+ /// [`X509_REQ_get_subject_name`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_subject_name.html
-+ pub fn subject_name(&self) -> &X509NameRef {
-+ unsafe {
-+ let name = X509_REQ_get_subject_name(self.as_ptr());
-+ X509NameRef::from_const_ptr_opt(name).expect("subject name must not be null")
-+ }
-+ }
-+
-+ /// Returns the public key of the certificate request.
-+ ///
-+ /// This corresponds to [`X509_REQ_get_pubkey"]
-+ ///
-+ /// [`X509_REQ_get_pubkey`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_get_pubkey.html
-+ pub fn public_key(&self) -> Result<PKey<Public>, ErrorStack> {
-+ unsafe {
-+ let key = cvt_p(ffi::X509_REQ_get_pubkey(self.as_ptr()))?;
-+ Ok(PKey::from_ptr(key))
-+ }
-+ }
-+
-+ /// Check if the certificate request is signed using the given public key.
-+ ///
-+ /// Returns `true` if verification succeeds.
-+ ///
-+ /// This corresponds to [`X509_REQ_verify"].
-+ ///
-+ /// [`X509_REQ_verify`]: https://www.openssl.org/docs/manmaster/crypto/X509_REQ_verify.html
-+ pub fn verify<T>(&self, key: &PKeyRef<T>) -> Result<bool, ErrorStack>
-+ where
-+ T: HasPublic,
-+ {
-+ unsafe { cvt_n(ffi::X509_REQ_verify(self.as_ptr(), key.as_ptr())).map(|n| n != 0) }
-+ }
-+
-+ /// Returns the extensions of the certificate request.
-+ ///
-+ /// This corresponds to [`X509_REQ_get_extensions"]
-+ pub fn extensions(&self) -> Result<Stack<X509Extension>, ErrorStack> {
-+ unsafe {
-+ let extensions = cvt_p(ffi::X509_REQ_get_extensions(self.as_ptr()))?;
-+ Ok(Stack::from_ptr(extensions))
-+ }
-+ }
-+}
-+
-+/// The result of peer certificate verification.
-+#[derive(Copy, Clone, PartialEq, Eq)]
-+pub struct X509VerifyResult(c_int);
-+
-+impl fmt::Debug for X509VerifyResult {
-+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ fmt.debug_struct("X509VerifyResult")
-+ .field("code", &self.0)
-+ .field("error", &self.error_string())
-+ .finish()
-+ }
-+}
-+
-+impl fmt::Display for X509VerifyResult {
-+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ fmt.write_str(self.error_string())
-+ }
-+}
-+
-+impl Error for X509VerifyResult {}
-+
-+impl X509VerifyResult {
-+ /// Creates an `X509VerifyResult` from a raw error number.
-+ ///
-+ /// # Safety
-+ ///
-+ /// Some methods on `X509VerifyResult` are not thread safe if the error
-+ /// number is invalid.
-+ pub unsafe fn from_raw(err: c_int) -> X509VerifyResult {
-+ X509VerifyResult(err)
-+ }
-+
-+ /// Return the integer representation of an `X509VerifyResult`.
-+ #[allow(clippy::trivially_copy_pass_by_ref)]
-+ pub fn as_raw(&self) -> c_int {
-+ self.0
-+ }
-+
-+ /// Return a human readable error string from the verification error.
-+ ///
-+ /// This corresponds to [`X509_verify_cert_error_string`].
-+ ///
-+ /// [`X509_verify_cert_error_string`]: https://www.openssl.org/docs/manmaster/crypto/X509_verify_cert_error_string.html
-+ #[allow(clippy::trivially_copy_pass_by_ref)]
-+ pub fn error_string(&self) -> &'static str {
-+ ffi::init();
-+
-+ unsafe {
-+ let s = ffi::X509_verify_cert_error_string(self.0 as c_long);
-+ str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap()
-+ }
-+ }
-+
-+ /// Successful peer certificate verification.
-+ pub const OK: X509VerifyResult = X509VerifyResult(ffi::X509_V_OK);
-+ /// Application verification failure.
-+ pub const APPLICATION_VERIFICATION: X509VerifyResult =
-+ X509VerifyResult(ffi::X509_V_ERR_APPLICATION_VERIFICATION);
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::GENERAL_NAME;
-+ fn drop = ffi::GENERAL_NAME_free;
-+
-+ /// An `X509` certificate alternative names.
-+ pub struct GeneralName;
-+ /// Reference to `GeneralName`.
-+ pub struct GeneralNameRef;
-+}
-+
-+impl GeneralNameRef {
-+ fn ia5_string(&self, ffi_type: c_int) -> Option<&str> {
-+ unsafe {
-+ if (*self.as_ptr()).type_ != ffi_type {
-+ return None;
-+ }
-+
-+ #[cfg(boringssl)]
-+ let d = (*self.as_ptr()).d.ptr;
-+ #[cfg(not(boringssl))]
-+ let d = (*self.as_ptr()).d;
-+
-+ let ptr = ASN1_STRING_get0_data(d as *mut _);
-+ let len = ffi::ASN1_STRING_length(d as *mut _);
-+
-+ let slice = slice::from_raw_parts(ptr as *const u8, len as usize);
-+ // IA5Strings are stated to be ASCII (specifically IA5). Hopefully
-+ // OpenSSL checks that when loading a certificate but if not we'll
-+ // use this instead of from_utf8_unchecked just in case.
-+ str::from_utf8(slice).ok()
-+ }
-+ }
-+
-+ /// Returns the contents of this `GeneralName` if it is an `rfc822Name`.
-+ pub fn email(&self) -> Option<&str> {
-+ self.ia5_string(ffi::GEN_EMAIL)
-+ }
-+
-+ /// Returns the contents of this `GeneralName` if it is a `dNSName`.
-+ pub fn dnsname(&self) -> Option<&str> {
-+ self.ia5_string(ffi::GEN_DNS)
-+ }
-+
-+ /// Returns the contents of this `GeneralName` if it is an `uniformResourceIdentifier`.
-+ pub fn uri(&self) -> Option<&str> {
-+ self.ia5_string(ffi::GEN_URI)
-+ }
-+
-+ /// Returns the contents of this `GeneralName` if it is an `iPAddress`.
-+ pub fn ipaddress(&self) -> Option<&[u8]> {
-+ unsafe {
-+ if (*self.as_ptr()).type_ != ffi::GEN_IPADD {
-+ return None;
-+ }
-+ #[cfg(boringssl)]
-+ let d: *const ffi::ASN1_STRING = std::mem::transmute((*self.as_ptr()).d);
-+ #[cfg(not(boringssl))]
-+ let d = (*self.as_ptr()).d;
-+
-+ let ptr = ASN1_STRING_get0_data(d as *mut _);
-+ let len = ffi::ASN1_STRING_length(d as *mut _);
-+
-+ Some(slice::from_raw_parts(ptr as *const u8, len as usize))
-+ }
-+ }
-+}
-+
-+impl fmt::Debug for GeneralNameRef {
-+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
-+ if let Some(email) = self.email() {
-+ formatter.write_str(email)
-+ } else if let Some(dnsname) = self.dnsname() {
-+ formatter.write_str(dnsname)
-+ } else if let Some(uri) = self.uri() {
-+ formatter.write_str(uri)
-+ } else if let Some(ipaddress) = self.ipaddress() {
-+ let address = <[u8; 16]>::try_from(ipaddress)
-+ .map(IpAddr::from)
-+ .or_else(|_| <[u8; 4]>::try_from(ipaddress).map(IpAddr::from));
-+ match address {
-+ Ok(a) => fmt::Debug::fmt(&a, formatter),
-+ Err(_) => fmt::Debug::fmt(ipaddress, formatter),
-+ }
-+ } else {
-+ formatter.write_str("(empty)")
-+ }
-+ }
-+}
-+
-+impl Stackable for GeneralName {
-+ type StackType = ffi::stack_st_GENERAL_NAME;
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::ACCESS_DESCRIPTION;
-+ fn drop = ffi::ACCESS_DESCRIPTION_free;
-+
-+ /// `AccessDescription` of certificate authority information.
-+ pub struct AccessDescription;
-+ /// Reference to `AccessDescription`.
-+ pub struct AccessDescriptionRef;
-+}
-+
-+impl AccessDescriptionRef {
-+ /// Returns the access method OID.
-+ pub fn method(&self) -> &Asn1ObjectRef {
-+ unsafe { Asn1ObjectRef::from_ptr((*self.as_ptr()).method) }
-+ }
-+
-+ // Returns the access location.
-+ pub fn location(&self) -> &GeneralNameRef {
-+ unsafe { GeneralNameRef::from_ptr((*self.as_ptr()).location) }
-+ }
-+}
-+
-+impl Stackable for AccessDescription {
-+ type StackType = ffi::stack_st_ACCESS_DESCRIPTION;
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_ALGOR;
-+ fn drop = ffi::X509_ALGOR_free;
-+
-+ /// An `X509` certificate signature algorithm.
-+ pub struct X509Algorithm;
-+ /// Reference to `X509Algorithm`.
-+ pub struct X509AlgorithmRef;
-+}
-+
-+impl X509AlgorithmRef {
-+ /// Returns the ASN.1 OID of this algorithm.
-+ pub fn object(&self) -> &Asn1ObjectRef {
-+ unsafe {
-+ let mut oid = ptr::null();
-+ X509_ALGOR_get0(&mut oid, ptr::null_mut(), ptr::null_mut(), self.as_ptr());
-+ Asn1ObjectRef::from_const_ptr_opt(oid).expect("algorithm oid must not be null")
-+ }
-+ }
-+}
-+
-+foreign_type_and_impl_send_sync! {
-+ type CType = ffi::X509_OBJECT;
-+ fn drop = X509_OBJECT_free;
-+
-+ /// An `X509` or an X509 certificate revocation list.
-+ pub struct X509Object;
-+ /// Reference to `X509Object`
-+ pub struct X509ObjectRef;
-+}
-+
-+impl X509ObjectRef {
-+ pub fn x509(&self) -> Option<&X509Ref> {
-+ unsafe {
-+ let ptr = X509_OBJECT_get0_X509(self.as_ptr());
-+ X509Ref::from_const_ptr_opt(ptr)
-+ }
-+ }
-+}
-+
-+impl Stackable for X509Object {
-+ type StackType = ffi::stack_st_X509_OBJECT;
-+}
-+
-+cfg_if! {
-+ if #[cfg(any(boringssl, ossl110, libressl273))] {
-+ use ffi::{X509_getm_notAfter, X509_getm_notBefore, X509_up_ref, X509_get0_signature};
-+ } else {
-+ #[allow(bad_style)]
-+ unsafe fn X509_getm_notAfter(x: *mut ffi::X509) -> *mut ffi::ASN1_TIME {
-+ (*(*(*x).cert_info).validity).notAfter
-+ }
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_getm_notBefore(x: *mut ffi::X509) -> *mut ffi::ASN1_TIME {
-+ (*(*(*x).cert_info).validity).notBefore
-+ }
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_up_ref(x: *mut ffi::X509) {
-+ ffi::CRYPTO_add_lock(
-+ &mut (*x).references,
-+ 1,
-+ ffi::CRYPTO_LOCK_X509,
-+ "mod.rs\0".as_ptr() as *const _,
-+ line!() as c_int,
-+ );
-+ }
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_get0_signature(
-+ psig: *mut *const ffi::ASN1_BIT_STRING,
-+ palg: *mut *const ffi::X509_ALGOR,
-+ x: *const ffi::X509,
-+ ) {
-+ if !psig.is_null() {
-+ *psig = (*x).signature;
-+ }
-+ if !palg.is_null() {
-+ *palg = (*x).sig_alg;
-+ }
-+ }
-+ }
-+}
-+
-+cfg_if! {
-+ if #[cfg(any(boringssl, ossl110, libressl350))] {
-+ use ffi::{
-+ X509_ALGOR_get0, ASN1_STRING_get0_data, X509_STORE_CTX_get0_chain, X509_set1_notAfter,
-+ X509_set1_notBefore, X509_REQ_get_version, X509_REQ_get_subject_name,
-+ };
-+ } else {
-+ use ffi::{
-+ ASN1_STRING_data as ASN1_STRING_get0_data,
-+ X509_STORE_CTX_get_chain as X509_STORE_CTX_get0_chain,
-+ X509_set_notAfter as X509_set1_notAfter,
-+ X509_set_notBefore as X509_set1_notBefore,
-+ };
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_REQ_get_version(x: *mut ffi::X509_REQ) -> ::libc::c_long {
-+ ffi::ASN1_INTEGER_get((*(*x).req_info).version)
-+ }
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_REQ_get_subject_name(x: *mut ffi::X509_REQ) -> *mut ::ffi::X509_NAME {
-+ (*(*x).req_info).subject
-+ }
-+
-+ #[allow(bad_style)]
-+ unsafe fn X509_ALGOR_get0(
-+ paobj: *mut *const ffi::ASN1_OBJECT,
-+ pptype: *mut c_int,
-+ pval: *mut *mut ::libc::c_void,
-+ alg: *const ffi::X509_ALGOR,
-+ ) {
-+ if !paobj.is_null() {
-+ *paobj = (*alg).algorithm;
-+ }
-+ assert!(pptype.is_null());
-+ assert!(pval.is_null());
-+ }
-+ }
-+}
-+
-+cfg_if! {
-+ if #[cfg(any(ossl110, boringssl, libressl270))] {
-+ use ffi::X509_OBJECT_get0_X509;
-+ } else {
-+ #[allow(bad_style)]
-+ unsafe fn X509_OBJECT_get0_X509(x: *mut ffi::X509_OBJECT) -> *mut ffi::X509 {
-+ if (*x).type_ == ffi::X509_LU_X509 {
-+ (*x).data.x509
-+ } else {
-+ ptr::null_mut()
-+ }
-+ }
-+ }
-+}
-+
-+cfg_if! {
-+ if #[cfg(any(ossl110, libressl350))] {
-+ use ffi::X509_OBJECT_free;
-+ } else if #[cfg(boringssl)] {
-+ use ffi::X509_OBJECT_free_contents as X509_OBJECT_free;
-+ } else {
-+ #[allow(bad_style)]
-+ unsafe fn X509_OBJECT_free(x: *mut ffi::X509_OBJECT) {
-+ ffi::X509_OBJECT_free_contents(x);
-+ ffi::CRYPTO_free(x as *mut libc::c_void);
-+ }
-+ }
-+}
-+
-+#[derive(Copy, Clone, PartialEq, Eq)]
-+pub struct X509PurposeId(c_int);
-+
-+impl X509PurposeId {
-+ pub const SSL_CLIENT: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SSL_CLIENT);
-+ pub const SSL_SERVER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SSL_SERVER);
-+ pub const NS_SSL_SERVER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_NS_SSL_SERVER);
-+ pub const SMIME_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SMIME_SIGN);
-+ pub const SMIME_ENCRYPT: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_SMIME_ENCRYPT);
-+ pub const CRL_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_CRL_SIGN);
-+ pub const ANY: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_ANY);
-+ pub const OCSP_HELPER: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_OCSP_HELPER);
-+ pub const TIMESTAMP_SIGN: X509PurposeId = X509PurposeId(ffi::X509_PURPOSE_TIMESTAMP_SIGN);
-+
-+ /// Constructs an `X509PurposeId` from a raw OpenSSL value.
-+ pub fn from_raw(id: c_int) -> Self {
-+ X509PurposeId(id)
-+ }
-+
-+ /// Returns the raw OpenSSL value represented by this type.
-+ pub fn as_raw(&self) -> c_int {
-+ self.0
-+ }
-+}
-+
-+/// A reference to an [`X509_PURPOSE`].
-+pub struct X509PurposeRef(Opaque);
-+
-+/// Implements a wrapper type for the static `X509_PURPOSE` table in OpenSSL.
-+impl ForeignTypeRef for X509PurposeRef {
-+ type CType = ffi::X509_PURPOSE;
-+}
-+
-+impl X509PurposeRef {
-+ /// Get the internal table index of an X509_PURPOSE for a given short name. Valid short
-+ /// names include
-+ /// - "sslclient",
-+ /// - "sslserver",
-+ /// - "nssslserver",
-+ /// - "smimesign",
-+ /// - "smimeencrypt",
-+ /// - "crlsign",
-+ /// - "any",
-+ /// - "ocsphelper",
-+ /// - "timestampsign"
-+ /// The index can be used with `X509PurposeRef::from_idx()` to get the purpose.
-+ #[allow(clippy::unnecessary_cast)]
-+ pub fn get_by_sname(sname: &str) -> Result<c_int, ErrorStack> {
-+ unsafe {
-+ let sname = CString::new(sname).unwrap();
-+ cfg_if! {
-+ if #[cfg(any(ossl110, libressl280))] {
-+ let purpose = cvt_n(ffi::X509_PURPOSE_get_by_sname(sname.as_ptr() as *const _))?;
-+ } else {
-+ let purpose = cvt_n(ffi::X509_PURPOSE_get_by_sname(sname.as_ptr() as *mut _))?;
-+ }
-+ }
-+ Ok(purpose)
-+ }
-+ }
-+ /// Get an `X509PurposeRef` for a given index value. The index can be obtained from e.g.
-+ /// `X509PurposeRef::get_by_sname()`.
-+ #[corresponds(X509_PURPOSE_get0)]
-+ pub fn from_idx(idx: c_int) -> Result<&'static X509PurposeRef, ErrorStack> {
-+ unsafe {
-+ let ptr = cvt_p(ffi::X509_PURPOSE_get0(idx))?;
-+ Ok(X509PurposeRef::from_ptr(ptr))
-+ }
-+ }
-+
-+ /// Get the purpose value from an X509Purpose structure. This value is one of
-+ /// - `X509_PURPOSE_SSL_CLIENT`
-+ /// - `X509_PURPOSE_SSL_SERVER`
-+ /// - `X509_PURPOSE_NS_SSL_SERVER`
-+ /// - `X509_PURPOSE_SMIME_SIGN`
-+ /// - `X509_PURPOSE_SMIME_ENCRYPT`
-+ /// - `X509_PURPOSE_CRL_SIGN`
-+ /// - `X509_PURPOSE_ANY`
-+ /// - `X509_PURPOSE_OCSP_HELPER`
-+ /// - `X509_PURPOSE_TIMESTAMP_SIGN`
-+ pub fn purpose(&self) -> X509PurposeId {
-+ unsafe {
-+ let x509_purpose: *mut ffi::X509_PURPOSE = self.as_ptr();
-+ X509PurposeId::from_raw((*x509_purpose).purpose)
-+ }
-+ }
-+}
---
-2.40.1.495.gc816e09b53d-goog
-
diff --git a/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch b/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch
new file mode 100644
index 0000000..0ee52b1
--- /dev/null
+++ b/nearby/scripts/openssl-patches/0001-Apply-android-patches.patch
@@ -0,0 +1,1017 @@
+From 5c9103f02cb56f0f04444b16dd67eeaa05429d47 Mon Sep 17 00:00:00 2001
+From: Nabil Wadih <nwadih@google.com>
+Date: Thu, 24 Aug 2023 15:51:26 -0700
+Subject: [PATCH] Apply android patches
+
+---
+ openssl/.cargo/config.toml | 2 +
+ openssl/src/asn1.rs | 2 +-
+ openssl/src/bio.rs | 6 +-
+ openssl/src/bn.rs | 2 +-
+ openssl/src/cipher.rs | 4 +
+ openssl/src/dh.rs | 2 +-
+ openssl/src/dsa.rs | 5 +-
+ openssl/src/ec.rs | 20 ++++
+ openssl/src/ecdsa.rs | 2 +-
+ openssl/src/encrypt.rs | 4 +-
+ openssl/src/hash.rs | 2 +-
+ openssl/src/hkdf.rs | 89 +++++++++++++++
+ openssl/src/hmac.rs | 217 +++++++++++++++++++++++++++++++++++++
+ openssl/src/lib.rs | 12 ++
+ openssl/src/md_ctx.rs | 2 +-
+ openssl/src/pkey.rs | 22 ++--
+ openssl/src/pkey_ctx.rs | 21 +++-
+ openssl/src/rsa.rs | 2 +-
+ openssl/src/sign.rs | 10 +-
+ openssl/src/symm.rs | 7 +-
+ openssl/src/x509/mod.rs | 52 +++++++--
+ 21 files changed, 439 insertions(+), 46 deletions(-)
+ create mode 100644 openssl/.cargo/config.toml
+ create mode 100644 openssl/src/hkdf.rs
+ create mode 100644 openssl/src/hmac.rs
+
+diff --git a/openssl/.cargo/config.toml b/openssl/.cargo/config.toml
+new file mode 100644
+index 00000000..e2b197d8
+--- /dev/null
++++ b/openssl/.cargo/config.toml
+@@ -0,0 +1,2 @@
++[patch.crates-io]
++bssl-ffi = { package = "bssl-sys", version = "0.1.0", path = "../../../boringssl/build/rust", optional=true }
+diff --git a/openssl/src/asn1.rs b/openssl/src/asn1.rs
+index b02f9ac4..939a1732 100644
+--- a/openssl/src/asn1.rs
++++ b/openssl/src/asn1.rs
+@@ -651,7 +651,7 @@ impl fmt::Debug for Asn1ObjectRef {
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl273))] {
++ if #[cfg(any(ossl110, libressl273, boringssl))] {
+ use ffi::ASN1_STRING_get0_data;
+ } else {
+ #[allow(bad_style)]
+diff --git a/openssl/src/bio.rs b/openssl/src/bio.rs
+index 6a72552a..03242188 100644
+--- a/openssl/src/bio.rs
++++ b/openssl/src/bio.rs
+@@ -4,7 +4,7 @@ use std::marker::PhantomData;
+ use std::ptr;
+ use std::slice;
+
+-use crate::cvt_p;
++use crate::{cvt_p, SignedLenType};
+ use crate::error::ErrorStack;
+
+ pub struct MemBioSlice<'a>(*mut ffi::BIO, PhantomData<&'a [u8]>);
+@@ -25,7 +25,7 @@ impl<'a> MemBioSlice<'a> {
+ let bio = unsafe {
+ cvt_p(BIO_new_mem_buf(
+ buf.as_ptr() as *const _,
+- buf.len() as c_int,
++ buf.len() as SignedLenType,
+ ))?
+ };
+
+@@ -78,7 +78,7 @@ cfg_if! {
+ use ffi::BIO_new_mem_buf;
+ } else {
+ #[allow(bad_style)]
+- unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: ::libc::c_int) -> *mut ffi::BIO {
++ unsafe fn BIO_new_mem_buf(buf: *const ::libc::c_void, len: SignedLenType) -> *mut ffi::BIO {
+ ffi::BIO_new_mem_buf(buf as *mut _, len)
+ }
+ }
+diff --git a/openssl/src/bn.rs b/openssl/src/bn.rs
+index 1cd00dd4..dbd7ae94 100644
+--- a/openssl/src/bn.rs
++++ b/openssl/src/bn.rs
+@@ -814,7 +814,7 @@ impl BigNumRef {
+ /// assert_eq!(&bn_vec, &[0, 0, 0x45, 0x43]);
+ /// ```
+ #[corresponds(BN_bn2binpad)]
+- #[cfg(ossl110)]
++ #[cfg(any(boringssl, ossl110))]
+ pub fn to_vec_padded(&self, pad_to: i32) -> Result<Vec<u8>, ErrorStack> {
+ let mut v = Vec::with_capacity(pad_to as usize);
+ unsafe {
+diff --git a/openssl/src/cipher.rs b/openssl/src/cipher.rs
+index ab5f49d2..84a82654 100644
+--- a/openssl/src/cipher.rs
++++ b/openssl/src/cipher.rs
+@@ -208,6 +208,7 @@ impl Cipher {
+ unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb1() as *mut _) }
+ }
+
++ #[cfg(not(boringssl))]
+ pub fn aes_192_cfb128() -> &'static CipherRef {
+ unsafe { CipherRef::from_ptr(ffi::EVP_aes_192_cfb128() as *mut _) }
+ }
+@@ -253,6 +254,7 @@ impl Cipher {
+ unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb1() as *mut _) }
+ }
+
++ #[cfg(not(boringssl))]
+ pub fn aes_256_cfb128() -> &'static CipherRef {
+ unsafe { CipherRef::from_ptr(ffi::EVP_aes_256_cfb128() as *mut _) }
+ }
+@@ -282,11 +284,13 @@ impl Cipher {
+ }
+
+ #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
++ #[cfg(not(boringssl))]
+ pub fn bf_cbc() -> &'static CipherRef {
+ unsafe { CipherRef::from_ptr(ffi::EVP_bf_cbc() as *mut _) }
+ }
+
+ #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
++ #[cfg(not(boringssl))]
+ pub fn bf_ecb() -> &'static CipherRef {
+ unsafe { CipherRef::from_ptr(ffi::EVP_bf_ecb() as *mut _) }
+ }
+diff --git a/openssl/src/dh.rs b/openssl/src/dh.rs
+index 12170b99..e781543e 100644
+--- a/openssl/src/dh.rs
++++ b/openssl/src/dh.rs
+@@ -239,7 +239,7 @@ where
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl270))] {
++ if #[cfg(any(ossl110, libressl270, boringssl))] {
+ use ffi::{DH_set0_pqg, DH_get0_pqg, DH_get0_key, DH_set0_key};
+ } else {
+ #[allow(bad_style)]
+diff --git a/openssl/src/dsa.rs b/openssl/src/dsa.rs
+index 5f59ba8a..0aceeb55 100644
+--- a/openssl/src/dsa.rs
++++ b/openssl/src/dsa.rs
+@@ -7,6 +7,7 @@
+
+ use cfg_if::cfg_if;
+ use foreign_types::{ForeignType, ForeignTypeRef};
++#[cfg(not(boringssl))]
+ use libc::c_int;
+ use std::fmt;
+ use std::mem;
+@@ -283,7 +284,7 @@ impl<T> fmt::Debug for Dsa<T> {
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl273))] {
++ if #[cfg(any(ossl110, libressl273, boringssl))] {
+ use ffi::{DSA_get0_key, DSA_get0_pqg, DSA_set0_key, DSA_set0_pqg};
+ } else {
+ #[allow(bad_style)]
+@@ -462,7 +463,7 @@ impl DsaSigRef {
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl273))] {
++ if #[cfg(any(ossl110, libressl273, boringssl))] {
+ use ffi::{DSA_SIG_set0, DSA_SIG_get0};
+ } else {
+ #[allow(bad_style)]
+diff --git a/openssl/src/ec.rs b/openssl/src/ec.rs
+index 24b38322..20785428 100644
+--- a/openssl/src/ec.rs
++++ b/openssl/src/ec.rs
+@@ -954,6 +954,26 @@ impl EcKey<Private> {
+ EcKey<Private>,
+ ffi::d2i_ECPrivateKey
+ }
++
++ /// Decodes a DER-encoded elliptic curve private key structure for the specified curve.
++ #[corresponds(EC_KEY_parse_private_key)]
++ #[cfg(boringssl)]
++ pub fn private_key_from_der_for_group(
++ der: &[u8],
++ group: &EcGroupRef,
++ ) -> Result<EcKey<Private>, ErrorStack> {
++ unsafe {
++ let mut cbs = ffi::CBS {
++ data: der.as_ptr(),
++ len: der.len(),
++ };
++ cvt_p(ffi::EC_KEY_parse_private_key(
++ &mut cbs as *mut ffi::CBS,
++ group.as_ptr(),
++ ))
++ .map(|p| EcKey::from_ptr(p))
++ }
++ }
+ }
+
+ impl<T> Clone for EcKey<T> {
+diff --git a/openssl/src/ecdsa.rs b/openssl/src/ecdsa.rs
+index 0a960e7b..f3b27b39 100644
+--- a/openssl/src/ecdsa.rs
++++ b/openssl/src/ecdsa.rs
+@@ -110,7 +110,7 @@ impl EcdsaSigRef {
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl273))] {
++ if #[cfg(any(ossl110, libressl273, boringssl))] {
+ use ffi::{ECDSA_SIG_set0, ECDSA_SIG_get0};
+ } else {
+ #[allow(bad_style)]
+diff --git a/openssl/src/encrypt.rs b/openssl/src/encrypt.rs
+index 3cb10fcc..34a9eb8b 100644
+--- a/openssl/src/encrypt.rs
++++ b/openssl/src/encrypt.rs
+@@ -148,7 +148,7 @@ impl<'a> Encrypter<'a> {
+ /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`].
+ ///
+ /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html
+- #[cfg(any(ossl102, libressl310))]
++ #[cfg(any(ossl102, libressl310, boringssl))]
+ pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
+ unsafe {
+ cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md(
+@@ -352,7 +352,7 @@ impl<'a> Decrypter<'a> {
+ /// This corresponds to [`EVP_PKEY_CTX_set_rsa_oaep_md`].
+ ///
+ /// [`EVP_PKEY_CTX_set_rsa_oaep_md`]: https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_oaep_md.html
+- #[cfg(any(ossl102, libressl310))]
++ #[cfg(any(ossl102, libressl310, boringssl))]
+ pub fn set_rsa_oaep_md(&mut self, md: MessageDigest) -> Result<(), ErrorStack> {
+ unsafe {
+ cvt(ffi::EVP_PKEY_CTX_set_rsa_oaep_md(
+diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs
+index 8e27505a..7f6fa89e 100644
+--- a/openssl/src/hash.rs
++++ b/openssl/src/hash.rs
+@@ -43,7 +43,7 @@ use crate::nid::Nid;
+ use crate::{cvt, cvt_p};
+
+ cfg_if! {
+- if #[cfg(ossl110)] {
++ if #[cfg(any(ossl110, boringssl))] {
+ use ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new};
+ } else {
+ use ffi::{EVP_MD_CTX_create as EVP_MD_CTX_new, EVP_MD_CTX_destroy as EVP_MD_CTX_free};
+diff --git a/openssl/src/hkdf.rs b/openssl/src/hkdf.rs
+new file mode 100644
+index 00000000..cc7e5b3a
+--- /dev/null
++++ b/openssl/src/hkdf.rs
+@@ -0,0 +1,89 @@
++use crate::cvt;
++use crate::error::ErrorStack;
++use crate::md::MdRef;
++use foreign_types::ForeignTypeRef;
++use openssl_macros::corresponds;
++
++/// Computes HKDF (as specified by RFC 5869).
++///
++/// HKDF is an Extract-and-Expand algorithm. It does not do any key stretching,
++/// and as such, is not suited to be used alone to generate a key from a
++/// password.
++#[corresponds(HKDF)]
++#[inline]
++pub fn hkdf(
++ out_key: &mut [u8],
++ md: &MdRef,
++ secret: &[u8],
++ salt: &[u8],
++ info: &[u8],
++) -> Result<(), ErrorStack> {
++ unsafe {
++ cvt(ffi::HKDF(
++ out_key.as_mut_ptr(),
++ out_key.len(),
++ md.as_ptr(),
++ secret.as_ptr(),
++ secret.len(),
++ salt.as_ptr(),
++ salt.len(),
++ info.as_ptr(),
++ info.len(),
++ ))?;
++ }
++
++ Ok(())
++}
++
++/// Computes a HKDF PRK (as specified by RFC 5869).
++///
++/// WARNING: This function orders the inputs differently from RFC 5869
++/// specification. Double-check which parameter is the secret/IKM and which is
++/// the salt when using.
++#[corresponds(HKDF_extract)]
++#[inline]
++pub fn hkdf_extract<'a>(
++ out_key: &'a mut [u8],
++ md: &MdRef,
++ secret: &[u8],
++ salt: &[u8],
++) -> Result<&'a [u8], ErrorStack> {
++ let mut out_len = out_key.len();
++ unsafe {
++ cvt(ffi::HKDF_extract(
++ out_key.as_mut_ptr(),
++ &mut out_len,
++ md.as_ptr(),
++ secret.as_ptr(),
++ secret.len(),
++ salt.as_ptr(),
++ salt.len(),
++ ))?;
++ }
++
++ Ok(&out_key[..out_len])
++}
++
++/// Computes a HKDF OKM (as specified by RFC 5869).
++#[corresponds(HKDF_expand)]
++#[inline]
++pub fn hkdf_expand(
++ out_key: &mut [u8],
++ md: &MdRef,
++ prk: &[u8],
++ info: &[u8],
++) -> Result<(), ErrorStack> {
++ unsafe {
++ cvt(ffi::HKDF_expand(
++ out_key.as_mut_ptr(),
++ out_key.len(),
++ md.as_ptr(),
++ prk.as_ptr(),
++ prk.len(),
++ info.as_ptr(),
++ info.len(),
++ ))?;
++ }
++
++ Ok(())
++}
+diff --git a/openssl/src/hmac.rs b/openssl/src/hmac.rs
+new file mode 100644
+index 00000000..465781e2
+--- /dev/null
++++ b/openssl/src/hmac.rs
+@@ -0,0 +1,217 @@
++use crate::error::ErrorStack;
++use crate::md::MdRef;
++use crate::{cvt, cvt_p};
++use ffi::HMAC_CTX;
++use foreign_types::ForeignTypeRef;
++use libc::{c_uint, c_void};
++use openssl_macros::corresponds;
++use std::convert::TryFrom;
++use std::ptr;
++
++/// Computes the HMAC as a one-shot operation.
++///
++/// Calculates the HMAC of data, using the given |key|
++/// and hash function |md|, and returns the result re-using the space from
++/// buffer |out|. On entry, |out| must contain at least |EVP_MD_size| bytes
++/// of space. The actual length of the result is used to resize the returned
++/// slice. An output size of |EVP_MAX_MD_SIZE| will always be large enough.
++/// It returns a resized |out| or ErrorStack on error.
++#[corresponds(HMAC)]
++#[inline]
++pub fn hmac<'a>(
++ md: &MdRef,
++ key: &[u8],
++ data: &[u8],
++ out: &'a mut [u8],
++) -> Result<&'a [u8], ErrorStack> {
++ assert!(out.len() >= md.size());
++ let mut out_len = c_uint::try_from(out.len()).unwrap();
++ unsafe {
++ cvt_p(ffi::HMAC(
++ md.as_ptr(),
++ key.as_ptr() as *const c_void,
++ key.len(),
++ data.as_ptr(),
++ data.len(),
++ out.as_mut_ptr(),
++ &mut out_len,
++ ))?;
++ }
++ Ok(&out[..out_len as usize])
++}
++
++/// A context object used to perform HMAC operations.
++///
++/// HMAC is a MAC (message authentication code), i.e. a keyed hash function used for message
++/// authentication, which is based on a hash function.
++///
++/// Note: Only available in boringssl. For openssl, use `PKey::hmac` instead.
++#[cfg(boringssl)]
++pub struct HmacCtx {
++ ctx: *mut HMAC_CTX,
++ output_size: usize,
++}
++
++#[cfg(boringssl)]
++impl HmacCtx {
++ /// Creates a new [HmacCtx] to use the hash function `md` and key `key`.
++ #[corresponds(HMAC_Init_ex)]
++ pub fn new(key: &[u8], md: &MdRef) -> Result<Self, ErrorStack> {
++ unsafe {
++ // Safety: If an error occurred, the resulting null from HMAC_CTX_new is converted into
++ // ErrorStack in the returned result by `cvt_p`.
++ let ctx = cvt_p(ffi::HMAC_CTX_new())?;
++ // Safety:
++ // - HMAC_Init_ex must be called with a context previously created with HMAC_CTX_new,
++ // which is the line above.
++ // - HMAC_Init_ex may return an error if key is null but the md is different from
++ // before. This is avoided here since key is guaranteed to be non-null.
++ cvt(ffi::HMAC_Init_ex(
++ ctx,
++ key.as_ptr() as *const c_void,
++ key.len(),
++ md.as_ptr(),
++ ptr::null_mut(),
++ ))?;
++ Ok(Self {
++ ctx,
++ output_size: md.size(),
++ })
++ }
++ }
++
++ /// `update` can be called repeatedly with chunks of the message `data` to be authenticated.
++ #[corresponds(HMAC_Update)]
++ pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
++ unsafe {
++ // Safety: HMAC_Update returns 0 on error, and that is converted into ErrorStack in the
++ // returned result by `cvt`.
++ cvt(ffi::HMAC_Update(self.ctx, data.as_ptr(), data.len())).map(|_| ())
++ }
++ }
++
++ /// Finishes the HMAC process, and places the message authentication code in `output`.
++ /// The number of bytes written to `output` is returned.
++ ///
++ /// # Panics
++ ///
++ /// Panics if the `output` is smaller than the required size. The output size is indicated by
++ /// `md.size()` for the `Md` instance passed in [new]. An output size of |EVP_MAX_MD_SIZE| will
++ /// always be large enough.
++ #[corresponds(HMAC_Final)]
++ pub fn finalize(&mut self, output: &mut [u8]) -> Result<usize, ErrorStack> {
++ assert!(output.len() >= self.output_size);
++ unsafe {
++ // Safety: The length assertion above makes sure that `HMAC_Final` will not write longer
++ // than the length of `output`.
++ let mut size: c_uint = 0;
++ cvt(ffi::HMAC_Final(
++ self.ctx,
++ output.as_mut_ptr(),
++ &mut size as *mut c_uint,
++ ))
++ .map(|_| size as usize)
++ }
++ }
++}
++
++impl Drop for HmacCtx {
++ #[corresponds(HMAC_CTX_free)]
++ fn drop(&mut self) {
++ unsafe {
++ ffi::HMAC_CTX_free(self.ctx);
++ }
++ }
++}
++
++#[cfg(test)]
++mod tests {
++ use super::*;
++ use crate::md::Md;
++
++ const SHA_256_DIGEST_SIZE: usize = 32;
++
++ #[test]
++ fn hmac_sha256_test() {
++ let expected_hmac = [
++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++ 0x2e, 0x32, 0xcf, 0xf7,
++ ];
++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
++ let key: [u8; 20] = [0x0b; 20];
++ let data = b"Hi There";
++ let hmac_result =
++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
++ assert_eq!(&hmac_result, &expected_hmac);
++ }
++
++ #[test]
++ #[should_panic]
++ fn hmac_sha256_output_too_short() {
++ let mut out = vec![0_u8; 1];
++ let key: [u8; 20] = [0x0b; 20];
++ let data = b"Hi There";
++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
++ }
++
++ #[test]
++ fn hmac_sha256_test_big_buffer() {
++ let expected_hmac = [
++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++ 0x2e, 0x32, 0xcf, 0xf7,
++ ];
++ let mut out: [u8; 100] = [0; 100];
++ let key: [u8; 20] = [0x0b; 20];
++ let data = b"Hi There";
++ let hmac_result =
++ hmac(Md::sha256(), &key, data, &mut out).expect("Couldn't calculate sha256 hmac");
++ assert_eq!(hmac_result.len(), SHA_256_DIGEST_SIZE);
++ assert_eq!(&hmac_result, &expected_hmac);
++ }
++
++ #[test]
++ fn hmac_sha256_update_test() {
++ let expected_hmac = [
++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++ 0x2e, 0x32, 0xcf, 0xf7,
++ ];
++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
++ let key: [u8; 20] = [0x0b; 20];
++ let data = b"Hi There";
++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++ hmac_ctx.update(data).unwrap();
++ let size = hmac_ctx.finalize(&mut out).unwrap();
++ assert_eq!(&out, &expected_hmac);
++ assert_eq!(size, SHA_256_DIGEST_SIZE);
++ }
++
++ #[test]
++ fn hmac_sha256_update_chunks_test() {
++ let expected_hmac = [
++ 0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0xb,
++ 0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x0, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
++ 0x2e, 0x32, 0xcf, 0xf7,
++ ];
++ let mut out: [u8; SHA_256_DIGEST_SIZE] = [0; SHA_256_DIGEST_SIZE];
++ let key: [u8; 20] = [0x0b; 20];
++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++ hmac_ctx.update(b"Hi").unwrap();
++ hmac_ctx.update(b" There").unwrap();
++ let size = hmac_ctx.finalize(&mut out).unwrap();
++ assert_eq!(&out, &expected_hmac);
++ assert_eq!(size, SHA_256_DIGEST_SIZE);
++ }
++
++ #[test]
++ #[should_panic]
++ fn hmac_sha256_update_output_too_short() {
++ let mut out = vec![0_u8; 1];
++ let key: [u8; 20] = [0x0b; 20];
++ let mut hmac_ctx = HmacCtx::new(&key, Md::sha256()).unwrap();
++ hmac_ctx.update(b"Hi There").unwrap();
++ hmac_ctx.finalize(&mut out).unwrap();
++ }
++}
+diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs
+index 891651ec..e8d07d8a 100644
+--- a/openssl/src/lib.rs
++++ b/openssl/src/lib.rs
+@@ -120,6 +120,9 @@
+ #![doc(html_root_url = "https://docs.rs/openssl/0.10")]
+ #![warn(rust_2018_idioms)]
+
++#[cfg(all(soong, boringssl))]
++extern crate bssl_ffi as ffi;
++
+ #[doc(inline)]
+ pub use ffi::init;
+
+@@ -155,6 +158,10 @@ pub mod ex_data;
+ #[cfg(not(any(libressl, ossl300)))]
+ pub mod fips;
+ pub mod hash;
++#[cfg(boringssl)]
++pub mod hkdf;
++#[cfg(boringssl)]
++pub mod hmac;
+ #[cfg(ossl300)]
+ pub mod lib_ctx;
+ pub mod md;
+@@ -189,6 +196,11 @@ type LenType = libc::size_t;
+ #[cfg(not(boringssl))]
+ type LenType = libc::c_int;
+
++#[cfg(boringssl)]
++type SignedLenType = libc::ssize_t;
++#[cfg(not(boringssl))]
++type SignedLenType = libc::c_int;
++
+ #[inline]
+ fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
+ if r.is_null() {
+diff --git a/openssl/src/md_ctx.rs b/openssl/src/md_ctx.rs
+index c4d3f06b..156f3c2f 100644
+--- a/openssl/src/md_ctx.rs
++++ b/openssl/src/md_ctx.rs
+@@ -93,7 +93,7 @@ use std::convert::TryFrom;
+ use std::ptr;
+
+ cfg_if! {
+- if #[cfg(ossl110)] {
++ if #[cfg(any(ossl110, boringssl))] {
+ use ffi::{EVP_MD_CTX_free, EVP_MD_CTX_new};
+ } else {
+ use ffi::{EVP_MD_CTX_create as EVP_MD_CTX_new, EVP_MD_CTX_destroy as EVP_MD_CTX_free};
+diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs
+index 2039e7e9..21ba7118 100644
+--- a/openssl/src/pkey.rs
++++ b/openssl/src/pkey.rs
+@@ -47,7 +47,7 @@ use crate::dh::Dh;
+ use crate::dsa::Dsa;
+ use crate::ec::EcKey;
+ use crate::error::ErrorStack;
+-#[cfg(ossl110)]
++#[cfg(any(boringssl, ossl110))]
+ use crate::pkey_ctx::PkeyCtx;
+ use crate::rsa::Rsa;
+ use crate::symm::Cipher;
+@@ -86,14 +86,14 @@ impl Id {
+ pub const DH: Id = Id(ffi::EVP_PKEY_DH);
+ pub const EC: Id = Id(ffi::EVP_PKEY_EC);
+
+- #[cfg(ossl110)]
++ #[cfg(any(boringssl, ossl110))]
+ pub const HKDF: Id = Id(ffi::EVP_PKEY_HKDF);
+
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub const ED25519: Id = Id(ffi::EVP_PKEY_ED25519);
+ #[cfg(ossl111)]
+ pub const ED448: Id = Id(ffi::EVP_PKEY_ED448);
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub const X25519: Id = Id(ffi::EVP_PKEY_X25519);
+ #[cfg(ossl111)]
+ pub const X448: Id = Id(ffi::EVP_PKEY_X448);
+@@ -252,7 +252,7 @@ where
+ /// This function only works for algorithms that support raw public keys.
+ /// Currently this is: [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`].
+ #[corresponds(EVP_PKEY_get_raw_public_key)]
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn raw_public_key(&self) -> Result<Vec<u8>, ErrorStack> {
+ unsafe {
+ let mut len = 0;
+@@ -303,7 +303,7 @@ where
+ /// This function only works for algorithms that support raw private keys.
+ /// Currently this is: [`Id::HMAC`], [`Id::X25519`], [`Id::ED25519`], [`Id::X448`] or [`Id::ED448`].
+ #[corresponds(EVP_PKEY_get_raw_private_key)]
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn raw_private_key(&self) -> Result<Vec<u8>, ErrorStack> {
+ unsafe {
+ let mut len = 0;
+@@ -484,7 +484,7 @@ impl PKey<Private> {
+ ctx.keygen()
+ }
+
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ fn generate_eddsa(id: Id) -> Result<PKey<Private>, ErrorStack> {
+ let mut ctx = PkeyCtx::new_id(id)?;
+ ctx.keygen_init()?;
+@@ -514,7 +514,7 @@ impl PKey<Private> {
+ /// assert_eq!(secret.len(), 32);
+ /// # Ok(()) }
+ /// ```
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn generate_x25519() -> Result<PKey<Private>, ErrorStack> {
+ PKey::generate_eddsa(Id::X25519)
+ }
+@@ -568,7 +568,7 @@ impl PKey<Private> {
+ /// assert_eq!(signature.len(), 64);
+ /// # Ok(()) }
+ /// ```
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn generate_ed25519() -> Result<PKey<Private>, ErrorStack> {
+ PKey::generate_eddsa(Id::ED25519)
+ }
+@@ -718,7 +718,7 @@ impl PKey<Private> {
+ ///
+ /// Algorithm types that support raw private keys are HMAC, X25519, ED25519, X448 or ED448
+ #[corresponds(EVP_PKEY_new_raw_private_key)]
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn private_key_from_raw_bytes(
+ bytes: &[u8],
+ key_type: Id,
+@@ -759,7 +759,7 @@ impl PKey<Public> {
+ ///
+ /// Algorithm types that support raw public keys are X25519, ED25519, X448 or ED448
+ #[corresponds(EVP_PKEY_new_raw_public_key)]
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn public_key_from_raw_bytes(
+ bytes: &[u8],
+ key_type: Id,
+diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs
+index f79372fb..3d4203fa 100644
+--- a/openssl/src/pkey_ctx.rs
++++ b/openssl/src/pkey_ctx.rs
+@@ -470,7 +470,7 @@ impl<T> PkeyCtxRef<T> {
+ ///
+ /// Requires OpenSSL 1.1.0 or newer.
+ #[corresponds(EVP_PKEY_CTX_set_hkdf_md)]
+- #[cfg(ossl110)]
++ #[cfg(any(ossl110, boringssl))]
+ #[inline]
+ pub fn set_hkdf_md(&mut self, digest: &MdRef) -> Result<(), ErrorStack> {
+ unsafe {
+@@ -503,10 +503,13 @@ impl<T> PkeyCtxRef<T> {
+ ///
+ /// Requires OpenSSL 1.1.0 or newer.
+ #[corresponds(EVP_PKEY_CTX_set1_hkdf_key)]
+- #[cfg(ossl110)]
++ #[cfg(any(ossl110, boringssl))]
+ #[inline]
+ pub fn set_hkdf_key(&mut self, key: &[u8]) -> Result<(), ErrorStack> {
++ #[cfg(not(boringssl))]
+ let len = c_int::try_from(key.len()).unwrap();
++ #[cfg(boringssl)]
++ let len = key.len();
+
+ unsafe {
+ cvt(ffi::EVP_PKEY_CTX_set1_hkdf_key(
+@@ -523,10 +526,13 @@ impl<T> PkeyCtxRef<T> {
+ ///
+ /// Requires OpenSSL 1.1.0 or newer.
+ #[corresponds(EVP_PKEY_CTX_set1_hkdf_salt)]
+- #[cfg(ossl110)]
++ #[cfg(any(ossl110, boringssl))]
+ #[inline]
+ pub fn set_hkdf_salt(&mut self, salt: &[u8]) -> Result<(), ErrorStack> {
++ #[cfg(not(boringssl))]
+ let len = c_int::try_from(salt.len()).unwrap();
++ #[cfg(boringssl)]
++ let len = salt.len();
+
+ unsafe {
+ cvt(ffi::EVP_PKEY_CTX_set1_hkdf_salt(
+@@ -543,10 +549,13 @@ impl<T> PkeyCtxRef<T> {
+ ///
+ /// Requires OpenSSL 1.1.0 or newer.
+ #[corresponds(EVP_PKEY_CTX_add1_hkdf_info)]
+- #[cfg(ossl110)]
++ #[cfg(any(ossl110, boringssl))]
+ #[inline]
+ pub fn add_hkdf_info(&mut self, info: &[u8]) -> Result<(), ErrorStack> {
++ #[cfg(not(boringssl))]
+ let len = c_int::try_from(info.len()).unwrap();
++ #[cfg(boringssl)]
++ let len = info.len();
+
+ unsafe {
+ cvt(ffi::EVP_PKEY_CTX_add1_hkdf_info(
+@@ -604,7 +613,7 @@ mod test {
+ #[cfg(not(boringssl))]
+ use crate::cipher::Cipher;
+ use crate::ec::{EcGroup, EcKey};
+- #[cfg(any(ossl102, libressl310))]
++ #[cfg(any(ossl102, libressl310, boringssl))]
+ use crate::md::Md;
+ use crate::nid::Nid;
+ use crate::pkey::PKey;
+@@ -689,7 +698,7 @@ mod test {
+ }
+
+ #[test]
+- #[cfg(ossl110)]
++ #[cfg(any(ossl110, boringssl))]
+ fn hkdf() {
+ let mut ctx = PkeyCtx::new_id(Id::HKDF).unwrap();
+ ctx.derive_init().unwrap();
+diff --git a/openssl/src/rsa.rs b/openssl/src/rsa.rs
+index 68cf64b0..f155b12d 100644
+--- a/openssl/src/rsa.rs
++++ b/openssl/src/rsa.rs
+@@ -581,7 +581,7 @@ impl<T> fmt::Debug for Rsa<T> {
+ }
+
+ cfg_if! {
+- if #[cfg(any(ossl110, libressl273))] {
++ if #[cfg(any(ossl110, libressl273, boringssl))] {
+ use ffi::{
+ RSA_get0_key, RSA_get0_factors, RSA_get0_crt_params, RSA_set0_key, RSA_set0_factors,
+ RSA_set0_crt_params,
+diff --git a/openssl/src/sign.rs b/openssl/src/sign.rs
+index b675825e..e5e80608 100644
+--- a/openssl/src/sign.rs
++++ b/openssl/src/sign.rs
+@@ -290,7 +290,7 @@ impl<'a> Signer<'a> {
+ self.len_intern()
+ }
+
+- #[cfg(not(ossl111))]
++ #[cfg(not(any(boringssl, ossl111)))]
+ fn len_intern(&self) -> Result<usize, ErrorStack> {
+ unsafe {
+ let mut len = 0;
+@@ -303,7 +303,7 @@ impl<'a> Signer<'a> {
+ }
+ }
+
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ fn len_intern(&self) -> Result<usize, ErrorStack> {
+ unsafe {
+ let mut len = 0;
+@@ -360,7 +360,7 @@ impl<'a> Signer<'a> {
+ /// OpenSSL documentation at [`EVP_DigestSign`].
+ ///
+ /// [`EVP_DigestSign`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestSign.html
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn sign_oneshot(
+ &mut self,
+ sig_buf: &mut [u8],
+@@ -382,7 +382,7 @@ impl<'a> Signer<'a> {
+ /// Returns the signature.
+ ///
+ /// This is a simple convenience wrapper over `len` and `sign_oneshot`.
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn sign_oneshot_to_vec(&mut self, data_buf: &[u8]) -> Result<Vec<u8>, ErrorStack> {
+ let mut sig_buf = vec![0; self.len()?];
+ let len = self.sign_oneshot(&mut sig_buf, data_buf)?;
+@@ -596,7 +596,7 @@ impl<'a> Verifier<'a> {
+ /// OpenSSL documentation at [`EVP_DigestVerify`].
+ ///
+ /// [`EVP_DigestVerify`]: https://www.openssl.org/docs/man1.1.1/man3/EVP_DigestVerify.html
+- #[cfg(ossl111)]
++ #[cfg(any(boringssl, ossl111))]
+ pub fn verify_oneshot(&mut self, signature: &[u8], buf: &[u8]) -> Result<bool, ErrorStack> {
+ unsafe {
+ let r = ffi::EVP_DigestVerify(
+diff --git a/openssl/src/symm.rs b/openssl/src/symm.rs
+index c75bbc0c..beff5fc2 100644
+--- a/openssl/src/symm.rs
++++ b/openssl/src/symm.rs
+@@ -119,6 +119,7 @@ impl Cipher {
+ unsafe { Cipher(ffi::EVP_aes_128_cfb1()) }
+ }
+
++ #[cfg(not(boringssl))]
+ pub fn aes_128_cfb128() -> Cipher {
+ unsafe { Cipher(ffi::EVP_aes_128_cfb128()) }
+ }
+@@ -164,6 +165,7 @@ impl Cipher {
+ unsafe { Cipher(ffi::EVP_aes_192_cfb1()) }
+ }
+
++ #[cfg(not(boringssl))]
+ pub fn aes_192_cfb128() -> Cipher {
+ unsafe { Cipher(ffi::EVP_aes_192_cfb128()) }
+ }
+@@ -214,6 +216,7 @@ impl Cipher {
+ unsafe { Cipher(ffi::EVP_aes_256_cfb1()) }
+ }
+
++ #[cfg(not(boringssl))]
+ pub fn aes_256_cfb128() -> Cipher {
+ unsafe { Cipher(ffi::EVP_aes_256_cfb128()) }
+ }
+@@ -242,12 +245,12 @@ impl Cipher {
+ unsafe { Cipher(ffi::EVP_aes_256_ocb()) }
+ }
+
+- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
++ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))]
+ pub fn bf_cbc() -> Cipher {
+ unsafe { Cipher(ffi::EVP_bf_cbc()) }
+ }
+
+- #[cfg(not(osslconf = "OPENSSL_NO_BF"))]
++ #[cfg(not(any(boringssl, osslconf = "OPENSSL_NO_BF")))]
+ pub fn bf_ecb() -> Cipher {
+ unsafe { Cipher(ffi::EVP_bf_ecb()) }
+ }
+diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs
+index edd54aa8..a03a8aa6 100644
+--- a/openssl/src/x509/mod.rs
++++ b/openssl/src/x509/mod.rs
+@@ -353,6 +353,19 @@ impl X509Builder {
+ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), hash.as_ptr())).map(|_| ()) }
+ }
+
++ /// Signs the certificate with a private key but without a digest.
++ ///
++ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
++ /// message digest.
++ #[cfg(boringssl)]
++ #[corresponds(X509_sign)]
++ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
++ where
++ T: HasPrivate,
++ {
++ unsafe { cvt(ffi::X509_sign(self.0.as_ptr(), key.as_ptr(), ptr::null())).map(|_| ()) }
++ }
++
+ /// Consumes the builder, returning the certificate.
+ pub fn build(self) -> X509 {
+ self.0
+@@ -880,13 +893,13 @@ impl X509NameBuilder {
+ pub fn append_entry_by_text(&mut self, field: &str, value: &str) -> Result<(), ErrorStack> {
+ unsafe {
+ let field = CString::new(field).unwrap();
+- assert!(value.len() <= c_int::max_value() as usize);
++ assert!(value.len() <= isize::max_value() as usize);
+ cvt(ffi::X509_NAME_add_entry_by_txt(
+ self.0.as_ptr(),
+ field.as_ptr() as *mut _,
+ ffi::MBSTRING_UTF8,
+ value.as_ptr(),
+- value.len() as c_int,
++ value.len() as isize,
+ -1,
+ 0,
+ ))
+@@ -907,13 +920,13 @@ impl X509NameBuilder {
+ ) -> Result<(), ErrorStack> {
+ unsafe {
+ let field = CString::new(field).unwrap();
+- assert!(value.len() <= c_int::max_value() as usize);
++ assert!(value.len() <= isize::max_value() as usize);
+ cvt(ffi::X509_NAME_add_entry_by_txt(
+ self.0.as_ptr(),
+ field.as_ptr() as *mut _,
+ ty.as_raw(),
+ value.as_ptr(),
+- value.len() as c_int,
++ value.len() as isize,
+ -1,
+ 0,
+ ))
+@@ -928,13 +941,13 @@ impl X509NameBuilder {
+ /// [`X509_NAME_add_entry_by_NID`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_NAME_add_entry_by_NID.html
+ pub fn append_entry_by_nid(&mut self, field: Nid, value: &str) -> Result<(), ErrorStack> {
+ unsafe {
+- assert!(value.len() <= c_int::max_value() as usize);
++ assert!(value.len() <= isize::max_value() as usize);
+ cvt(ffi::X509_NAME_add_entry_by_NID(
+ self.0.as_ptr(),
+ field.as_raw(),
+ ffi::MBSTRING_UTF8,
+ value.as_ptr() as *mut _,
+- value.len() as c_int,
++ value.len() as isize,
+ -1,
+ 0,
+ ))
+@@ -954,13 +967,13 @@ impl X509NameBuilder {
+ ty: Asn1Type,
+ ) -> Result<(), ErrorStack> {
+ unsafe {
+- assert!(value.len() <= c_int::max_value() as usize);
++ assert!(value.len() <= isize::max_value() as usize);
+ cvt(ffi::X509_NAME_add_entry_by_NID(
+ self.0.as_ptr(),
+ field.as_raw(),
+ ty.as_raw(),
+ value.as_ptr() as *mut _,
+- value.len() as c_int,
++ value.len() as isize,
+ -1,
+ 0,
+ ))
+@@ -1260,6 +1273,29 @@ impl X509ReqBuilder {
+ }
+ }
+
++ /// Sign the request using a private key without a digest.
++ ///
++ /// This is the only way to sign with Ed25519 keys as BoringSSL doesn't support the null
++ /// message digest.
++ ///
++ /// This corresponds to [`X509_REQ_sign`].
++ ///
++ /// [`X509_REQ_sign`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_sign.html
++ #[cfg(boringssl)]
++ pub fn sign_without_digest<T>(&mut self, key: &PKeyRef<T>) -> Result<(), ErrorStack>
++ where
++ T: HasPrivate,
++ {
++ unsafe {
++ cvt(ffi::X509_REQ_sign(
++ self.0.as_ptr(),
++ key.as_ptr(),
++ ptr::null(),
++ ))
++ .map(|_| ())
++ }
++ }
++
+ /// Returns the `X509Req`.
+ pub fn build(self) -> X509Req {
+ self.0
+--
+2.42.0.rc2.253.gd59a3bf2b4-goog
+
diff --git a/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch b/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch
deleted file mode 100644
index 030e46e..0000000
--- a/nearby/scripts/openssl-patches/0002-fix-boringssl-dsa-build-errors.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 097eaa7166ad1f6298c41bc66e094a15a9a4e73e Mon Sep 17 00:00:00 2001
-From: Nabil Wadih <nwadih@google.com>
-Date: Tue, 6 Jun 2023 15:57:04 -0700
-Subject: [PATCH 2/2] fix boringssl dsa build errors
-
----
- openssl/src/dsa.rs | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/openssl/src/dsa.rs b/openssl/src/dsa.rs
-index c550f654..ffebdf8a 100644
---- a/openssl/src/dsa.rs
-+++ b/openssl/src/dsa.rs
-@@ -283,7 +283,7 @@ impl<T> fmt::Debug for Dsa<T> {
- }
-
- cfg_if! {
-- if #[cfg(any(ossl110, libressl273))] {
-+ if #[cfg(any(ossl110, libressl273, boringssl))] {
- use ffi::{DSA_get0_key, DSA_get0_pqg, DSA_set0_key, DSA_set0_pqg};
- } else {
- #[allow(bad_style)]
---
-2.41.0.162.gfafddb0af9-goog
-
diff --git a/nearby/src/crypto_ffi.rs b/nearby/src/crypto_ffi.rs
index 3118b8a..20d1d5d 100644
--- a/nearby/src/crypto_ffi.rs
+++ b/nearby/src/crypto_ffi.rs
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::support::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
-use crate::BuildBoringSslOptions;
use anyhow::anyhow;
+use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
use owo_colors::OwoColorize as _;
use semver::{Version, VersionReq};
use std::{
@@ -22,7 +21,15 @@
path::{Path, PathBuf},
};
-pub fn build_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow::Result<()> {
+use crate::CargoOptions;
+
+pub fn boringssl_check_everything(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
+ check_boringssl(root, cargo_options)?;
+ check_openssl(root, cargo_options)?;
+ Ok(())
+}
+
+pub fn build_boringssl(root: &Path) -> anyhow::Result<()> {
let bindgen_version_req = VersionReq::parse(">=0.61.0")?;
let bindgen_version = get_bindgen_version()?;
@@ -30,23 +37,16 @@
return Err(anyhow!("Bindgen does not match expected version: {bindgen_version_req}"));
}
- let mut vendor_dir =
- root.parent().ok_or_else(|| anyhow!("project root dir no parent dir"))?.to_path_buf();
- vendor_dir.push("boringssl-build");
+ let vendor_dir = root
+ .parent()
+ .ok_or_else(|| anyhow!("project root dir no parent dir"))?
+ .join("boringssl-build");
fs::create_dir_all(&vendor_dir)?;
- let mut build_dir = clone_repo_if_needed(
- &vendor_dir,
- "boringssl",
- "https://boringssl.googlesource.com/boringssl",
- )?;
-
- run_cmd_shell_with_color::<YellowStderr>(
- &build_dir,
- format!("git checkout {}", &options.commit_hash),
- )?;
-
- build_dir.push("build");
+ let build_dir = root
+ .parent()
+ .ok_or_else(|| anyhow!("project root dir no parent dir"))?
+ .join("third_party/boringssl/build");
fs::create_dir_all(&build_dir)?;
let target = run_cmd_shell_with_color::<YellowStderr>(&vendor_dir, "rustc -vV")?
@@ -70,19 +70,23 @@
Ok(())
}
-pub fn check_boringssl(root: &Path, options: &BuildBoringSslOptions) -> anyhow::Result<()> {
+pub fn check_boringssl(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
log::info!("Checking boringssl");
- build_boringssl(root, options)?;
+ build_boringssl(root)?;
- let mut bssl_dir = root.to_path_buf();
- bssl_dir.push("crypto/crypto_provider_boringssl");
+ let bssl_dir = root.join("crypto/crypto_provider_boringssl");
- run_cmd_shell(&bssl_dir, "cargo check")?;
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
+
+ run_cmd_shell(&bssl_dir, format!("cargo check {locked_arg}"))?;
run_cmd_shell(&bssl_dir, "cargo fmt --check")?;
run_cmd_shell(&bssl_dir, "cargo clippy --all-targets")?;
- run_cmd_shell(&bssl_dir, "cargo test -- --color=always")?;
+ run_cmd_shell(&bssl_dir, format!("cargo test {locked_arg} -- --color=always"))?;
run_cmd_shell(&bssl_dir, "cargo doc --no-deps")?;
+
+ run_cmd_shell(root, "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_boringssl")?;
+
Ok(())
}
@@ -100,7 +104,7 @@
run_cmd_shell_with_color::<YellowStderr>(
&repo_dir,
- "git checkout 11797d9ecb73e94b7f55a49274318abc9dc074d2",
+ "git checkout 7df56869c5e1e32369091ab106750d644d3aa0c4",
)?;
run_cmd_shell_with_color::<YellowStderr>(&repo_dir, "git branch -f BASE_COMMIT")?;
run_cmd_shell_with_color::<YellowStderr>(
@@ -121,16 +125,20 @@
Ok(())
}
-pub fn check_openssl(root: &Path) -> anyhow::Result<()> {
+pub fn check_openssl(root: &Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
log::info!("Checking rust openssl");
prepare_patched_rust_openssl(root)?;
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
// test the openssl crate with the boringssl feature
run_cmd_shell(
root,
- concat!(
- "cargo --config .cargo/config-boringssl.toml test -p crypto_provider_openssl ",
- "--features=boringssl -- --color=always"
+ format!(
+ concat!(
+ "cargo --config .cargo/config-boringssl.toml test {locked_arg} -p crypto_provider_openssl ",
+ "--features=boringssl -- --color=always"
+ ),
+ locked_arg=locked_arg
),
)?;
diff --git a/nearby/src/ffi.rs b/nearby/src/ffi.rs
index 44479be..01fe58a 100644
--- a/nearby/src/ffi.rs
+++ b/nearby/src/ffi.rs
@@ -12,56 +12,58 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
+use crate::CargoOptions;
+use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
use std::{fs, path};
// wrapper for checking all ffi related things
-pub fn check_everything(root: &path::Path) -> anyhow::Result<()> {
- check_np_ffi(root)?;
- check_ldt_ffi(root)?;
- check_cmake_projects(root)?;
+pub fn check_everything(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
+ check_np_ffi_rust(root, cargo_options)?;
+ check_ldt_ffi_rust(root)?;
+ check_ldt_cmake(root, cargo_options)?;
+ check_np_ffi_cmake(root, cargo_options)?;
Ok(())
}
-pub fn check_np_ffi(root: &path::Path) -> anyhow::Result<()> {
+pub fn check_np_ffi_rust(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
log::info!("Checking np_c_ffi cargo build");
- let mut ffi_dir = root.to_path_buf();
- ffi_dir.push("presence/np_c_ffi");
+ let ffi_dir = root.join("presence/np_c_ffi");
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
for cargo_cmd in [
"fmt --check",
- // Default build, RustCrypto + no_std
- "build --release",
- "clippy --release",
+ // Default build, RustCrypto
+ format!("check {locked_arg} --quiet").as_str(),
+ // Build with BoringSSL for crypto
+ format!("check {locked_arg} --no-default-features --features=boringssl").as_str(),
+ "clippy",
+ "deny check",
+ "doc --quiet --no-deps",
] {
run_cmd_shell(&ffi_dir, format!("cargo {}", cargo_cmd))?;
}
Ok(())
}
-pub fn check_ldt_ffi(root: &path::Path) -> anyhow::Result<()> {
+pub fn check_ldt_ffi_rust(root: &path::Path) -> anyhow::Result<()> {
log::info!("Checking LFT ffi cargo build");
- let mut ffi_dir = root.to_path_buf();
- ffi_dir.push("presence/ldt_np_adv_ffi");
+ let ffi_dir = root.to_path_buf().join("presence/ldt_np_adv_ffi");
for cargo_cmd in [
"fmt --check",
// Default build, RustCrypto + no_std
- "build --release",
+ "check --quiet",
// Turn on std, still using RustCrypto
- "build --features=std",
+ "check --quiet --features=std",
// Turn off default features and try to build with std",
- "build --no-default-features --features=std",
- // Turn off RustCrypto and use openssl
- "build --no-default-features --features=openssl",
+ "check --quiet --no-default-features --features=std",
// Turn off RustCrypto and use boringssl
- "--config .cargo/config-boringssl.toml build --no-default-features --features=boringssl",
- "doc --no-deps",
+ "check --quiet --no-default-features --features=boringssl",
+ "doc --quiet --no-deps",
"clippy --release",
"clippy --features=std",
"clippy --no-default-features --features=openssl",
"clippy --no-default-features --features=std",
- // TODO also clippy for boringssl?
"deny check",
] {
run_cmd_shell(&ffi_dir, format!("cargo {}", cargo_cmd))?;
@@ -70,44 +72,91 @@
Ok(())
}
-pub fn check_cmake_projects(root: &path::Path) -> anyhow::Result<()> {
- log::info!("Checking CMake build and tests (for ffi c/c++ code)");
- let mut build_dir = root.to_path_buf();
- build_dir.push("presence/cmake-build");
+pub fn check_np_ffi_cmake(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
+ log::info!("Checking CMake build and tests for np ffi c/c++ code");
+ let build_dir = root.to_path_buf().join("presence/cmake-build");
fs::create_dir_all(&build_dir)?;
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake .. -DENABLE_TESTS=true")?;
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?;
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
- // run the np_cpp_ffi unit tests
- let mut np_cpp_tests_dir = build_dir.clone();
- np_cpp_tests_dir.push("np_cpp_ffi/tests");
- run_cmd_shell_with_color::<YellowStderr>(&np_cpp_tests_dir, "ctest")?;
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake -G Ninja -DENABLE_TESTS=true -DCMAKE_BUILD_TYPE=Release ..",
+ )?;
+
+ // verify sample and benchmarks build
+ let np_ffi_crate_dir = root.to_path_buf().join("presence/np_c_ffi");
+ run_cmd_shell(&np_ffi_crate_dir, format!("cargo build {locked_arg} --release"))?;
+ run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target np_cpp_sample")?;
+ run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target np_ffi_bench")?;
+
+ // Run tests with different crypto backends
+ let tests_dir = build_dir.to_path_buf().join("np_cpp_ffi/tests");
+ for build_config in [
+ // test with default build settings (rustcrypto)
+ format!("build {locked_arg} --quiet --release"),
+ // test with boringssl
+ format!("build {locked_arg} --quiet --no-default-features --features=boringssl"),
+ ] {
+ let _ = run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "rm np_cpp_ffi/tests/np_ffi_tests",
+ );
+ run_cmd_shell(&np_ffi_crate_dir, format!("cargo {}", build_config))?;
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake --build . --target np_ffi_tests",
+ )?;
+ run_cmd_shell_with_color::<YellowStderr>(&tests_dir, "ctest")?;
+ }
+
+ Ok(())
+}
+
+pub fn check_ldt_cmake(root: &path::Path, cargo_options: &CargoOptions) -> anyhow::Result<()> {
+ log::info!("Checking CMake build and tests for ldt c/c++ code");
+ let build_dir = root.to_path_buf().join("presence/cmake-build");
+ fs::create_dir_all(&build_dir)?;
+
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
+
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake -G Ninja -DENABLE_TESTS=true -DCMAKE_BUILD_TYPE=Release ..",
+ )?;
+
+ // verify sample and benchmarks build
+ let ldt_ffi_crate_dir = root.to_path_buf().join("presence/ldt_np_adv_ffi");
+ run_cmd_shell(&ldt_ffi_crate_dir, format!("cargo build {locked_arg} --release"))?;
+ run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build . --target ldt_c_sample")?;
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake --build . --target ldt_benchmarks",
+ )?;
// Run the LDT ffi unit tests. These are rebuilt and tested against all of the different
// Cargo build configurations based on the feature flags.
- let mut ldt_tests_dir = build_dir.clone();
- ldt_tests_dir.push("ldt_np_c_sample/tests");
-
- let mut ldt_ffi_crate_dir = root.to_path_buf();
- ldt_ffi_crate_dir.push("presence/ldt_np_adv_ffi");
-
+ let ldt_tests_dir = build_dir.to_path_buf().join("ldt_np_c_sample/tests");
for build_config in [
// test with default build settings (rustcrypto, no_std)
- "build --release",
+ format!("build {locked_arg} --quiet --release"),
// test with std and default features
- "build --features std --release",
+ format!("build {locked_arg} --quiet --features std --release"),
// test with boringssl crypto feature flag
- "--config .cargo/config-boringssl.toml build --no-default-features --features boringssl --release",
- // test with openssl feature flag
- "build --no-default-features --features openssl --release",
+ format!("build {locked_arg} --quiet --no-default-features --features boringssl --release"),
// test without defaults and std feature flag
- "build --no-default-features --features std --release",
+ format!("build {locked_arg} --quiet --no-default-features --features std --release"),
] {
run_cmd_shell(&ldt_ffi_crate_dir, format!("cargo {}", build_config))?;
// Force detection of updated `ldt_np_adv_ffi` static lib
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "rm -rf ldt_np_c_sample/tests/*")?;
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?;
+ let _ = run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "rm ldt_np_c_sample/tests/ldt_ffi_tests",
+ );
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake --build . --target ldt_ffi_tests",
+ )?;
run_cmd_shell_with_color::<YellowStderr>(&ldt_tests_dir, "ctest")?;
}
diff --git a/nearby/src/file_header/license.rs b/nearby/src/file_header/license.rs
deleted file mode 100644
index b463ad0..0000000
--- a/nearby/src/file_header/license.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Support for license-oriented usage of `file_header`.
-use super::*;
-use chrono::Datelike as _;
-
-/// The Apache 2 license for the current year and provided `copyright_holder`.
-pub fn apache_2(copyright_holder: &str) -> Header<impl HeaderChecker> {
- Header::new(
- asl2_checker(),
- format!(
- r#"Copyright {} {}
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License."#,
- chrono::prelude::Utc::now().year(),
- copyright_holder
- ),
- )
-}
-
-pub(crate) fn asl2_checker() -> impl HeaderChecker {
- SingleLineChecker::new("Licensed under the Apache License, Version 2.0".to_string(), 10)
-}
diff --git a/nearby/src/file_header/mod.rs b/nearby/src/file_header/mod.rs
deleted file mode 100644
index 275a7d0..0000000
--- a/nearby/src/file_header/mod.rs
+++ /dev/null
@@ -1,559 +0,0 @@
-// Copyright 2023 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Tools for checking for, or adding, headers (e.g. licenses, etc) in files.
-
-use std::{
- fs,
- io::{self, BufRead as _, Write as _},
- iter::FromIterator,
- path, thread,
-};
-
-pub mod license;
-
-/// A file header to check for or add to files.
-#[derive(Clone)]
-pub struct Header<C: HeaderChecker> {
- checker: C,
- header: String,
-}
-
-impl<C: HeaderChecker> Header<C> {
- /// Construct a new `Header` with the `checker` used to determine if the header is already
- /// present, and the plain `header` text to add (without any applicable comment syntax, etc).
- pub fn new(checker: C, header: String) -> Self {
- Self { checker, header }
- }
-
- /// Return true if the file has the desired header, false otherwise.
- pub fn header_present(&self, input: &mut impl io::Read) -> io::Result<bool> {
- self.checker.check(input)
- }
-
- /// Add the header, with appropriate formatting for the type of file indicated by `p`'s
- /// extension, if the header is not already present.
- /// Returns true if the header was added.
- pub fn add_header_if_missing(&self, p: &path::Path) -> Result<bool, AddHeaderError> {
- let err_mapper = |e| AddHeaderError::IoError(p.to_path_buf(), e);
- let contents = fs::read_to_string(p).map_err(err_mapper)?;
-
- if self.header_present(&mut contents.as_bytes()).map_err(err_mapper)? {
- return Ok(false);
- }
-
- let mut effective_header = header_delimiters(p)
- .ok_or_else(|| AddHeaderError::UnknownExtension(p.to_path_buf()))
- .map(|d| wrap_header(&self.header, d))?;
-
- let mut after_header = contents.as_str();
- // check for a magic first line
- if let Some((first_line, rest)) = contents.split_once('\n') {
- if MAGIC_FIRST_LINES.iter().any(|l| first_line.contains(l)) {
- let mut first_line = first_line.to_string();
- first_line.push('\n');
- effective_header.insert_str(0, &first_line);
- after_header = rest;
- }
- }
-
- // write the license
- let mut f =
- fs::OpenOptions::new().write(true).truncate(true).open(p).map_err(err_mapper)?;
- f.write_all(effective_header.as_bytes()).map_err(err_mapper)?;
- // newline to separate the header from previous contents
- f.write_all("\n".as_bytes()).map_err(err_mapper)?;
- f.write_all(after_header.as_bytes()).map_err(err_mapper)?;
-
- Ok(true)
- }
-}
-
-/// Errors that can occur when adding a header
-#[derive(Debug, thiserror::Error)]
-pub enum AddHeaderError {
- #[error("I/O error at {0:?}: {1}")]
- IoError(path::PathBuf, io::Error),
- #[error("Unknown file extension: {0:?}")]
- UnknownExtension(path::PathBuf),
-}
-
-/// Checks for headers in files, like licenses or author attribution.
-pub trait HeaderChecker: Send + Clone {
- /// Return true if the file has the desired header, false otherwise.
- fn check(&self, file: &mut impl io::Read) -> io::Result<bool>;
-}
-
-/// Checks for a in the first several lines of each file.
-#[derive(Clone)]
-pub struct SingleLineChecker {
- /// Pattern to do a substring match on in each of the first `max_lines` lines of the file
- pattern: String,
- /// Number of lines to search through
- max_lines: usize,
-}
-
-impl SingleLineChecker {
- /// Construct a `SingleLineChecker` that looks for `pattern` in the first `max_lines` of a file.
- pub(crate) fn new(pattern: String, max_lines: usize) -> Self {
- Self { pattern, max_lines }
- }
-}
-
-impl HeaderChecker for SingleLineChecker {
- fn check(&self, input: &mut impl io::Read) -> io::Result<bool> {
- let mut reader = io::BufReader::new(input);
- let mut lines_read = 0;
- // reuse buffer to minimize allocation
- let mut line = String::new();
- // only read the first bit of the file
- while lines_read < self.max_lines {
- line.clear();
- let bytes = reader.read_line(&mut line)?;
- if bytes == 0 {
- // EOF
- return Ok(false);
- }
- lines_read += 1;
-
- if line.contains(&self.pattern) {
- return Ok(true);
- }
- }
-
- Ok(false)
- }
-}
-
-#[derive(Copy, Clone)]
-enum CheckStatus {
- MisMatchedHeader,
- BinaryFile,
-}
-
-#[derive(Clone)]
-struct FileResult {
- path: path::PathBuf,
- status: CheckStatus,
-}
-
-#[derive(Clone, Default)]
-pub struct FileResults {
- pub mismatched_files: Vec<path::PathBuf>,
- pub binary_files: Vec<path::PathBuf>,
-}
-
-impl FileResults {
- pub fn has_failure(&self) -> bool {
- !self.mismatched_files.is_empty() || !self.binary_files.is_empty()
- }
-}
-
-impl FromIterator<FileResult> for FileResults {
- fn from_iter<I>(iter: I) -> FileResults
- where
- I: IntoIterator<Item = FileResult>,
- {
- let mut results = FileResults::default();
- for result in iter {
- match result.status {
- CheckStatus::MisMatchedHeader => results.mismatched_files.push(result.path),
- CheckStatus::BinaryFile => results.binary_files.push(result.path),
- }
- }
- results
- }
-}
-
-/// Recursively check for `header` in every file in `root` that matches `path_predicate`.
-///
-/// Returns a [`FileResults`] object containing the paths without headers detected.
-pub fn check_headers_recursively(
- root: &path::Path,
- path_predicate: impl Fn(&path::Path) -> bool,
- header: Header<impl HeaderChecker + 'static>,
- num_threads: usize,
-) -> Result<FileResults, CheckHeadersRecursivelyError> {
- let (path_tx, path_rx) = crossbeam::channel::unbounded::<path::PathBuf>();
- let (result_tx, result_rx) = crossbeam::channel::unbounded();
-
- // spawn a few threads to handle files in parallel
- let handles = (0..num_threads)
- .map(|_| {
- let path_rx = path_rx.clone();
- let result_tx = result_tx.clone();
- let header = header.clone();
- thread::spawn(move || {
- for p in path_rx {
- match fs::File::open(&p).and_then(|mut f| header.header_present(&mut f)) {
- Ok(header_present) => {
- if header_present {
- // no op
- } else {
- let res =
- FileResult { path: p, status: CheckStatus::MisMatchedHeader };
- result_tx.send(Ok(res)).unwrap();
- }
- }
- Err(e) if e.kind() == io::ErrorKind::InvalidData => {
- // Binary file - add to ignore in license.rs
- let res = FileResult { path: p, status: CheckStatus::BinaryFile };
- result_tx.send(Ok(res)).unwrap();
- }
- Err(e) => result_tx
- .send(Err(CheckHeadersRecursivelyError::IoError(p, e)))
- .unwrap(),
- }
- }
-
- // no more files
- })
- })
- .collect::<Vec<thread::JoinHandle<()>>>();
- // make sure result channel closes when threads complete
- drop(result_tx);
-
- find_files(root, path_predicate, path_tx)?;
-
- let res: FileResults = result_rx.into_iter().collect::<Result<_, _>>()?;
-
- for h in handles {
- h.join().unwrap();
- }
-
- Ok(res)
-}
-
-/// Errors that can occur when checking for headers recursively
-#[derive(Debug, thiserror::Error)]
-pub enum CheckHeadersRecursivelyError {
- #[error("I/O error at {0:?}: {1}")]
- IoError(path::PathBuf, io::Error),
- #[error("Walkdir error: {0}")]
- WalkdirError(#[from] walkdir::Error),
-}
-
-/// Add the provided `header` to any file in `root` that matches `path_predicate` and that doesn't
-/// already have a header as determined by `checker`.
-/// Returns a list of paths that had headers added.
-pub fn add_headers_recursively(
- root: &path::Path,
- path_predicate: impl Fn(&path::Path) -> bool,
- header: Header<impl HeaderChecker>,
-) -> Result<Vec<path::PathBuf>, AddHeadersRecursivelyError> {
- // likely no need for threading since adding headers is only done occasionally
- let (path_tx, path_rx) = crossbeam::channel::unbounded::<path::PathBuf>();
- find_files(root, path_predicate, path_tx)?;
-
- path_rx
- .into_iter()
- // keep the errors, or the ones with added headers
- .filter_map(|p| {
- match header.add_header_if_missing(&p).map_err(|e| match e {
- AddHeaderError::IoError(p, e) => AddHeadersRecursivelyError::IoError(p, e),
- AddHeaderError::UnknownExtension(e) => {
- AddHeadersRecursivelyError::UnknownExtension(e)
- }
- }) {
- Ok(added) => {
- if added {
- Some(Ok(p))
- } else {
- None
- }
- }
- Err(e) => Some(Err(e)),
- }
- })
- .collect::<Result<Vec<_>, _>>()
-}
-
-/// Errors that can occur when adding a header recursively
-#[derive(Debug, thiserror::Error)]
-pub enum AddHeadersRecursivelyError {
- #[error("I/O error at {0:?}: {1}")]
- IoError(path::PathBuf, io::Error),
- #[error("Walkdir error: {0}")]
- WalkdirError(#[from] walkdir::Error),
- #[error("Unknown file extension: {0:?}")]
- UnknownExtension(path::PathBuf),
-}
-
-/// Find all files starting from `root` that do not match the globs in `ignore`, publishing the
-/// resulting paths into `dest`.
-fn find_files(
- root: &path::Path,
- path_predicate: impl Fn(&path::Path) -> bool,
- dest: crossbeam::channel::Sender<path::PathBuf>,
-) -> Result<(), walkdir::Error> {
- for r in walkdir::WalkDir::new(root).into_iter() {
- let entry = r?;
- if entry.path().is_dir() || !path_predicate(entry.path()) {
- continue;
- }
- dest.send(entry.into_path()).unwrap()
- }
-
- Ok(())
-}
-
-/// Prepare a header for inclusion in a particular file syntax by wrapping it with
-/// comment characters as per the provided `delim`.
-fn wrap_header(orig_header: &str, delim: HeaderDelimiters) -> String {
- let mut out = String::new();
-
- if !delim.first_line.is_empty() {
- out.push_str(delim.first_line);
- out.push('\n');
- }
-
- // assumes header uses \n
- for line in orig_header.split('\n') {
- out.push_str(delim.content_line_prefix);
- out.push_str(line);
- // Remove any trailing whitespaces (excluding newlines) from `content_line_prefix + line`.
- // For example, if `content_line_prefix` is `// ` and `line` is empty, the resulting string
- // should be truncated to `//`.
- out.truncate(out.trim_end_matches([' ', '\t']).len());
- out.push('\n');
- }
-
- if !delim.last_line.is_empty() {
- out.push_str(delim.last_line);
- out.push('\n');
- }
-
- out
-}
-
-/// Returns the header prefix line, content line prefix, and suffix line for the extension of the
-/// provided path, or `None` if the extension is not recognized.
-fn header_delimiters(p: &path::Path) -> Option<HeaderDelimiters> {
- match p
- .extension()
- // if the extension isn't UTF-8, oh well
- .and_then(|os_str| os_str.to_str())
- .unwrap_or("")
- {
- "c" | "h" | "gv" | "java" | "scala" | "kt" | "kts" => Some(("/*", " * ", " */")),
- "js" | "mjs" | "cjs" | "jsx" | "tsx" | "css" | "scss" | "sass" | "ts" => {
- Some(("/**", " * ", " */"))
- }
- "cc" | "cpp" | "cs" | "go" | "hcl" | "hh" | "hpp" | "m" | "mm" | "proto" | "rs"
- | "swift" | "dart" | "groovy" | "v" | "sv" => Some(("", "// ", "")),
- "py" | "sh" | "yaml" | "yml" | "dockerfile" | "rb" | "gemfile" | "tcl" | "tf" | "bzl"
- | "pl" | "pp" | "build" => Some(("", "# ", "")),
- "el" | "lisp" => Some(("", ";; ", "")),
- "erl" => Some(("", "% ", "")),
- "hs" | "lua" | "sql" | "sdl" => Some(("", "-- ", "")),
- "html" | "xml" | "vue" | "wxi" | "wxl" | "wxs" => Some(("<!--", " ", "-->")),
- "php" => Some(("", "// ", "")),
- "ml" | "mli" | "mll" | "mly" => Some(("(**", " ", "*)")),
- // also handle whole filenames if extensions didn't match
- _ => match p.file_name().and_then(|os_str| os_str.to_str()).unwrap_or("") {
- "Dockerfile" => Some(("", "# ", "")),
- _ => None,
- },
- }
- .map(|(first_line, content_line_prefix, last_line)| HeaderDelimiters {
- first_line,
- content_line_prefix,
- last_line,
- })
-}
-
-/// Delimiters to use around and inside a header for a particular file syntax.
-#[derive(Clone, Copy)]
-struct HeaderDelimiters {
- /// Line to prepend before the header
- first_line: &'static str,
- /// Prefix before each line of the header itself
- content_line_prefix: &'static str,
- /// Line to append after the header
- last_line: &'static str,
-}
-
-const MAGIC_FIRST_LINES: [&str; 8] = [
- "#!", // shell script
- "<?xml", // XML declaratioon
- "<!doctype", // HTML doctype
- "# encoding:", // Ruby encoding
- "# frozen_string_literal:", // Ruby interpreter instruction
- "<?php", // PHP opening tag
- "# escape", // Dockerfile directive https://docs.docker.com/engine/reference/builder/#parser-directives
- "# syntax", // Dockerfile directive https://docs.docker.com/engine/reference/builder/#parser-directives
-];
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn single_line_checker_finds_header_when_present() {
- let input = r#"foo
- some license
- bar"#;
-
- assert!(test_header().checker.check(&mut input.as_bytes()).unwrap());
- }
-
- #[test]
- fn single_line_checker_doesnt_find_header_when_missing() {
- let input = r#"foo
- wrong license
- bar"#;
-
- assert!(!test_header().checker.check(&mut input.as_bytes()).unwrap());
- }
-
- #[test]
- fn single_line_checker_throws_error_when_missing_and_file_is_non_utf8() {
- let input = b"foo
- \x00\xff
- bar";
-
- assert_eq!(
- io::ErrorKind::InvalidData,
- test_header().checker.check(&mut input.as_slice()).unwrap_err().kind()
- );
- }
-
- #[test]
- fn single_line_checker_doesnt_panic_when_file_is_non_utf8() {
- let inputs: [&'static [u8]; 3] = [
- b"foo
- \x00\xff
- bar",
- b"foo
- some license
- \x00\xff
- bar",
- b"foo
- \x00\xff
- some license
- bar",
- ];
-
- for mut input in inputs {
- // Output is not defined for non-utf-8 files, but we should handle them with grace
- let _ = test_header().checker.check(&mut input);
- }
- }
-
- #[test]
- fn adds_header_with_empty_delimiters() {
- let file = tempfile::Builder::new().suffix(".rs").tempfile().unwrap();
- fs::write(file.path(), r#"not a license"#).unwrap();
-
- test_header().add_header_if_missing(file.path()).unwrap();
-
- assert_eq!(
- "// some license etc etc etc
-
-not a license",
- fs::read_to_string(file.path()).unwrap()
- );
- }
-
- #[test]
- fn adds_header_with_nonempty_delimiters() {
- let file = tempfile::Builder::new().suffix(".c").tempfile().unwrap();
- fs::write(file.path(), r#"not a license"#).unwrap();
-
- test_header().add_header_if_missing(file.path()).unwrap();
-
- assert_eq!(
- "/*
- * some license etc etc etc
- */
-
-not a license",
- fs::read_to_string(file.path()).unwrap()
- );
- }
-
- #[test]
- fn adds_header_trim_trailing_whitespace() {
- let file = tempfile::Builder::new().suffix(".c").tempfile().unwrap();
- fs::write(file.path(), r#"not a license"#).unwrap();
-
- test_header_with_blank_lines_and_trailing_whitespace()
- .add_header_if_missing(file.path())
- .unwrap();
-
- assert_eq!(
- "/*
- * some license
- * line with trailing whitespace.
- *
- * etc
- */
-
-not a license",
- fs::read_to_string(file.path()).unwrap()
- );
- }
-
- #[test]
- fn doesnt_add_header_when_already_present() {
- let file = tempfile::Builder::new().suffix(".rs").tempfile().unwrap();
- let initial_content = r#"
- // some license etc etc etc already present
- not a license"#;
- fs::write(file.path(), initial_content).unwrap();
-
- test_header().add_header_if_missing(file.path()).unwrap();
-
- assert_eq!(initial_content, fs::read_to_string(file.path()).unwrap());
- }
-
- #[test]
- fn adds_header_after_magic_first_line() {
- let file = tempfile::Builder::new().suffix(".xml").tempfile().unwrap();
- fs::write(
- file.path(),
- r#"<?xml version="1.0" encoding="UTF-8"?>
-<root />
-"#,
- )
- .unwrap();
-
- test_header().add_header_if_missing(file.path()).unwrap();
-
- assert_eq!(
- r#"<?xml version="1.0" encoding="UTF-8"?>
-<!--
- some license etc etc etc
--->
-
-<root />
-"#,
- fs::read_to_string(file.path()).unwrap()
- );
- }
-
- fn test_header() -> Header<SingleLineChecker> {
- Header::new(
- SingleLineChecker::new("some license".to_string(), 100),
- r#"some license etc etc etc"#.to_string(),
- )
- }
-
- fn test_header_with_blank_lines_and_trailing_whitespace() -> Header<SingleLineChecker> {
- Header::new(
- SingleLineChecker::new("some license".to_string(), 100),
- "some license\nline with trailing whitespace. \n\netc".to_string(),
- )
- }
-}
diff --git a/nearby/src/fuzzers.rs b/nearby/src/fuzzers.rs
index 6c84582..192d59c 100644
--- a/nearby/src/fuzzers.rs
+++ b/nearby/src/fuzzers.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::{run_cmd_shell_with_color, YellowStderr};
+use cmd_runner::{run_cmd_shell_with_color, YellowStderr};
use std::{fs, path};
pub(crate) fn run_rust_fuzzers(root: &path::Path) -> anyhow::Result<()> {
@@ -70,8 +70,11 @@
fs::remove_dir_all(&build_dir)?;
}
fs::create_dir_all(&build_dir)?;
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake ../.. -DENABLE_FUZZ=true")?;
- run_cmd_shell_with_color::<YellowStderr>(&build_dir, "make")?;
+ run_cmd_shell_with_color::<YellowStderr>(
+ &build_dir,
+ "cmake -G Ninja ../.. -DENABLE_FUZZ=true",
+ )?;
+ run_cmd_shell_with_color::<YellowStderr>(&build_dir, "cmake --build .")?;
fs::remove_dir_all(&build_dir)?;
Ok(())
diff --git a/nearby/src/jni.rs b/nearby/src/jni.rs
index 903ac6d..22a1da2 100644
--- a/nearby/src/jni.rs
+++ b/nearby/src/jni.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::support::run_cmd_shell;
+use cmd_runner::run_cmd_shell;
use std::path;
// This has to happen after both boringssl has been built and prepare rust openssl patches has been run.
@@ -20,6 +20,11 @@
for feature in ["opensslbssl", "boringssl"] {
run_cmd_shell(root, format!("cargo --config .cargo/config-boringssl.toml build -p ldt_np_jni --no-default-features --features={}", feature))?;
}
+ Ok(())
+}
+pub fn run_kotlin_tests(root: &path::Path) -> anyhow::Result<()> {
+ let kotlin_lib_path = root.to_path_buf().join("presence/ldt_np_jni/java/LdtNpJni");
+ run_cmd_shell(&kotlin_lib_path, "./gradlew :test")?;
Ok(())
}
diff --git a/nearby/src/license.rs b/nearby/src/license.rs
index 657a62e..e479526 100644
--- a/nearby/src/license.rs
+++ b/nearby/src/license.rs
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::file_header::{self, check_headers_recursively};
+use chrono::Datelike;
+use file_header::{check_headers_recursively, license::spdx::*};
use std::path;
pub(crate) fn check_license_headers(root: &path::Path) -> anyhow::Result<()> {
@@ -21,11 +22,14 @@
let results = check_headers_recursively(
root,
|p| !ignore.is_match(p),
- file_header::license::apache_2("Google LLC"),
+ APACHE_2_0.build_header(YearCopyrightOwnerValue::new(
+ u32::try_from(chrono::Utc::now().year())?,
+ "Google LLC".to_string(),
+ )),
4,
)?;
- for path in results.mismatched_files.iter() {
+ for path in results.no_header_files.iter() {
eprintln!("Header not present: {path:?}");
}
@@ -48,7 +52,10 @@
for p in file_header::add_headers_recursively(
root,
|p| !ignore.is_match(p),
- file_header::license::apache_2("Google LLC"),
+ APACHE_2_0.build_header(YearCopyrightOwnerValue::new(
+ u32::try_from(chrono::Utc::now().year())?,
+ "Google LLC".to_string(),
+ )),
)? {
println!("Added header: {:?}", p);
}
@@ -91,7 +98,7 @@
"**/.DS_Store",
"**/fuzz/corpus/**",
"**/.*.swp",
- "**/Session.vim",
+ "**/*.vim",
"**/*.properties",
"**/third_party/**",
"**/*.png",
@@ -99,5 +106,7 @@
"**/node_modules/**",
"**/.angular/**",
"**/.editorconfig",
+ "**/*.class",
+ "**/fuzz/artifacts/**",
]
}
diff --git a/nearby/src/main.rs b/nearby/src/main.rs
index 4c1104f..eaf3ed2 100644
--- a/nearby/src/main.rs
+++ b/nearby/src/main.rs
@@ -15,17 +15,15 @@
extern crate core;
use clap::Parser as _;
+use cmd_runner::run_cmd_shell;
use env_logger::Env;
use std::{env, path};
-use support::*;
mod crypto_ffi;
mod ffi;
-mod file_header;
mod fuzzers;
mod jni;
mod license;
-mod support;
mod ukey2;
fn main() -> anyhow::Result<()> {
@@ -37,28 +35,29 @@
);
match cli.subcommand {
- Subcommand::CheckEverything { ref check_options, ref bssl_options } => {
- check_everything(&root_dir, check_options, bssl_options)?
+ Subcommand::CheckEverything { ref check_options } => {
+ check_everything(&root_dir, check_options)?
}
Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?,
- Subcommand::FfiCheckEverything => ffi::check_everything(&root_dir)?,
- Subcommand::BuildBoringssl(ref bssl_options) => {
- crypto_ffi::build_boringssl(&root_dir, bssl_options)?
+ Subcommand::FfiCheckEverything(ref options) => ffi::check_everything(&root_dir, options)?,
+ Subcommand::BoringsslCheckEverything(ref options) => {
+ crypto_ffi::boringssl_check_everything(&root_dir, options)?
}
- Subcommand::CheckBoringssl(ref bssl_options) => {
- crypto_ffi::check_boringssl(&root_dir, bssl_options)?
- }
+ Subcommand::BuildBoringssl => crypto_ffi::build_boringssl(&root_dir)?,
+ Subcommand::CheckBoringssl(ref options) => crypto_ffi::check_boringssl(&root_dir, options)?,
Subcommand::PrepareRustOpenssl => crypto_ffi::prepare_patched_rust_openssl(&root_dir)?,
- Subcommand::CheckOpenssl => crypto_ffi::check_openssl(&root_dir)?,
+ Subcommand::CheckOpenssl(ref options) => crypto_ffi::check_openssl(&root_dir, options)?,
Subcommand::RunRustFuzzers => fuzzers::run_rust_fuzzers(&root_dir)?,
Subcommand::BuildFfiFuzzers => fuzzers::build_ffi_fuzzers(&root_dir)?,
Subcommand::CheckLicenseHeaders => license::check_license_headers(&root_dir)?,
Subcommand::AddLicenseHeaders => license::add_license_headers(&root_dir)?,
- Subcommand::CheckLdtFfi => ffi::check_ldt_ffi(&root_dir)?,
- Subcommand::CheckUkey2Ffi => ukey2::check_ukey2_ffi(&root_dir)?,
+ Subcommand::CheckLdtFfi => ffi::check_ldt_ffi_rust(&root_dir)?,
+ Subcommand::CheckUkey2Ffi(ref options) => ukey2::check_ukey2_ffi(&root_dir, options)?,
Subcommand::CheckLdtJni => jni::check_ldt_jni(&root_dir)?,
- Subcommand::CheckNpFfi => ffi::check_np_ffi(&root_dir)?,
- Subcommand::CheckCmakeProjects => ffi::check_cmake_projects(&root_dir)?,
+ Subcommand::CheckNpFfi(ref options) => ffi::check_np_ffi_rust(&root_dir, options)?,
+ Subcommand::CheckLdtCmake(ref options) => ffi::check_ldt_cmake(&root_dir, options)?,
+ Subcommand::CheckNpFfiCmake(ref options) => ffi::check_np_ffi_cmake(&root_dir, options)?,
+ Subcommand::RunKotlinTests => jni::run_kotlin_tests(&root_dir)?,
}
Ok(())
@@ -73,14 +72,21 @@
// ensure formatting is correct (Check for it first because it is fast compared to running tests)
fmt_command,
// make sure everything compiles
- "cargo check --workspace --all-targets",
+ "cargo check --workspace --all-targets --quiet",
// run all the tests
//TODO: re-enable the openssl tests, this was potentially failing due to UB code in the
// upstream rust-openssl crate's handling of empty slices. This repros consistently when
// using the rust-openssl crate backed by openssl-sys on Ubuntu 20.04.
"cargo test --workspace --quiet --exclude crypto_provider_openssl -- --color=always",
+ // Test ukey2 builds with different crypto providers
+ "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_rustcrypto",
+ "cargo test -p ukey2_connections -p ukey2_rs --no-default-features --features test_openssl",
// ensure the docs are valid (cross-references to other code, etc)
- "RUSTDOCFLAGS='--deny warnings' cargo doc --workspace --no-deps",
+ concat!(
+ "RUSTDOCFLAGS='--deny warnings -Z unstable-options --enable-index-page --generate-link-to-definition' ",
+ "cargo +nightly doc --quiet --workspace --no-deps --document-private-items ",
+ "--target-dir target/dist_docs",
+ ),
"cargo clippy --all-targets --workspace -- --deny warnings",
"cargo deny --workspace check",
] {
@@ -89,18 +95,15 @@
Ok(())
}
-pub fn check_everything(
- root: &path::Path,
- check_options: &CheckOptions,
- bssl_options: &BuildBoringSslOptions,
-) -> anyhow::Result<()> {
+pub fn check_everything(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
license::check_license_headers(root)?;
check_workspace(root, check_options)?;
- crypto_ffi::check_boringssl(root, bssl_options)?;
- crypto_ffi::check_openssl(root)?;
- ffi::check_everything(root)?;
+ crypto_ffi::check_boringssl(root, &check_options.cargo_options)?;
+ crypto_ffi::check_openssl(root, &check_options.cargo_options)?;
+ ffi::check_everything(root, &check_options.cargo_options)?;
jni::check_ldt_jni(root)?;
- ukey2::check_ukey2_ffi(root)?;
+ jni::run_kotlin_tests(root)?;
+ ukey2::check_ukey2_ffi(root, &check_options.cargo_options)?;
fuzzers::run_rust_fuzzers(root)?;
fuzzers::build_ffi_fuzzers(root)?;
@@ -119,56 +122,58 @@
CheckEverything {
#[command(flatten)]
check_options: CheckOptions,
- #[command(flatten)]
- bssl_options: BuildBoringSslOptions,
},
/// Checks everything included in the top level workspace
CheckWorkspace(CheckOptions),
+ /// Checks everything related to the boringssl version (equivalent of running check-boringssl
+ /// + check-openssl)
+ BoringsslCheckEverything(CargoOptions),
/// Clones boringssl and uses bindgen to generate the rust crate
- BuildBoringssl(BuildBoringSslOptions),
+ BuildBoringssl,
/// Run crypto provider tests using boringssl backend
- CheckBoringssl(BuildBoringSslOptions),
+ CheckBoringssl(CargoOptions),
/// Applies AOSP specific patches to the 3p `openssl` crate so that it can use a boringssl
/// backend
PrepareRustOpenssl,
/// Run crypto provider tests using openssl crate with boringssl backend
- CheckOpenssl,
+ CheckOpenssl(CargoOptions),
/// Build and run pure Rust fuzzers for 10000 runs
RunRustFuzzers,
/// Build FFI fuzzers
BuildFfiFuzzers,
/// Builds and runs tests for all C/C++ projects. This is a combination of CheckNpFfi,
/// CheckLdtFfi, and CheckCmakeBuildAndTests
- FfiCheckEverything,
+ FfiCheckEverything(CargoOptions),
/// Builds the crate checks the cbindgen generation of C/C++ bindings
- CheckNpFfi,
+ CheckNpFfi(CargoOptions),
/// Builds ldt_np_adv_ffi crate with all possible different sets of feature flags
CheckLdtFfi,
/// Checks the CMake build and runs all of the C/C++ tests
- CheckCmakeProjects,
+ CheckLdtCmake(CargoOptions),
+ /// Checks the CMake build and runs all of the C/C++ tests
+ CheckNpFfiCmake(CargoOptions),
/// Checks the workspace 3rd party crates and makes sure they have a valid license
CheckLicenseHeaders,
/// Generate new headers for any files that are missing them
AddLicenseHeaders,
/// Builds and runs tests for the UKEY2 FFI
- CheckUkey2Ffi,
+ CheckUkey2Ffi(CargoOptions),
/// Checks the build of ldt_jni wrapper with non default features, ie rust-openssl, and boringssl
CheckLdtJni,
+ /// Runs the kotlin tests of the LDT Jni API
+ RunKotlinTests,
}
#[derive(clap::Args, Debug, Clone, Default)]
pub struct CheckOptions {
#[arg(long, help = "reformat files with cargo fmt")]
reformat: bool,
+ #[command(flatten)]
+ cargo_options: CargoOptions,
}
#[derive(clap::Args, Debug, Clone, Default)]
-pub struct BuildBoringSslOptions {
- #[arg(
- long,
- // the commit after this one causes failures in rust-openssl
- default_value = "d995d82ad53133017e34b009e9c6912b2ef6aeb7",
- help = "Commit hash to use when checking out boringssl"
- )]
- commit_hash: String,
+pub struct CargoOptions {
+ #[arg(long, help = "whether to run cargo with --locked")]
+ locked: bool,
}
diff --git a/nearby/src/ukey2.rs b/nearby/src/ukey2.rs
index f0e1081..e548ab0 100644
--- a/nearby/src/ukey2.rs
+++ b/nearby/src/ukey2.rs
@@ -12,26 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
+use cmd_runner::{run_cmd_shell, run_cmd_shell_with_color, YellowStderr};
use std::{fs, path};
-pub(crate) fn check_ukey2_ffi(root: &path::Path) -> anyhow::Result<()> {
+use crate::CargoOptions;
+
+pub(crate) fn check_ukey2_ffi(
+ root: &path::Path,
+ cargo_options: &CargoOptions,
+) -> anyhow::Result<()> {
log::info!("Checking Ukey2 ffi");
- let mut ffi_dir = root.to_path_buf();
- ffi_dir.push("connections/ukey2/ukey2_c_ffi");
+ let ffi_dir = root.join("connections/ukey2/ukey2_c_ffi");
+
+ let locked_arg = if cargo_options.locked { "--locked" } else { "" };
// Default build, RustCrypto
- run_cmd_shell(&ffi_dir, "cargo build --release --lib")?;
+ run_cmd_shell(&ffi_dir, format!("cargo build {locked_arg} --quiet --release --lib"))?;
// OpenSSL
- run_cmd_shell(&ffi_dir, "cargo build --no-default-features --features=openssl")?;
+ run_cmd_shell(
+ &ffi_dir,
+ format!("cargo build {locked_arg} --quiet --no-default-features --features=openssl"),
+ )?;
- run_cmd_shell(&ffi_dir, "cargo doc --no-deps")?;
+ run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?;
run_cmd_shell(&ffi_dir, "cargo clippy --no-default-features --features=openssl")?;
run_cmd_shell(&ffi_dir, "cargo deny check")?;
- let mut ffi_build_dir = ffi_dir.to_path_buf();
- ffi_build_dir.push("cpp/build");
+ let ffi_build_dir = ffi_dir.join("cpp/build");
fs::create_dir_all(&ffi_build_dir)?;
run_cmd_shell_with_color::<YellowStderr>(&ffi_build_dir, "cmake ..")?;
run_cmd_shell_with_color::<YellowStderr>(&ffi_build_dir, "cmake --build .")?;
diff --git a/nearby/presence/handle_map/Cargo.toml b/nearby/util/handle_map/Cargo.toml
similarity index 69%
rename from nearby/presence/handle_map/Cargo.toml
rename to nearby/util/handle_map/Cargo.toml
index 8f00bf7..ee9dbf3 100644
--- a/nearby/presence/handle_map/Cargo.toml
+++ b/nearby/util/handle_map/Cargo.toml
@@ -5,14 +5,12 @@
publish.workspace = true
[dependencies]
-hashbrown.workspace = true
-lock_api.workspace = true
-portable-atomic.workspace = true
-spin.workspace = true
+lock_adapter.workspace = true
crypto_provider.workspace = true
[dev-dependencies]
criterion.workspace = true
+lazy_static.workspace = true
[[bench]]
name = "benches"
diff --git a/nearby/presence/handle_map/benches/benches.rs b/nearby/util/handle_map/benches/benches.rs
similarity index 100%
rename from nearby/presence/handle_map/benches/benches.rs
rename to nearby/util/handle_map/benches/benches.rs
diff --git a/nearby/util/handle_map/src/declare_handle_map.rs b/nearby/util/handle_map/src/declare_handle_map.rs
new file mode 100644
index 0000000..dbdc77f
--- /dev/null
+++ b/nearby/util/handle_map/src/declare_handle_map.rs
@@ -0,0 +1,161 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Implementation of the `declare_handle_map!` macro
+
+#[macro_export]
+/// ```ignore
+/// declare_handle_map! {
+/// mod $handle_module_name {
+/// #[dimensions = $map_dimension_provider]
+/// type $handle_type_name: HandleLike<Object = $wrapped_type>;
+/// }
+/// }
+/// ```
+///
+/// Declares a new public module with name `handle_module_name` which includes a new type
+/// `handle_type_name` which is `#[repr(C)]` and represents FFI-accessible handles
+/// to values of type `wrapped_type`.
+///
+/// Internal to the generated module, a new static `SingletonHandleMap` is created, where the
+/// maximum number of active handles and the number of shards are given by
+/// the dimensions returned by evaluation of the `map_dimension_provider` expression.
+///
+/// Note: `map_dimension_provider` will be evaluated within the defined module's scope,
+/// so you will likely need to use `super` to refer to definitions in the enclosing scope.
+///
+/// # Example
+/// The following code defines an FFI-safe type `StringHandle` which references
+/// the `String` data-type, and uses it to define a (contrived)
+/// function `sample` which will print "Hello World".
+///
+/// ```
+/// #[macro_use]///
+/// extern crate lazy_static;
+///
+/// use core::ops::Deref;
+/// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike};
+///
+/// fn get_string_handle_map_dimensions() -> HandleMapDimensions {
+/// HandleMapDimensions {
+/// num_shards: 8,
+/// max_active_handles: 100,
+/// }
+/// }
+///
+/// declare_handle_map! {
+/// mod string_handle {
+/// #[dimensions = super::get_string_handle_map_dimensions()]
+/// type StringHandle: HandleLike<Object = String>;
+/// }
+/// }
+///
+/// use string_handle::StringHandle;
+///
+/// fn main() {
+/// // Note: this method could panic if there are
+/// // more than 99 outstanding handles.
+///
+/// // Allocate a new string-handle pointing to the string "Hello"
+/// let handle = StringHandle::allocate(|| { "Hello".to_string() }).unwrap();
+/// {
+/// // Obtain a write-guard on the contents of our handle
+/// let mut handle_write_guard = handle.get_mut().unwrap();
+/// handle_write_guard.push_str(" World");
+/// // Write guard is auto-dropped at the end of this block.
+/// }
+/// {
+/// // Obtain a read-guard on the contents of our handle.
+/// // Note that we had to ensure that the write-guard was
+/// // dropped prior to doing this, or else execution
+/// // could potentially hang.
+/// let handle_read_guard = handle.get().unwrap();
+/// println!("{}", handle_read_guard.deref());
+/// }
+/// // Clean up the data behind the created handle
+/// handle.deallocate().unwrap();
+/// }
+///
+/// ```
+macro_rules! declare_handle_map {
+ (
+ mod $handle_module_name:ident {
+ #[dimensions = $map_dimension_provider:expr]
+ type $handle_type_name:ident: HandleLike<Object = $wrapped_type:ty>;
+ }
+ ) => {
+ #[doc = ::core::concat!(
+ "Macro-generated (via `handle_map::declare_handle_map!`) module which",
+ " defines the `", ::core::stringify!($handle_module_name), "::",
+ ::core::stringify!($handle_type_name), "` FFI-transmissible handle type ",
+ " which references values of type `", ::core::stringify!($wrapped_type), "`."
+ )]
+ pub mod $handle_module_name {
+
+ lazy_static! {
+ static ref GLOBAL_HANDLE_MAP: $crate::HandleMap<$wrapped_type> =
+ $crate::HandleMap::with_dimensions($map_dimension_provider);
+ }
+
+ #[doc = ::core::concat!(
+ "A `#[repr(C)]` handle to a value of type `",
+ ::core::stringify!($wrapped_type), "`."
+ )]
+ #[repr(C)]
+ #[derive(Clone, Copy, PartialEq, Eq)]
+ pub struct $handle_type_name {
+ handle_id: u64,
+ }
+
+ impl $handle_type_name {
+ /// Cast the given raw Handle to this HandleLike
+ pub fn from_handle(handle: $crate::Handle) -> Self {
+ Self { handle_id: handle.get_id() }
+ }
+
+ /// Get this HandleLike as a raw Handle.
+ pub fn get_as_handle(&self) -> $crate::Handle {
+ $crate::Handle::from_id(self.handle_id)
+ }
+ }
+ impl $crate::HandleLike for $handle_type_name {
+ type Object = $wrapped_type;
+ fn allocate(
+ initial_value_provider: impl FnOnce() -> $wrapped_type,
+ ) -> Result<Self, $crate::HandleMapFullError> {
+ GLOBAL_HANDLE_MAP
+ .allocate(initial_value_provider)
+ .map(|derived_handle| Self { handle_id: derived_handle.get_id() })
+ }
+ fn get(
+ &self,
+ ) -> Result<$crate::ObjectReadGuardImpl<$wrapped_type>, $crate::HandleNotPresentError>
+ {
+ GLOBAL_HANDLE_MAP.get(self.get_as_handle())
+ }
+ fn get_mut(
+ &self,
+ ) -> Result<
+ $crate::ObjectReadWriteGuardImpl<$wrapped_type>,
+ $crate::HandleNotPresentError,
+ > {
+ GLOBAL_HANDLE_MAP.get_mut(self.get_as_handle())
+ }
+ fn deallocate(self) -> Result<$wrapped_type, $crate::HandleNotPresentError> {
+ GLOBAL_HANDLE_MAP.deallocate(self.get_as_handle())
+ }
+ }
+ }
+ };
+}
diff --git a/nearby/util/handle_map/src/guard.rs b/nearby/util/handle_map/src/guard.rs
new file mode 100644
index 0000000..4a96bb5
--- /dev/null
+++ b/nearby/util/handle_map/src/guard.rs
@@ -0,0 +1,143 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::Handle;
+use core::ops::{Deref, DerefMut};
+use lock_adapter::std::RwMapping;
+use std::collections::HashMap;
+use std::marker::PhantomData;
+
+/// A RAII read lock guard for an object in a [`HandleMap`](crate::HandleMap)
+/// pointed-to by a given [`Handle`]. When this struct is
+/// dropped, the underlying read lock on the associated
+/// shard will be dropped.
+pub struct ObjectReadGuardImpl<'a, T: 'a> {
+ pub(crate) guard: lock_adapter::std::MappedRwLockReadGuard<
+ 'a,
+ <Self as ObjectReadGuard>::Arg,
+ <Self as ObjectReadGuard>::Ret,
+ <Self as ObjectReadGuard>::Mapping,
+ >,
+}
+
+/// Trait implemented for an ObjectReadGuard which defines the associated types of the guard and a
+/// mapping for how the guard is retrieved from its parent object
+pub trait ObjectReadGuard: Deref<Target = Self::Ret> {
+ /// The mapping which defines how a guard is retrieved for `Self::Ret` from `Self::Arg`
+ type Mapping: RwMapping<Arg = Self::Arg, Ret = Self::Ret>;
+ /// The argument type input to the mapping functions
+ type Arg;
+ /// The Return type of the mapping functions
+ type Ret;
+}
+
+impl<'a, T> Deref for ObjectReadGuardImpl<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.guard.deref()
+ }
+}
+
+pub struct ObjectReadGuardMapping<'a, T> {
+ pub(crate) handle: Handle,
+ pub(crate) _marker: PhantomData<&'a T>,
+}
+
+impl<'a, T> RwMapping for ObjectReadGuardMapping<'a, T> {
+ type Arg = HashMap<Handle, T>;
+ type Ret = T;
+
+ fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret {
+ // We know that the entry exists, since we've locked the
+ // shard and already checked that it exists prior to
+ // handing out this new, mapped read-lock.
+ arg.get(&self.handle).unwrap()
+ }
+
+ fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret {
+ // We know that the entry exists, since we've locked the
+ // shard and already checked that it exists prior to
+ // handing out this new, mapped read-lock.
+ arg.get_mut(&self.handle).unwrap()
+ }
+}
+
+impl<'a, T> ObjectReadGuard for ObjectReadGuardImpl<'a, T> {
+ type Mapping = ObjectReadGuardMapping<'a, T>;
+ type Arg = HashMap<Handle, T>;
+ type Ret = T;
+}
+
+/// A RAII read-write lock guard for an object in a [`HandleMap`](crate::HandleMap)
+/// pointed-to by a given [`Handle`]. When this struct is
+/// dropped, the underlying read-write lock on the associated
+/// shard will be dropped.
+pub struct ObjectReadWriteGuardImpl<'a, T: 'a> {
+ pub(crate) guard: lock_adapter::std::MappedRwLockWriteGuard<
+ 'a,
+ <Self as ObjectReadWriteGuard>::Arg,
+ <Self as ObjectReadWriteGuard>::Ret,
+ <Self as ObjectReadWriteGuard>::Mapping,
+ >,
+}
+
+/// Trait implemented for an object read guard which defines the associated types of the guard and a
+/// mapping for how the guard is retrieved from its parent object
+pub trait ObjectReadWriteGuard: Deref<Target = Self::Ret> + DerefMut<Target = Self::Ret> {
+ /// The mapping which defines how a guard is retrieved for `Ret` from `Arg`
+ type Mapping: RwMapping<Arg = Self::Arg, Ret = Self::Ret>;
+ /// The argument type input to the mapping functions
+ type Arg;
+ /// The Return type of the mapping functions
+ type Ret;
+}
+
+pub struct ObjectReadWriteGuardMapping<'a, T> {
+ pub(crate) handle: Handle,
+ pub(crate) _marker: PhantomData<&'a T>,
+}
+
+impl<'a, T> Deref for ObjectReadWriteGuardImpl<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.guard.deref()
+ }
+}
+
+impl<'a, T> DerefMut for ObjectReadWriteGuardImpl<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.guard.deref_mut()
+ }
+}
+
+impl<'a, T> ObjectReadWriteGuard for ObjectReadWriteGuardImpl<'a, T> {
+ type Mapping = ObjectReadWriteGuardMapping<'a, T>;
+ type Arg = HashMap<Handle, T>;
+ type Ret = T;
+}
+
+impl<'a, T> RwMapping for ObjectReadWriteGuardMapping<'a, T> {
+ type Arg = HashMap<Handle, T>;
+ type Ret = T;
+
+ fn map<'b>(&self, arg: &'b Self::Arg) -> &'b Self::Ret {
+ arg.get(&self.handle).unwrap()
+ }
+
+ fn map_mut<'b>(&self, arg: &'b mut Self::Arg) -> &'b mut Self::Ret {
+ arg.get_mut(&self.handle).unwrap()
+ }
+}
diff --git a/nearby/util/handle_map/src/lib.rs b/nearby/util/handle_map/src/lib.rs
new file mode 100644
index 0000000..bb420e3
--- /dev/null
+++ b/nearby/util/handle_map/src/lib.rs
@@ -0,0 +1,242 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! A thread-safe implementation of a map for managing object handles,
+//! a safer alternative to raw pointers for FFI interop.
+
+#![forbid(unsafe_code)]
+#![deny(missing_docs)]
+
+use std::boxed::Box;
+use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
+use std::vec::Vec;
+
+pub mod declare_handle_map;
+mod guard;
+pub(crate) mod shard;
+
+#[cfg(test)]
+mod tests;
+
+pub use guard::{ObjectReadGuardImpl, ObjectReadWriteGuardImpl};
+
+use shard::{HandleMapShard, ShardAllocationError};
+
+/// An individual handle to be given out by a [`HandleMap`].
+/// This representation is untyped, and just a wrapper
+/// around a handle-id, in contrast to implementors of `HandleLike`.
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Handle {
+ handle_id: u64,
+}
+
+impl From<&Handle> for Handle {
+ fn from(handle: &Handle) -> Self {
+ *handle
+ }
+}
+
+impl Handle {
+ /// Constructs a handle wrapping the given ID.
+ ///
+ /// No validity checks are done on the wrapped ID
+ /// to ensure that the given ID is active in
+ /// any specific handle-map, and the type
+ /// of the handle is not represented.
+ ///
+ /// As a result, this method is only useful for
+ /// allowing access to handles from an FFI layer.
+ pub fn from_id(handle_id: u64) -> Self {
+ Self { handle_id }
+ }
+
+ /// Gets the ID for this handle.
+ ///
+ /// Since the underlying handle is un-typed,`
+ /// this method is only suitable for
+ /// transmitting handles across an FFI layer.
+ pub fn get_id(&self) -> u64 {
+ self.handle_id
+ }
+
+ /// Derives the shard index from the handle id
+ fn get_shard_index(&self, num_shards: u8) -> usize {
+ (self.handle_id % (num_shards as u64)) as usize
+ }
+}
+
+/// Error raised when attempting to allocate into a full handle-map.
+#[derive(Debug)]
+pub struct HandleMapFullError;
+
+/// Error raised when the entry for a given [`Handle`] doesn't exist.
+#[derive(Debug)]
+pub struct HandleNotPresentError;
+
+/// FFI-transmissible structure expressing the dimensions
+/// (max # of allocatable slots, number of shards) of a handle-map
+/// to be used upon initialization.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct HandleMapDimensions {
+ /// The number of shards which are employed
+ /// by the associated handle-map.
+ pub num_shards: u8,
+ /// The maximum number of active handles which may be
+ /// stored within the associated handle-map.
+ pub max_active_handles: u32,
+}
+
+/// A thread-safe mapping from "handle"s [like pointers, but safer]
+/// to underlying structures, supporting allocations, reads, writes,
+/// and deallocations of objects behind handles.
+pub struct HandleMap<T: Send + Sync> {
+ /// The dimensions of this handle-map
+ dimensions: HandleMapDimensions,
+
+ /// The individually-lockable "shards" of the handle-map,
+ /// among which the keys will be roughly uniformly-distributed.
+ handle_map_shards: Box<[HandleMapShard<T>]>,
+
+ /// An atomically-incrementing counter which tracks the
+ /// next handle ID which allocations will attempt to use.
+ new_handle_id_counter: AtomicU64,
+
+ /// An atomic integer roughly tracking the number of
+ /// currently-outstanding allocated entries in this
+ /// handle-map among all [`HandleMapShard`]s.
+ outstanding_allocations_counter: AtomicU32,
+}
+
+impl<T: Send + Sync> HandleMap<T> {
+ /// Creates a new handle-map with the given `HandleMapDimensions`.
+ pub fn with_dimensions(dimensions: HandleMapDimensions) -> Self {
+ let mut handle_map_shards = Vec::with_capacity(dimensions.num_shards as usize);
+ for _ in 0..dimensions.num_shards {
+ handle_map_shards.push(HandleMapShard::default());
+ }
+ let handle_map_shards = handle_map_shards.into_boxed_slice();
+ Self {
+ dimensions,
+ handle_map_shards,
+ new_handle_id_counter: AtomicU64::new(0),
+ outstanding_allocations_counter: AtomicU32::new(0),
+ }
+ }
+}
+
+impl<T: Send + Sync> HandleMap<T> {
+ /// Allocates a new object within the given handle-map, returning
+ /// a handle to the location it was stored at. This operation
+ /// may fail if attempting to allocate over the `dimensions.max_active_handles`
+ /// limit imposed on the handle-map, in which case this method
+ /// will return a `HandleMapFullError`.
+ pub fn allocate(
+ &self,
+ initial_value_provider: impl FnOnce() -> T,
+ ) -> Result<Handle, HandleMapFullError> {
+ let mut initial_value_provider = initial_value_provider;
+ loop {
+ // Increment the new-handle-ID counter using relaxed memory ordering,
+ // since the only invariant that we want to enforce is that concurrently-running
+ // threads always get distinct new handle-ids.
+ let new_handle_id = self.new_handle_id_counter.fetch_add(1, Ordering::Relaxed);
+ let new_handle = Handle::from_id(new_handle_id);
+ let shard_index = new_handle.get_shard_index(self.dimensions.num_shards);
+
+ // Now, check the shard to see if we can actually allocate into it.
+ let shard_allocate_result = self.handle_map_shards[shard_index].try_allocate(
+ new_handle,
+ initial_value_provider,
+ &self.outstanding_allocations_counter,
+ self.dimensions.max_active_handles,
+ );
+ match shard_allocate_result {
+ Ok(_) => {
+ return Ok(new_handle);
+ }
+ Err(ShardAllocationError::ExceedsAllocationLimit) => {
+ return Err(HandleMapFullError);
+ }
+ Err(ShardAllocationError::EntryOccupied(thrown_back_provider)) => {
+ // We need to do the whole thing again with a new ID
+ initial_value_provider = thrown_back_provider;
+ }
+ }
+ }
+ }
+
+ /// Gets a read-only reference to an object within the given handle-map,
+ /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`].
+ pub fn get(&self, handle: Handle) -> Result<ObjectReadGuardImpl<T>, HandleNotPresentError> {
+ let shard_index = handle.get_shard_index(self.dimensions.num_shards);
+ self.handle_map_shards[shard_index].get(handle)
+ }
+
+ /// Gets a read+write reference to an object within the given handle-map,
+ /// if the given handle is present. Otherwise, returns [`HandleNotPresentError`].
+ pub fn get_mut(
+ &self,
+ handle: Handle,
+ ) -> Result<ObjectReadWriteGuardImpl<T>, HandleNotPresentError> {
+ let shard_index = handle.get_shard_index(self.dimensions.num_shards);
+ self.handle_map_shards[shard_index].get_mut(handle)
+ }
+
+ /// Removes the object pointed to by the given handle in
+ /// the handle-map, returning the removed object if it
+ /// exists. Otherwise, returns [`HandleNotPresentError`].
+ pub fn deallocate(&self, handle: Handle) -> Result<T, HandleNotPresentError> {
+ let shard_index = handle.get_shard_index(self.dimensions.num_shards);
+ self.handle_map_shards[shard_index]
+ .deallocate(handle, &self.outstanding_allocations_counter)
+ }
+
+ /// Gets the actual number of elements stored in the entire map.
+ /// Only suitable for single-threaded sections of tests.
+ #[cfg(test)]
+ pub(crate) fn len(&self) -> usize {
+ self.handle_map_shards.iter().map(|s| s.len()).sum()
+ }
+
+ /// Sets the new-handle-id counter to the given value.
+ /// Only suitable for tests.
+ #[cfg(test)]
+ pub(crate) fn set_new_handle_id_counter(&mut self, value: u64) {
+ self.new_handle_id_counter = AtomicU64::new(value);
+ }
+}
+
+/// Externally-facing trait for things which behave like handle-map handles
+/// with a globally-defined handle-map for the type.
+pub trait HandleLike: Sized {
+ /// The underlying object type pointed-to by this handle
+ type Object: Send + Sync;
+
+ /// Tries to allocate a new handle using the given provider
+ /// to construct the underlying stored object as a new
+ /// entry into the global handle table for this type.
+ fn allocate(
+ initial_value_provider: impl FnOnce() -> Self::Object,
+ ) -> Result<Self, HandleMapFullError>;
+
+ /// Gets a RAII read-guard on the contents behind this handle.
+ fn get(&self) -> Result<ObjectReadGuardImpl<Self::Object>, HandleNotPresentError>;
+
+ /// Gets a RAII read-write guard on the contents behind this handle.
+ fn get_mut(&self) -> Result<ObjectReadWriteGuardImpl<Self::Object>, HandleNotPresentError>;
+
+ /// Deallocates the contents behind this handle.
+ fn deallocate(self) -> Result<Self::Object, HandleNotPresentError>;
+}
diff --git a/nearby/util/handle_map/src/shard.rs b/nearby/util/handle_map/src/shard.rs
new file mode 100644
index 0000000..da3bd57
--- /dev/null
+++ b/nearby/util/handle_map/src/shard.rs
@@ -0,0 +1,185 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use core::ops::{Deref, DerefMut};
+use lock_adapter::std::{RwLock, RwLockReadGuard, RwLockWriteGuard};
+use lock_adapter::RwLock as _;
+use std::collections::hash_map::Entry::{Occupied, Vacant};
+use std::collections::HashMap;
+use std::marker::PhantomData;
+use std::sync::atomic::{AtomicU32, Ordering};
+
+use crate::guard::{
+ ObjectReadGuardImpl, ObjectReadGuardMapping, ObjectReadWriteGuardImpl,
+ ObjectReadWriteGuardMapping,
+};
+use crate::{Handle, HandleNotPresentError};
+
+// Bunch o' type aliases to make talking about them much easier in the shard code.
+type ShardMapType<T> = HashMap<Handle, T>;
+type ShardReadWriteLock<T> = RwLock<ShardMapType<T>>;
+type ShardReadGuard<'a, T> = RwLockReadGuard<'a, ShardMapType<T>>;
+type ShardReadWriteGuard<'a, T> = RwLockWriteGuard<'a, ShardMapType<T>>;
+
+/// Internal error enum for failed allocations into a given shard.
+pub(crate) enum ShardAllocationError<T, F: FnOnce() -> T> {
+ /// Error for when the entry for the handle is occupied,
+ /// in which case we spit out the object-provider to try again
+ /// with a new handle-id.
+ EntryOccupied(F),
+ /// Error for when we would exceed the maximum number of allocations.
+ ExceedsAllocationLimit,
+}
+
+/// An individual handle-map shard, which is ultimately
+/// just a hash-map behind a lock.
+pub(crate) struct HandleMapShard<T: Send + Sync> {
+ data: RwLock<ShardMapType<T>>,
+}
+
+impl<T: Send + Sync> Default for HandleMapShard<T> {
+ fn default() -> Self {
+ Self { data: RwLock::new(HashMap::new()) }
+ }
+}
+
+impl<T: Send + Sync> HandleMapShard<T> {
+ pub fn get(&self, handle: Handle) -> Result<ObjectReadGuardImpl<T>, HandleNotPresentError> {
+ let map_read_guard = ShardReadWriteLock::<T>::read(&self.data);
+ let read_only_map_ref = map_read_guard.deref();
+ if read_only_map_ref.contains_key(&handle) {
+ let object_read_guard = ShardReadGuard::<T>::map(
+ map_read_guard,
+ ObjectReadGuardMapping { handle, _marker: PhantomData },
+ );
+ Ok(ObjectReadGuardImpl { guard: object_read_guard })
+ } else {
+ // Auto-drop the read guard, and return an error
+ Err(HandleNotPresentError)
+ }
+ }
+ /// Gets a read-write guard on the entire shard map if an entry for the given
+ /// handle exists, but if not, yield [`HandleNotPresentError`].
+ fn get_read_write_guard_if_entry_exists(
+ &self,
+ handle: Handle,
+ ) -> Result<ShardReadWriteGuard<T>, HandleNotPresentError> {
+ let contains_key = {
+ let map_ref = self.data.read();
+ map_ref.contains_key(&handle)
+ };
+ if contains_key {
+ // If we know that the entry exists, and we're currently
+ // holding a read-lock, we know that we're safe to request
+ // an upgrade to a write lock, since only one write or
+ // upgradable read lock can be outstanding at any one time.
+ let write_guard = self.data.write();
+ Ok(write_guard)
+ } else {
+ // Auto-drop the read guard, we don't need to allow a write.
+ Err(HandleNotPresentError)
+ }
+ }
+
+ pub fn get_mut(
+ &self,
+ handle: Handle,
+ ) -> Result<ObjectReadWriteGuardImpl<T>, HandleNotPresentError> {
+ let map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?;
+ // Expose only the pointed-to object with a mapped read-write guard
+ let object_read_write_guard = ShardReadWriteGuard::<T>::map(
+ map_read_write_guard,
+ ObjectReadWriteGuardMapping { handle, _marker: PhantomData },
+ );
+ Ok(ObjectReadWriteGuardImpl { guard: object_read_write_guard })
+ }
+
+ pub fn deallocate(
+ &self,
+ handle: Handle,
+ outstanding_allocations_counter: &AtomicU32,
+ ) -> Result<T, HandleNotPresentError> {
+ let mut map_read_write_guard = self.get_read_write_guard_if_entry_exists(handle)?;
+ // We don't need to worry about double-decrements, since the above call
+ // got us an upgradable read guard for our read, which means it's the only
+ // outstanding upgradeable guard on the shard. See `spin` documentation.
+ // Remove the pointed-to object from the map, and return it,
+ // releasing the lock when the guard goes out of scope.
+ let removed_object = map_read_write_guard.deref_mut().remove(&handle).unwrap();
+ // Decrement the allocations counter. Release ordering because we want
+ // to ensure that clearing the map entry never gets re-ordered to after when
+ // this counter gets decremented.
+ let _ = outstanding_allocations_counter.fetch_sub(1, Ordering::Release);
+ Ok(removed_object)
+ }
+
+ pub fn try_allocate<F>(
+ &self,
+ handle: Handle,
+ object_provider: F,
+ outstanding_allocations_counter: &AtomicU32,
+ max_active_handles: u32,
+ ) -> Result<(), ShardAllocationError<T, F>>
+ where
+ F: FnOnce() -> T,
+ {
+ let mut read_write_guard = self.data.write();
+ match read_write_guard.entry(handle) {
+ Occupied(_) => {
+ // We've already allocated for that handle-id, so yield
+ // the object provider back to the caller.
+ Err(ShardAllocationError::EntryOccupied(object_provider))
+ }
+ Vacant(vacant_entry) => {
+ // An entry is open, but we haven't yet checked the allocations count.
+ // Try to increment the total allocations count atomically.
+ // Use acquire ordering on a successful bump, because we don't want
+ // to invoke the allocation closure before we have a guaranteed slot.
+ // On the other hand, upon failure, we don't care about ordering
+ // of surrounding operations, and so we use a relaxed ordering there.
+ let allocation_count_bump_result = outstanding_allocations_counter.fetch_update(
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ |old_total_allocations| {
+ if old_total_allocations >= max_active_handles {
+ None
+ } else {
+ Some(old_total_allocations + 1)
+ }
+ },
+ );
+ match allocation_count_bump_result {
+ Ok(_) => {
+ // We're good to actually allocate
+ let object = object_provider();
+ vacant_entry.insert(object);
+ Ok(())
+ }
+ Err(_) => {
+ // The allocation would cause us to exceed the allowed allocations,
+ // so release all locks and error.
+ Err(ShardAllocationError::ExceedsAllocationLimit)
+ }
+ }
+ }
+ }
+ }
+ /// Gets the actual number of elements stored in this shard.
+ /// Only suitable for single-threaded sections of tests.
+ #[cfg(test)]
+ pub fn len(&self) -> usize {
+ let guard = ShardReadWriteLock::<T>::read(&self.data);
+ guard.deref().len()
+ }
+}
diff --git a/nearby/presence/handle_map/src/tests.rs b/nearby/util/handle_map/src/tests.rs
similarity index 99%
rename from nearby/presence/handle_map/src/tests.rs
rename to nearby/util/handle_map/src/tests.rs
index d97657f..a0774d6 100644
--- a/nearby/presence/handle_map/src/tests.rs
+++ b/nearby/util/handle_map/src/tests.rs
@@ -13,7 +13,8 @@
// limitations under the License.
use crate::*;
-use hashbrown::HashSet;
+use core::ops::{Deref, DerefMut};
+use std::collections::HashSet;
use std::sync::Arc;
use std::thread;
diff --git a/nearby/connections/ukey2/lock_adapter/Cargo.toml b/nearby/util/lock_adapter/Cargo.toml
similarity index 91%
rename from nearby/connections/ukey2/lock_adapter/Cargo.toml
rename to nearby/util/lock_adapter/Cargo.toml
index 713f224..eae1fc9 100644
--- a/nearby/connections/ukey2/lock_adapter/Cargo.toml
+++ b/nearby/util/lock_adapter/Cargo.toml
@@ -8,6 +8,6 @@
spin = { workspace = true, optional = true }
[features]
-default = ["spin"]
-spin = ["dep:spin"]
+default = ["std"]
std = []
+spin = ["dep:spin"]
diff --git a/nearby/connections/ukey2/lock_adapter/src/lib.rs b/nearby/util/lock_adapter/src/lib.rs
similarity index 72%
rename from nearby/connections/ukey2/lock_adapter/src/lib.rs
rename to nearby/util/lock_adapter/src/lib.rs
index 098031b..3d75c0d 100644
--- a/nearby/connections/ukey2/lock_adapter/src/lib.rs
+++ b/nearby/util/lock_adapter/src/lib.rs
@@ -12,6 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+//! An abstraction layer for Rust synchronization primitives which provides both no_std and std library
+//! based implementations
+
+#![forbid(unsafe_code)]
+#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
/// A Spinlock-based implementation of Mutex using the `spin` crate that can be used in `no_std`
@@ -58,3 +63,23 @@
/// Creates a new mutex in an unlocked state ready for use.
fn new(value: T) -> Self;
}
+
+/// A reader-writer lock. This type of lock allows a number of readers or at most one writer at
+/// any point in time.
+pub trait RwLock<T> {
+ /// RAII structure used to release the shared read access of a lock when dropped.
+ type RwLockReadGuard<'a>
+ where
+ Self: 'a;
+
+ /// RAII structure used to release the exclusive write access of a lock when dropped.
+ type RwLockWriteGuard<'a>
+ where
+ Self: 'a;
+
+ /// Locks this RwLock with shared read access, blocking the current thread until it can be acquired.
+ fn read(&self) -> Self::RwLockReadGuard<'_>;
+
+ /// Locks this RwLock with exclusive write access, blocking the current thread until it can be acquired.
+ fn write(&self) -> Self::RwLockWriteGuard<'_>;
+}
diff --git a/nearby/util/lock_adapter/src/spin.rs b/nearby/util/lock_adapter/src/spin.rs
new file mode 100644
index 0000000..e894cef
--- /dev/null
+++ b/nearby/util/lock_adapter/src/spin.rs
@@ -0,0 +1,60 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::NoPoisonMutex;
+
+/// A mutual exclusion primitive useful for protecting shared data
+pub struct Mutex<T>(spin::Mutex<T>);
+
+impl<T> NoPoisonMutex<T> for Mutex<T> {
+ type MutexGuard<'a> = spin::MutexGuard<'a, T> where T: 'a;
+
+ fn lock(&self) -> Self::MutexGuard<'_> {
+ self.0.lock()
+ }
+
+ fn try_lock(&self) -> Option<Self::MutexGuard<'_>> {
+ self.0.try_lock()
+ }
+
+ fn new(value: T) -> Self {
+ Self(spin::Mutex::new(value))
+ }
+}
+
+/// A reader-writer lock
+/// This type of lock allows a number of readers or at most one writer at any point in time.
+/// The write portion of this lock typically allows modification of the underlying data (exclusive access)
+/// and the read portion of this lock typically allows for read-only access (shared access).
+pub struct RwLock<T>(spin::RwLock<T>);
+
+impl<T> RwLock<T> {
+ /// Creates a new instance of an `RwLock<T>` which is unlocked.
+ pub const fn new(inner: T) -> Self {
+ Self(spin::RwLock::new(inner))
+ }
+}
+
+impl<T> crate::RwLock<T> for RwLock<T> {
+ type RwLockReadGuard<'a> = spin::RwLockReadGuard<'a, T> where T: 'a;
+ type RwLockWriteGuard<'a> = spin::RwLockWriteGuard<'a, T> where T: 'a;
+
+ fn read(&self) -> Self::RwLockReadGuard<'_> {
+ self.0.read()
+ }
+
+ fn write(&self) -> Self::RwLockWriteGuard<'_> {
+ self.0.write()
+ }
+}
diff --git a/nearby/util/lock_adapter/src/std.rs b/nearby/util/lock_adapter/src/std.rs
new file mode 100644
index 0000000..c950d85
--- /dev/null
+++ b/nearby/util/lock_adapter/src/std.rs
@@ -0,0 +1,175 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::NoPoisonMutex;
+use std::ops::{Deref, DerefMut};
+
+/// A mutual exclusion primitive useful for protecting shared data
+pub struct Mutex<T>(std::sync::Mutex<T>);
+
+impl<T> NoPoisonMutex<T> for Mutex<T> {
+ type MutexGuard<'a> = std::sync::MutexGuard<'a, T> where T: 'a;
+
+ fn lock(&self) -> Self::MutexGuard<'_> {
+ self.0.lock().unwrap_or_else(|poison| poison.into_inner())
+ }
+
+ fn try_lock(&self) -> Option<Self::MutexGuard<'_>> {
+ match self.0.try_lock() {
+ Ok(guard) => Some(guard),
+ Err(std::sync::TryLockError::Poisoned(guard)) => Some(guard.into_inner()),
+ Err(std::sync::TryLockError::WouldBlock) => None,
+ }
+ }
+
+ fn new(value: T) -> Self {
+ Self(std::sync::Mutex::new(value))
+ }
+}
+
+/// A reader-writer lock
+/// This type of lock allows a number of readers or at most one writer at any point in time.
+/// The write portion of this lock typically allows modification of the underlying data (exclusive access)
+/// and the read portion of this lock typically allows for read-only access (shared access).
+pub struct RwLock<T>(std::sync::RwLock<T>);
+
+impl<T> RwLock<T> {
+ /// Creates a new instance of an `RwLock<T>` which is unlocked.
+ pub const fn new(value: T) -> Self {
+ Self(std::sync::RwLock::new(value))
+ }
+}
+
+impl<T> crate::RwLock<T> for RwLock<T> {
+ type RwLockReadGuard<'a> = RwLockReadGuard<'a, T> where T: 'a;
+
+ type RwLockWriteGuard<'a> = RwLockWriteGuard<'a, T> where T: 'a;
+
+ fn read(&self) -> Self::RwLockReadGuard<'_> {
+ RwLockReadGuard(self.0.read().unwrap_or_else(|e| e.into_inner()))
+ }
+
+ fn write(&self) -> Self::RwLockWriteGuard<'_> {
+ RwLockWriteGuard(self.0.write().unwrap_or_else(|e| e.into_inner()))
+ }
+}
+
+/// RAII structure used to release the shared read access of a lock when dropped.
+pub struct RwLockReadGuard<'a, T>(std::sync::RwLockReadGuard<'a, T>);
+
+impl<'a, T> RwLockReadGuard<'a, T> {
+ /// Make a new MappedRwLockReadGuard for a component of the locked data.
+ pub fn map<U, M>(s: Self, mapping: M) -> MappedRwLockReadGuard<'a, T, U, M>
+ where
+ M: RwMapping<Arg = T, Ret = U>,
+ {
+ MappedRwLockReadGuard { mapping, guard: s }
+ }
+}
+
+impl<'a, T> Deref for RwLockReadGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
+ }
+}
+
+/// An RAII read lock guard returned by RwLockReadGuard::map, which can point to a subfield of the protected data.
+pub struct MappedRwLockReadGuard<'a, T, U, M>
+where
+ M: RwMapping<Arg = T, Ret = U>,
+{
+ mapping: M,
+ guard: RwLockReadGuard<'a, T>,
+}
+
+impl<'a, T, U, M> Deref for MappedRwLockReadGuard<'a, T, U, M>
+where
+ M: RwMapping<Arg = T, Ret = U>,
+{
+ type Target = U;
+
+ fn deref(&self) -> &Self::Target {
+ self.mapping.map(&*self.guard)
+ }
+}
+
+/// RAII structure used to release the exclusive write access of a lock when dropped.
+pub struct RwLockWriteGuard<'a, T>(std::sync::RwLockWriteGuard<'a, T>);
+
+impl<'a, T> RwLockWriteGuard<'a, T> {
+ /// Make a new MappedRwLockWriteGuard for a component of the locked data.
+ pub fn map<U, M>(s: Self, mapping: M) -> MappedRwLockWriteGuard<'a, T, U, M>
+ where
+ M: RwMapping<Arg = T, Ret = U>,
+ {
+ MappedRwLockWriteGuard { mapping, guard: s }
+ }
+}
+
+impl<'a, T> Deref for RwLockWriteGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.deref()
+ }
+}
+
+impl<'a, T> DerefMut for RwLockWriteGuard<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.deref_mut()
+ }
+}
+
+/// An RAII read lock guard returned by RwLockWriteGuard::map, which can point to a subfield of the protected data.
+pub struct MappedRwLockWriteGuard<'a, T, U, M>
+where
+ M: RwMapping<Arg = T, Ret = U>,
+{
+ mapping: M,
+ guard: RwLockWriteGuard<'a, T>,
+}
+
+impl<'a, P, T, M> Deref for MappedRwLockWriteGuard<'a, P, T, M>
+where
+ M: RwMapping<Arg = P, Ret = T>,
+{
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.mapping.map(&*self.guard)
+ }
+}
+
+impl<'a, P, T, M> DerefMut for MappedRwLockWriteGuard<'a, P, T, M>
+where
+ M: RwMapping<Arg = P, Ret = T>,
+{
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.mapping.map_mut(&mut *self.guard)
+ }
+}
+
+/// Mapping functions which define how to map from one locked data type to a component of that locked data
+pub trait RwMapping {
+ /// The original locked data type
+ type Arg;
+ /// The returned mapped locked data type which is a component of the original locked data
+ type Ret;
+ /// Maps from Arg into Ret
+ fn map<'a>(&self, arg: &'a Self::Arg) -> &'a Self::Ret;
+ /// Mutably maps from Arg into Ret
+ fn map_mut<'a>(&self, arg: &'a mut Self::Arg) -> &'a mut Self::Ret;
+}
diff --git a/remoteauth/Cargo.lock b/remoteauth/Cargo.lock
new file mode 100644
index 0000000..55841b9
--- /dev/null
+++ b/remoteauth/Cargo.lock
@@ -0,0 +1,469 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anstream"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
+
+[[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
+[[package]]
+name = "build-scripts"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "cmd-runner",
+ "env_logger",
+ "log",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "clap"
+version = "4.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+ "once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+
+[[package]]
+name = "cmd-runner"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "owo-colors",
+ "shell-escape",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "ctap_protocol"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f478948fd84d9f8e86967bf432640e46adfb5a4bd4f14ef7e864ab38220534ae"
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "platform"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+
+[[package]]
+name = "remote_auth_protocol"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "shell-escape"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "2.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/remoteauth/Cargo.toml b/remoteauth/Cargo.toml
new file mode 100644
index 0000000..20b2ec3
--- /dev/null
+++ b/remoteauth/Cargo.toml
@@ -0,0 +1,27 @@
+[workspace]
+members = [
+ "ctap_protocol",
+ "platform",
+ "remote_auth_protocol",
+]
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[workspace.dependencies]
+anyhow = "1.0.72"
+
+[package]
+name = "build-scripts"
+version.workspace = true
+edition.workspace = true
+publish.workspace = true
+
+[dependencies]
+anyhow.workspace = true
+clap = { version = "4.0.25", features = ["derive"] }
+cmd-runner = { path = "../cmd-runner" }
+env_logger = "0.10.0"
+log = "0.4.17"
\ No newline at end of file
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/ctap_protocol/Cargo.toml
similarity index 80%
copy from nearby/crypto/bssl-crypto/Cargo.toml
copy to remoteauth/ctap_protocol/Cargo.toml
index bfe3964..8a06840 100644
--- a/nearby/crypto/bssl-crypto/Cargo.toml
+++ b/remoteauth/ctap_protocol/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "bssl-crypto"
+name = "ctap_protocol"
version.workspace = true
edition.workspace = true
publish.workspace = true
@@ -7,3 +7,4 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow.workspace = true
diff --git a/remoteauth/ctap_protocol/src/command.rs b/remoteauth/ctap_protocol/src/command.rs
new file mode 100644
index 0000000..e512755
--- /dev/null
+++ b/remoteauth/ctap_protocol/src/command.rs
@@ -0,0 +1,227 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! # CTAP Protocol
+//!
+//! This crate represents CTAP messages and turns them into a binary representation to be sent to a
+//! remote device.
+
+use anyhow::anyhow;
+
+/// The Rust representation of CTAP values (or identifiers).
+#[derive(Debug, PartialEq)]
+pub enum Value {
+ AuthenticatorMakeCredential = 0x01,
+ AuthenticatorGetAssertion = 0x02,
+ AuthenticatorGetInfo = 0x04,
+ AuthenticatorClientPIN = 0x06,
+ AuthenticatorReset = 0x07,
+ AuthenticatorGetNextAssertion = 0x08,
+}
+
+/// The Rust representation of CTAP parameters.
+#[derive(Debug, PartialEq)]
+pub enum Parameters {
+ AuthenticatorMakeCredential,
+ AuthenticatorGetAssertion,
+ AuthenticatorClientPIN,
+}
+
+impl From<Parameters> for Vec<u8> {
+ fn from(message: Parameters) -> Vec<u8> {
+ // TODO: serialize parameters correctly
+ match message {
+ Parameters::AuthenticatorMakeCredential => vec![],
+ Parameters::AuthenticatorGetAssertion => vec![],
+ Parameters::AuthenticatorClientPIN => vec![],
+ }
+ }
+}
+
+pub struct Command {
+ pub value: Value,
+ pub parameters: Option<Parameters>,
+}
+
+impl From<Command> for Vec<u8> {
+ /// Converts the given CTAP command into its binary representation.
+ fn from(message: Command) -> Vec<u8> {
+ let mut result = vec![message.value as u8];
+ if let Some(p) = message.parameters {
+ result.append(&mut p.into());
+ }
+ result
+ }
+}
+
+impl TryFrom<Vec<u8>> for Command {
+ type Error = anyhow::Error;
+ /// Convert a binary message to its Rust representation.
+ fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> {
+ if bytes.is_empty() {
+ Err(anyhow!("Binary message was empty."))
+ } else {
+ match bytes[0] {
+ _x if _x == Value::AuthenticatorMakeCredential as u8 => Ok(Self {
+ value: Value::AuthenticatorMakeCredential,
+ parameters: Some(Parameters::AuthenticatorMakeCredential),
+ }),
+ _x if _x == Value::AuthenticatorGetAssertion as u8 => Ok(Self {
+ value: Value::AuthenticatorGetAssertion,
+ parameters: Some(Parameters::AuthenticatorGetAssertion),
+ }),
+ _x if _x == Value::AuthenticatorGetInfo as u8 => Ok(Self {
+ value: Value::AuthenticatorGetInfo,
+ parameters: None,
+ }),
+ _x if _x == Value::AuthenticatorClientPIN as u8 => Ok(Self {
+ value: Value::AuthenticatorClientPIN,
+ parameters: Some(Parameters::AuthenticatorClientPIN),
+ }),
+ _x if _x == Value::AuthenticatorReset as u8 => Ok(Self {
+ value: Value::AuthenticatorReset,
+ parameters: None,
+ }),
+ _x if _x == Value::AuthenticatorGetNextAssertion as u8 => Ok(Self {
+ value: Value::AuthenticatorGetNextAssertion,
+ parameters: None,
+ }),
+ _ => Err(anyhow!("Unknown message type.")),
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::bail;
+
+ #[test]
+ fn translate_message_to_bytes() {
+ let mut message = Command {
+ value: Value::AuthenticatorReset,
+ parameters: None,
+ };
+ let mut bytes: Vec<u8> = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorReset as u8]);
+
+ message = Command {
+ value: Value::AuthenticatorMakeCredential,
+ parameters: None,
+ };
+ bytes = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorMakeCredential as u8]);
+
+ message = Command {
+ value: Value::AuthenticatorGetAssertion,
+ parameters: None,
+ };
+ bytes = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorGetAssertion as u8]);
+
+ message = Command {
+ value: Value::AuthenticatorGetInfo,
+ parameters: None,
+ };
+ bytes = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorGetInfo as u8]);
+
+ message = Command {
+ value: Value::AuthenticatorClientPIN,
+ parameters: None,
+ };
+ bytes = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorClientPIN as u8]);
+
+ message = Command {
+ value: Value::AuthenticatorGetNextAssertion,
+ parameters: None,
+ };
+ bytes = message.into();
+
+ assert_eq!(bytes, vec![Value::AuthenticatorGetNextAssertion as u8]);
+ }
+
+ #[test]
+ fn translate_bytes_to_message() -> anyhow::Result<()> {
+ let mut bytes = vec![Value::AuthenticatorReset as u8];
+ let mut message: Command = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorReset);
+ assert_eq!(message.parameters, None);
+
+ bytes = vec![Value::AuthenticatorMakeCredential as u8];
+ message = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorMakeCredential);
+ assert_eq!(
+ message.parameters,
+ Some(Parameters::AuthenticatorMakeCredential)
+ );
+
+ bytes = vec![Value::AuthenticatorGetAssertion as u8];
+ message = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorGetAssertion);
+ assert_eq!(
+ message.parameters,
+ Some(Parameters::AuthenticatorGetAssertion)
+ );
+
+ bytes = vec![Value::AuthenticatorGetInfo as u8];
+ message = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorGetInfo);
+ assert_eq!(message.parameters, None);
+
+ bytes = vec![Value::AuthenticatorClientPIN as u8];
+ message = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorClientPIN);
+ assert_eq!(message.parameters, Some(Parameters::AuthenticatorClientPIN));
+
+ bytes = vec![Value::AuthenticatorGetNextAssertion as u8];
+ message = bytes.try_into()?;
+ assert_eq!(message.value, Value::AuthenticatorGetNextAssertion);
+ assert_eq!(message.parameters, None);
+ Ok(())
+ }
+
+ #[test]
+ fn translate_empty_bytes() -> anyhow::Result<()> {
+ let bytes = vec![];
+ let res: anyhow::Result<Command> = bytes.try_into();
+ match res {
+ Err(e) => {
+ assert_eq!(e.to_string(), "Binary message was empty.");
+ Ok(())
+ }
+ _ => bail!("Should not parse empty bytes"),
+ }
+ }
+
+ #[test]
+ fn translate_unknown_message() -> anyhow::Result<()> {
+ let bytes = vec![0x10];
+ let res: anyhow::Result<Command> = bytes.try_into();
+ match res {
+ Err(e) => {
+ assert_eq!(e.to_string(), "Unknown message type.");
+ Ok(())
+ }
+ _ => bail!("Should not parse unknown message."),
+ }
+ }
+}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/remoteauth/ctap_protocol/src/lib.rs
similarity index 78%
rename from nearby/crypto/bssl-crypto/src/lib.rs
rename to remoteauth/ctap_protocol/src/lib.rs
index 89e6968..b42193e 100644
--- a/nearby/crypto/bssl-crypto/src/lib.rs
+++ b/remoteauth/ctap_protocol/src/lib.rs
@@ -12,5 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the
-//! `build-boringssl` subcommand of the top level crate.
+//! # CTAP Protocol
+//!
+//! This crate represents CTAP messages and turns them into a binary representation to be sent to a
+//! remote device.
+
+pub mod command;
diff --git a/remoteauth/deny.toml b/remoteauth/deny.toml
new file mode 100644
index 0000000..2bf5920
--- /dev/null
+++ b/remoteauth/deny.toml
@@ -0,0 +1,219 @@
+# This template contains all of the possible sections and their default values
+
+# Note that all fields that take a lint level have these possible values:
+# * deny - An error will be produced and the check will fail
+# * warn - A warning will be produced, but the check will not fail
+# * allow - No warning or error will be produced, though in some cases a note
+# will be
+
+# The values provided in this template are the default values that will be used
+# when any section or field is not specified in your own configuration
+
+# If 1 or more target triples (and optionally, target_features) are specified,
+# only the specified targets will be checked when running `cargo deny check`.
+# This means, if a particular package is only ever used as a target specific
+# dependency, such as, for example, the `nix` crate only being used via the
+# `target_family = "unix"` configuration, that only having windows targets in
+# this list would mean the nix crate, as well as any of its exclusive
+# dependencies not shared by any other crates, would be ignored, as the target
+# list here is effectively saying which targets you are building for.
+targets = [
+ # The triple can be any string, but only the target triples built in to
+ # rustc (as of 1.40) can be checked against actual config expressions
+ #{ triple = "x86_64-unknown-linux-musl" },
+ # You can also specify which target_features you promise are enabled for a
+ # particular target. target_features are currently not validated against
+ # the actual valid features supported by the target architecture.
+ #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
+]
+
+# This section is considered when running `cargo deny check advisories`
+# More documentation for the advisories section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
+[advisories]
+# The path where the advisory database is cloned/fetched into
+db-path = "~/.cargo/advisory-db"
+# The url(s) of the advisory databases to use
+db-urls = ["https://github.com/rustsec/advisory-db"]
+# The lint level for security vulnerabilities
+vulnerability = "deny"
+# The lint level for unmaintained crates
+unmaintained = "warn"
+# The lint level for crates that have been yanked from their source registry
+yanked = "warn"
+# The lint level for crates with security notices. Note that as of
+# 2019-12-17 there are no security notice advisories in
+# https://github.com/rustsec/advisory-db
+notice = "warn"
+# A list of advisory IDs to ignore. Note that ignored advisories will still
+# output a note when they are encountered.
+ignore = [
+ # criterion 0.4.0 depends on a version of atty w/unaligned reads
+]
+# Threshold for security vulnerabilities, any vulnerability with a CVSS score
+# lower than the range specified will be ignored. Note that ignored advisories
+# will still output a note when they are encountered.
+# * None - CVSS Score 0.0
+# * Low - CVSS Score 0.1 - 3.9
+# * Medium - CVSS Score 4.0 - 6.9
+# * High - CVSS Score 7.0 - 8.9
+# * Critical - CVSS Score 9.0 - 10.0
+#severity-threshold =
+
+# If this is true, then cargo deny will use the git executable to fetch advisory database.
+# If this is false, then it uses a built-in git library.
+# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
+# See Git Authentication for more information about setting up git authentication.
+#git-fetch-with-cli = true
+
+# This section is considered when running `cargo deny check licenses`
+# More documentation for the licenses section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
+[licenses]
+# The lint level for crates which do not have a detectable license
+unlicensed = "deny"
+unused-allowed-license = "allow"
+# List of explicitly allowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+allow = [
+ "MIT",
+ "Apache-2.0",
+ "Apache-2.0 WITH LLVM-exception",
+ "BSD-3-Clause",
+ "BSD-2-Clause",
+ "ISC",
+ "Unicode-DFS-2016",
+ "OpenSSL",
+ "Unlicense"
+]
+# List of explicitly disallowed licenses
+# See https://spdx.org/licenses/ for list of possible licenses
+# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
+deny = [
+ #"Nokia",
+]
+# Lint level for licenses considered copyleft
+copyleft = "warn"
+# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
+# * both - The license will be approved if it is both OSI-approved *AND* FSF
+# * either - The license will be approved if it is either OSI-approved *OR* FSF
+# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
+# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
+# * neither - This predicate is ignored and the default lint level is used
+allow-osi-fsf-free = "neither"
+# Lint level used when no other predicates are matched
+# 1. License isn't in the allow or deny lists
+# 2. License isn't copyleft
+# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
+default = "deny"
+# The confidence threshold for detecting a license from license text.
+# The higher the value, the more closely the license text must be to the
+# canonical license text of a valid SPDX license file.
+# [possible values: any between 0.0 and 1.0].
+confidence-threshold = 0.8
+# Allow 1 or more licenses on a per-crate basis, so that particular licenses
+# aren't accepted for every possible crate as with the normal allow list
+exceptions = [
+ # Each entry is the crate and version constraint, and its specific allow
+ # list
+ #{ allow = ["Zlib"], name = "adler32", version = "*" },
+]
+
+# Some crates don't have (easily) machine readable licensing information,
+# adding a clarification entry for it allows you to manually specify the
+# licensing information
+#[[licenses.clarify]]
+# The name of the crate the clarification applies to
+#name = "ring"
+# The optional version constraint for the crate
+#version = "*"
+# The SPDX expression for the license requirements of the crate
+#expression = "MIT AND ISC AND OpenSSL"
+# One or more files in the crate's source used as the "source of truth" for
+# the license expression. If the contents match, the clarification will be used
+# when running the license check, otherwise the clarification will be ignored
+# and the crate will be checked normally, which may produce warnings or errors
+# depending on the rest of your configuration
+#license-files = [
+ # Each entry is a crate relative path, and the (opaque) hash of its contents
+ #{ path = "LICENSE", hash = 0xbd0eed23 }
+#]
+
+[[licenses.clarify]]
+name = "ring"
+version = "*"
+expression = "MIT AND ISC AND OpenSSL"
+license-files = [
+ # Each entry is a crate relative path, and the (opaque) hash of its contents
+ { path = "LICENSE", hash = 0xbd0eed23 }
+]
+
+[licenses.private]
+# If true, ignores workspace crates that aren't published, or are only
+# published to private registries.
+# To see how to mark a crate as unpublished (to the official registry),
+# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
+ignore = true
+# One or more private registries that you might publish crates to, if a crate
+# is only published to private registries, and ignore is true, the crate will
+# not have its license(s) checked
+registries = [
+ #"https://sekretz.com/registry
+]
+
+# This section is considered when running `cargo deny check bans`.
+# More documentation about the 'bans' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
+[bans]
+# Lint level for when multiple versions of the same crate are detected
+multiple-versions = "allow"
+# Lint level for when a crate version requirement is `*`
+wildcards = "allow"
+# The graph highlighting used when creating dotgraphs for crates
+# with multiple versions
+# * lowest-version - The path to the lowest versioned duplicate is highlighted
+# * simplest-path - The path to the version with the fewest edges is highlighted
+# * all - Both lowest-version and simplest-path are used
+highlight = "all"
+# List of crates that are allowed. Use with care!
+allow = [
+ #{ name = "ansi_term", version = "=0.11.0" },
+]
+# List of crates to deny
+deny = [
+ # Each entry the name of a crate and a version range. If version is
+ # not specified, all versions will be matched.
+ #{ name = "ansi_term", version = "=0.11.0" },
+ #
+ # Wrapper crates can optionally be specified to allow the crate when it
+ # is a direct dependency of the otherwise banned crate
+ #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
+]
+# Certain crates/versions that will be skipped when doing duplicate detection.
+skip = [
+ #{ name = "ansi_term", version = "=0.11.0" },
+]
+# Similarly to `skip` allows you to skip certain crates during duplicate
+# detection. Unlike skip, it also includes the entire tree of transitive
+# dependencies starting at the specified crate, up to a certain depth, which is
+# by default infinite
+skip-tree = [
+ #{ name = "ansi_term", version = "=0.11.0", depth = 20 },
+]
+
+# This section is considered when running `cargo deny check sources`.
+# More documentation about the 'sources' section can be found here:
+# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
+[sources]
+# Lint level for what to happen when a crate from a crate registry that is not
+# in the allow list is encountered
+unknown-registry = "warn"
+# Lint level for what to happen when a crate from a git repository that is not
+# in the allow list is encountered
+unknown-git = "warn"
+# List of URLs for allowed crate registries. Defaults to the crates.io index
+# if not specified. If it is specified but empty, no registries are allowed.
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+# List of URLs for allowed Git repositories
+allow-git = []
\ No newline at end of file
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/platform/Cargo.toml
similarity index 82%
copy from nearby/crypto/bssl-crypto/Cargo.toml
copy to remoteauth/platform/Cargo.toml
index bfe3964..0136c27 100644
--- a/nearby/crypto/bssl-crypto/Cargo.toml
+++ b/remoteauth/platform/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "bssl-crypto"
+name = "platform"
version.workspace = true
edition.workspace = true
publish.workspace = true
@@ -7,3 +7,4 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow.workspace = true
diff --git a/remoteauth/platform/src/lib.rs b/remoteauth/platform/src/lib.rs
new file mode 100644
index 0000000..798c756
--- /dev/null
+++ b/remoteauth/platform/src/lib.rs
@@ -0,0 +1,29 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod listeners;
+
+use crate::listeners::SendRequestListener;
+
+/// # RemoteAuth Platform
+/// This trait represents the capabilities that RemoteAuth requires from the platform.
+pub trait Platform {
+ /// Send a binary message to the remote with the given connection id and return the response.
+ fn send_request(
+ &self,
+ connection_id: i32,
+ request: &[u8],
+ listener: Box<dyn SendRequestListener + Send>,
+ );
+}
diff --git a/nearby/crypto/bssl-crypto/src/lib.rs b/remoteauth/platform/src/listeners.rs
similarity index 71%
copy from nearby/crypto/bssl-crypto/src/lib.rs
copy to remoteauth/platform/src/listeners.rs
index 89e6968..e354fea 100644
--- a/nearby/crypto/bssl-crypto/src/lib.rs
+++ b/remoteauth/platform/src/listeners.rs
@@ -12,5 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Placeholder crate to satisfy cargo. If actually using boring ssl, please run the
-//! `build-boringssl` subcommand of the top level crate.
+/// SendRequestListener handles the result or an error from calling send_request in the Platform.
+pub trait SendRequestListener {
+ fn on_response(&mut self, response: Vec<u8>);
+ fn on_error(&mut self, error_code: i32);
+}
diff --git a/nearby/crypto/bssl-crypto/Cargo.toml b/remoteauth/remote_auth_protocol/Cargo.toml
similarity index 78%
copy from nearby/crypto/bssl-crypto/Cargo.toml
copy to remoteauth/remote_auth_protocol/Cargo.toml
index bfe3964..e9fd6d2 100644
--- a/nearby/crypto/bssl-crypto/Cargo.toml
+++ b/remoteauth/remote_auth_protocol/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "bssl-crypto"
+name = "remote_auth_protocol"
version.workspace = true
edition.workspace = true
publish.workspace = true
@@ -7,3 +7,4 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow.workspace = true
diff --git a/remoteauth/remote_auth_protocol/src/lib.rs b/remoteauth/remote_auth_protocol/src/lib.rs
new file mode 100644
index 0000000..bc8315b
--- /dev/null
+++ b/remoteauth/remote_auth_protocol/src/lib.rs
@@ -0,0 +1,42 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub mod remote_auth_service;
+
+/// Struct representing the remote device.
+pub struct RemoteDevice {
+ pub id: i32,
+}
+
+/// Trait to be implemented by anything that wants to be notified when remote devices are discovered
+/// or lost.
+pub trait DeviceDiscoveryListener {
+ fn on_discovered(&mut self, remote_device: &RemoteDevice);
+ fn on_lost(&mut self, remote_device: &RemoteDevice);
+ fn on_timeout(&mut self);
+}
+
+/// Trait to be implemented by the struct responsible for scanning for remote devices.
+pub trait DiscoveryPublisher<'a, T: DeviceDiscoveryListener> {
+ /// Add a listener to the list and return a handle to that listener so it can be removed later.
+ fn add_listener(&mut self, listener: &'a mut T) -> i32;
+ /// Remove the listener with the given handle.
+ fn remove_listener(&mut self, listener_handle: i32);
+ /// Notify all listeners that a remote device has been discovered.
+ fn device_discovered(&mut self, remote_device: &RemoteDevice);
+ /// Notify all listeners that a previously discovered remote device has been lost.
+ fn device_lost(&mut self, remote_device: &RemoteDevice);
+ /// Notify all listeners that timeout has occurred.
+ fn timed_out(&mut self);
+}
diff --git a/remoteauth/remote_auth_protocol/src/remote_auth_service.rs b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs
new file mode 100644
index 0000000..5da33c7
--- /dev/null
+++ b/remoteauth/remote_auth_protocol/src/remote_auth_service.rs
@@ -0,0 +1,255 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{DeviceDiscoveryListener, DiscoveryPublisher, RemoteDevice};
+
+pub struct RemoteAuthService<'a, T: DeviceDiscoveryListener> {
+ listeners: Vec<&'a mut T>,
+}
+
+impl<'a, T: DeviceDiscoveryListener + PartialEq> RemoteAuthService<'a, T> {
+ pub fn new() -> RemoteAuthService<'a, T> {
+ RemoteAuthService {
+ listeners: Vec::new(),
+ }
+ }
+
+ pub fn is_remote_auth_supported() -> bool {
+ true
+ }
+}
+
+impl<'a, T: DeviceDiscoveryListener + PartialEq> DiscoveryPublisher<'a, T>
+ for RemoteAuthService<'a, T>
+{
+ fn add_listener(&mut self, listener: &'a mut T) -> i32 {
+ self.listeners.push(listener);
+ (self.listeners.len() - 1) as i32
+ }
+
+ fn remove_listener(&mut self, listener_id: i32) {
+ let listener_index = listener_id as usize;
+ if self.listeners.len() > listener_index {
+ self.listeners.remove(listener_index);
+ }
+ }
+
+ fn device_discovered(&mut self, remote_device: &RemoteDevice) {
+ for listener in self.listeners.iter_mut() {
+ listener.on_discovered(remote_device);
+ }
+ }
+
+ fn device_lost(&mut self, remote_device: &RemoteDevice) {
+ for listener in self.listeners.iter_mut() {
+ listener.on_lost(remote_device);
+ }
+ }
+
+ fn timed_out(&mut self) {
+ for listener in self.listeners.iter_mut() {
+ listener.on_timeout();
+ }
+ }
+}
+
+impl<'a, T: DeviceDiscoveryListener + PartialEq> Default for RemoteAuthService<'a, T> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[derive(PartialEq)]
+ struct TestListener {
+ pub remote_ids: Vec<i32>,
+ pub got_timeout: bool,
+ }
+
+ impl TestListener {
+ pub fn new() -> TestListener {
+ TestListener {
+ remote_ids: vec![],
+ got_timeout: false,
+ }
+ }
+ }
+
+ impl DeviceDiscoveryListener for TestListener {
+ fn on_discovered(&mut self, remote_device: &RemoteDevice) {
+ self.remote_ids.push(remote_device.id);
+ }
+
+ fn on_lost(&mut self, remote_device: &RemoteDevice) {
+ if let Some(index) = self.remote_ids.iter().position(|i| *i == remote_device.id) {
+ self.remote_ids.remove(index);
+ }
+ }
+
+ fn on_timeout(&mut self) {
+ self.got_timeout = true;
+ }
+ }
+
+ #[test]
+ fn single_listener_discover_notification() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ ras.add_listener(&mut listener);
+ ras.device_discovered(&remote_device);
+
+ assert_eq!(listener.remote_ids.len(), 1);
+ assert_eq!(listener.remote_ids[0], 42);
+ }
+
+ #[test]
+ fn all_listeners_discover_notification() {
+ let mut listener1 = TestListener::new();
+ let mut listener2 = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ ras.add_listener(&mut listener1);
+ ras.add_listener(&mut listener2);
+ ras.device_discovered(&remote_device);
+
+ assert_eq!(listener1.remote_ids.len(), 1);
+ assert_eq!(listener2.remote_ids.len(), 1);
+ assert_eq!(listener1.remote_ids[0], 42);
+ assert_eq!(listener2.remote_ids[0], 42);
+ }
+
+ #[test]
+ fn listeners_discover_notification_for_all_devices() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device1 = RemoteDevice { id: 42 };
+ let remote_device2 = RemoteDevice { id: 13 };
+
+ ras.add_listener(&mut listener);
+ ras.device_discovered(&remote_device1);
+ ras.device_discovered(&remote_device2);
+
+ assert_eq!(listener.remote_ids.len(), 2);
+ assert_eq!(listener.remote_ids[0], 42);
+ assert_eq!(listener.remote_ids[1], 13);
+ }
+
+ #[test]
+ fn single_listener_lost_notification() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ ras.add_listener(&mut listener);
+ ras.device_discovered(&remote_device);
+ ras.device_lost(&remote_device);
+
+ assert_eq!(listener.remote_ids.len(), 0);
+ }
+
+ #[test]
+ fn all_listeners_lost_notification() {
+ let mut listener1 = TestListener::new();
+ let mut listener2 = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ ras.add_listener(&mut listener1);
+ ras.add_listener(&mut listener2);
+ ras.device_discovered(&remote_device);
+ ras.device_lost(&remote_device);
+
+ assert_eq!(listener1.remote_ids.len(), 0);
+ assert_eq!(listener2.remote_ids.len(), 0);
+ }
+
+ #[test]
+ fn listeners_lost_notification_for_all_devices() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device1 = RemoteDevice { id: 42 };
+ let remote_device2 = RemoteDevice { id: 13 };
+ let remote_device3 = RemoteDevice { id: 99 };
+
+ ras.add_listener(&mut listener);
+ ras.device_discovered(&remote_device1);
+ ras.device_discovered(&remote_device2);
+ ras.device_discovered(&remote_device3);
+
+ ras.device_lost(&remote_device2);
+ ras.device_lost(&remote_device3);
+
+ assert_eq!(listener.remote_ids.len(), 1);
+ assert_eq!(listener.remote_ids[0], 42);
+ }
+
+ #[test]
+ fn single_listener_timeout_notification() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+
+ ras.add_listener(&mut listener);
+ ras.timed_out();
+
+ assert!(listener.got_timeout);
+ }
+
+ #[test]
+ fn all_listeners_timeout_notification() {
+ let mut listener1 = TestListener::new();
+ let mut listener2 = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+
+ ras.add_listener(&mut listener1);
+ ras.add_listener(&mut listener2);
+ ras.timed_out();
+
+ assert!(listener1.got_timeout);
+ assert!(listener2.got_timeout);
+ }
+
+ #[test]
+ fn listener_can_be_removed() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ let id = ras.add_listener(&mut listener);
+ ras.remove_listener(id);
+ ras.device_discovered(&remote_device);
+
+ assert_eq!(listener.remote_ids.len(), 0);
+ }
+
+ #[test]
+ fn listener_can_be_removed_after_notification() {
+ let mut listener = TestListener::new();
+ let mut ras = RemoteAuthService::new();
+ let remote_device = RemoteDevice { id: 42 };
+
+ let id = ras.add_listener(&mut listener);
+ ras.device_discovered(&remote_device);
+ ras.remove_listener(id);
+ ras.device_discovered(&remote_device);
+
+ assert_eq!(listener.remote_ids.len(), 1);
+ assert_eq!(listener.remote_ids[0], 42);
+ }
+}
diff --git a/remoteauth/src/ctap_protocol.rs b/remoteauth/src/ctap_protocol.rs
new file mode 100644
index 0000000..7e35e6c
--- /dev/null
+++ b/remoteauth/src/ctap_protocol.rs
@@ -0,0 +1,29 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use cmd_runner::run_cmd_shell;
+use std::path;
+
+pub(crate) fn check_ctap_protocol(root: &path::Path) -> anyhow::Result<()> {
+ log::info!("Checking CTAP Protocol");
+ let mut ffi_dir = root.to_path_buf();
+ ffi_dir.push("ctap_protocol");
+
+ run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?;
+ run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?;
+ run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?;
+ run_cmd_shell(&ffi_dir, "cargo deny check")?;
+
+ Ok(())
+}
diff --git a/remoteauth/src/main.rs b/remoteauth/src/main.rs
new file mode 100644
index 0000000..e0851eb
--- /dev/null
+++ b/remoteauth/src/main.rs
@@ -0,0 +1,103 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+extern crate core;
+
+use clap::Parser as _;
+use cmd_runner::run_cmd_shell;
+use env_logger::Env;
+use std::{env, path};
+
+mod ctap_protocol;
+mod platform;
+mod remote_auth_protocol;
+
+fn main() -> anyhow::Result<()> {
+ env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
+ let cli: Cli = Cli::parse();
+
+ let root_dir: path::PathBuf = env::var("CARGO_MANIFEST_DIR")
+ .expect("Must be run via Cargo to establish root directory")
+ .into();
+
+ match cli.subcommand {
+ Subcommand::CheckEverything(ref options) => check_everything(&root_dir, options)?,
+ Subcommand::CheckWorkspace(ref options) => check_workspace(&root_dir, options)?,
+ Subcommand::CheckCtapProtocol => ctap_protocol::check_ctap_protocol(&root_dir)?,
+ Subcommand::CheckPlatform => platform::check_platform(&root_dir)?,
+ Subcommand::CheckRemoteAuthProtocol => {
+ remote_auth_protocol::check_remote_auth_protocol(&root_dir)?
+ }
+ }
+
+ Ok(())
+}
+
+pub fn check_everything(root: &path::Path, check_options: &CheckOptions) -> anyhow::Result<()> {
+ check_workspace(root, check_options)?;
+ ctap_protocol::check_ctap_protocol(root)?;
+ platform::check_platform(root)?;
+ remote_auth_protocol::check_remote_auth_protocol(root)?;
+ Ok(())
+}
+
+pub fn check_workspace(root: &path::Path, options: &CheckOptions) -> anyhow::Result<()> {
+ log::info!("Running cargo checks on workspace");
+
+ let fmt_command = if options.reformat {
+ "cargo fmt"
+ } else {
+ "cargo fmt --check"
+ };
+
+ for cargo_cmd in [
+ fmt_command,
+ "cargo check --workspace --all-targets --quiet",
+ "cargo test --workspace --quiet -- --color=always",
+ "cargo doc --quiet --no-deps",
+ "cargo deny --workspace check",
+ "cargo clippy --all-targets --workspace -- --deny warnings",
+ ] {
+ run_cmd_shell(root, cargo_cmd)?;
+ }
+
+ Ok(())
+}
+
+#[derive(clap::Parser)]
+struct Cli {
+ #[clap(subcommand)]
+ subcommand: Subcommand,
+}
+
+#[derive(clap::Subcommand, Debug, Clone)]
+#[allow(clippy::enum_variant_names)]
+enum Subcommand {
+ /// Checks everything in remoteauth
+ CheckEverything(CheckOptions),
+ /// Checks everything included in the top level workspace
+ CheckWorkspace(CheckOptions),
+ /// Build and run tests for the CTAP Protocol
+ CheckCtapProtocol,
+ /// Builds and run tests for the Platform
+ CheckPlatform,
+ /// Builds and run tests for the Remote Auth Protocol
+ CheckRemoteAuthProtocol,
+}
+
+#[derive(clap::Args, Debug, Clone, Default)]
+pub struct CheckOptions {
+ #[arg(long, help = "reformat files with cargo fmt")]
+ reformat: bool,
+}
diff --git a/remoteauth/src/platform.rs b/remoteauth/src/platform.rs
new file mode 100644
index 0000000..481b4f7
--- /dev/null
+++ b/remoteauth/src/platform.rs
@@ -0,0 +1,29 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use cmd_runner::run_cmd_shell;
+use std::path;
+
+pub(crate) fn check_platform(root: &path::Path) -> anyhow::Result<()> {
+ log::info!("Checking Platform");
+ let mut ffi_dir = root.to_path_buf();
+ ffi_dir.push("platform");
+
+ run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?;
+ run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?;
+ run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?;
+ run_cmd_shell(&ffi_dir, "cargo deny check")?;
+
+ Ok(())
+}
diff --git a/remoteauth/src/remote_auth_protocol.rs b/remoteauth/src/remote_auth_protocol.rs
new file mode 100644
index 0000000..e47517d
--- /dev/null
+++ b/remoteauth/src/remote_auth_protocol.rs
@@ -0,0 +1,29 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use cmd_runner::run_cmd_shell;
+use std::path;
+
+pub(crate) fn check_remote_auth_protocol(root: &path::Path) -> anyhow::Result<()> {
+ log::info!("Checking Remote Auth Protocol");
+ let mut ffi_dir = root.to_path_buf();
+ ffi_dir.push("remote_auth_protocol");
+
+ run_cmd_shell(&ffi_dir, "cargo build --quiet --release --lib")?;
+ run_cmd_shell(&ffi_dir, "cargo test --quiet -- --color=always")?;
+ run_cmd_shell(&ffi_dir, "cargo doc --quiet --no-deps")?;
+ run_cmd_shell(&ffi_dir, "cargo deny check")?;
+
+ Ok(())
+}