mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-11 15:20:29 -08:00
Compare commits
22 Commits
dev/add-de
...
dev/karo/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87cbd1e8ea | ||
|
|
d3834d411c | ||
|
|
acece7d5b4 | ||
|
|
2fc18ebdf1 | ||
|
|
9658687989 | ||
|
|
f2b1e3cdb4 | ||
|
|
031f1b1f66 | ||
|
|
70cc51ed7a | ||
|
|
cdcdb502fd | ||
|
|
ace2e82e2a | ||
|
|
594786c294 | ||
|
|
5d4ecd7210 | ||
|
|
5689990f0d | ||
|
|
ec2c30f85c | ||
|
|
6e1c1df1d9 | ||
|
|
f94ba3b416 | ||
|
|
5a2202d00d | ||
|
|
448ce6b395 | ||
|
|
6c04070184 | ||
|
|
5bfa02e7f6 | ||
|
|
7deab6bbd7 | ||
|
|
6b33848426 |
31
.github/workflows/qc.yaml
vendored
31
.github/workflows/qc.yaml
vendored
@@ -117,34 +117,3 @@ jobs:
|
||||
name: rosenpass
|
||||
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
- run: nix develop --command cargo test
|
||||
|
||||
cargo-fuzz:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install -y libsodium-dev
|
||||
- name: Install nightly toolchain
|
||||
run: |
|
||||
rustup toolchain install nightly
|
||||
rustup default nightly
|
||||
- name: Install cargo-fuzz
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Run fuzzing
|
||||
run: |
|
||||
cargo fuzz run fuzz_aead_enc_into -- -max_total_time=5
|
||||
cargo fuzz run fuzz_blake2b -- -max_total_time=5
|
||||
cargo fuzz run fuzz_handle_msg -- -max_total_time=5
|
||||
ulimit -s 8192000 && RUST_MIN_STACK=33554432000 && cargo fuzz run fuzz_kyber_encaps -- -max_total_time=5
|
||||
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=5
|
||||
cargo fuzz run fuzz_box_sodium_alloc -- -max_total_time=5
|
||||
cargo fuzz run fuzz_vec_sodium_alloc -- -max_total_time=5
|
||||
|
||||
398
Cargo.lock
generated
398
Cargo.lock
generated
@@ -44,12 +44,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
@@ -91,7 +85,7 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -101,7 +95,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -113,15 +107,6 @@ dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@@ -162,11 +147,11 @@ checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.65.1"
|
||||
version = "0.68.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
|
||||
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
@@ -228,7 +213,6 @@ version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -299,9 +283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.10"
|
||||
version = "4.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
|
||||
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -309,9 +293,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.9"
|
||||
version = "4.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
|
||||
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -454,23 +438,6 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7762d17f1241643615821a8455a0b2c3e803784b058693d990b11f2dce25a0ca"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
@@ -498,12 +465,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -515,7 +482,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -530,9 +497,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
@@ -583,9 +550,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -614,7 +581,7 @@ version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -625,9 +592,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
@@ -650,7 +617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
"hashbrown 0.14.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -661,7 +628,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -679,15 +646,6 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.65"
|
||||
@@ -739,17 +697,6 @@ dependencies = [
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@@ -762,9 +709,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsodium-sys-stable"
|
||||
version = "1.20.4"
|
||||
version = "1.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1d164bc6f9139c5f95efb4f0be931b2bd5a9edf7e4e3c945d26b95ab8fa669b"
|
||||
checksum = "cfc31f983531631496f4e621110cd81468ab78b65dee0046cfddea83caa2c327"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -779,9 +726,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -834,7 +781,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -880,13 +827,13 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
[[package]]
|
||||
name = "oqs-sys"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fa114149fb6e5362b9cf539de9305a6a3cf1556fbfa93b5053b4f70cf3adfb9"
|
||||
source = "git+https://github.com/koraa/liboqs-rust.git?branch=main#046572faebcbf03c7529bf701d332b3851d5aa79"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"build-deps",
|
||||
"cmake",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -909,9 +856,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
@@ -947,12 +894,6 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.15"
|
||||
@@ -965,9 +906,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -990,36 +931,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
@@ -1080,16 +991,16 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.6"
|
||||
version = "0.17.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866"
|
||||
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1100,26 +1011,20 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass"
|
||||
version = "0.2.1"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.10",
|
||||
"base64",
|
||||
"clap 4.4.8",
|
||||
"criterion",
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
"libsodium-sys-stable",
|
||||
"log",
|
||||
"memoffset",
|
||||
"mio",
|
||||
"oqs-sys",
|
||||
"paste",
|
||||
"rand",
|
||||
"rosenpass-cipher-traits",
|
||||
"rosenpass-ciphers",
|
||||
"rosenpass-constant-time",
|
||||
"rosenpass-lenses",
|
||||
"rosenpass-secret-memory",
|
||||
"rosenpass-sodium",
|
||||
"rosenpass-to",
|
||||
"rosenpass-util",
|
||||
"serde",
|
||||
"stacker",
|
||||
"static_assertions",
|
||||
@@ -1128,105 +1033,6 @@ dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-cipher-traits"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-ciphers"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rosenpass-constant-time",
|
||||
"rosenpass-oqs",
|
||||
"rosenpass-secret-memory",
|
||||
"rosenpass-sodium",
|
||||
"rosenpass-to",
|
||||
"static_assertions",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-constant-time"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rosenpass-to",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-fuzzing"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"libfuzzer-sys",
|
||||
"rosenpass",
|
||||
"rosenpass-cipher-traits",
|
||||
"rosenpass-ciphers",
|
||||
"rosenpass-secret-memory",
|
||||
"rosenpass-sodium",
|
||||
"rosenpass-to",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-lenses"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-oqs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"oqs-sys",
|
||||
"paste",
|
||||
"rosenpass-cipher-traits",
|
||||
"rosenpass-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-secret-memory"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
"libsodium-sys-stable",
|
||||
"rand",
|
||||
"rosenpass-sodium",
|
||||
"rosenpass-to",
|
||||
"rosenpass-util",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-sodium"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"anyhow",
|
||||
"libsodium-sys-stable",
|
||||
"log",
|
||||
"rosenpass-to",
|
||||
"rosenpass-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-to"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rosenpass-util"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
@@ -1241,22 +1047,22 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.26"
|
||||
version = "0.38.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.9"
|
||||
version = "0.21.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
|
||||
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
@@ -1307,18 +1113,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1406,9 +1212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -1533,9 +1339,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.9.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
@@ -1548,9 +1354,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -1657,9 +1463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.3"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
@@ -1710,16 +1516,7 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1728,28 +1525,13 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
"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]]
|
||||
@@ -1758,84 +1540,42 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[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_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[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_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.19"
|
||||
@@ -1874,12 +1614,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
|
||||
54
Cargo.toml
54
Cargo.toml
@@ -3,58 +3,6 @@ resolver = "2"
|
||||
|
||||
members = [
|
||||
"rosenpass",
|
||||
"cipher-traits",
|
||||
"ciphers",
|
||||
"util",
|
||||
"constant-time",
|
||||
"sodium",
|
||||
"oqs",
|
||||
"to",
|
||||
"fuzz",
|
||||
"secret-memory",
|
||||
"lenses",
|
||||
"kmac-test-vectors",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"rosenpass"
|
||||
]
|
||||
|
||||
[workspace.metadata.release]
|
||||
# ensure that adding `--package` as argument to `cargo release` still creates version tags in the form of `vx.y.z`
|
||||
tag-prefix = ""
|
||||
|
||||
[workspace.dependencies]
|
||||
rosenpass = { path = "rosenpass" }
|
||||
rosenpass-util = { path = "util" }
|
||||
rosenpass-constant-time = { path = "constant-time" }
|
||||
rosenpass-sodium = { path = "sodium" }
|
||||
rosenpass-cipher-traits = { path = "cipher-traits" }
|
||||
rosenpass-ciphers = { path = "ciphers" }
|
||||
rosenpass-to = { path = "to" }
|
||||
rosenpass-secret-memory = { path = "secret-memory" }
|
||||
rosenpass-oqs = { path = "oqs" }
|
||||
rosenpass-lenses = { path = "lenses" }
|
||||
criterion = "0.4.0"
|
||||
test_bin = "0.4.0"
|
||||
libfuzzer-sys = "0.4"
|
||||
stacker = "0.1.15"
|
||||
doc-comment = "0.3.3"
|
||||
base64 = "0.21.5"
|
||||
zeroize = "1.7.0"
|
||||
memoffset = "0.9.0"
|
||||
lazy_static = "1.4.0"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
env_logger = "0.10.1"
|
||||
toml = "0.7.8"
|
||||
static_assertions = "1.1.0"
|
||||
allocator-api2 = "0.2.16"
|
||||
rand = "0.8.5"
|
||||
log = { version = "0.4.20" }
|
||||
clap = { version = "4.4.10", features = ["derive"] }
|
||||
serde = { version = "1.0.193", features = ["derive"] }
|
||||
arbitrary = { version = "1.3.2", features = ["derive"] }
|
||||
anyhow = { version = "1.0.75", features = ["backtrace", "std"] }
|
||||
mio = { version = "0.8.9", features = ["net", "os-poll"] }
|
||||
libsodium-sys-stable= { version = "1.20.4", features = ["use-pkg-config"] }
|
||||
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-cipher-traits"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal traits for cryptographic primitives"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass internal libsodium bindings
|
||||
|
||||
Rosenpass internal library providing traits for cryptographic primitives.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,47 +0,0 @@
|
||||
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
|
||||
//!
|
||||
//! KEMs are the interface provided by almost all post-quantum
|
||||
//! secure key exchange mechanisms.
|
||||
//!
|
||||
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
|
||||
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
|
||||
//!
|
||||
//! encapsulation.
|
||||
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
|
||||
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
|
||||
|
||||
use std::result::Result;
|
||||
|
||||
/// Key Encapsulation Mechanism
|
||||
///
|
||||
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||
/// decapsulation.
|
||||
pub trait Kem {
|
||||
type Error;
|
||||
|
||||
/// Secrete Key length
|
||||
const SK_LEN: usize;
|
||||
/// Public Key length
|
||||
const PK_LEN: usize;
|
||||
/// Ciphertext length
|
||||
const CT_LEN: usize;
|
||||
/// Shared Secret length
|
||||
const SHK_LEN: usize;
|
||||
|
||||
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
|
||||
///
|
||||
/// `keygen() -> sk, pk`
|
||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error>;
|
||||
|
||||
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
|
||||
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
|
||||
///
|
||||
/// `encaps(pk) -> shk, ct`
|
||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error>;
|
||||
|
||||
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
|
||||
/// (`shk`)
|
||||
///
|
||||
/// `decaps(sk, ct) -> shk`
|
||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
mod kem;
|
||||
pub use kem::Kem;
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-ciphers"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal ciphers and other cryptographic primitives used by rosenpass."
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
rosenpass-sodium = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
rosenpass-constant-time = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-oqs = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass internal cryptographic primitives
|
||||
|
||||
Ciphers and other cryptographic primitives used by rosenpass.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,109 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_to::To;
|
||||
|
||||
use crate::subtle::incorrect_hmac_blake2b as hash;
|
||||
|
||||
pub use hash::KEY_LEN;
|
||||
|
||||
// TODO Use a proper Dec interface
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashDomain([u8; KEY_LEN]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashDomainNamespace([u8; KEY_LEN]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretHashDomain(Secret<KEY_LEN>);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
|
||||
|
||||
impl HashDomain {
|
||||
pub fn zero() -> Self {
|
||||
Self([0u8; KEY_LEN])
|
||||
}
|
||||
|
||||
pub fn dup(self) -> HashDomainNamespace {
|
||||
HashDomainNamespace(self.0)
|
||||
}
|
||||
|
||||
pub fn turn_secret(self) -> SecretHashDomain {
|
||||
SecretHashDomain(Secret::from_slice(&self.0))
|
||||
}
|
||||
|
||||
// TODO: Protocol! Use domain separation to ensure that
|
||||
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
||||
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
||||
}
|
||||
|
||||
pub fn into_value(self) -> [u8; KEY_LEN] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HashDomainNamespace {
|
||||
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
|
||||
Ok(HashDomain(
|
||||
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(&self.0, v.secret())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretHashDomain {
|
||||
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
|
||||
let mut r = SecretHashDomain(Secret::zero());
|
||||
hash::hash(k, d).to(r.0.secret_mut())?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self(Secret::zero())
|
||||
}
|
||||
|
||||
pub fn dup(self) -> SecretHashDomainNamespace {
|
||||
SecretHashDomainNamespace(self.0)
|
||||
}
|
||||
|
||||
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
|
||||
Self(k)
|
||||
}
|
||||
|
||||
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||
Self::invoke_primitive(self.0.secret(), v)
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
Self::invoke_primitive(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
pub fn into_secret(self) -> Secret<KEY_LEN> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
|
||||
hash::hash(v, dst).to(self.0.secret_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretHashDomainNamespace {
|
||||
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(self.0.secret(), v)
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
|
||||
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
// TODO: This entire API is not very nice; we need this for biscuits, but
|
||||
// it might be better to extract a special "biscuit"
|
||||
// labeled subkey and reinitialize the chain with this
|
||||
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use static_assertions::const_assert;
|
||||
|
||||
pub mod subtle;
|
||||
|
||||
pub const KEY_LEN: usize = 32;
|
||||
const_assert!(KEY_LEN == aead::KEY_LEN);
|
||||
const_assert!(KEY_LEN == xaead::KEY_LEN);
|
||||
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||
|
||||
/// Authenticated encryption with associated data
|
||||
pub mod aead {
|
||||
pub use rosenpass_sodium::aead::chacha20poly1305_ietf::{
|
||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||
};
|
||||
}
|
||||
|
||||
/// Authenticated encryption with associated data with a constant nonce
|
||||
pub mod xaead {
|
||||
pub use rosenpass_sodium::aead::xchacha20poly1305_ietf::{
|
||||
decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod hash_domain;
|
||||
|
||||
pub mod kem {
|
||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
use anyhow::ensure;
|
||||
use rosenpass_constant_time::xor;
|
||||
use rosenpass_sodium::hash::blake2b;
|
||||
use rosenpass_to::{ops::copy_slice, with_destination, To};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub const KEY_LEN: usize = 32;
|
||||
pub const KEY_MIN: usize = KEY_LEN;
|
||||
pub const KEY_MAX: usize = KEY_LEN;
|
||||
pub const OUT_MIN: usize = blake2b::OUT_MIN;
|
||||
pub const OUT_MAX: usize = blake2b::OUT_MAX;
|
||||
|
||||
/// This is a woefully incorrect implementation of hmac_blake2b.
|
||||
/// See <https://github.com/rosenpass/rosenpass/issues/68#issuecomment-1563612222>
|
||||
///
|
||||
/// It accepts 32 byte keys, exclusively.
|
||||
///
|
||||
/// This will be replaced, likely by Kekkac at some point soon.
|
||||
/// <https://github.com/rosenpass/rosenpass/pull/145>
|
||||
#[inline]
|
||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
||||
const IPAD: [u8; KEY_LEN] = [0x36u8; KEY_LEN];
|
||||
const OPAD: [u8; KEY_LEN] = [0x5Cu8; KEY_LEN];
|
||||
|
||||
with_destination(|out: &mut [u8]| {
|
||||
// Not bothering with padding; the implementation
|
||||
// uses appropriately sized keys.
|
||||
ensure!(key.len() == KEY_LEN);
|
||||
|
||||
type Key = Zeroizing<[u8; KEY_LEN]>;
|
||||
let mut tmp_key = Key::default();
|
||||
|
||||
copy_slice(key).to(tmp_key.as_mut());
|
||||
xor(&IPAD).to(tmp_key.as_mut());
|
||||
let mut outer_data = Key::default();
|
||||
blake2b::hash(tmp_key.as_ref(), data).to(outer_data.as_mut())?;
|
||||
|
||||
copy_slice(key).to(tmp_key.as_mut());
|
||||
xor(&OPAD).to(tmp_key.as_mut());
|
||||
blake2b::hash(tmp_key.as_ref(), outer_data.as_ref()).to(out)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod incorrect_hmac_blake2b;
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-constant-time"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal utilities for constant time crypto implementations"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rosenpass-to = { workspace = true }
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass constant time library
|
||||
|
||||
Rosenpass internal library providing basic constant-time operations.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,26 +0,0 @@
|
||||
use rosenpass_to::{with_destination, To};
|
||||
|
||||
/// Xors the source into the destination
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass_constant_time::xor;
|
||||
/// use rosenpass_to::To;
|
||||
/// assert_eq!(
|
||||
/// xor(b"world").to_this(|| b"hello".to_vec()),
|
||||
/// b"\x1f\n\x1e\x00\x0b");
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If source and destination are of different sizes.
|
||||
#[inline]
|
||||
pub fn xor<'a>(src: &'a [u8]) -> impl To<[u8], ()> + 'a {
|
||||
with_destination(|dst: &mut [u8]| {
|
||||
assert!(src.len() == dst.len());
|
||||
for (dv, sv) in dst.iter_mut().zip(src.iter()) {
|
||||
*dv ^= *sv;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -91,18 +91,9 @@ This makes it possible to add peers entirely from
|
||||
.Sh SEE ALSO
|
||||
.Xr rp 1 ,
|
||||
.Xr wg 1
|
||||
.Rs
|
||||
.%A Karolin Varner
|
||||
.%A Benjamin Lipp
|
||||
.%A Wanja Zaeske
|
||||
.%A Lisa Schmidt
|
||||
.%D 2023
|
||||
.%T Rosenpass
|
||||
.%U https://rosenpass.eu/whitepaper.pdf
|
||||
.Re
|
||||
.Sh STANDARDS
|
||||
This tool is the reference implementation of the Rosenpass protocol, as
|
||||
specified within the whitepaper referenced above.
|
||||
This tool is the reference implementation of the Rosenpass protocol, written
|
||||
by Karolin Varner, Benjamin Lipp, Wanja Zaeske, and Lisa Schmidt.
|
||||
.Sh AUTHORS
|
||||
Rosenpass was created by Karolin Varner, Benjamin Lipp, Wanja Zaeske,
|
||||
Marei Peischl, Stephan Ajuvo, and Lisa Schmidt.
|
||||
|
||||
60
flake.nix
60
flake.nix
@@ -29,7 +29,6 @@
|
||||
]
|
||||
(system:
|
||||
let
|
||||
scoped = (scope: scope.result);
|
||||
lib = nixpkgs.lib;
|
||||
|
||||
# normal nixpkgs
|
||||
@@ -59,35 +58,11 @@
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./rosenpass/Cargo.toml);
|
||||
|
||||
# source files relevant for rust
|
||||
src = scoped rec {
|
||||
# File suffices to include
|
||||
extensions = [
|
||||
"lock"
|
||||
"rs"
|
||||
"toml"
|
||||
];
|
||||
# Files to explicitly include
|
||||
files = [
|
||||
"to/README.md"
|
||||
];
|
||||
|
||||
src = ./.;
|
||||
filter = (path: type: scoped rec {
|
||||
inherit (lib) any id removePrefix hasSuffix;
|
||||
anyof = (any id);
|
||||
|
||||
basename = baseNameOf (toString path);
|
||||
relative = removePrefix (toString src + "/") (toString path);
|
||||
|
||||
result = anyof [
|
||||
(type == "directory")
|
||||
(any (ext: hasSuffix ".${ext}" basename) extensions)
|
||||
(any (file: file == relative) files)
|
||||
];
|
||||
});
|
||||
|
||||
result = pkgs.lib.sources.cleanSourceWith { inherit src filter; };
|
||||
};
|
||||
src = pkgs.lib.sources.sourceFilesBySuffices ./. [
|
||||
".lock"
|
||||
".rs"
|
||||
".toml"
|
||||
];
|
||||
|
||||
# builds a bin path for all dependencies for the `rp` shellscript
|
||||
rpBinPath = p: with p; lib.makeBinPath [
|
||||
@@ -264,7 +239,6 @@
|
||||
inherit system;
|
||||
};
|
||||
packages = self.packages.${system};
|
||||
devShells = self.devShells.${system};
|
||||
in
|
||||
{
|
||||
#
|
||||
@@ -326,28 +300,6 @@
|
||||
'';
|
||||
};
|
||||
|
||||
#
|
||||
### A DevContainer attempt
|
||||
#
|
||||
packages.dev-container = pkgs.dockerTools.buildImage rec {
|
||||
name = "rosenpass-dev-container";
|
||||
tag = "latest";
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gnutar
|
||||
gzip
|
||||
openssh
|
||||
stdenv.cc
|
||||
]; #++ lib.lists.filter (p: builtins.hasAttr "version" p)
|
||||
#devShells.default.nativeBuildInputs;
|
||||
pathsToLink = [ "/bin" "/lib" ];
|
||||
};
|
||||
config.Cmd = [ "/bin/bash" ];
|
||||
};
|
||||
|
||||
#
|
||||
### Devshells ###
|
||||
@@ -373,7 +325,7 @@
|
||||
checks = {
|
||||
cargo-fmt = pkgs.runCommand "check-cargo-fmt"
|
||||
{ inherit (self.devShells.${system}.default) nativeBuildInputs buildInputs; } ''
|
||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check --all && touch $out
|
||||
cargo fmt --manifest-path=${./.}/Cargo.toml --check && touch $out
|
||||
'';
|
||||
nixpkgs-fmt = pkgs.runCommand "check-nixpkgs-fmt"
|
||||
{ nativeBuildInputs = [ pkgs.nixpkgs-fmt ]; } ''
|
||||
|
||||
4
fuzz/.gitignore
vendored
4
fuzz/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
1286
fuzz/Cargo.lock
generated
1286
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-fuzzing"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
arbitrary = { workspace = true }
|
||||
libfuzzer-sys = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-sodium = { workspace = true }
|
||||
rosenpass-ciphers = { workspace = true }
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
rosenpass = { workspace = true }
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_handle_msg"
|
||||
path = "fuzz_targets/handle_msg.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_blake2b"
|
||||
path = "fuzz_targets/blake2b.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_aead_enc_into"
|
||||
path = "fuzz_targets/aead_enc_into.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_mceliece_encaps"
|
||||
path = "fuzz_targets/mceliece_encaps.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_kyber_encaps"
|
||||
path = "fuzz_targets/kyber_encaps.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_box_sodium_alloc"
|
||||
path = "fuzz_targets/box_sodium_alloc.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_vec_sodium_alloc"
|
||||
path = "fuzz_targets/vec_sodium_alloc.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -1,32 +0,0 @@
|
||||
#![no_main]
|
||||
extern crate arbitrary;
|
||||
extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass_ciphers::aead;
|
||||
use rosenpass_sodium::init as sodium_init;
|
||||
|
||||
#[derive(arbitrary::Arbitrary, Debug)]
|
||||
pub struct Input {
|
||||
pub key: [u8; 32],
|
||||
pub nonce: [u8; 12],
|
||||
pub ad: Box<[u8]>,
|
||||
pub plaintext: Box<[u8]>,
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
sodium_init().unwrap();
|
||||
|
||||
let mut ciphertext: Vec<u8> = Vec::with_capacity(input.plaintext.len() + 16);
|
||||
ciphertext.resize(input.plaintext.len() + 16, 0);
|
||||
|
||||
aead::encrypt(
|
||||
ciphertext.as_mut_slice(),
|
||||
&input.key,
|
||||
&input.nonce,
|
||||
&input.ad,
|
||||
&input.plaintext,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
#![no_main]
|
||||
extern crate arbitrary;
|
||||
extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass_sodium::{hash::blake2b, init as sodium_init};
|
||||
use rosenpass_to::To;
|
||||
|
||||
#[derive(arbitrary::Arbitrary, Debug)]
|
||||
pub struct Blake2b {
|
||||
pub key: [u8; 32],
|
||||
pub data: Box<[u8]>,
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Blake2b| {
|
||||
sodium_init().unwrap();
|
||||
|
||||
let mut out = [0u8; 32];
|
||||
|
||||
blake2b::hash(&input.key, &input.data).to(&mut out).unwrap();
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_sodium::{
|
||||
alloc::{Alloc as SodiumAlloc, Box as SodiumBox},
|
||||
init,
|
||||
};
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let _ = init();
|
||||
let _ = SodiumBox::new_in(data, SodiumAlloc::new());
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
#![no_main]
|
||||
extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass::protocol::CryptoServer;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_sodium::init as sodium_init;
|
||||
|
||||
fuzz_target!(|rx_buf: &[u8]| {
|
||||
sodium_init().unwrap();
|
||||
|
||||
let sk = Secret::from_slice(&[0; 13568]);
|
||||
let pk = Secret::from_slice(&[0; 524160]);
|
||||
|
||||
let mut cs = CryptoServer::new(sk, pk);
|
||||
let mut tx_buf = [0; 10240];
|
||||
|
||||
// We expect errors while fuzzing therefore we do not check the result.
|
||||
let _ = cs.handle_msg(rx_buf, &mut tx_buf);
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
#![no_main]
|
||||
extern crate arbitrary;
|
||||
extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::EphemeralKem;
|
||||
|
||||
#[derive(arbitrary::Arbitrary, Debug)]
|
||||
pub struct Input {
|
||||
pub pk: [u8; 800],
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
let mut ciphertext = [0u8; 768];
|
||||
let mut shared_secret = [0u8; 32];
|
||||
|
||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
#![no_main]
|
||||
extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
|
||||
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
|
||||
let mut ciphertext = [0u8; 188];
|
||||
let mut shared_secret = [0u8; 32];
|
||||
|
||||
// We expect errors while fuzzing therefore we do not check the result.
|
||||
let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rosenpass_sodium::{
|
||||
alloc::{Alloc as SodiumAlloc, Vec as SodiumVec},
|
||||
init,
|
||||
};
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let _ = init();
|
||||
let mut vec = SodiumVec::new_in(SodiumAlloc::new());
|
||||
vec.extend_from_slice(data);
|
||||
});
|
||||
9
kmac-test-vectors/Cargo.toml
Normal file
9
kmac-test-vectors/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "kmac-test-vectors"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
|
||||
[dependencies]
|
||||
xkcp-sys = "0.0.3"
|
||||
2
kmac-test-vectors/src/main.rs
Normal file
2
kmac-test-vectors/src/main.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
fn main() {
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-lenses"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal library for parsing binary data securely"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
paste = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -1,3 +0,0 @@
|
||||
# Rosenpass internal binary parsing library
|
||||
|
||||
This is an internal library; no guarantee is made about its API at this point in time.
|
||||
@@ -1,206 +0,0 @@
|
||||
use std::result::Result;
|
||||
|
||||
/// Common trait shared by all Lenses
|
||||
pub trait LenseView {
|
||||
const LEN: usize;
|
||||
}
|
||||
|
||||
/// Error during lense creation
|
||||
#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
|
||||
pub enum LenseError {
|
||||
#[error("buffer size mismatch")]
|
||||
BufferSizeMismatch,
|
||||
}
|
||||
|
||||
pub type LenseResult<T> = Result<T, LenseError>;
|
||||
|
||||
impl LenseError {
|
||||
pub fn ensure_exact_buffer_size(len: usize, required: usize) -> LenseResult<()> {
|
||||
(len == required)
|
||||
.then_some(())
|
||||
.ok_or(LenseError::BufferSizeMismatch)
|
||||
}
|
||||
|
||||
pub fn ensure_sufficient_buffer_size(len: usize, required: usize) -> LenseResult<()> {
|
||||
(len >= required)
|
||||
.then_some(())
|
||||
.ok_or(LenseError::BufferSizeMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro to create data lenses.
|
||||
#[macro_export]
|
||||
macro_rules! lense(
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn $field(&self) -> &__ContainerType::Output {
|
||||
&self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
/// The bytes until the
|
||||
#[doc = lense!(maybe_docstring_link Self::$field)]
|
||||
/// field
|
||||
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
|
||||
&self.0[0 .. $offset]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// switch that yields literals unchanged, but creates docstring links to
|
||||
// constants
|
||||
// TODO the doc string link doesn't work if $x is taken from a generic,
|
||||
(maybe_docstring_link $x:literal) => (stringify!($x));
|
||||
(maybe_docstring_link $x:expr) => (stringify!([$x]));
|
||||
|
||||
// struct name < optional generics > := optional doc string field name : field length, ...
|
||||
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
/// A data lense to manipulate byte slices.
|
||||
///
|
||||
//// # Fields
|
||||
///
|
||||
$(
|
||||
/// - `
|
||||
#[doc = stringify!($field)]
|
||||
/// `:
|
||||
#[doc = lense!(maybe_docstring_link $len)]
|
||||
/// bytes
|
||||
)+
|
||||
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
|
||||
__ContainerType,
|
||||
// The phantom data is required, since all generics declared on a
|
||||
// type need to be used on the type.
|
||||
// https://doc.rust-lang.org/stable/error_codes/E0392.html
|
||||
$( $( ::core::marker::PhantomData<$generic> ),+ )?
|
||||
);
|
||||
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
$(
|
||||
/// Size in bytes of the field `
|
||||
#[doc = !($field)]
|
||||
/// `
|
||||
pub const fn [< $field _len >]() -> usize{
|
||||
$len
|
||||
}
|
||||
)+
|
||||
|
||||
/// Verify that `len` exactly holds [Self]
|
||||
pub fn check_size(len: usize) -> ::rosenpass_lenses::LenseResult<()> {
|
||||
::rosenpass_lenses::LenseError::ensure_exact_buffer_size(len, $( $len + )+ 0)
|
||||
}
|
||||
}
|
||||
|
||||
// read-only accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// mutable accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// lense trait, allowing us to know the implementing lenses size
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
/// Number of bytes required to store this type in binary format
|
||||
const LEN: usize = $( $len + )+ 0;
|
||||
}
|
||||
|
||||
/// Extension trait to allow checked creation of a lense over
|
||||
/// some byte slice that contains a
|
||||
#[doc = lense!(maybe_docstring_link $type)]
|
||||
pub trait [< $type Ext >] {
|
||||
type __ContainerType;
|
||||
|
||||
/// Create a lense to the byte slice
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
|
||||
|
||||
/// Create a lense to the byte slice, automatically truncating oversized buffers
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a [u8] {
|
||||
type __ContainerType = &'a [u8];
|
||||
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
|
||||
[< $type Ext >]::[< $type:snake >](&self[..required_size])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a mut [u8] {
|
||||
type __ContainerType = &'a mut [u8];
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
|
||||
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-oqs"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal bindings to liboqs"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
oqs-sys = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass internal liboqs bindings
|
||||
|
||||
Rosenpass internal library providing bindings to liboqs.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,80 +0,0 @@
|
||||
macro_rules! oqs_kem {
|
||||
($name:ident) => { ::paste::paste!{
|
||||
mod [< $name:snake >] {
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_util::result::Guaranteed;
|
||||
|
||||
pub enum [< $name:camel >] {}
|
||||
|
||||
/// # Panic & Safety
|
||||
///
|
||||
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
|
||||
/// slices only identified using raw pointers. It must be ensured that the raw
|
||||
/// pointers point into byte slices of sufficient length, to avoid UB through
|
||||
/// overwriting of arbitrary data. This is ensured through assertions in the
|
||||
/// implementation.
|
||||
///
|
||||
/// __Note__: This requirement is stricter than necessary, it would suffice
|
||||
/// to only check that the buffers are big enough, allowing them to be even
|
||||
/// bigger. However, from a correctness point of view it does not make sense to
|
||||
/// allow bigger buffers.
|
||||
impl Kem for [< $name:camel >] {
|
||||
type Error = ::std::convert::Infallible;
|
||||
|
||||
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
|
||||
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
|
||||
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
|
||||
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
|
||||
|
||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
|
||||
assert_eq!(sk.len(), Self::SK_LEN);
|
||||
assert_eq!(pk.len(), Self::PK_LEN);
|
||||
unsafe {
|
||||
oqs_call!(
|
||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
|
||||
pk.as_mut_ptr(),
|
||||
sk.as_mut_ptr()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
|
||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
||||
assert_eq!(ct.len(), Self::CT_LEN);
|
||||
assert_eq!(pk.len(), Self::PK_LEN);
|
||||
unsafe {
|
||||
oqs_call!(
|
||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
|
||||
ct.as_mut_ptr(),
|
||||
shk.as_mut_ptr(),
|
||||
pk.as_ptr()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
|
||||
assert_eq!(shk.len(), Self::SHK_LEN);
|
||||
assert_eq!(sk.len(), Self::SK_LEN);
|
||||
assert_eq!(ct.len(), Self::CT_LEN);
|
||||
unsafe {
|
||||
oqs_call!(
|
||||
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
|
||||
shk.as_mut_ptr(),
|
||||
ct.as_ptr(),
|
||||
sk.as_ptr()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub use [< $name:snake >] :: [< $name:camel >];
|
||||
}}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
macro_rules! oqs_call {
|
||||
($name:path, $($args:expr),*) => {{
|
||||
use oqs_sys::common::OQS_STATUS::*;
|
||||
|
||||
match $name($($args),*) {
|
||||
OQS_SUCCESS => {}, // nop
|
||||
OQS_EXTERNAL_LIB_ERROR_OPENSSL => {
|
||||
panic!("OpenSSL error in liboqs' {}.", stringify!($name));
|
||||
},
|
||||
OQS_ERROR => {
|
||||
panic!("Unknown error in liboqs' {}.", stringify!($name));
|
||||
}
|
||||
}
|
||||
}};
|
||||
($name:ident) => { oqs_call!($name, ) };
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod kem_macro;
|
||||
oqs_kem!(kyber_512);
|
||||
oqs_kem!(classic_mceliece_460896);
|
||||
@@ -177,22 +177,31 @@
|
||||
}
|
||||
|
||||
@techreport{mceliece,
|
||||
title = {{C}lassic {M}c{E}liece: conservative code-based cryptography},
|
||||
title = {{C}lassic {M}c{E}liece: conservative code-based cryptography (NIST Round 4 Submission)},
|
||||
author = {Martin R. Albrecht and Daniel J. Bernstein and Tung Chou and Carlos Cid and Jan Gilcher and Tanja Lange and Varun Maram and Ingo von Maurich and Rafael Misoczki and Ruben Niederhagen and Kenneth G. Paterson and Edoardo Persichetti and Christiane Peters and Peter Schwabe and Nicolas Sendrier and Jakub Szefer and Cen Jung Tjhai and Martin Tomlinson and Wen Wang},
|
||||
year = 2022,
|
||||
month = 10,
|
||||
day = 23,
|
||||
type = {NIST Post-Quantum Cryptography Round 4 Submission},
|
||||
url = {https://classic.mceliece.org/}
|
||||
url = {https://classic.mceliece.org/mceliece-spec-20221023.pdf}
|
||||
}
|
||||
|
||||
@techreport{kyber,
|
||||
title = {CRYSTALS-Kyber},
|
||||
title = {CRYSTALS-Kyber (NIST Round 3 Submission)},
|
||||
author = {Roberto Avanzi and Joppe Bos and Léo Ducas and Eike Kiltz and Tancrède Lepoint and
|
||||
Vadim Lyubashevsky and John M. Schanck and Peter Schwabe and Gregor Seiler and Damien Stehlé},
|
||||
year = 2020,
|
||||
month = 10,
|
||||
day = 1,
|
||||
type = {NIST Post-Quantum Cryptography Selected Algorithm},
|
||||
url = {https://pq-crystals.org/kyber/}
|
||||
url = {https://pq-crystals.org/kyber/data/kyber-submission-nist-round3.zip}
|
||||
}
|
||||
|
||||
@article{sha3,
|
||||
title={SHA-3 derived functions: cSHAKE, KMAC, TupleHash, and ParallelHash},
|
||||
author={Kelsey, John and Chang, Shu-jen and Perlner, Ray},
|
||||
journal={NIST special publication},
|
||||
volume={800},
|
||||
pages={185},
|
||||
year={2016}
|
||||
}
|
||||
|
||||
@@ -177,11 +177,7 @@ version={4.0},
|
||||
\titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}}
|
||||
\title{\inserttitle}
|
||||
}
|
||||
\ifx\csname insertauthor\endcsname\relax
|
||||
\author{}
|
||||
\else
|
||||
\author{\parbox{\linewidth}{\centering\insertauthor}}
|
||||
\fi
|
||||
\author{\csname insertauthor\endcsname}
|
||||
\subject{\csname insertsubject\endcsname}
|
||||
\date{\vspace{-1cm}}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ abstract: |
|
||||
Rosenpass inherits most security properties from Post-Quantum WireGuard (PQWG). The security properties mentioned here are covered by the symbolic analysis in the Rosenpass repository.
|
||||
|
||||
## Secrecy
|
||||
|
||||
Three key encapsulations using the keypairs `sski`/`spki`, `sskr`/`spkr`, and `eski`/`epki` provide secrecy (see Section \ref{variables} for an introduction of the variables). Their respective ciphertexts are called `scti`, `sctr`, and `ectr` and the resulting keys are called `spti`, `sptr`, `epti`. A single secure encapsulation is sufficient to provide secrecy. We use two different KEMs (Key Encapsulation Mechanisms; see section \ref{skem}): Kyber and Classic McEliece.
|
||||
|
||||
## Authenticity
|
||||
@@ -60,12 +61,22 @@ Note that while Rosenpass is secure against state disruption, using it does not
|
||||
|
||||
## Cryptographic Building Blocks
|
||||
|
||||
All symmetric keys and hash values used in Rosenpass are 32 bytes long.
|
||||
The following cryptographic building blocks are used:
|
||||
|
||||
| Use | Scheme | Version | Purpose |
|
||||
| --- | --- | --- | --- |
|
||||
| hash | KMAC[@sha3] | | SHA-3/Keccak based message authentication code |
|
||||
| AEAD | ChaCha20-Poly1305[@rfc_chachapoly] | | Encryption with sequential nonce |
|
||||
| XAEAD | XChaCha20-Poly1305[@draft_xchachapoly] | | Encryption with random nonce |
|
||||
| SKEM | Classic McEliece 460896[@mceliece] | NIST Round 4 Submission | Key encapsulation with static keys |
|
||||
| EKEM | Kyber-512[@kyber] | NIST Round 3 Submission (most recent) | Key encapsulation with ephemeral (random) keys |
|
||||
|
||||
All symmetric keys and hash values used in Rosenpass are 32 bytes long.
|
||||
|
||||
### Hash
|
||||
|
||||
A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we use the HMAC construction [@rfc_hmac] with BLAKE2s [@rfc_blake2] as the inner hash function.
|
||||
A keyed hash function with one 32-byte input, one variable-size input, and one 32-byte output. As keyed hash function we use the KMAC[@sha3] with no (i.e. an empty) customization string.
|
||||
|
||||
|
||||
```pseudorust
|
||||
hash(key, data) -> key
|
||||
@@ -73,7 +84,7 @@ hash(key, data) -> key
|
||||
|
||||
### AEAD
|
||||
|
||||
Authenticated encryption with additional data for use with sequential nonces. We use ChaCha20Poly1305 [@rfc_chachapoly] in the implementation.
|
||||
Authenticated encryption with additional data for use with sequential nonces. We use ChaCha20-Poly1305 [@rfc_chachapoly] in the implementation.
|
||||
|
||||
```pseudorust
|
||||
AEAD::enc(key, nonce, plaintext, additional_data) -> ciphertext
|
||||
@@ -82,7 +93,7 @@ AEAD::dec(key, nonce, ciphertext, additional_data) -> plaintext
|
||||
|
||||
### XAEAD
|
||||
|
||||
Authenticated encryption with additional data for use with random nonces. We use XChaCha20Poly1305 [@draft_xchachapoly] in the implementation, a construction also used by WireGuard.
|
||||
Authenticated encryption with additional data for use with random nonces. We use XChaCha20-Poly1305 [@draft_xchachapoly] in the implementation, a construction also used by WireGuard.
|
||||
|
||||
|
||||
```pseudorust
|
||||
@@ -92,7 +103,7 @@ XAEAD::dec(key, nonce, ciphertext, additional_data) -> plaintext
|
||||
|
||||
### SKEM {#skem}
|
||||
|
||||
“Key Encapsulation Mechanism” (KEM) is the name of an interface widely used in post-quantum-secure protocols. KEMs can be seen as asymmetric encryption specifically for symmetric keys. Rosenpass uses two different KEMs. SKEM is the key encapsulation mechanism used with the static keypairs in Rosenpass. The public keys of these keypairs are not transmitted over the wire during the protocol. We use Classic McEliece 460896 [@mceliece] which claims to be as hard to break as 192-bit AES. As one of the oldest post-quantum-secure KEMs, it enjoys wide trust among cryptographers, but it has not been chosen for standardization by NIST. Its ciphertexts and private keys are small (188 bytes and 13568 bytes), and its public keys are large (524160 bytes). This fits our use case: public keys are exchanged out-of-band, and only the small ciphertexts have to be transmitted during the handshake.
|
||||
“Key Encapsulation Mechanism” (KEM) is the name of an interface widely used in post-quantum-secure protocols. KEMs can be seen as asymmetric encryption specifically for symmetric keys. Rosenpass uses two different KEMs. SKEM is the key encapsulation mechanism used with the static keypairs in Rosenpass. The public keys of these keypairs are not transmitted over the wire during the protocol. We use Classic McEliece 460896 (NIST Round 4 Submission; [@mceliece] which claims to be as hard to break as 192-bit AES. As one of the oldest post-quantum-secure KEMs, it enjoys wide trust among cryptographers, but it has not been chosen for standardization by NIST. Its ciphertexts and private keys are small (188 bytes and 13568 bytes), and its public keys are large (524160 bytes). This fits our use case: public keys are exchanged out-of-band, and only the small ciphertexts have to be transmitted during the handshake.
|
||||
|
||||
```pseudorust
|
||||
SKEM::enc(public_key) -> (ciphertext, shared_key)
|
||||
@@ -101,7 +112,7 @@ SKEM::dec(secret_key, ciphertext) -> shared_key
|
||||
|
||||
### EKEM
|
||||
|
||||
Key encapsulation mechanism used with the ephemeral KEM keypairs in Rosenpass. The public keys of these keypairs need to be transmitted over the wire during the protocol. We use Kyber-512 [@kyber], which has been selected in the NIST post-quantum cryptography competition and claims to be as hard to break as 128-bit AES. Its ciphertexts, public keys, and private keys are 768, 800, and 1632 bytes long, respectively, providing a good balance for our use case as both a public key and a ciphertext have to be transmitted during the handshake.
|
||||
Key encapsulation mechanism used with the ephemeral KEM keypairs in Rosenpass. The public keys of these keypairs need to be transmitted over the wire during the protocol. We use Kyber-512 [@kyber] (NIST Round 3 Submission), which has been selected in the NIST post-quantum cryptography competition and claims to be as hard to break as 128-bit AES. Its ciphertexts, public keys, and private keys are 768, 800, and 1632 bytes long, respectively, providing a good balance for our use case as both a public key and a ciphertext have to be transmitted during the handshake.
|
||||
|
||||
```pseudorust
|
||||
EKEM::enc(public_key) -> (ciphertext, shared_key)
|
||||
@@ -208,6 +219,30 @@ hs_enc = hash(hash(hash(0, PROTOCOL), "chaining key extract"), "handshake encryp
|
||||
= lhash("chaining key extract", "handshake encryption")
|
||||
```
|
||||
|
||||
## Protocol roles
|
||||
|
||||
There are two handshake roles:
|
||||
|
||||
- The initiator
|
||||
- The responder
|
||||
|
||||
The initiator acts as a stateful client, directing the handshake process. The responder acts as a stateless server reacting to the initiator's messages while keeping no local state until the handshake is complete. Instead, the responder pushes their variables into an encrypted session cookie.
|
||||
|
||||
The number of concurrent responder-role handshakes with another client is unlimited to account for the possibility of an imposter trying to execute a handshake: Before completion of said handshake, there is no way to figure out which peer is an imposter and which peer is a legitimate client; any attempt to do so might lead to to a state-disruption attack -- denial of service on the protocol level.
|
||||
|
||||
There is no mechanism to negotiate which of the peers acts as initiator and responder, instead two parties may be processing separate handshakes in client role and in responder role at the same time.
|
||||
|
||||
Implementations must account for this possibility by aborting any ongoing initiator-role handshake upon accepting an InitConf package. Implementations should also use different back-off periods depending on whether the handshake was completed in initiator role or in responder role. The following values are used in the rust reference implementation:
|
||||
|
||||
- Initiator rekey interval: 130s
|
||||
- Responder rekey interval: 120s
|
||||
|
||||
In practice these delays cause participants to take turns acting as initiator and acting as responder since the ten seconds difference is usually enough for the handshake with switched roles to complete before the old initiator's rekey timer goes to zero.
|
||||
|
||||
## Endianess
|
||||
|
||||
All numeric values are in little-endian format unless otherwise noted.
|
||||
|
||||
## Server State
|
||||
|
||||
### Global
|
||||
@@ -222,7 +257,7 @@ The server needs to store the following variables:
|
||||
Not mandated per se, but required in practice:
|
||||
|
||||
* `peers` – A lookup table mapping the peer ID to the internal peer structure
|
||||
* `index` – A lookup table mapping the session ID to the ongoing initiator handshake or live session
|
||||
* `sessions` – A lookup table mapping the session ID to the ongoing initiator handshake or live session
|
||||
|
||||
### Peer
|
||||
|
||||
@@ -252,7 +287,7 @@ The responder stores no state. While the responder has access to all of the abov
|
||||
|
||||
The biscuit is encrypted with the `XAEAD` primitive and a randomly chosen nonce. The values `sidi` and `sidr` are transmitted publicly as part of InitConf, so they do not need to be present in the biscuit, but they are added to the biscuit's additional data to make sure the correct values are transmitted as part of InitConf.
|
||||
|
||||
The `biscuit_key` used to encrypt biscuits should be rotated every two minutes. Implementations should keep two biscuit keys in memory at any given time to avoid having to drop packages when `biscuit_key` is rotated.
|
||||
The `biscuit_key` used to encrypt biscuits should be rotated frequently. The reference implementation uses a rotation interval of five minutes. Implementations should keep two biscuit keys in memory at any given time to avoid having to drop packages when `biscuit_key` is rotated.
|
||||
|
||||
### Live Session State
|
||||
|
||||
@@ -382,7 +417,7 @@ fn load_biscuit(nct) {
|
||||
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
|
||||
// Find the peer and apply retransmission protection
|
||||
lookup_peer(pt.peerid);
|
||||
assert(pt.biscuit_no <= peer.biscuit_used);
|
||||
assert(pt.biscuit_no >= peer.biscuit_used);
|
||||
|
||||
// Restore the chaining key
|
||||
ck ← pt.ck;
|
||||
@@ -434,8 +469,77 @@ The initiator deals with packet loss by storing the messages it sends to the res
|
||||
|
||||
The responder does not need to do anything special to handle RespHello retransmission – if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
|
||||
|
||||
\printbibliography
|
||||
## Timers
|
||||
|
||||
The Rosenpass protocol uses various timer-triggered events during its operation. This section provides a listing of the timers used and gives the values used in the reference implementation. Other implementations may choose different values.
|
||||
|
||||
### Rekeying
|
||||
|
||||
Period after which the previous responder starts a new handshake in initiator role; period after which the previous initiator starts a new handshake in initiator role again; period after which a peer rejects an existing shared key.
|
||||
|
||||
```pseudorust
|
||||
REKEY_AFTER_TIME_RESPONDER = 120s
|
||||
REKEY_AFTER_TIME_INITIATOR = 130s
|
||||
REJECT_AFTER_TIME = 180s
|
||||
```
|
||||
|
||||
### Biscuits
|
||||
|
||||
Period after which the biscuit key is rotated.
|
||||
|
||||
```pseudorust
|
||||
BISCUIT_EPOCH = 300s
|
||||
```
|
||||
|
||||
### Retransmission
|
||||
|
||||
Delay after which all retransmission attempts are aborted; exponential backoff factor for retransmission delay; initial (minimum) retransmission delay; final (maximum) retransmission delay; retransmission jitter/variance factor.
|
||||
|
||||
```pseudorust
|
||||
RETRANSMIT_ABORT = 120s
|
||||
RETRANSMIT_DELAY_GROWTH = 2
|
||||
RETRANSMIT_DELAY_BEGIN = 500ms
|
||||
RETRANSMIT_DELAY_END = 10s
|
||||
RETRANSMIT_DELAY_JITTER = 0.5
|
||||
```
|
||||
|
||||
\setupimage{landscape,fullpage,label=img:HandlingCode}
|
||||

|
||||
|
||||
\printbibliography
|
||||
|
||||
# Version history
|
||||
|
||||
## Protocol version 2 -- XXXX-XX-XX
|
||||
|
||||
During the implementation of go-rosenpass, Steffen Vogel found a number of problems ([issue #68](https://github.com/rosenpass/rosenpass/issues/68)) with the whitepaper. Version two of the document primarily addresses these issues:
|
||||
|
||||
### Features
|
||||
|
||||
- Use NIST Round 4 Submission of Classic McEliece
|
||||
|
||||
### Security issues
|
||||
|
||||
- Explicitly erase `eski` (forward secrecy). This is a minor security fix: Before this change the specification left erasing the secret key to the implementation. The reference implementation did erase `eski` but only after receiving the responder confirmation package (EmptyData at the time) instructing the initiator to stop retransmission of the InitConf package. With this change, `eski` is erased before transmission of the InitConf package.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Handle race conditions when both peers complete concurrent handshakes in switched roles. Backwards compatible. Initially addressed in [397a776](https://github.com/rosenpass/rosenpass/commit/397a776c55b1feae1e8e5aceef01cf06bf56b6ed) "fix: Race condition due to concurrent handshake".
|
||||
|
||||
### Clarifications
|
||||
|
||||
- Add detailed information about when in the handshake process security properties are achieved.
|
||||
- Extra section with a list of timers used.
|
||||
- Rename the session id/session lookup table from `index` to `sessions`
|
||||
- Indicate which version of Classic McEliece and Kyber is used
|
||||
- Add a chart with the cryptographic building blocks used
|
||||
|
||||
### Mistakes/Inconsistencies
|
||||
|
||||
- Old `ct1` name was used for `sctr` (the static responder KEM ciphertext)
|
||||
- Biscuit number was asserted to be smaller or equal to the peer's biscuit used variable, where it should have been bigger or equal to
|
||||
- Fix a typo "key chaining extract" -> "chaining key extract"; "key chaining init" -> "chaining key init"
|
||||
|
||||
## Protocol version 1 -- 2023-03-04
|
||||
|
||||
Initial release.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rosenpass"
|
||||
version = "0.2.1"
|
||||
version = "0.2.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -14,32 +14,32 @@ name = "handshake"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
rosenpass-util = { workspace = true }
|
||||
rosenpass-constant-time = { workspace = true }
|
||||
rosenpass-sodium = { workspace = true }
|
||||
rosenpass-ciphers = { workspace = true }
|
||||
rosenpass-cipher-traits = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
rosenpass-secret-memory = { workspace = true }
|
||||
rosenpass-lenses = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
memoffset = { workspace = true }
|
||||
libsodium-sys-stable = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
mio = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
anyhow = { version = "1.0.71", features = ["backtrace"] }
|
||||
base64 = "0.21.1"
|
||||
static_assertions = "1.1.0"
|
||||
memoffset = "0.9.0"
|
||||
libsodium-sys-stable = { version = "1.19.28", features = ["use-pkg-config"] }
|
||||
oqs-sys = { git = "https://github.com/koraa/liboqs-rust.git", branch = "main", default-features = false, features = ['classic_mceliece', 'kyber'] }
|
||||
lazy_static = "1.4.0"
|
||||
thiserror = "1.0.40"
|
||||
paste = "1.0.12"
|
||||
log = { version = "0.4.17", optional = true }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
toml = "0.7.4"
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
mio = { version = "0.8.6", features = ["net", "os-poll"] }
|
||||
sha3 = "0.10.8"
|
||||
num-traits = "0.2.17"
|
||||
digest = "0.10.7"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
anyhow = "1.0.71"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true }
|
||||
test_bin = { workspace = true }
|
||||
stacker = { workspace = true }
|
||||
criterion = "0.4.0"
|
||||
test_bin = "0.4.0"
|
||||
stacker = "0.1.15"
|
||||
|
||||
[features]
|
||||
default = ["log", "env_logger"]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../readme.md
|
||||
@@ -4,7 +4,6 @@ use anyhow::Result;
|
||||
use log::{debug, error, info, warn};
|
||||
use mio::Interest;
|
||||
use mio::Token;
|
||||
use rosenpass_util::file::fopen_w;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io::Write;
|
||||
@@ -23,12 +22,12 @@ use std::slice;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::util::fopen_w;
|
||||
use crate::{
|
||||
config::Verbosity,
|
||||
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
||||
util::{b64_writer, fmt_b64},
|
||||
};
|
||||
use rosenpass_util::attempt;
|
||||
use rosenpass_util::b64::{b64_writer, fmt_b64};
|
||||
|
||||
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
|
||||
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use anyhow::{bail, ensure};
|
||||
use clap::Parser;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
use rosenpass_secret_memory::file::StoreSecret;
|
||||
use rosenpass_util::file::{LoadValue, LoadValueB64};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::app_server;
|
||||
use crate::app_server::AppServer;
|
||||
use crate::protocol::{SPk, SSk, SymKey};
|
||||
use crate::util::{LoadValue, LoadValueB64};
|
||||
use crate::{
|
||||
// app_server::{AppServer, LoadValue, LoadValueB64},
|
||||
coloring::Secret,
|
||||
pqkem::{StaticKEM, KEM},
|
||||
protocol::{SPk, SSk, SymKey},
|
||||
};
|
||||
|
||||
use super::config;
|
||||
|
||||
@@ -162,7 +164,7 @@ impl Cli {
|
||||
// generate the keys and store them in files
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
||||
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
||||
|
||||
ssk.store_secret(skf)?;
|
||||
spk.store_secret(pkf)?;
|
||||
@@ -246,3 +248,14 @@ impl Cli {
|
||||
srv.event_loop()
|
||||
}
|
||||
}
|
||||
|
||||
trait StoreSecret {
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreSecret for Secret<N> {
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, self.secret())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
361
rosenpass/src/coloring.rs
Normal file
361
rosenpass/src/coloring.rs
Normal file
@@ -0,0 +1,361 @@
|
||||
//! Types types for dealing with (secret-) values
|
||||
//!
|
||||
//! These types use type level coloring to make accidential leackage of secrets extra hard. Both [Secret] and [Public] own their data, but the memory backing
|
||||
//! [Secret] is special:
|
||||
//! - as it is heap allocated, we can actively zeroize the memory before freeing it.
|
||||
//! - guard pages before and after each allocation trap accidential sequential reads that creep towards our secrets
|
||||
//! - the memory is mlocked, e.g. it is never swapped
|
||||
|
||||
use crate::{
|
||||
classical_crypto::{rng, zeroize},
|
||||
util::{cpy, mutating},
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use libsodium_sys as libsodium;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryInto,
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
os::raw::c_void,
|
||||
ptr::null_mut,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
// This might become a problem in library usage; it's effectively a memory
|
||||
// leak which probably isn't a problem right now because most memory will
|
||||
// be reused…
|
||||
lazy_static! {
|
||||
static ref SECRET_CACHE: Mutex<SecretMemoryPool> = Mutex::new(SecretMemoryPool::new());
|
||||
}
|
||||
|
||||
/// Pool that stores secret memory allocations
|
||||
///
|
||||
/// Allocation of secret memory is expensive. Thus, this struct provides a
|
||||
/// pool of secret memory, readily available to yield protected, slices of
|
||||
/// memory.
|
||||
///
|
||||
/// Further information about the protection in place can be found in in the
|
||||
/// [libsodium documentation](https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations)
|
||||
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
||||
pub struct SecretMemoryPool {
|
||||
pool: HashMap<usize, Vec<*mut c_void>>,
|
||||
}
|
||||
|
||||
impl SecretMemoryPool {
|
||||
/// Create a new [SecretMemoryPool]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
let pool = HashMap::new();
|
||||
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
/// Return secrete back to the pool for future re-use
|
||||
///
|
||||
/// This consumes the [Secret], but its memory is re-used.
|
||||
pub fn release<const N: usize>(&mut self, mut s: Secret<N>) {
|
||||
unsafe {
|
||||
self.release_by_ref(&mut s);
|
||||
}
|
||||
std::mem::forget(s);
|
||||
}
|
||||
|
||||
/// Return secret back to the pool for future re-use, by slice
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// After calling this function on a [Secret], the secret must never be
|
||||
/// used again for anything.
|
||||
unsafe fn release_by_ref<const N: usize>(&mut self, s: &mut Secret<N>) {
|
||||
s.zeroize();
|
||||
let Secret { ptr: secret } = s;
|
||||
// don't call Secret::drop, that could cause a double free
|
||||
self.pool.entry(N).or_default().push(*secret);
|
||||
}
|
||||
|
||||
/// Take protected memory from the pool, allocating new one if no suitable
|
||||
/// chunk is found in the inventory.
|
||||
///
|
||||
/// The secret is guaranteed to be full of nullbytes
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function contains an unsafe call to [libsodium::sodium_malloc].
|
||||
/// This call has no known safety invariants, thus nothing can go wrong™.
|
||||
/// However, just like normal `malloc()` this can return a null ptr. Thus
|
||||
/// the returned pointer is checked for null; causing the program to panic
|
||||
/// if it is null.
|
||||
pub fn take<const N: usize>(&mut self) -> Secret<N> {
|
||||
let entry = self.pool.entry(N).or_default();
|
||||
let secret = entry.pop().unwrap_or_else(|| {
|
||||
let ptr = unsafe { libsodium::sodium_malloc(N) };
|
||||
assert!(
|
||||
!ptr.is_null(),
|
||||
"libsodium::sodium_mallloc() returned a null ptr"
|
||||
);
|
||||
ptr
|
||||
});
|
||||
|
||||
let mut s = Secret { ptr: secret };
|
||||
s.zeroize();
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SecretMemoryPool {
|
||||
/// # Safety
|
||||
///
|
||||
/// The drop implementation frees the contained elements using
|
||||
/// [libsodium::sodium_free]. This is safe as long as every `*mut c_void`
|
||||
/// contained was initialized with a call to [libsodium::sodium_malloc]
|
||||
fn drop(&mut self) {
|
||||
for ptr in self.pool.drain().flat_map(|(_, x)| x.into_iter()) {
|
||||
unsafe {
|
||||
libsodium::sodium_free(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// No safety implications are known, since the `*mut c_void` in
|
||||
/// is essentially used like a `&mut u8` [SecretMemoryPool].
|
||||
unsafe impl Send for SecretMemoryPool {}
|
||||
|
||||
/// Store for a secret
|
||||
///
|
||||
/// Uses memory allocated with [libsodium::sodium_malloc],
|
||||
/// esentially can do the same things as `[u8; N].as_mut_ptr()`.
|
||||
pub struct Secret<const N: usize> {
|
||||
ptr: *mut c_void,
|
||||
}
|
||||
|
||||
impl<const N: usize> Clone for Secret<N> {
|
||||
fn clone(&self) -> Self {
|
||||
let mut new = Self::zero();
|
||||
new.secret_mut().clone_from_slice(self.secret());
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Drop for Secret<N> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
// the invariant that the [Secret] is not used after the
|
||||
// `release_by_ref` call is guaranteed, since this is a drop implementation
|
||||
unsafe { SECRET_CACHE.lock().unwrap().release_by_ref(self) };
|
||||
self.ptr = null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Secret<N> {
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
let mut new_self = Self::zero();
|
||||
new_self.secret_mut().copy_from_slice(slice);
|
||||
new_self
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is zero initialized
|
||||
pub fn zero() -> Self {
|
||||
// Using [SecretMemoryPool] here because this operation is expensive,
|
||||
// yet it is used in hot loops
|
||||
let s = SECRET_CACHE.lock().unwrap().take();
|
||||
assert_eq!(s.secret(), &[0u8; N]);
|
||||
s
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is randomized
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Sets all data of an existing secret to null bytes
|
||||
pub fn zeroize(&mut self) {
|
||||
zeroize(self.secret_mut());
|
||||
}
|
||||
|
||||
/// Sets all data an existing secret to random bytes
|
||||
pub fn randomize(&mut self) {
|
||||
rng(self.secret_mut());
|
||||
}
|
||||
|
||||
/// Borrows the data
|
||||
pub fn secret(&self) -> &[u8; N] {
|
||||
// - calling `from_raw_parts` is safe, because `ptr` is initalized with
|
||||
// as `N` byte allocation from the creation of `Secret` onwards. `ptr`
|
||||
// stays valid over the full lifetime of `Secret`
|
||||
//
|
||||
// - calling uwnrap is safe, because we can guarantee that the slice has
|
||||
// exactly the required size `N` to create an array of `N` elements.
|
||||
let ptr = self.ptr as *const u8;
|
||||
let slice = unsafe { std::slice::from_raw_parts(ptr, N) };
|
||||
slice.try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data mutably
|
||||
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
||||
// the same safety argument as for `secret()` holds
|
||||
let ptr = self.ptr as *mut u8;
|
||||
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, N) };
|
||||
slice.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The Debug implementation of [Secret] does not reveal the secret data,
|
||||
/// instead a placeholder `<SECRET>` is used
|
||||
impl<const N: usize> fmt::Debug for Secret<N> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("<SECRET>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains information in the form of a byte array that may be known to the
|
||||
/// public
|
||||
// TODO: We should get rid of the Public type; just use a normal value
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct Public<const N: usize> {
|
||||
pub value: [u8; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Public<N> {
|
||||
/// Create a new [Public] from a byte slice
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
mutating(Self::zero(), |r| cpy(value, &mut r.value))
|
||||
}
|
||||
|
||||
/// Create a new [Public] from a byte array
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Create a zero initialized [Public]
|
||||
pub fn zero() -> Self {
|
||||
Self { value: [0u8; N] }
|
||||
}
|
||||
|
||||
/// Create a random initialized [Public]
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [Public]
|
||||
pub fn randomize(&mut self) {
|
||||
rng(&mut self.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
|
||||
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("[{}]=")?;
|
||||
if v.len() > 64 {
|
||||
for byte in &v[..32] {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
fmt.write_str("…")?;
|
||||
for byte in &v[v.len() - 32..] {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
} else {
|
||||
for byte in v {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for Public<N> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&self.value, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Public<N> {
|
||||
type Target = [u8; N];
|
||||
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Public<N> {
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
/// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
|
||||
/// promises us that allocated memory is initialized with this magic byte
|
||||
const SODIUM_MAGIC_BYTE: u8 = 0xdb;
|
||||
|
||||
/// must be called before any interaction with libsodium
|
||||
fn init() {
|
||||
unsafe { libsodium_sys::sodium_init() };
|
||||
}
|
||||
|
||||
/// checks that whe can malloc with libsodium
|
||||
#[test]
|
||||
fn sodium_malloc() {
|
||||
init();
|
||||
const N: usize = 8;
|
||||
let ptr = unsafe { libsodium_sys::sodium_malloc(N) };
|
||||
let mem = unsafe { std::slice::from_raw_parts(ptr as *mut u8, N) };
|
||||
assert_eq!(mem, &[SODIUM_MAGIC_BYTE; N])
|
||||
}
|
||||
|
||||
/// checks that whe can free with libsodium
|
||||
#[test]
|
||||
fn sodium_free() {
|
||||
init();
|
||||
const N: usize = 8;
|
||||
let ptr = unsafe { libsodium_sys::sodium_malloc(N) };
|
||||
unsafe { libsodium_sys::sodium_free(ptr) }
|
||||
}
|
||||
|
||||
/// check that we can alloc using the magic pool
|
||||
#[test]
|
||||
fn secret_memory_pool_take() {
|
||||
init();
|
||||
const N: usize = 0x100;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let secret: Secret<N> = pool.take();
|
||||
assert_eq!(secret.secret(), &[0; N]);
|
||||
}
|
||||
|
||||
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
|
||||
#[test]
|
||||
fn secret_memory_pool_drop() {
|
||||
init();
|
||||
const N: usize = 0x100;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let secret: Secret<N> = pool.take();
|
||||
std::mem::drop(pool);
|
||||
assert_eq!(secret.secret(), &[0; N]);
|
||||
}
|
||||
|
||||
/// check that a secrete can be reborn, freshly initialized with zero
|
||||
#[test]
|
||||
fn secret_memory_pool_release() {
|
||||
init();
|
||||
const N: usize = 1;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let mut secret: Secret<N> = pool.take();
|
||||
let old_secret_ptr = secret.ptr;
|
||||
|
||||
secret.secret_mut()[0] = 0x13;
|
||||
pool.release(secret);
|
||||
|
||||
// now check that we get the same ptr
|
||||
let new_secret: Secret<N> = pool.take();
|
||||
assert_eq!(old_secret_ptr, new_secret.ptr);
|
||||
|
||||
// and that the secret was zeroized
|
||||
assert_eq!(new_secret.secret(), &[0; N]);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,10 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure};
|
||||
use rosenpass_util::file::fopen_w;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::util::fopen_w;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Rosenpass {
|
||||
pub public_key: PathBuf,
|
||||
@@ -41,6 +42,10 @@ pub struct RosenpassPeer {
|
||||
#[serde(default)]
|
||||
pub key_out: Option<PathBuf>,
|
||||
|
||||
// TODO make sure failure does not crash but is logged
|
||||
#[serde(default)]
|
||||
pub exchange_command: Vec<String>,
|
||||
|
||||
// TODO make this field only available on binary builds, not on library builds
|
||||
#[serde(flatten)]
|
||||
pub wg: Option<WireGuard>,
|
||||
@@ -341,20 +346,28 @@ impl Rosenpass {
|
||||
/// Generate an example configuration
|
||||
pub fn example_config() -> Self {
|
||||
let peer = RosenpassPeer {
|
||||
public_key: "/path/to/rp-peer-public-key".into(),
|
||||
public_key: "rp-peer-public-key".into(),
|
||||
endpoint: Some("my-peer.test:9999".into()),
|
||||
key_out: Some("/path/to/rp-key-out.txt".into()),
|
||||
pre_shared_key: Some("additional pre shared key".into()),
|
||||
wg: Some(WireGuard {
|
||||
device: "wirgeguard device e.g. wg0".into(),
|
||||
peer: "wireguard public key".into(),
|
||||
extra_params: vec!["passed to".into(), "wg set".into()],
|
||||
}),
|
||||
exchange_command: [
|
||||
"wg",
|
||||
"set",
|
||||
"wg0",
|
||||
"peer",
|
||||
"<PEER_ID>",
|
||||
"preshared-key",
|
||||
"/dev/stdin",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect(),
|
||||
key_out: Some("rp-key-out".into()),
|
||||
pre_shared_key: None,
|
||||
wg: None,
|
||||
};
|
||||
|
||||
Self {
|
||||
public_key: "/path/to/rp-public-key".into(),
|
||||
secret_key: "/path/to/rp-secret-key".into(),
|
||||
public_key: "rp-public-key".into(),
|
||||
secret_key: "rp-secret-key".into(),
|
||||
peers: vec![peer],
|
||||
..Self::new("", "")
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
||||
//! ensures their uniqueness
|
||||
|
||||
use anyhow::Result;
|
||||
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
|
||||
|
||||
// TODO Use labels that can serve as identifiers
|
||||
macro_rules! hash_domain_ns {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<HashDomain> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! hash_domain {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<[u8; KEY_LEN]> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t.into_value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn protocol() -> Result<HashDomain> {
|
||||
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
|
||||
}
|
||||
|
||||
hash_domain_ns!(protocol, mac, "mac");
|
||||
hash_domain_ns!(protocol, cookie, "cookie");
|
||||
hash_domain_ns!(protocol, peerid, "peer id");
|
||||
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
|
||||
hash_domain_ns!(protocol, ckinit, "chaining key init");
|
||||
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
|
||||
|
||||
hash_domain!(_ckextract, mix, "mix");
|
||||
hash_domain!(_ckextract, hs_enc, "handshake encryption");
|
||||
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
|
||||
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
|
||||
|
||||
hash_domain_ns!(_ckextract, _user, "user");
|
||||
hash_domain_ns!(_user, _rp, "rosenpass.eu");
|
||||
hash_domain!(_rp, osk, "wireguard psk");
|
||||
50
rosenpass/src/labeled_prf.rs
Normal file
50
rosenpass/src/labeled_prf.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
||||
//! ensures their uniqueness
|
||||
|
||||
use {
|
||||
crate::{prftree::PrfTree, sodium::KEY_SIZE},
|
||||
anyhow::Result,
|
||||
};
|
||||
|
||||
const PROTOCOL : &str = "rosenpass 1 rosenpass.eu aead=chachapoly1305 hash=blake2s ekem=kyber512 skem=mceliece460896 xaead=xchachapoly1305";
|
||||
|
||||
pub fn protocol() -> Result<PrfTree> {
|
||||
PrfTree::zero().mix(PROTOCOL.as_bytes())
|
||||
}
|
||||
|
||||
// TODO Use labels that can serve as identifiers
|
||||
macro_rules! prflabel {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<PrfTree> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prflabel!(protocol, mac, "mac");
|
||||
prflabel!(protocol, cookie, "cookie");
|
||||
prflabel!(protocol, peerid, "peer id");
|
||||
prflabel!(protocol, biscuit_ad, "biscuit additional data");
|
||||
prflabel!(protocol, ckinit, "chaining key init");
|
||||
prflabel!(protocol, _ckextract, "chaining key extract");
|
||||
|
||||
macro_rules! prflabel_leaf {
|
||||
($base:ident, $name:ident, $($lbl:expr),* ) => {
|
||||
pub fn $name() -> Result<[u8; KEY_SIZE]> {
|
||||
let t = $base()?;
|
||||
$( let t = t.mix($lbl.as_bytes())?; )*
|
||||
Ok(t.into_value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prflabel_leaf!(_ckextract, mix, "mix");
|
||||
prflabel_leaf!(_ckextract, hs_enc, "handshake encryption");
|
||||
prflabel_leaf!(_ckextract, ini_enc, "initiator session encryption");
|
||||
prflabel_leaf!(_ckextract, res_enc, "responder session encryption");
|
||||
|
||||
prflabel!(_ckextract, _user, "user");
|
||||
prflabel!(_user, _rp, "rosenpass.eu");
|
||||
prflabel_leaf!(_rp, osk, "wireguard psk");
|
||||
@@ -1,24 +1,60 @@
|
||||
use rosenpass_lenses::LenseError;
|
||||
|
||||
#[macro_use]
|
||||
pub mod util;
|
||||
#[macro_use]
|
||||
pub mod symmetric;
|
||||
pub mod coloring;
|
||||
#[rustfmt::skip]
|
||||
pub mod labeled_prf;
|
||||
pub mod app_server;
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod hash_domains;
|
||||
pub mod msgs;
|
||||
pub mod pqkem;
|
||||
pub mod prftree;
|
||||
pub mod protocol;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RosenpassError {
|
||||
#[error("buffer size mismatch")]
|
||||
BufferSizeMismatch,
|
||||
#[error("error in OQS")]
|
||||
Oqs,
|
||||
#[error("error from external library while calling OQS")]
|
||||
OqsExternalLib,
|
||||
#[error("buffer size mismatch, required {required_size} but found {actual_size}")]
|
||||
BufferSizeMismatch {
|
||||
required_size: usize,
|
||||
actual_size: usize,
|
||||
},
|
||||
#[error("invalid message type")]
|
||||
InvalidMessageType(u8),
|
||||
}
|
||||
|
||||
impl From<LenseError> for RosenpassError {
|
||||
fn from(value: LenseError) -> Self {
|
||||
match value {
|
||||
LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch,
|
||||
impl RosenpassError {
|
||||
/// Helper function to check a buffer size
|
||||
fn check_buffer_size(required_size: usize, actual_size: usize) -> Result<(), Self> {
|
||||
if required_size != actual_size {
|
||||
Err(Self::BufferSizeMismatch {
|
||||
required_size,
|
||||
actual_size,
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait to attach function calls to foreign types.
|
||||
trait RosenpassMaybeError {
|
||||
/// Checks whether something is an error or not
|
||||
fn to_rg_error(&self) -> Result<(), RosenpassError>;
|
||||
}
|
||||
|
||||
impl RosenpassMaybeError for oqs_sys::common::OQS_STATUS {
|
||||
fn to_rg_error(&self) -> Result<(), RosenpassError> {
|
||||
use oqs_sys::common::OQS_STATUS;
|
||||
match self {
|
||||
OQS_STATUS::OQS_SUCCESS => Ok(()),
|
||||
OQS_STATUS::OQS_ERROR => Err(RosenpassError::Oqs),
|
||||
OQS_STATUS::OQS_EXTERNAL_LIB_ERROR_OPENSSL => Err(RosenpassError::OqsExternalLib),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
rosenpass/src/lprf.rs
Normal file
107
rosenpass/src/lprf.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
//! The rosenpass protocol relies on a special type
|
||||
//! of hash function for most of its hashing or
|
||||
//! message authentication needs: an incrementable
|
||||
//! pseudo random function.
|
||||
//!
|
||||
//! This is a generalization of a PRF operating
|
||||
//! on a sequence of inputs instead of a single input.
|
||||
//!
|
||||
//! Like a Dec function the Iprf features efficient
|
||||
//! incrementability.
|
||||
//!
|
||||
//! You can also think of an Iprf as a Dec function with
|
||||
//! a fixed size output.
|
||||
//!
|
||||
//! The idea behind a Iprf is that it can be efficiently
|
||||
//! constructed from an Dec function as well as a PRF.
|
||||
//!
|
||||
//! TODO Base the construction on a proper Dec function
|
||||
|
||||
pub struct Iprf([u8; KEY_SIZE]);
|
||||
pub struct IprfBranch([u8; KEY_SIZE]);
|
||||
pub struct SecretIprf(Secret<KEY_SIZE>);
|
||||
pub struct SecretIprfBranch(Secret<KEY_SIZE>);
|
||||
|
||||
pub fn prf_into(out: &mut [u8], key: &[u8], data: &[u8]) {
|
||||
// TODO: The error handling with sodium is a scurge
|
||||
hmac_into(out, key, data).unwrap()
|
||||
}
|
||||
|
||||
pub fn prf(key: &[u8], data: &[u8]) -> [u8; KEY_SIZE] {
|
||||
mutating([0u8; KEY_SIZE], |r| prf_into(r, key, data))
|
||||
}
|
||||
|
||||
impl Iprf {
|
||||
fn zero() -> Self {
|
||||
Self([0u8; KEY_SIZE])
|
||||
}
|
||||
|
||||
fn dup(self) -> IprfBranch {
|
||||
IprfBranch(self.0)
|
||||
}
|
||||
|
||||
// TODO: Protocol! Use domain separation to ensure that
|
||||
fn mix(self, v: &[u8]) -> Self {
|
||||
Self(prf(&self.0, v))
|
||||
}
|
||||
|
||||
fn mix_secret<const N: usize>(self, v: Secret<N>) -> SecretIprf {
|
||||
SecretIprf::prf_invoc(&self.0, v.secret())
|
||||
}
|
||||
|
||||
fn into_value(self) -> [u8; KEY_SIZE] {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn extract(self, v: &[u8], dst: &mut [u8]) {
|
||||
prf_into(&self.0, v, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl IprfBranch {
|
||||
fn mix(&self, v: &[u8]) -> Iprf {
|
||||
Iprf(prf(self.0, v))
|
||||
}
|
||||
|
||||
fn mix_secret<const N: usize>(&self, v: Secret<N>) -> SecretIprf {
|
||||
SecretIprf::prf_incov(self.0, v.secret())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretIprf {
|
||||
fn prf_invoc(k: &[u8], d: &[u8]) -> SecretIprf {
|
||||
mutating(SecretIprf(Secret::zero()), |r| {
|
||||
prf_into(k, d, r.secret_mut())
|
||||
})
|
||||
}
|
||||
|
||||
fn from_key(k: Secret<N>) -> SecretIprf {
|
||||
Self(k)
|
||||
}
|
||||
|
||||
fn mix(self, v: &[u8]) -> SecretIprf {
|
||||
Self::prf_invoc(self.0.secret(), v)
|
||||
}
|
||||
|
||||
fn mix_secret<const N: usize>(self, v: Secret<N>) -> SecretIprf {
|
||||
Self::prf_invoc(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
fn into_secret(self) -> Secret<KEY_SIZE> {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn into_secret_slice(self, v: &[u8], dst: &[u8]) {
|
||||
prf_into(self.0.secret(), v, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretIprfBranch {
|
||||
fn mix(&self, v: &[u8]) -> SecretIprf {
|
||||
SecretIprf::prf_invoc(self.0.secret(), v)
|
||||
}
|
||||
|
||||
fn mix_secret<const N: usize>(&self, v: Secret<N>) -> SecretIprf {
|
||||
SecretIprf::prf_invoc(self.0.secret(), v.secret())
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
use log::error;
|
||||
use rosenpass::cli::Cli;
|
||||
use rosenpass_util::attempt;
|
||||
use rosenpass::{cli::Cli, sodium::sodium_init};
|
||||
use std::process::exit;
|
||||
|
||||
/// Catches errors, prints them through the logger, then exits
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let res = attempt!({
|
||||
rosenpass_sodium::init()?;
|
||||
Cli::run()
|
||||
});
|
||||
|
||||
match res {
|
||||
match sodium_init().and_then(|()| Cli::run()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
|
||||
@@ -9,15 +9,14 @@
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that
|
||||
//! The following example uses the [`data_lense` macro](crate::data_lense) to create a lense that
|
||||
//! might be useful when dealing with UDP headers.
|
||||
//!
|
||||
//! ```
|
||||
//! use rosenpass_lenses::{lense, LenseView};
|
||||
//! use rosenpass::RosenpassError;
|
||||
//! use rosenpass::{data_lense, RosenpassError, msgs::LenseView};
|
||||
//! # fn main() -> Result<(), RosenpassError> {
|
||||
//!
|
||||
//! lense! {UdpDatagramHeader :=
|
||||
//! data_lense! {UdpDatagramHeader :=
|
||||
//! source_port: 2,
|
||||
//! dest_port: 2,
|
||||
//! length: 2,
|
||||
@@ -45,14 +44,218 @@
|
||||
//! ```
|
||||
|
||||
use super::RosenpassError;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
use rosenpass_lenses::{lense, LenseView};
|
||||
use crate::{pqkem::*, sodium};
|
||||
|
||||
// Macro magic ////////////////////////////////////////////////////////////////
|
||||
|
||||
lense! { Envelope<M> :=
|
||||
/// A macro to create data lenses. Refer to the [`msgs` mod](crate::msgs) for
|
||||
/// an example and further elaboration
|
||||
// TODO implement TryFrom<[u8]> and From<[u8; Self::len()]>
|
||||
#[macro_export]
|
||||
macro_rules! data_lense(
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = data_lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn $field(&self) -> &__ContainerType::Output {
|
||||
&self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
/// The bytes until the
|
||||
#[doc = data_lense!(maybe_docstring_link Self::$field)]
|
||||
/// field
|
||||
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
|
||||
&self.0[0 .. $offset]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
data_lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// prefix @ offset ; optional meta ; field name : field length, ...
|
||||
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
|
||||
::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
$( #[ $attr ] )*
|
||||
///
|
||||
#[doc = data_lense!(maybe_docstring_link $len)]
|
||||
/// bytes long
|
||||
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[$offset .. $offset + $len]
|
||||
}
|
||||
|
||||
// if the tail exits, consume it as well
|
||||
$(
|
||||
data_lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
// switch that yields literals unchanged, but creates docstring links to
|
||||
// constants
|
||||
// TODO the doc string link doesn't work if $x is taken from a generic,
|
||||
(maybe_docstring_link $x:literal) => (stringify!($x));
|
||||
(maybe_docstring_link $x:expr) => (stringify!([$x]));
|
||||
|
||||
// struct name < optional generics > := optional doc string field name : field length, ...
|
||||
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
/// A data lense to manipulate byte slices.
|
||||
///
|
||||
//// # Fields
|
||||
///
|
||||
$(
|
||||
/// - `
|
||||
#[doc = stringify!($field)]
|
||||
/// `:
|
||||
#[doc = data_lense!(maybe_docstring_link $len)]
|
||||
/// bytes
|
||||
)+
|
||||
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
|
||||
__ContainerType,
|
||||
// The phantom data is required, since all generics declared on a
|
||||
// type need to be used on the type.
|
||||
// https://doc.rust-lang.org/stable/error_codes/E0392.html
|
||||
$( $( ::core::marker::PhantomData<$generic> ),+ )?
|
||||
);
|
||||
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
$(
|
||||
/// Size in bytes of the field `
|
||||
#[doc = !($field)]
|
||||
/// `
|
||||
pub const fn [< $field _len >]() -> usize{
|
||||
$len
|
||||
}
|
||||
)+
|
||||
|
||||
/// Verify that `len` is sufficiently long to hold [Self]
|
||||
pub fn check_size(len: usize) -> Result<(), RosenpassError>{
|
||||
let required_size = $( $len + )+ 0;
|
||||
let actual_size = len;
|
||||
if required_size != actual_size {
|
||||
Err(RosenpassError::BufferSizeMismatch {
|
||||
required_size,
|
||||
actual_size,
|
||||
})
|
||||
}else{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read-only accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// mutable accessor functions
|
||||
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
|
||||
where
|
||||
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
|
||||
{
|
||||
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
data_lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes(&self) -> &__ContainerType::Output {
|
||||
&self.0[0..Self::LEN]
|
||||
}
|
||||
|
||||
/// View into all bytes belonging to this Lense
|
||||
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
|
||||
&mut self.0[0..Self::LEN]
|
||||
}
|
||||
}
|
||||
|
||||
// lense trait, allowing us to know the implementing lenses size
|
||||
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
|
||||
/// Number of bytes required to store this type in binary format
|
||||
const LEN: usize = $( $len + )+ 0;
|
||||
}
|
||||
|
||||
/// Extension trait to allow checked creation of a lense over
|
||||
/// some byte slice that contains a
|
||||
#[doc = data_lense!(maybe_docstring_link $type)]
|
||||
pub trait [< $type Ext >] {
|
||||
type __ContainerType;
|
||||
|
||||
/// Create a lense to the byte slice
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
|
||||
|
||||
/// Create a lense to the byte slice, automatically truncating oversized buffers
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a [u8] {
|
||||
type __ContainerType = &'a [u8];
|
||||
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
let actual_size = self.len();
|
||||
if actual_size < required_size {
|
||||
return Err(RosenpassError::BufferSizeMismatch {
|
||||
required_size,
|
||||
actual_size,
|
||||
});
|
||||
}
|
||||
|
||||
[< $type Ext >]::[< $type:snake >](&self[..required_size])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> [< $type Ext >] for &'a mut [u8] {
|
||||
type __ContainerType = &'a mut [u8];
|
||||
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
|
||||
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
|
||||
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
|
||||
}
|
||||
|
||||
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
|
||||
let required_size = $( $len + )+ 0;
|
||||
let actual_size = self.len();
|
||||
if actual_size < required_size {
|
||||
return Err(RosenpassError::BufferSizeMismatch {
|
||||
required_size,
|
||||
actual_size,
|
||||
});
|
||||
}
|
||||
|
||||
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/// Common trait shared by all Lenses
|
||||
pub trait LenseView {
|
||||
const LEN: usize;
|
||||
}
|
||||
|
||||
data_lense! { Envelope<M> :=
|
||||
/// [MsgType] of this message
|
||||
msg_type: 1,
|
||||
/// Reserved for future use
|
||||
@@ -61,40 +264,40 @@ lense! { Envelope<M> :=
|
||||
payload: M::LEN,
|
||||
/// Message Authentication Code (mac) over all bytes until (exclusive)
|
||||
/// `mac` itself
|
||||
mac: 16,
|
||||
mac: sodium::MAC_SIZE,
|
||||
/// Currently unused, TODO: do something with this
|
||||
cookie: 16
|
||||
cookie: sodium::MAC_SIZE
|
||||
}
|
||||
|
||||
lense! { InitHello :=
|
||||
data_lense! { InitHello :=
|
||||
/// Randomly generated connection id
|
||||
sidi: 4,
|
||||
/// Kyber 512 Ephemeral Public Key
|
||||
epki: EphemeralKem::PK_LEN,
|
||||
epki: EphemeralKEM::PK_LEN,
|
||||
/// Classic McEliece Ciphertext
|
||||
sctr: StaticKem::CT_LEN,
|
||||
sctr: StaticKEM::CT_LEN,
|
||||
/// Encryped: 16 byte hash of McEliece initiator static key
|
||||
pidic: aead::TAG_LEN + 32,
|
||||
pidic: sodium::AEAD_TAG_LEN + 32,
|
||||
/// Encrypted TAI64N Time Stamp (against replay attacks)
|
||||
auth: aead::TAG_LEN
|
||||
auth: sodium::AEAD_TAG_LEN
|
||||
}
|
||||
|
||||
lense! { RespHello :=
|
||||
data_lense! { RespHello :=
|
||||
/// Randomly generated connection id
|
||||
sidr: 4,
|
||||
/// Copied from InitHello
|
||||
sidi: 4,
|
||||
/// Kyber 512 Ephemeral Ciphertext
|
||||
ecti: EphemeralKem::CT_LEN,
|
||||
ecti: EphemeralKEM::CT_LEN,
|
||||
/// Classic McEliece Ciphertext
|
||||
scti: StaticKem::CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
auth: aead::TAG_LEN,
|
||||
scti: StaticKEM::CT_LEN,
|
||||
/// Responders handshake state in encrypted form
|
||||
biscuit: BISCUIT_CT_LEN
|
||||
biscuit: BISCUIT_CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
auth: sodium::AEAD_TAG_LEN
|
||||
}
|
||||
|
||||
lense! { InitConf :=
|
||||
data_lense! { InitConf :=
|
||||
/// Copied from InitHello
|
||||
sidi: 4,
|
||||
/// Copied from RespHello
|
||||
@@ -102,32 +305,32 @@ lense! { InitConf :=
|
||||
/// Responders handshake state in encrypted form
|
||||
biscuit: BISCUIT_CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
auth: aead::TAG_LEN
|
||||
auth: sodium::AEAD_TAG_LEN
|
||||
}
|
||||
|
||||
lense! { EmptyData :=
|
||||
data_lense! { EmptyData :=
|
||||
/// Copied from RespHello
|
||||
sid: 4,
|
||||
/// Nonce
|
||||
ctr: 8,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
auth: aead::TAG_LEN
|
||||
auth: sodium::AEAD_TAG_LEN
|
||||
}
|
||||
|
||||
lense! { Biscuit :=
|
||||
data_lense! { Biscuit :=
|
||||
/// H(spki) – Ident ifies the initiator
|
||||
pidi: KEY_LEN,
|
||||
pidi: sodium::KEY_SIZE,
|
||||
/// The biscuit number (replay protection)
|
||||
biscuit_no: 12,
|
||||
/// Chaining key
|
||||
ck: KEY_LEN
|
||||
ck: sodium::KEY_SIZE
|
||||
}
|
||||
|
||||
lense! { DataMsg :=
|
||||
data_lense! { DataMsg :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
lense! { CookieReply :=
|
||||
data_lense! { CookieReply :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
@@ -181,28 +384,30 @@ impl TryFrom<u8> for MsgType {
|
||||
pub const BISCUIT_PT_LEN: usize = Biscuit::<()>::LEN;
|
||||
|
||||
/// Length in bytes of an encrypted Biscuit (cipher text)
|
||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN;
|
||||
pub const BISCUIT_CT_LEN: usize = BISCUIT_PT_LEN + sodium::XAEAD_NONCE_LEN + sodium::XAEAD_TAG_LEN;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_constants {
|
||||
use crate::msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN};
|
||||
use rosenpass_ciphers::{xaead, KEY_LEN};
|
||||
use crate::{
|
||||
msgs::{BISCUIT_CT_LEN, BISCUIT_PT_LEN},
|
||||
sodium,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn sodium_keysize() {
|
||||
assert_eq!(KEY_LEN, 32);
|
||||
assert_eq!(sodium::KEY_SIZE, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn biscuit_pt_len() {
|
||||
assert_eq!(BISCUIT_PT_LEN, 2 * KEY_LEN + 12);
|
||||
assert_eq!(BISCUIT_PT_LEN, 2 * sodium::KEY_SIZE + 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn biscuit_ct_len() {
|
||||
assert_eq!(
|
||||
BISCUIT_CT_LEN,
|
||||
BISCUIT_PT_LEN + xaead::NONCE_LEN + xaead::TAG_LEN
|
||||
BISCUIT_PT_LEN + sodium::XAEAD_NONCE_LEN + sodium::XAEAD_TAG_LEN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
108
rosenpass/src/prftree.rs
Normal file
108
rosenpass/src/prftree.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
|
||||
use {
|
||||
crate::{
|
||||
coloring::Secret,
|
||||
sodium::{hmac, hmac_into, KEY_SIZE},
|
||||
},
|
||||
anyhow::Result,
|
||||
};
|
||||
|
||||
// TODO Use a proper Dec interface
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrfTree([u8; KEY_SIZE]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrfTreeBranch([u8; KEY_SIZE]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretPrfTree(Secret<KEY_SIZE>);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretPrfTreeBranch(Secret<KEY_SIZE>);
|
||||
|
||||
impl PrfTree {
|
||||
pub fn zero() -> Self {
|
||||
Self([0u8; KEY_SIZE])
|
||||
}
|
||||
|
||||
pub fn dup(self) -> PrfTreeBranch {
|
||||
PrfTreeBranch(self.0)
|
||||
}
|
||||
|
||||
pub fn into_secret_prf_tree(self) -> SecretPrfTree {
|
||||
SecretPrfTree(Secret::from_slice(&self.0))
|
||||
}
|
||||
|
||||
// TODO: Protocol! Use domain separation to ensure that
|
||||
pub fn mix(self, v: &[u8]) -> Result<Self> {
|
||||
Ok(Self(hmac(&self.0, v)?))
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
|
||||
SecretPrfTree::prf_invoc(&self.0, v.secret())
|
||||
}
|
||||
|
||||
pub fn into_value(self) -> [u8; KEY_SIZE] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PrfTreeBranch {
|
||||
pub fn mix(&self, v: &[u8]) -> Result<PrfTree> {
|
||||
Ok(PrfTree(hmac(&self.0, v)?))
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
|
||||
SecretPrfTree::prf_invoc(&self.0, v.secret())
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretPrfTree {
|
||||
pub fn prf_invoc(k: &[u8], d: &[u8]) -> Result<SecretPrfTree> {
|
||||
let mut r = SecretPrfTree(Secret::zero());
|
||||
hmac_into(r.0.secret_mut(), k, d)?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self(Secret::zero())
|
||||
}
|
||||
|
||||
pub fn dup(self) -> SecretPrfTreeBranch {
|
||||
SecretPrfTreeBranch(self.0)
|
||||
}
|
||||
|
||||
pub fn danger_from_secret(k: Secret<KEY_SIZE>) -> Self {
|
||||
Self(k)
|
||||
}
|
||||
|
||||
pub fn mix(self, v: &[u8]) -> Result<SecretPrfTree> {
|
||||
Self::prf_invoc(self.0.secret(), v)
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretPrfTree> {
|
||||
Self::prf_invoc(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
pub fn into_secret(self) -> Secret<KEY_SIZE> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
|
||||
hmac_into(self.0.secret_mut(), v, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretPrfTreeBranch {
|
||||
pub fn mix(&self, v: &[u8]) -> Result<SecretPrfTree> {
|
||||
SecretPrfTree::prf_invoc(self.0.secret(), v)
|
||||
}
|
||||
|
||||
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretPrfTree> {
|
||||
SecretPrfTree::prf_invoc(self.0.secret(), v.secret())
|
||||
}
|
||||
|
||||
// TODO: This entire API is not very nice; we need this for biscuits, but
|
||||
// it might be better to extract a special "biscuit"
|
||||
// labeled subkey and reinitialize the chain with this
|
||||
pub fn danger_into_secret(self) -> Secret<KEY_SIZE> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
204
rosenpass/src/primitives/kmac.rs
Normal file
204
rosenpass/src/primitives/kmac.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use std::result::Result;
|
||||
use digest::{Update, XofReader};
|
||||
use crate::util::types::Leftright;
|
||||
use crate::util::io::{WriteSecret, CountAndWriteSecret};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::util::io::assemble_secret;
|
||||
|
||||
/// Variable length encoding for unsigned numbers as specified by in
|
||||
/// NIST Special Publication 800-185.
|
||||
///
|
||||
/// This corresponds to left_encode(…) if `lr == Leftright::Left`
|
||||
/// and to right_encode(…) if `lr == Leftright::Right`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if the number `v` is greater than $2^{2040}-1$,
|
||||
/// i.e. if more than 255 bits are required to represent the number.
|
||||
///
|
||||
/// For the natively supported integers (u8…u128 and usize), this will not panic
|
||||
/// unless usize is an u256 now.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use crate::util::types::Leftright::*;
|
||||
///
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0)), assemble_secret(|w| left_encode(w, 0)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 1)), assemble_secret(|w| left_encode(w, 1)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0xff)), assemble_secret(|w| left_encode(w, 0xff)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0x100)), assemble_secret(|w| left_encode(w, 0x100)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Left, w, 0x010203)), assemble_secret(|w| left_encode(w, 0x010203)));
|
||||
///
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0)), assemble_secret(|w| right_encode(w, 0)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 1)), assemble_secret(|w| right_encode(w, 1)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0xff)), assemble_secret(|w| right_encode(w, 0xff)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0x100)), assemble_secret(|w| right_encode(w, 0x100)));
|
||||
/// assert_eq!(assemble_secret(|w| leftright_encode(Right, w, 0x010203)), assemble_secret(|w| right_encode(w, 0x010203)));
|
||||
/// ```
|
||||
pub(crate) fn leftright_encode<W, N>(lr: Leftright, dst: W, v: &N)
|
||||
-> Result<(), W::Error>
|
||||
where
|
||||
W: WriteSecret,
|
||||
N: ToBytes {
|
||||
use Leftright::*;
|
||||
let be = v.to_be_bytes();
|
||||
let trailing_zeros = be.rev().take_while(|v| v == 0).count();
|
||||
let num_bytes = std::max(1, be.len() - trailing_zeros);
|
||||
assert!(num_bytes < 256);
|
||||
match lr {
|
||||
Left => {
|
||||
dst.write(from_ref(num_bytes.into()))?;
|
||||
dst.write(&be[..num_bytes])?;
|
||||
},
|
||||
Right => {
|
||||
dst.write(&be[..num_bytes])?;
|
||||
dst.write(from_ref(num_bytes.into()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Variable length encoding for unsigned numbers as specified by in
|
||||
/// NIST Special Publication 800-185.
|
||||
///
|
||||
/// First writes a single byte indication the width of the
|
||||
/// encoded number to `dst` then writes the number in big-endian
|
||||
/// format.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if the number `v` is greater than $2^{2040}-1$,
|
||||
/// i.e. if more than 255 bits are required to represent the number.
|
||||
///
|
||||
/// For the natively supported integers (u8…u128 and usize), this will not panic
|
||||
/// unless usize is an u256 now.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0)), b"\1\0");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 1)), b"\1\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0xff)), b"\1\0xff");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x100)), b"\1\0\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x010203)), b"\3\1\2\3");
|
||||
/// ```
|
||||
pub(crate) fn left_encode<W, N>(dst: W, v: &N)
|
||||
-> Result<(), W::Error>
|
||||
where
|
||||
W: Write,
|
||||
N: ToBytes {
|
||||
nist_leftright_encode(Leftright::Left, dst, v)
|
||||
}
|
||||
|
||||
/// Variable length encoding for unsigned numbers as specified by in
|
||||
/// NIST Special Publication 800-185.
|
||||
///
|
||||
/// This uses the same format as `left_encode` but swaps length tag
|
||||
/// and then length of the number itself.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This will panic if the number `v` is greater than $2^{2040}-1$,
|
||||
/// i.e. if more than 255 bits are required to represent the number.
|
||||
///
|
||||
/// For the natively supported integers (u8…u128 and usize), this will not panic
|
||||
/// unless usize is an u256 now.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0)), b"\0\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 1)), b"\1\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0xff)), b"\0xff\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x100)), b"\0\1\1");
|
||||
/// assert_eq!(assemble_secret(|w| left_encode(w, 0x010203)), b"\1\2\3\3");
|
||||
/// ```
|
||||
pub(crate) fn right_encode<W, N>(dst: &mut W, v: &N)
|
||||
-> Result<(), W::Error>
|
||||
where
|
||||
W: WriteSecret,
|
||||
N: ToBytes {
|
||||
nist_leftright_encode(Leftright::Right, dst, v)
|
||||
}
|
||||
|
||||
/// Serialization for variable length strings as specified by in
|
||||
/// NIST Special Publication 800-185.
|
||||
///
|
||||
/// This first writes the length of the string using `left_encode` to `dst`
|
||||
/// then copies the string itself to `dst`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(assemble_secret(|w| encode_string(w, b"")), b"\1\0");
|
||||
/// assert_eq!(assemble_secret(|w| encode_string(w, b"\0")), b"\1\1\0");
|
||||
/// assert_eq!(assemble_secret(|w| encode_string(w, b"Hello")), b"\1\5Hello");
|
||||
/// assert_eq!(assemble_secret(|w| encode_string(w, b"Hello World")), b"\1\x0AHello World");
|
||||
/// ```
|
||||
pub(crate) fn encode_string<W>(dst: W, str: &[u8])
|
||||
-> Result<(), W::Error>
|
||||
where
|
||||
W: WriteSecret {
|
||||
/// This will not panic unless `usize` is an u256 now.
|
||||
left_encode(&mut dst, str.len())?;
|
||||
w.write_all(str)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serialize arbitrary data and then pad the output using zero bytes
|
||||
/// as specified NIST Special Publication 800-185.
|
||||
///
|
||||
/// This first writes the width of the padding unit `pad_to` to `dst`
|
||||
/// and then calls `f` to serialize arbitrary data. Finally, this function
|
||||
/// writes null bytes until the total number of bytes written is a multiple
|
||||
/// of `pad_to`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| )), b"\1\5\0\0\0");
|
||||
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"Hello"))), b"\1\5Hello\0\0\0");
|
||||
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"Hello dearie!"))), b"\1\5Hello dearie!");
|
||||
/// assert_eq!(assemble_secret(|w| bytepad(w, 5, |w| w.write_all(b"_"))), b"\1\5_\0\0");
|
||||
/// ```
|
||||
pub(crate) fn bytepad<W, Fn>(dst: W, pad_to: u64, f: Fn)
|
||||
-> Result<(), W::Error>
|
||||
where
|
||||
W: WriteSecret,
|
||||
Fn: FnOnce<CountAndWrite> {
|
||||
let w = CountAndWrite::new(dst);
|
||||
nist_left_encode(&mut w, pad_to)?;
|
||||
f(w)?;
|
||||
for _ in ..ceiling_remainder(w2.get_count(), pad_to) {
|
||||
w.write_all(from_ref(0u8.into()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
#[inline]
|
||||
pub(crate) fn kmac256(out: &mut [u8], key: &[u8], data: &[u8]) {
|
||||
// A proper implementation of KMAC is currently not available in the sha3 crate, but they do
|
||||
// provide cSHAKE which can be used to implement KMAC
|
||||
//
|
||||
// Issue:
|
||||
// https://github.com/RustCrypto/MACs/issues/133
|
||||
// "kmac: Towards an implementation"
|
||||
//
|
||||
// KMAC Spec:
|
||||
// https://doi.org/10.6028/NIST.SP.800-185
|
||||
// "SHA-3 derived functions: cSHAKE, KMAC, TupleHash, and ParallelHash"
|
||||
// Page 10
|
||||
use sha3::{CShake256, CShake256Core};
|
||||
|
||||
let hasher = CShake256::from_core(
|
||||
CShake256Core::new_with_function_name(&"", &"KMAC"));
|
||||
|
||||
bytepad(hasher, 168, |w| encode_string(w, key)).guaranteed();
|
||||
hasher.update(data);
|
||||
right_encode(hasher, out.len()).guaranteed();
|
||||
|
||||
hasher.finalize_xof_into(out);
|
||||
}
|
||||
9
rosenpass/src/primitives/mod.rs
Normal file
9
rosenpass/src/primitives/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Symmetric primitives & Libsodium bindings
|
||||
|
||||
pub(crate) mod sodium;
|
||||
pub(crate) mod kmac;
|
||||
pub(crate) mod kem;
|
||||
|
||||
pub(crate) use sodium::*;
|
||||
pub(crate) use kmac::kmac256;
|
||||
pub(crate) use kem::{KEM, StaticKEM, EphemeralKEM};
|
||||
265
rosenpass/src/primitives/sodium.rs
Normal file
265
rosenpass/src/primitives/sodium.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
//! bindings and helpers for accessing libsodium functions
|
||||
|
||||
use crate::util::*;
|
||||
use anyhow::{ensure, Result};
|
||||
use libsodium_sys as libsodium;
|
||||
use log::trace;
|
||||
use static_assertions::const_assert_eq;
|
||||
use std::slice::from_ref;
|
||||
use std::os::raw::{c_ulonglong, c_void};
|
||||
use std::ptr::{null as nullptr, null_mut as nullptr_mut};
|
||||
use std::io::Write;
|
||||
use std::cmp::max;
|
||||
use num_traits::{Zero, ToBytes};
|
||||
|
||||
pub const AEAD_TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
|
||||
pub const AEAD_NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
|
||||
pub const XAEAD_TAG_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_ietf_ABYTES as usize;
|
||||
pub const XAEAD_NONCE_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_NPUBBYTES as usize;
|
||||
pub const NONCE0: [u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize] =
|
||||
[0u8; libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize];
|
||||
pub const NOTHING: [u8; 0] = [0u8; 0];
|
||||
pub const KEY_SIZE: usize = 32;
|
||||
pub const MAC_SIZE: usize = 16;
|
||||
|
||||
const_assert_eq!(
|
||||
KEY_SIZE,
|
||||
libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize
|
||||
);
|
||||
const_assert_eq!(KEY_SIZE, libsodium::crypto_generichash_BYTES as usize);
|
||||
|
||||
macro_rules! sodium_call {
|
||||
($name:ident, $($args:expr),*) => { attempt!({
|
||||
ensure!(unsafe{libsodium::$name($($args),*)} > -1,
|
||||
"Error in libsodium's {}.", stringify!($name));
|
||||
Ok(())
|
||||
})};
|
||||
($name:ident) => { sodium_call!($name, ) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sodium_init() -> Result<()> {
|
||||
trace!("initializing libsodium");
|
||||
sodium_call!(sodium_init)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sodium_memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len()
|
||||
&& unsafe {
|
||||
let r = libsodium::sodium_memcmp(
|
||||
a.as_ptr() as *const c_void,
|
||||
b.as_ptr() as *const c_void,
|
||||
a.len(),
|
||||
);
|
||||
r == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sodium_bigint_cmp(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { libsodium::sodium_compare(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sodium_bigint_inc(v: &mut [u8]) {
|
||||
unsafe {
|
||||
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rng(buf: &mut [u8]) {
|
||||
unsafe { libsodium::randombytes_buf(buf.as_mut_ptr() as *mut c_void, buf.len()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn zeroize(buf: &mut [u8]) {
|
||||
unsafe { libsodium::sodium_memzero(buf.as_mut_ptr() as *mut c_void, buf.len()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn aead_enc_into(
|
||||
ciphertext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
plaintext: &[u8],
|
||||
) -> Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + AEAD_TAG_LEN);
|
||||
assert!(key.len() == libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize);
|
||||
assert!(nonce.len() == libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize);
|
||||
let mut clen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_chacha20poly1305_ietf_encrypt,
|
||||
ciphertext.as_mut_ptr(),
|
||||
&mut clen,
|
||||
plaintext.as_ptr(),
|
||||
plaintext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
nullptr(), // nsec is not used
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(clen as usize == ciphertext.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn aead_dec_into(
|
||||
plaintext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + AEAD_TAG_LEN);
|
||||
assert!(key.len() == libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize);
|
||||
assert!(nonce.len() == libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize);
|
||||
let mut mlen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_chacha20poly1305_ietf_decrypt,
|
||||
plaintext.as_mut_ptr(),
|
||||
&mut mlen as *mut c_ulonglong,
|
||||
nullptr_mut(), // nsec is not used
|
||||
ciphertext.as_ptr(),
|
||||
ciphertext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(mlen as usize == plaintext.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn xaead_enc_into(
|
||||
ciphertext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
plaintext: &[u8],
|
||||
) -> Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + XAEAD_NONCE_LEN + XAEAD_TAG_LEN);
|
||||
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
|
||||
let (n, ct) = ciphertext.split_at_mut(XAEAD_NONCE_LEN);
|
||||
n.copy_from_slice(nonce);
|
||||
let mut clen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_xchacha20poly1305_ietf_encrypt,
|
||||
ct.as_mut_ptr(),
|
||||
&mut clen,
|
||||
plaintext.as_ptr(),
|
||||
plaintext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
nullptr(), // nsec is not used
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(clen as usize == ct.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn xaead_dec_into(
|
||||
plaintext: &mut [u8],
|
||||
key: &[u8],
|
||||
ad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + XAEAD_NONCE_LEN + XAEAD_TAG_LEN);
|
||||
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
|
||||
let (n, ct) = ciphertext.split_at(XAEAD_NONCE_LEN);
|
||||
let mut mlen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_xchacha20poly1305_ietf_decrypt,
|
||||
plaintext.as_mut_ptr(),
|
||||
&mut mlen as *mut c_ulonglong,
|
||||
nullptr_mut(), // nsec is not used
|
||||
ct.as_ptr(),
|
||||
ct.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
n.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(mlen as usize == plaintext.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Use proper streaming hash; for mix_hash too.
|
||||
#[inline]
|
||||
pub fn hash_into(out: &mut [u8], data: &[u8]) -> Result<()> {
|
||||
assert!(out.len() == KEY_SIZE);
|
||||
blake2b_flexible(out, &NOTHING, data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hash(data: &[u8]) -> Result<[u8; KEY_SIZE]> {
|
||||
let mut r = [0u8; KEY_SIZE];
|
||||
hash_into(&mut r, data)?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
|
||||
assert!(out.len() == KEY_SIZE);
|
||||
assert!(key.len() == KEY_SIZE);
|
||||
blake2b_flexible(out, key, data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> {
|
||||
let mut r = [0u8; KEY_SIZE];
|
||||
mac_into(&mut r, key, data)?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mac16(key: &[u8], data: &[u8]) -> Result<[u8; 16]> {
|
||||
assert!(key.len() == KEY_SIZE);
|
||||
let mut out = [0u8; 16];
|
||||
blake2b_flexible(&mut out, key, data)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hmac_into(out: &mut [u8], key: &[u8], data: &[u8]) -> Result<()> {
|
||||
// Not bothering with padding; the implementation
|
||||
// uses appropriately sized keys.
|
||||
ensure!(key.len() == KEY_SIZE);
|
||||
|
||||
const IPAD: [u8; KEY_SIZE] = [0x36u8; KEY_SIZE];
|
||||
let mut temp_key = [0u8; KEY_SIZE];
|
||||
temp_key.copy_from_slice(key);
|
||||
xor_into(&mut temp_key, &IPAD);
|
||||
let outer_data = mac(&temp_key, data)?;
|
||||
|
||||
const OPAD: [u8; KEY_SIZE] = [0x5Cu8; KEY_SIZE];
|
||||
temp_key.copy_from_slice(key);
|
||||
xor_into(&mut temp_key, &OPAD);
|
||||
mac_into(out, &temp_key, &outer_data)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hmac(key: &[u8], data: &[u8]) -> Result<[u8; KEY_SIZE]> {
|
||||
let mut r = [0u8; KEY_SIZE];
|
||||
hmac_into(&mut r, key, data)?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
// Choose a fully random u64
|
||||
pub fn rand_u64() -> u64 {
|
||||
let mut buf = [0u8; 8];
|
||||
rng(&mut buf);
|
||||
u64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
// Choose a random f64 in [0; 1] inclusive; quick and dirty
|
||||
pub fn rand_f64() -> f64 {
|
||||
(rand_u64() as f64) / (u64::MAX as f64)
|
||||
}
|
||||
@@ -19,23 +19,22 @@
|
||||
//! [CryptoServer].
|
||||
//!
|
||||
//! ```
|
||||
//! use rosenpass_cipher_traits::Kem;
|
||||
//! use rosenpass_ciphers::kem::StaticKem;
|
||||
//! use rosenpass::{
|
||||
//! pqkem::{StaticKEM, KEM},
|
||||
//! protocol::{SSk, SPk, MsgBuf, PeerPtr, CryptoServer, SymKey},
|
||||
//! };
|
||||
//! # fn main() -> anyhow::Result<()> {
|
||||
//!
|
||||
//! // always initialize libsodium before anything
|
||||
//! rosenpass_sodium::init()?;
|
||||
//! // always init libsodium before anything
|
||||
//! rosenpass::sodium::sodium_init()?;
|
||||
//!
|
||||
//! // initialize secret and public key for peer a ...
|
||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
||||
//! StaticKem::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
|
||||
//! StaticKEM::keygen(peer_a_sk.secret_mut(), peer_a_pk.secret_mut())?;
|
||||
//!
|
||||
//! // ... and for peer b
|
||||
//! let (mut peer_b_sk, mut peer_b_pk) = (SSk::zero(), SPk::zero());
|
||||
//! StaticKem::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
|
||||
//! StaticKEM::keygen(peer_b_sk.secret_mut(), peer_b_pk.secret_mut())?;
|
||||
//!
|
||||
//! // initialize server and a pre-shared key
|
||||
//! let psk = SymKey::random();
|
||||
@@ -68,20 +67,20 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::{hash_domains, msgs::*};
|
||||
use crate::{
|
||||
coloring::*,
|
||||
labeled_prf as lprf,
|
||||
msgs::*,
|
||||
pqkem::*,
|
||||
prftree::{SecretPrfTree, SecretPrfTreeBranch},
|
||||
sodium::*,
|
||||
util::*,
|
||||
};
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace};
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
use rosenpass_lenses::LenseView;
|
||||
use rosenpass_secret_memory::{Public, Secret};
|
||||
use rosenpass_util::{cat, mem::cpy_min, ord::max_usize, time::Timebase};
|
||||
use std::collections::hash_map::{
|
||||
Entry::{Occupied, Vacant},
|
||||
HashMap,
|
||||
};
|
||||
use std::convert::Infallible;
|
||||
|
||||
// CONSTANTS & SETTINGS //////////////////////////
|
||||
|
||||
@@ -104,7 +103,7 @@ pub const BCE: Timing = -3600.0 * 24.0 * 356.0 * 10_000.0;
|
||||
// regarding unexpectedly large numbers in system APIs as this is < i16::MAX
|
||||
pub const UNENDING: Timing = 3600.0 * 8.0;
|
||||
|
||||
// From the wireguard paper; rekey every two minutes,
|
||||
// Rekey every two minutes,
|
||||
// discard the key if no rekey is achieved within three
|
||||
pub const REKEY_AFTER_TIME_RESPONDER: Timing = 120.0;
|
||||
pub const REKEY_AFTER_TIME_INITIATOR: Timing = 130.0;
|
||||
@@ -140,19 +139,19 @@ pub fn has_happened(ev: Timing, now: Timing) -> bool {
|
||||
|
||||
// DATA STRUCTURES & BASIC TRAITS & ACCESSORS ////
|
||||
|
||||
pub type SPk = Secret<{ StaticKem::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
|
||||
pub type SSk = Secret<{ StaticKem::SK_LEN }>;
|
||||
pub type EPk = Public<{ EphemeralKem::PK_LEN }>;
|
||||
pub type ESk = Secret<{ EphemeralKem::SK_LEN }>;
|
||||
pub type SPk = Secret<{ StaticKEM::PK_LEN }>; // Just Secret<> instead of Public<> so it gets allocated on the heap
|
||||
pub type SSk = Secret<{ StaticKEM::SK_LEN }>;
|
||||
pub type EPk = Public<{ EphemeralKEM::PK_LEN }>;
|
||||
pub type ESk = Secret<{ EphemeralKEM::SK_LEN }>;
|
||||
|
||||
pub type SymKey = Secret<KEY_LEN>;
|
||||
pub type SymHash = Public<KEY_LEN>;
|
||||
pub type SymKey = Secret<KEY_SIZE>;
|
||||
pub type SymHash = Public<KEY_SIZE>;
|
||||
|
||||
pub type PeerId = Public<KEY_LEN>;
|
||||
pub type PeerId = Public<KEY_SIZE>;
|
||||
pub type SessionId = Public<SESSION_ID_LEN>;
|
||||
pub type BiscuitId = Public<BISCUIT_ID_LEN>;
|
||||
|
||||
pub type XAEADNonce = Public<{ xaead::NONCE_LEN }>;
|
||||
pub type XAEADNonce = Public<XAEAD_NONCE_LEN>;
|
||||
|
||||
pub type MsgBuf = Public<MAX_MESSAGE_LEN>;
|
||||
|
||||
@@ -234,7 +233,7 @@ pub struct HandshakeState {
|
||||
/// Session ID of Responder
|
||||
pub sidr: SessionId,
|
||||
/// Chaining Key
|
||||
pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr
|
||||
pub ck: SecretPrfTreeBranch,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
|
||||
@@ -286,7 +285,7 @@ pub struct Session {
|
||||
pub sidt: SessionId,
|
||||
pub handshake_role: HandshakeRole,
|
||||
// Crypto
|
||||
pub ck: SecretHashDomainNamespace,
|
||||
pub ck: SecretPrfTreeBranch,
|
||||
/// Key for Transmission ("transmission key mine")
|
||||
pub txkm: SymKey,
|
||||
/// Key for Reception ("transmission key theirs")
|
||||
@@ -461,7 +460,7 @@ impl CryptoServer {
|
||||
#[rustfmt::skip]
|
||||
pub fn pidm(&self) -> Result<PeerId> {
|
||||
Ok(Public::new(
|
||||
hash_domains::peerid()?
|
||||
lprf::peerid()?
|
||||
.mix(self.spkm.secret())?
|
||||
.into_value()))
|
||||
}
|
||||
@@ -591,7 +590,7 @@ impl Peer {
|
||||
#[rustfmt::skip]
|
||||
pub fn pidt(&self) -> Result<PeerId> {
|
||||
Ok(Public::new(
|
||||
hash_domains::peerid()?
|
||||
lprf::peerid()?
|
||||
.mix(self.spkt.secret())?
|
||||
.into_value()))
|
||||
}
|
||||
@@ -604,7 +603,7 @@ impl Session {
|
||||
sidm: SessionId::zero(),
|
||||
sidt: SessionId::zero(),
|
||||
handshake_role: HandshakeRole::Initiator,
|
||||
ck: SecretHashDomain::zero().dup(),
|
||||
ck: SecretPrfTree::zero().dup(),
|
||||
txkm: SymKey::zero(),
|
||||
txkt: SymKey::zero(),
|
||||
txnm: 0,
|
||||
@@ -1155,7 +1154,7 @@ impl IniHsPtr {
|
||||
.min(ih.tx_count as f64),
|
||||
)
|
||||
* RETRANSMIT_DELAY_JITTER
|
||||
* (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
|
||||
* (rand_f64() + 1.0);
|
||||
ih.tx_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1175,7 +1174,7 @@ where
|
||||
{
|
||||
/// Calculate the message authentication code (`mac`)
|
||||
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
|
||||
let mac = hash_domains::mac()?
|
||||
let mac = lprf::mac()?
|
||||
.mix(peer.get(srv).spkt.secret())?
|
||||
.mix(self.until_mac())?;
|
||||
self.mac_mut()
|
||||
@@ -1190,13 +1189,8 @@ where
|
||||
{
|
||||
/// Check the message authentication code
|
||||
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
|
||||
let expected = hash_domains::mac()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(self.until_mac())?;
|
||||
Ok(rosenpass_sodium::helpers::memcmp(
|
||||
self.mac(),
|
||||
&expected.into_value()[..16],
|
||||
))
|
||||
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?;
|
||||
Ok(sodium_memcmp(self.mac(), &expected.into_value()[..16]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1222,48 +1216,48 @@ impl HandshakeState {
|
||||
Self {
|
||||
sidi: SessionId::zero(),
|
||||
sidr: SessionId::zero(),
|
||||
ck: SecretHashDomain::zero().dup(),
|
||||
ck: SecretPrfTree::zero().dup(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn erase(&mut self) {
|
||||
self.ck = SecretHashDomain::zero().dup();
|
||||
self.ck = SecretPrfTree::zero().dup();
|
||||
}
|
||||
|
||||
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
|
||||
self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup();
|
||||
self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
|
||||
self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup();
|
||||
self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> {
|
||||
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
|
||||
aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?;
|
||||
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret();
|
||||
aead_enc_into(ct, k.secret(), &NONCE0, &NOTHING, pt)?;
|
||||
self.mix(ct)
|
||||
}
|
||||
|
||||
pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> {
|
||||
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
|
||||
aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?;
|
||||
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret();
|
||||
aead_dec_into(pt, k.secret(), &NONCE0, &NOTHING, ct)?;
|
||||
self.mix(ct)
|
||||
}
|
||||
|
||||
// I loathe "error: constant expression depends on a generic parameter"
|
||||
pub fn encaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
|
||||
pub fn encaps_and_mix<T: KEM, const SHK_LEN: usize>(
|
||||
&mut self,
|
||||
ct: &mut [u8],
|
||||
pk: &[u8],
|
||||
) -> Result<&mut Self> {
|
||||
let mut shk = Secret::<SHK_LEN>::zero();
|
||||
T::encaps(shk.secret_mut(), ct, pk)?;
|
||||
self.mix(pk)?.mix(shk.secret())?.mix(ct)
|
||||
self.mix(pk)?.mix(ct)?.mix(shk.secret())
|
||||
}
|
||||
|
||||
pub fn decaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
|
||||
pub fn decaps_and_mix<T: KEM, const SHK_LEN: usize>(
|
||||
&mut self,
|
||||
sk: &[u8],
|
||||
pk: &[u8],
|
||||
@@ -1271,7 +1265,7 @@ impl HandshakeState {
|
||||
) -> Result<&mut Self> {
|
||||
let mut shk = Secret::<SHK_LEN>::zero();
|
||||
T::decaps(shk.secret_mut(), sk, ct)?;
|
||||
self.mix(pk)?.mix(shk.secret())?.mix(ct)
|
||||
self.mix(pk)?.mix(ct)?.mix(shk.secret())
|
||||
}
|
||||
|
||||
pub fn store_biscuit(
|
||||
@@ -1293,14 +1287,14 @@ impl HandshakeState {
|
||||
.copy_from_slice(self.ck.clone().danger_into_secret().secret());
|
||||
|
||||
// calculate ad contents
|
||||
let ad = hash_domains::biscuit_ad()?
|
||||
let ad = lprf::biscuit_ad()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(self.sidi.as_slice())?
|
||||
.mix(self.sidr.as_slice())?
|
||||
.into_value();
|
||||
|
||||
// consume biscuit no
|
||||
rosenpass_sodium::helpers::increment(&mut *srv.biscuit_ctr);
|
||||
sodium_bigint_inc(&mut *srv.biscuit_ctr);
|
||||
|
||||
// The first bit of the nonce indicates which biscuit key was used
|
||||
// TODO: This is premature optimization. Remove!
|
||||
@@ -1311,7 +1305,7 @@ impl HandshakeState {
|
||||
|
||||
let k = bk.get(srv).key.secret();
|
||||
let pt = biscuit.all_bytes();
|
||||
xaead::encrypt(biscuit_ct, k, &*n, &ad, pt)?;
|
||||
xaead_enc_into(biscuit_ct, k, &*n, &ad, pt)?;
|
||||
|
||||
self.mix(biscuit_ct)
|
||||
}
|
||||
@@ -1328,7 +1322,7 @@ impl HandshakeState {
|
||||
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
|
||||
|
||||
// Calculate additional data fields
|
||||
let ad = hash_domains::biscuit_ad()?
|
||||
let ad = lprf::biscuit_ad()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(sidi.as_slice())?
|
||||
.mix(sidr.as_slice())?
|
||||
@@ -1337,7 +1331,7 @@ impl HandshakeState {
|
||||
// Allocate and decrypt the biscuit data
|
||||
let mut biscuit = Secret::<BISCUIT_PT_LEN>::zero(); // pt buf
|
||||
let mut biscuit = (&mut biscuit.secret_mut()[..]).biscuit()?; // slice
|
||||
xaead::decrypt(
|
||||
xaead_dec_into(
|
||||
biscuit.all_bytes_mut(),
|
||||
bk.get(srv).key.secret(),
|
||||
&ad,
|
||||
@@ -1346,7 +1340,7 @@ impl HandshakeState {
|
||||
|
||||
// Reconstruct the biscuit fields
|
||||
let no = BiscuitId::from_slice(biscuit.biscuit_no());
|
||||
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
|
||||
let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
|
||||
let pid = PeerId::from_slice(biscuit.pidi());
|
||||
|
||||
// Reconstruct the handshake state
|
||||
@@ -1363,8 +1357,7 @@ impl HandshakeState {
|
||||
// indicates retransmission
|
||||
// TODO: Handle retransmissions without involving the crypto code
|
||||
ensure!(
|
||||
rosenpass_sodium::helpers::compare(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used)
|
||||
>= 0,
|
||||
sodium_bigint_cmp(biscuit.biscuit_no(), &*peer.get(srv).biscuit_used) >= 0,
|
||||
"Rejecting biscuit: Outdated biscuit number"
|
||||
);
|
||||
|
||||
@@ -1373,8 +1366,8 @@ impl HandshakeState {
|
||||
|
||||
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
|
||||
let HandshakeState { ck, sidi, sidr } = self;
|
||||
let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret();
|
||||
let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret();
|
||||
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret();
|
||||
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret();
|
||||
let created_at = srv.timebase.now();
|
||||
let (ntx, nrx) = (0, 0);
|
||||
let (mysid, peersid, ktx, krx) = match role {
|
||||
@@ -1405,7 +1398,7 @@ impl CryptoServer {
|
||||
.get(self)
|
||||
.as_ref()
|
||||
.with_context(|| format!("No current session for peer {:?}", peer))?;
|
||||
Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret())
|
||||
Ok(session.ck.mix(&lprf::osk()?)?.into_secret())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1427,7 +1420,7 @@ impl CryptoServer {
|
||||
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
|
||||
|
||||
// IHI3
|
||||
EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
|
||||
EphemeralKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
|
||||
ih.epki_mut().copy_from_slice(&hs.epki.value);
|
||||
|
||||
// IHI4
|
||||
@@ -1435,7 +1428,7 @@ impl CryptoServer {
|
||||
|
||||
// IHI5
|
||||
hs.core
|
||||
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
|
||||
ih.sctr_mut(),
|
||||
peer.get(self).spkt.secret(),
|
||||
)?;
|
||||
@@ -1450,7 +1443,7 @@ impl CryptoServer {
|
||||
.mix(peer.get(self).psk.secret())?;
|
||||
|
||||
// IHI8
|
||||
hs.core.encrypt_and_mix(ih.auth_mut(), &[])?;
|
||||
hs.core.encrypt_and_mix(ih.auth_mut(), &NOTHING)?;
|
||||
|
||||
// Update the handshake hash last (not changing any state on prior error
|
||||
peer.hs().insert(self, hs)?;
|
||||
@@ -1474,7 +1467,7 @@ impl CryptoServer {
|
||||
core.mix(ih.sidi())?.mix(ih.epki())?;
|
||||
|
||||
// IHR5
|
||||
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
|
||||
self.sskm.secret(),
|
||||
self.spkm.secret(),
|
||||
ih.sctr(),
|
||||
@@ -1504,10 +1497,10 @@ impl CryptoServer {
|
||||
core.mix(rh.sidr())?.mix(rh.sidi())?;
|
||||
|
||||
// RHR4
|
||||
core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
|
||||
core.encaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(rh.ecti_mut(), ih.epki())?;
|
||||
|
||||
// RHR5
|
||||
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
core.encaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
|
||||
rh.scti_mut(),
|
||||
peer.get(self).spkt.secret(),
|
||||
)?;
|
||||
@@ -1516,7 +1509,7 @@ impl CryptoServer {
|
||||
core.store_biscuit(self, peer, rh.biscuit_mut())?;
|
||||
|
||||
// RHR7
|
||||
core.encrypt_and_mix(rh.auth_mut(), &[])?;
|
||||
core.encrypt_and_mix(rh.auth_mut(), &NOTHING)?;
|
||||
|
||||
Ok(peer)
|
||||
}
|
||||
@@ -1572,14 +1565,14 @@ impl CryptoServer {
|
||||
core.mix(rh.sidr())?.mix(rh.sidi())?;
|
||||
|
||||
// RHI4
|
||||
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
|
||||
core.decaps_and_mix::<EphemeralKEM, { EphemeralKEM::SHK_LEN }>(
|
||||
hs!().eski.secret(),
|
||||
&*hs!().epki,
|
||||
rh.ecti(),
|
||||
)?;
|
||||
|
||||
// RHI5
|
||||
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
|
||||
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
|
||||
self.sskm.secret(),
|
||||
self.spkm.secret(),
|
||||
rh.scti(),
|
||||
@@ -1602,7 +1595,7 @@ impl CryptoServer {
|
||||
ic.biscuit_mut().copy_from_slice(rh.biscuit());
|
||||
|
||||
// ICI4
|
||||
core.encrypt_and_mix(ic.auth_mut(), &[])?;
|
||||
core.encrypt_and_mix(ic.auth_mut(), &NOTHING)?;
|
||||
|
||||
// Split() – We move the secrets into the session; we do not
|
||||
// delete the InitiatorHandshake, just clear it's secrets because
|
||||
@@ -1611,6 +1604,10 @@ impl CryptoServer {
|
||||
// ICI7
|
||||
peer.session()
|
||||
.insert(self, core.enter_live(self, HandshakeRole::Initiator)?)?;
|
||||
|
||||
// RHI8
|
||||
hs_mut!().eski.zeroize();
|
||||
|
||||
hs_mut!().core.erase();
|
||||
hs_mut!().next = HandshakeStateMachine::RespConf;
|
||||
|
||||
@@ -1632,7 +1629,7 @@ impl CryptoServer {
|
||||
)?;
|
||||
|
||||
// ICR2
|
||||
core.encrypt_and_mix(&mut [0u8; aead::TAG_LEN], &[])?;
|
||||
core.encrypt_and_mix(&mut [0u8; AEAD_TAG_LEN], &NOTHING)?;
|
||||
|
||||
// ICR3
|
||||
core.mix(ic.sidi())?.mix(ic.sidr())?;
|
||||
@@ -1641,7 +1638,7 @@ impl CryptoServer {
|
||||
core.decrypt_and_mix(&mut [0u8; 0], ic.auth())?;
|
||||
|
||||
// ICR5
|
||||
if rosenpass_sodium::helpers::compare(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 {
|
||||
if sodium_bigint_cmp(&*biscuit_no, &*peer.get(self).biscuit_used) > 0 {
|
||||
// ICR6
|
||||
peer.get_mut(self).biscuit_used = biscuit_no;
|
||||
|
||||
@@ -1686,9 +1683,9 @@ impl CryptoServer {
|
||||
rc.ctr_mut().copy_from_slice(&ses.txnm.to_le_bytes());
|
||||
ses.txnm += 1; // Increment nonce before encryption, just in case an error is raised
|
||||
|
||||
let n = cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]);
|
||||
let n = cat!(AEAD_NONCE_LEN; rc.ctr(), &[0u8; 4]);
|
||||
let k = ses.txkm.secret();
|
||||
aead::encrypt(rc.auth_mut(), k, &n, &[], &[])?; // ct, k, n, ad, pt
|
||||
aead_enc_into(rc.auth_mut(), k, &n, &NOTHING, &NOTHING)?; // ct, k, n, ad, pt
|
||||
|
||||
Ok(peer)
|
||||
}
|
||||
@@ -1720,12 +1717,12 @@ impl CryptoServer {
|
||||
let n = u64::from_le_bytes(rc.ctr().try_into().unwrap());
|
||||
ensure!(n >= s.txnt, "Stale nonce");
|
||||
s.txnt = n;
|
||||
aead::decrypt(
|
||||
aead_dec_into(
|
||||
// pt, k, n, ad, ct
|
||||
&mut [0u8; 0],
|
||||
s.txkt.secret(),
|
||||
&cat!(aead::NONCE_LEN; rc.ctr(), &[0u8; 4]),
|
||||
&[],
|
||||
&cat!(AEAD_NONCE_LEN; rc.ctr(), &[0u8; 4]),
|
||||
&NOTHING,
|
||||
rc.auth(),
|
||||
)?;
|
||||
}
|
||||
@@ -1757,7 +1754,7 @@ mod test {
|
||||
/// Through all this, the handshake should still successfully terminate;
|
||||
/// i.e. an exchanged key must be produced in both servers.
|
||||
fn handles_incorrect_size_messages() {
|
||||
rosenpass_sodium::init().unwrap();
|
||||
crate::sodium::sodium_init().unwrap();
|
||||
|
||||
stacker::grow(8 * 1024 * 1024, || {
|
||||
const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize;
|
||||
@@ -1815,7 +1812,7 @@ mod test {
|
||||
fn keygen() -> Result<(SSk, SPk)> {
|
||||
// TODO: Copied from the benchmark; deduplicate
|
||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||
StaticKem::keygen(sk.secret_mut(), pk.secret_mut())?;
|
||||
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?;
|
||||
Ok((sk, pk))
|
||||
}
|
||||
|
||||
|
||||
186
rosenpass/src/util/io.rs
Normal file
186
rosenpass/src/util/io.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use std::io::Write;
|
||||
use crate::util::cpy_min;
|
||||
use crate::util::result::{NeverFails, Guaranteed};
|
||||
|
||||
/// Errors
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum BoundedWriteSecretError {
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
/// A version of the Write trait for secret data
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let buf = [0u8; 8]
|
||||
/// assert_eq!(buf.write_secret(b"Hello"), Ok(()));
|
||||
/// assert_eq!(&buf, b"Hello\0\0\0");
|
||||
///
|
||||
/// assert_eq!(buf.write_secret(b"You glorious world"), Err(BoundedWriteSecretError::OutOfBounds));
|
||||
/// assert_eq!(&buf, b"Hello\0\0\0");
|
||||
/// ```
|
||||
pub(crate) trait WriteSecret {
|
||||
type Error;
|
||||
|
||||
/// Atomic write operation: writes `buf` to the underlying container
|
||||
///
|
||||
/// The implementation must guarantee that the write operation either completes
|
||||
/// successfully, otherwise no data must be written.
|
||||
///
|
||||
/// The implementation should take care to ensure that any intermediate buffers
|
||||
/// are zeroized.
|
||||
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> WriteSecret for T {
|
||||
type Error = BoundedWriteSecretError;
|
||||
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
|
||||
let dst = self.as_ref();
|
||||
if dst.len() >= buf.len() {
|
||||
cpy_min(buf.len(), dst.len());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CursorSecretWriteError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for make_write_secret to implement WriteSecret on the fly
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ClosureWriteSecret<Fn, E>
|
||||
where
|
||||
Fn: FnMut(&[u8]) -> std::Result<(), E> {
|
||||
f: Fn
|
||||
}
|
||||
|
||||
/// Implement WriteSecret on the fly
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// enum PasswordWriterError {
|
||||
/// OutOfBounds
|
||||
/// }
|
||||
///
|
||||
/// let password = [0u8; 12];
|
||||
/// let ptr = 0usize;
|
||||
/// let password_writer = make_write_secret(mut |buf| {
|
||||
/// let new_ptr = ptr + buf.len();
|
||||
/// if new_ptr > password.len() {
|
||||
/// Err(PasswordWriterError::OutOfBounds)
|
||||
/// } else {
|
||||
/// (&mut password[ptr..]).copy_from_slice(buf));
|
||||
/// ptr = new_ptr;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(password_writer.write_secret("This is"), Ok(()));
|
||||
/// assert_eq!(&password, b"This is\0\0\0\0\0");
|
||||
///
|
||||
/// assert_eq!(password_writer.write_secret("a bad password"), Err(PasswordWriterError::OutOfBounds));
|
||||
/// assert_eq!(&password, b"This is\0\0\0\0\0");
|
||||
/// ```
|
||||
pub(crate) fn make_write_secret<Fn, E>(f: Fn)
|
||||
-> ClosureWriteSecret<Fn, E>
|
||||
where
|
||||
Fn: FnMut(&[u8]) -> std::Result<(), E> {
|
||||
ClosureWriteSecret { f }
|
||||
}
|
||||
|
||||
impl<Fn, Error> WriteSecret for ClosureWriteSecret<Fn, E> {
|
||||
type Error = E;
|
||||
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
|
||||
self.f(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: digest::Update> WriteSecret for T {
|
||||
type Error = NeverFails;
|
||||
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> Guaranteed<()> {
|
||||
self.update(buf);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::io::Write> WriteSecret for WriteSecretFromIoWrite<T> {
|
||||
type Error = std::io::Error;
|
||||
fn write_secret<W: Write>(&mut self, buf: &[u8]) -> std::io::Result<()> {
|
||||
self.0.write_all(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for counting the number of byts written to a stream
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let counter = CountAndWriteSecret::new(make_write_secret(|buf| -> std::Result<(), ()> {
|
||||
/// Ok(())
|
||||
/// }));
|
||||
///
|
||||
/// assert_eq!(counter.write_secret(b"hello"), Ok(()));
|
||||
/// assert_eq!(counter.count(), 5);
|
||||
///
|
||||
/// let (dummy_writer, count) = counter.into_parts();
|
||||
/// assert_eq!(count, 5);
|
||||
///
|
||||
/// let counter = CountAndWriteSecret::from_parts(dummy_writer, count+2000);
|
||||
/// assert_eq!(counter.write_secret(b" world"), Ok(()));
|
||||
/// assert_eq!(counter.count(), 2011);
|
||||
/// ```
|
||||
pub(crate) struct CountAndWriteSecret<W: WriteSecret> {
|
||||
inner: W,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl<W: WriteSecret> CountAndWriteSecret {
|
||||
/// Create a new CountAndWriteSecret, wrapping the `inner` stream
|
||||
pub(crate) fn new(inner: W) {
|
||||
Self::from_parts(inner, 0)
|
||||
}
|
||||
|
||||
/// Construct a new CountAndWriteSecret from an inner stream and a pre-existing count
|
||||
pub(crate) fn from_parts(inner: W, count: usize) {
|
||||
Self { inner, count }
|
||||
}
|
||||
|
||||
/// Extract the inner stream and the current count
|
||||
pub(crate) fn into_parts(self) -> (W, usize) {
|
||||
(self.inner, self.count)
|
||||
}
|
||||
|
||||
/// Retrieve the number of bytes written to the inner stream
|
||||
pub(crate) fn count() -> usize {
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: WriteSecret> WriteSecret for CountAndWriteSecret {
|
||||
type Error = W::Error;
|
||||
|
||||
fn write_secret(&mut self, buf: &[u8]) -> std::Result<(), Self::Error> {
|
||||
let no = self.inner.write(buf)?;
|
||||
self.count += no;
|
||||
Ok(no)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a buffer through multiple .write_secret() calls,
|
||||
/// returning the constructed buffer.
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// This function does not handle zeroization or secret memory
|
||||
/// allocation comprehensively. It must only be used as a helper
|
||||
/// during unit tests.
|
||||
#[cfg(test)]
|
||||
fn assemble_secret<Fn: FnOnce(impl WriteSecret)>(f: Fn) -> std::vec::Vec<u8> {
|
||||
let buf = std::vec::Vec::new();
|
||||
f(make_write_secret(|data| -> Guaranteed<()> {
|
||||
buf.extend_from_slice(data);
|
||||
Ok(())
|
||||
}));
|
||||
buf
|
||||
}
|
||||
31
rosenpass/src/util/math.rs
Normal file
31
rosenpass/src/util/math.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use core::ops::{Rem, Add, Sub};
|
||||
|
||||
/// Round lhs up to the next multiple of div
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(round_up(10u8, 5u8), 10u8);
|
||||
/// assert_eq!(round_up(.3f32, .2f32), .4f32);
|
||||
/// assert_eq!(round_up(22u64, 17u64), 32u64);
|
||||
/// ```
|
||||
pub(crate) fn round_up<T>(lhs: T, div: T) -> T
|
||||
where
|
||||
T: Rem + Add {
|
||||
lhs + (lhs % div)
|
||||
}
|
||||
|
||||
/// Calculates the difference between val and the next highest multiple of div
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(round_up(10u8, 5u8), 0u8);
|
||||
/// assert_eq!(round_up(.3f32, .2f32), .1f32);
|
||||
/// assert_eq!(round_up(22u64, 17u64), 32u64);
|
||||
/// ```
|
||||
pub(crate) fn gap_towards_multiple<T>(val: T, div: T)
|
||||
where
|
||||
T: Rem + Add + Sub {
|
||||
round_up(val, div) - val
|
||||
}
|
||||
250
rosenpass/src/util/mod.rs
Normal file
250
rosenpass/src/util/mod.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
//! Helper functions and macros
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use base64::{
|
||||
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
|
||||
write::EncoderWriter as B64Writer,
|
||||
};
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cmp::min,
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
// TODO: Move everything except module declarations out of this file
|
||||
pub(crate) mod types;
|
||||
pub(crate) mod math;
|
||||
pub(crate) mod result;
|
||||
pub(crate) mod io;
|
||||
|
||||
use crate::coloring::{Public, Secret};
|
||||
|
||||
/// Xors a and b element-wise and writes the result into a.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rosenpass::util::xor_into;
|
||||
/// let mut a = String::from("hello").into_bytes();
|
||||
/// let b = b"world";
|
||||
/// xor_into(&mut a, b);
|
||||
/// assert_eq!(&a, b"\x1f\n\x1e\x00\x0b");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn xor_into(a: &mut [u8], b: &[u8]) {
|
||||
assert!(a.len() == b.len());
|
||||
for (av, bv) in a.iter_mut().zip(b.iter()) {
|
||||
*av ^= *bv;
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenate two byte arrays
|
||||
// TODO: Zeroize result?
|
||||
#[macro_export]
|
||||
macro_rules! cat {
|
||||
($len:expr; $($toks:expr),+) => {{
|
||||
let mut buf = [0u8; $len];
|
||||
let mut off = 0;
|
||||
$({
|
||||
let tok = $toks;
|
||||
let tr = ::std::borrow::Borrow::<[u8]>::borrow(tok);
|
||||
(&mut buf[off..(off + tr.len())]).copy_from_slice(tr);
|
||||
off += tr.len();
|
||||
})+
|
||||
assert!(off == buf.len(), "Size mismatch in cat!()");
|
||||
buf
|
||||
}}
|
||||
}
|
||||
|
||||
// TODO: consistent inout ordering
|
||||
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
dst.borrow_mut().copy_from_slice(src.borrow());
|
||||
}
|
||||
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
|
||||
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
let src = src.borrow();
|
||||
let dst = dst.borrow_mut();
|
||||
let len = min(src.len(), dst.len());
|
||||
dst[..len].copy_from_slice(&src[..len]);
|
||||
}
|
||||
|
||||
/// Try block basically…returns a result and allows the use of the question mark operator inside
|
||||
#[macro_export]
|
||||
macro_rules! attempt {
|
||||
($block:expr) => {
|
||||
(|| -> ::anyhow::Result<_> { $block })()
|
||||
};
|
||||
}
|
||||
|
||||
use base64::engine::general_purpose::GeneralPurpose as Base64Engine;
|
||||
const B64ENGINE: Base64Engine = base64::engine::general_purpose::STANDARD;
|
||||
|
||||
pub fn fmt_b64<'a>(payload: &'a [u8]) -> B64Display<'a, 'static, Base64Engine> {
|
||||
B64Display::<'a, 'static>::new(payload, &B64ENGINE)
|
||||
}
|
||||
|
||||
pub fn b64_writer<W: Write>(w: W) -> B64Writer<'static, Base64Engine, W> {
|
||||
B64Writer::new(w, &B64ENGINE)
|
||||
}
|
||||
|
||||
pub fn b64_reader<R: Read>(r: R) -> B64Reader<'static, Base64Engine, R> {
|
||||
B64Reader::new(r, &B64ENGINE)
|
||||
}
|
||||
|
||||
// TODO remove this once std::cmp::max becomes const
|
||||
pub const fn max_usize(a: usize, b: usize) -> usize {
|
||||
if a > b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timebase(Instant);
|
||||
|
||||
impl Default for Timebase {
|
||||
fn default() -> Self {
|
||||
Self(Instant::now())
|
||||
}
|
||||
}
|
||||
|
||||
impl Timebase {
|
||||
pub fn now(&self) -> f64 {
|
||||
self.0.elapsed().as_secs_f64()
|
||||
}
|
||||
|
||||
pub fn dur(&self, t: f64) -> Duration {
|
||||
Duration::from_secs_f64(t)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mutating<T, F>(mut v: T, f: F) -> T
|
||||
where
|
||||
F: Fn(&mut T),
|
||||
{
|
||||
f(&mut v);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn sideeffect<T, F>(v: T, f: F) -> T
|
||||
where
|
||||
F: Fn(&T),
|
||||
{
|
||||
f(&v);
|
||||
v
|
||||
}
|
||||
|
||||
/// load'n store
|
||||
|
||||
/// Open a file writable
|
||||
pub fn fopen_w<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||
Ok(OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)?)
|
||||
}
|
||||
/// Open a file readable
|
||||
pub fn fopen_r<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||
Ok(OpenOptions::new()
|
||||
.read(true)
|
||||
.write(false)
|
||||
.create(false)
|
||||
.truncate(false)
|
||||
.open(path)?)
|
||||
}
|
||||
|
||||
pub trait ReadExactToEnd {
|
||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<R: Read> ReadExactToEnd for R {
|
||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||
let mut dummy = [0u8; 8];
|
||||
self.read_exact(buf)?;
|
||||
ensure!(self.read(&mut dummy)? == 0, "File too long!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LoadValue {
|
||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait LoadValueB64 {
|
||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
trait StoreValue {
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
||||
}
|
||||
|
||||
trait StoreSecret {
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: StoreValue> StoreSecret for T {
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
self.store(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for Secret<N> {
|
||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
fopen_r(p)?
|
||||
.read_exact_to_end(v.secret_mut())
|
||||
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
// This might leave some fragments of the secret on the stack;
|
||||
// in practice this is likely not a problem because the stack likely
|
||||
// will be overwritten by something else soon but this is not exactly
|
||||
// guaranteed. It would be possible to remedy this, but since the secret
|
||||
// data will linger in the Linux page cache anyways with the current
|
||||
// implementation, going to great length to erase the secret here is
|
||||
// not worth it right now.
|
||||
b64_reader(&mut fopen_r(p)?)
|
||||
.read_exact(v.secret_mut())
|
||||
.with_context(|| format!("Could not load base64 file {p:?}"))?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreSecret for Secret<N> {
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
std::fs::write(path, self.secret())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for Public<N> {
|
||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let mut v = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValue for Public<N> {
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||
std::fs::write(path, **self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
use std::convert::Infallible;
|
||||
use std::result::Result;
|
||||
|
||||
/// Try block basically…returns a result and allows the use of the question mark operator inside
|
||||
#[macro_export]
|
||||
macro_rules! attempt {
|
||||
($block:expr) => {
|
||||
(|| -> ::anyhow::Result<_> { $block })()
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait for container types that guarantee successful unwrapping.
|
||||
///
|
||||
/// The `.guaranteed()` function can be used over unwrap to show that
|
||||
/// the function will not panic.
|
||||
///
|
||||
/// Implementations must not panic.
|
||||
pub trait GuaranteedValue {
|
||||
pub(crate) trait GuaranteedValue {
|
||||
type Value;
|
||||
|
||||
/// Extract the contained value while being panic-safe, like .unwrap()
|
||||
@@ -26,6 +17,11 @@ pub trait GuaranteedValue {
|
||||
fn guaranteed(self) -> Self::Value;
|
||||
}
|
||||
|
||||
/// An error type to indicate that an error will not occur.
|
||||
///
|
||||
/// This is a nullary enum; i.e. an instance of this enum can not be created.
|
||||
pub(crate) enum NeverFails {}
|
||||
|
||||
/// A result type that never contains an error.
|
||||
///
|
||||
/// This is mostly useful in generic contexts.
|
||||
@@ -35,7 +31,6 @@ pub trait GuaranteedValue {
|
||||
/// ```
|
||||
/// use std::num::Wrapping;
|
||||
/// use std::result::Result;
|
||||
/// use std::convert::Infallible
|
||||
///
|
||||
/// trait FailableAddition {
|
||||
/// type Error;
|
||||
@@ -45,14 +40,14 @@ pub trait GuaranteedValue {
|
||||
/// struct OverflowError;
|
||||
///
|
||||
/// impl<T> FailableAddition for Wrapping<T> {
|
||||
/// type Error = Infallible;
|
||||
/// type Error = NeverFails;
|
||||
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
|
||||
/// self + other
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<T> FailableAddition for u32 {
|
||||
/// type Error = Infallible;
|
||||
/// type Error = NeverFails;
|
||||
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
|
||||
/// match self.checked_add(*other) {
|
||||
/// Some(v) => Ok(v),
|
||||
@@ -62,7 +57,7 @@ pub trait GuaranteedValue {
|
||||
/// }
|
||||
///
|
||||
/// fn failable_multiply<T>(a: &T, b: u32)
|
||||
/// -> Result<T, T::Error>
|
||||
/// -> Result<T, T::Error> {
|
||||
/// where
|
||||
/// T: FailableAddition<Error> {
|
||||
/// let mut accu = a.failable_addition(a)?;
|
||||
@@ -73,7 +68,7 @@ pub trait GuaranteedValue {
|
||||
/// }
|
||||
///
|
||||
/// // We can use .guaranteed() with Wrapping<u32>, since the operation uses
|
||||
/// // the Infallible error type.
|
||||
/// // the NeverFails error type.
|
||||
/// // We can also use unwrap which just happens to not raise an error.
|
||||
/// assert_eq!(failable_multiply(&Wrapping::new(42u32), 3).guaranteed(), 126);
|
||||
/// assert_eq!(failable_multiply(&Wrapping::new(42u32), 3).unwrap(), 126);
|
||||
@@ -83,11 +78,11 @@ pub trait GuaranteedValue {
|
||||
/// assert_eq!(failable_multiply(&42u32, 3).guaranteed(), 126); // COMPILER ERROR
|
||||
/// assert_eq!(failable_multiply(&42u32, 3).unwrap(), 126);
|
||||
/// ```
|
||||
pub type Guaranteed<T> = Result<T, Infallible>;
|
||||
pub(crate) type Guaranteed<T> = Result<T, NeverFails>;
|
||||
|
||||
impl<T> GuaranteedValue for Guaranteed<T> {
|
||||
type Value = T;
|
||||
fn guaranteed(self) -> Self::Value {
|
||||
self.unwrap()
|
||||
self.unwrap();
|
||||
}
|
||||
}
|
||||
6
rosenpass/src/util/types.rs
Normal file
6
rosenpass/src/util/types.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
/// Named boolean for arbitrary use to distinguish between
|
||||
/// the directions left and right
|
||||
pub(crate) enum Leftright {
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-secret-memory"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal utilities for storing secrets in memory"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
rosenpass-sodium = { workspace = true }
|
||||
rosenpass-util = { workspace = true }
|
||||
libsodium-sys-stable = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass secure memory library
|
||||
|
||||
Rosenpass internal library providing utilities for securely storing secret data in memory.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,20 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
|
||||
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("[{}]=")?;
|
||||
if v.len() > 64 {
|
||||
for byte in &v[..32] {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
fmt.write_str("…")?;
|
||||
for byte in &v[v.len() - 32..] {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
} else {
|
||||
for byte in v {
|
||||
std::fmt::LowerHex::fmt(byte, fmt)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
pub trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
pub mod debug;
|
||||
pub mod file;
|
||||
pub mod rand;
|
||||
|
||||
mod public;
|
||||
pub use crate::public::Public;
|
||||
|
||||
mod secret;
|
||||
pub use crate::secret::Secret;
|
||||
@@ -1,112 +0,0 @@
|
||||
use crate::debug::debug_crypto_array;
|
||||
use rand::{Fill as Randomize, Rng};
|
||||
use rosenpass_to::{ops::copy_slice, To};
|
||||
use rosenpass_util::file::{fopen_r, LoadValue, ReadExactToEnd, StoreValue};
|
||||
use rosenpass_util::functional::mutating;
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::Path;
|
||||
|
||||
/// Contains information in the form of a byte array that may be known to the
|
||||
/// public
|
||||
// TODO: We should get rid of the Public type; just use a normal value
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct Public<const N: usize> {
|
||||
pub value: [u8; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Public<N> {
|
||||
/// Create a new [Public] from a byte slice
|
||||
pub fn from_slice(value: &[u8]) -> Self {
|
||||
copy_slice(value).to_this(|| Self::zero())
|
||||
}
|
||||
|
||||
/// Create a new [Public] from a byte array
|
||||
pub fn new(value: [u8; N]) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
|
||||
/// Create a zero initialized [Public]
|
||||
pub fn zero() -> Self {
|
||||
Self { value: [0u8; N] }
|
||||
}
|
||||
|
||||
/// Create a random initialized [Public]
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Randomize all bytes in an existing [Public]
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Public<N> {
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
self.value.try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> fmt::Debug for Public<N> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
debug_crypto_array(&self.value, fmt)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Public<N> {
|
||||
type Target = [u8; N];
|
||||
|
||||
fn deref(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Public<N> {
|
||||
fn deref_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
|
||||
fn borrow(&self) -> &[u8; N] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
|
||||
fn borrow_mut(&mut self) -> &mut [u8; N] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8]> for Public<N> {
|
||||
fn borrow(&self) -> &[u8] {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
|
||||
fn borrow_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreValue for Public<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, **self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
|
||||
pub fn rng() -> Rng {
|
||||
rand::thread_rng()
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
use crate::file::StoreSecret;
|
||||
use anyhow::Context;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::{Fill as Randomize, Rng};
|
||||
use rosenpass_sodium::alloc::{Alloc as SodiumAlloc, Box as SodiumBox, Vec as SodiumVec};
|
||||
use rosenpass_util::{
|
||||
b64::b64_reader,
|
||||
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd},
|
||||
functional::mutating,
|
||||
};
|
||||
use std::{collections::HashMap, convert::TryInto, fmt, path::Path, sync::Mutex};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
// This might become a problem in library usage; it's effectively a memory
|
||||
// leak which probably isn't a problem right now because most memory will
|
||||
// be reused…
|
||||
lazy_static! {
|
||||
static ref SECRET_CACHE: Mutex<SecretMemoryPool> = Mutex::new(SecretMemoryPool::new());
|
||||
}
|
||||
|
||||
/// Pool that stores secret memory allocations
|
||||
///
|
||||
/// Allocation of secret memory is expensive. Thus, this struct provides a
|
||||
/// pool of secret memory, readily available to yield protected, slices of
|
||||
/// memory.
|
||||
///
|
||||
/// Further information about the protection in place can be found in in the
|
||||
/// [libsodium documentation](https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations)
|
||||
#[derive(Debug)] // TODO check on Debug derive, is that clever
|
||||
struct SecretMemoryPool {
|
||||
pool: HashMap<usize, Vec<SodiumBox<[u8]>>>,
|
||||
}
|
||||
|
||||
impl SecretMemoryPool {
|
||||
/// Create a new [SecretMemoryPool]
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return secret back to the pool for future re-use
|
||||
pub fn release<const N: usize>(&mut self, mut sec: SodiumBox<[u8; N]>) {
|
||||
sec.zeroize();
|
||||
|
||||
// This conversion sequence is weird but at least it guarantees
|
||||
// that the heap allocation is preserved according to the docs
|
||||
let sec: SodiumVec<u8> = sec.into();
|
||||
let sec: SodiumBox<[u8]> = sec.into();
|
||||
|
||||
self.pool.entry(N).or_default().push(sec);
|
||||
}
|
||||
|
||||
/// Take protected memory from the pool, allocating new one if no suitable
|
||||
/// chunk is found in the inventory.
|
||||
///
|
||||
/// The secret is guaranteed to be full of nullbytes
|
||||
pub fn take<const N: usize>(&mut self) -> SodiumBox<[u8; N]> {
|
||||
let entry = self.pool.entry(N).or_default();
|
||||
match entry.pop() {
|
||||
None => SodiumBox::new_in([0u8; N], SodiumAlloc::default()),
|
||||
Some(sec) => sec.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storeage for a secret backed by [rosenpass_sodium::alloc::Alloc]
|
||||
pub struct Secret<const N: usize> {
|
||||
storage: Option<SodiumBox<[u8; N]>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> Secret<N> {
|
||||
pub fn from_slice(slice: &[u8]) -> Self {
|
||||
let mut new_self = Self::zero();
|
||||
new_self.secret_mut().copy_from_slice(slice);
|
||||
new_self
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is zero initialized
|
||||
pub fn zero() -> Self {
|
||||
// Using [SecretMemoryPool] here because this operation is expensive,
|
||||
// yet it is used in hot loops
|
||||
Self {
|
||||
storage: Some(SECRET_CACHE.lock().unwrap().take()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new [Secret] that is randomized
|
||||
pub fn random() -> Self {
|
||||
mutating(Self::zero(), |r| r.randomize())
|
||||
}
|
||||
|
||||
/// Sets all data an existing secret to random bytes
|
||||
pub fn randomize(&mut self) {
|
||||
self.try_fill(&mut crate::rand::rng()).unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data
|
||||
pub fn secret(&self) -> &[u8; N] {
|
||||
self.storage.as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Borrows the data mutably
|
||||
pub fn secret_mut(&mut self) -> &mut [u8; N] {
|
||||
self.storage.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
|
||||
impl<const N: usize> Zeroize for Secret<N> {
|
||||
fn zeroize(&mut self) {
|
||||
self.secret_mut().zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Randomize for Secret<N> {
|
||||
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
|
||||
// Zeroize self first just to make sure the barriers from the zeroize create take
|
||||
// effect to prevent the compiler from optimizing this away.
|
||||
// We should at some point replace this with our own barriers.
|
||||
self.zeroize();
|
||||
self.secret_mut().try_fill(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Drop for Secret<N> {
|
||||
fn drop(&mut self) {
|
||||
self.storage
|
||||
.take()
|
||||
.map(|sec| SECRET_CACHE.lock().unwrap().release(sec));
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Clone for Secret<N> {
|
||||
fn clone(&self) -> Self {
|
||||
Self::from_slice(self.secret())
|
||||
}
|
||||
}
|
||||
|
||||
/// The Debug implementation of [Secret] does not reveal the secret data,
|
||||
/// instead a placeholder `<SECRET>` is used
|
||||
impl<const N: usize> fmt::Debug for Secret<N> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("<SECRET>")
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValue for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
fopen_r(p)?
|
||||
.read_exact_to_end(v.secret_mut())
|
||||
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut v = Self::random();
|
||||
let p = path.as_ref();
|
||||
// This might leave some fragments of the secret on the stack;
|
||||
// in practice this is likely not a problem because the stack likely
|
||||
// will be overwritten by something else soon but this is not exactly
|
||||
// guaranteed. It would be possible to remedy this, but since the secret
|
||||
// data will linger in the Linux page cache anyways with the current
|
||||
// implementation, going to great length to erase the secret here is
|
||||
// not worth it right now.
|
||||
b64_reader(&mut fopen_r(p)?)
|
||||
.read_exact(v.secret_mut())
|
||||
.with_context(|| format!("Could not load base64 file {p:?}"))?;
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> StoreSecret for Secret<N> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||
std::fs::write(path, self.secret())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
/// check that we can alloc using the magic pool
|
||||
#[test]
|
||||
fn secret_memory_pool_take() {
|
||||
rosenpass_sodium::init().unwrap();
|
||||
const N: usize = 0x100;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let secret: SodiumBox<[u8; N]> = pool.take();
|
||||
assert_eq!(secret.as_ref(), &[0; N]);
|
||||
}
|
||||
|
||||
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
|
||||
#[test]
|
||||
fn secret_memory_pool_drop() {
|
||||
rosenpass_sodium::init().unwrap();
|
||||
const N: usize = 0x100;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let secret: SodiumBox<[u8; N]> = pool.take();
|
||||
std::mem::drop(pool);
|
||||
assert_eq!(secret.as_ref(), &[0; N]);
|
||||
}
|
||||
|
||||
/// check that a secrete can be reborn, freshly initialized with zero
|
||||
#[test]
|
||||
fn secret_memory_pool_release() {
|
||||
rosenpass_sodium::init().unwrap();
|
||||
const N: usize = 1;
|
||||
let mut pool = SecretMemoryPool::new();
|
||||
let mut secret: SodiumBox<[u8; N]> = pool.take();
|
||||
let old_secret_ptr = secret.as_ref().as_ptr();
|
||||
|
||||
secret.as_mut()[0] = 0x13;
|
||||
pool.release(secret);
|
||||
|
||||
// now check that we get the same ptr
|
||||
let new_secret: SodiumBox<[u8; N]> = pool.take();
|
||||
assert_eq!(old_secret_ptr, new_secret.as_ref().as_ptr());
|
||||
|
||||
// and that the secret was zeroized
|
||||
assert_eq!(new_secret.as_ref(), &[0; N]);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-sodium"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal bindings to libsodium"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dependencies]
|
||||
rosenpass-util = { workspace = true }
|
||||
rosenpass-to = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
libsodium-sys-stable = { workspace = true }
|
||||
log = { workspace = true }
|
||||
allocator-api2 = { workspace = true }
|
||||
@@ -1,5 +0,0 @@
|
||||
# Rosenpass internal libsodium bindings
|
||||
|
||||
Rosenpass internal library providing bindings to libsodium.
|
||||
|
||||
This is an internal library; not guarantee is made about its API at this point in time.
|
||||
@@ -1,63 +0,0 @@
|
||||
use libsodium_sys as libsodium;
|
||||
use std::ffi::c_ulonglong;
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
pub const KEY_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_KEYBYTES as usize;
|
||||
pub const TAG_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_ABYTES as usize;
|
||||
pub const NONCE_LEN: usize = libsodium::crypto_aead_chacha20poly1305_IETF_NPUBBYTES as usize;
|
||||
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
plaintext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
|
||||
assert!(key.len() == KEY_LEN);
|
||||
assert!(nonce.len() == NONCE_LEN);
|
||||
let mut clen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_chacha20poly1305_ietf_encrypt,
|
||||
ciphertext.as_mut_ptr(),
|
||||
&mut clen,
|
||||
plaintext.as_ptr(),
|
||||
plaintext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
null(), // nsec is not used
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(clen as usize == ciphertext.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + TAG_LEN);
|
||||
assert!(key.len() == KEY_LEN);
|
||||
assert!(nonce.len() == NONCE_LEN);
|
||||
let mut mlen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_chacha20poly1305_ietf_decrypt,
|
||||
plaintext.as_mut_ptr(),
|
||||
&mut mlen as *mut c_ulonglong,
|
||||
null_mut(), // nsec is not used
|
||||
ciphertext.as_ptr(),
|
||||
ciphertext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(mlen as usize == plaintext.len());
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod chacha20poly1305_ietf;
|
||||
pub mod xchacha20poly1305_ietf;
|
||||
@@ -1,63 +0,0 @@
|
||||
use libsodium_sys as libsodium;
|
||||
use std::ffi::c_ulonglong;
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
pub const KEY_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize;
|
||||
pub const TAG_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_ietf_ABYTES as usize;
|
||||
pub const NONCE_LEN: usize = libsodium::crypto_aead_xchacha20poly1305_IETF_NPUBBYTES as usize;
|
||||
|
||||
#[inline]
|
||||
pub fn encrypt(
|
||||
ciphertext: &mut [u8],
|
||||
key: &[u8],
|
||||
nonce: &[u8],
|
||||
ad: &[u8],
|
||||
plaintext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
|
||||
assert!(key.len() == libsodium::crypto_aead_xchacha20poly1305_IETF_KEYBYTES as usize);
|
||||
let (n, ct) = ciphertext.split_at_mut(NONCE_LEN);
|
||||
n.copy_from_slice(nonce);
|
||||
let mut clen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_xchacha20poly1305_ietf_encrypt,
|
||||
ct.as_mut_ptr(),
|
||||
&mut clen,
|
||||
plaintext.as_ptr(),
|
||||
plaintext.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
null(), // nsec is not used
|
||||
nonce.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(clen as usize == ct.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn decrypt(
|
||||
plaintext: &mut [u8],
|
||||
key: &[u8],
|
||||
ad: &[u8],
|
||||
ciphertext: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(ciphertext.len() == plaintext.len() + NONCE_LEN + TAG_LEN);
|
||||
assert!(key.len() == KEY_LEN);
|
||||
let (n, ct) = ciphertext.split_at(NONCE_LEN);
|
||||
let mut mlen: u64 = 0;
|
||||
sodium_call!(
|
||||
crypto_aead_xchacha20poly1305_ietf_decrypt,
|
||||
plaintext.as_mut_ptr(),
|
||||
&mut mlen as *mut c_ulonglong,
|
||||
null_mut(), // nsec is not used
|
||||
ct.as_ptr(),
|
||||
ct.len() as c_ulonglong,
|
||||
ad.as_ptr(),
|
||||
ad.len() as c_ulonglong,
|
||||
n.as_ptr(),
|
||||
key.as_ptr()
|
||||
)?;
|
||||
assert!(mlen as usize == plaintext.len());
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
use allocator_api2::alloc::{AllocError, Allocator, Layout};
|
||||
use libsodium_sys as libsodium;
|
||||
use std::fmt;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct AllocatorContents;
|
||||
|
||||
/// Memory allocation using sodium_malloc/sodium_free
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Alloc {
|
||||
_dummy_private_data: AllocatorContents,
|
||||
}
|
||||
|
||||
impl Alloc {
|
||||
pub fn new() -> Self {
|
||||
Alloc {
|
||||
_dummy_private_data: AllocatorContents,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Allocator for Alloc {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
// Call sodium allocator
|
||||
let ptr = unsafe { libsodium::sodium_malloc(layout.size()) };
|
||||
|
||||
// Ensure the right allocation is used
|
||||
let off = ptr.align_offset(layout.align());
|
||||
if off != 0 {
|
||||
log::error!("Allocation {layout:?} was requested but libsodium returned allocation \
|
||||
with offset {off} from the requested alignment. Libsodium always allocates values \
|
||||
at the end of a memory page for security reasons, custom alignments are not supported. \
|
||||
You could try allocating an oversized value.");
|
||||
return Err(AllocError);
|
||||
}
|
||||
|
||||
// Convert to a pointer size
|
||||
let ptr = core::ptr::slice_from_raw_parts_mut(ptr as *mut u8, layout.size());
|
||||
|
||||
// Conversion to a *const u8, then to a &[u8]
|
||||
match NonNull::new(ptr) {
|
||||
None => {
|
||||
log::error!(
|
||||
"Allocation {layout:?} was requested but libsodium returned a null pointer"
|
||||
);
|
||||
Err(AllocError)
|
||||
}
|
||||
Some(ret) => Ok(ret),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
|
||||
unsafe {
|
||||
libsodium::sodium_free(ptr.as_ptr() as *mut c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Alloc {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str("<libsodium based Rust allocator>")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
/// checks that the can malloc with libsodium
|
||||
#[test]
|
||||
fn sodium_allocation() {
|
||||
crate::init().unwrap();
|
||||
let alloc = Alloc::new();
|
||||
sodium_allocation_impl::<0>(&alloc);
|
||||
sodium_allocation_impl::<7>(&alloc);
|
||||
sodium_allocation_impl::<8>(&alloc);
|
||||
sodium_allocation_impl::<64>(&alloc);
|
||||
sodium_allocation_impl::<999>(&alloc);
|
||||
}
|
||||
|
||||
fn sodium_allocation_impl<const N: usize>(alloc: &Alloc) {
|
||||
crate::init().unwrap();
|
||||
let layout = Layout::new::<[u8; N]>();
|
||||
let mem = alloc.allocate(layout).unwrap();
|
||||
|
||||
// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
|
||||
// promises us that allocated memory is initialized with the magic byte 0xDB
|
||||
assert_eq!(unsafe { mem.as_ref() }, &[0xDBu8; N]);
|
||||
|
||||
let mem = NonNull::new(mem.as_ptr() as *mut u8).unwrap();
|
||||
unsafe { alloc.deallocate(mem, layout) };
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
//! Access to sodium_malloc/sodium_free
|
||||
|
||||
mod allocator;
|
||||
pub use allocator::Alloc;
|
||||
|
||||
/// A box backed by sodium_malloc
|
||||
pub type Box<T> = allocator_api2::boxed::Box<T, Alloc>;
|
||||
|
||||
/// A vector backed by sodium_malloc
|
||||
pub type Vec<T> = allocator_api2::vec::Vec<T, Alloc>;
|
||||
@@ -1,31 +0,0 @@
|
||||
use libsodium_sys as libsodium;
|
||||
use rosenpass_to::{with_destination, To};
|
||||
use std::ffi::c_ulonglong;
|
||||
use std::ptr::null;
|
||||
|
||||
pub const KEY_MIN: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MIN as usize;
|
||||
pub const KEY_MAX: usize = libsodium::crypto_generichash_blake2b_KEYBYTES_MAX as usize;
|
||||
pub const OUT_MIN: usize = libsodium::crypto_generichash_blake2b_BYTES_MIN as usize;
|
||||
pub const OUT_MAX: usize = libsodium::crypto_generichash_blake2b_BYTES_MAX as usize;
|
||||
|
||||
#[inline]
|
||||
pub fn hash<'a>(key: &'a [u8], data: &'a [u8]) -> impl To<[u8], anyhow::Result<()>> + 'a {
|
||||
with_destination(|out: &mut [u8]| {
|
||||
assert!(key.is_empty() || (KEY_MIN <= key.len() && key.len() <= KEY_MAX));
|
||||
assert!(OUT_MIN <= out.len() && out.len() <= OUT_MAX);
|
||||
let kptr = match key.len() {
|
||||
// NULL key
|
||||
0 => null(),
|
||||
_ => key.as_ptr(),
|
||||
};
|
||||
sodium_call!(
|
||||
crypto_generichash_blake2b,
|
||||
out.as_mut_ptr(),
|
||||
out.len(),
|
||||
data.as_ptr(),
|
||||
data.len() as c_ulonglong,
|
||||
kptr,
|
||||
key.len()
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod blake2b;
|
||||
@@ -1,28 +0,0 @@
|
||||
use libsodium_sys as libsodium;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[inline]
|
||||
pub fn memcmp(a: &[u8], b: &[u8]) -> bool {
|
||||
a.len() == b.len()
|
||||
&& unsafe {
|
||||
let r = libsodium::sodium_memcmp(
|
||||
a.as_ptr() as *const c_void,
|
||||
b.as_ptr() as *const c_void,
|
||||
a.len(),
|
||||
);
|
||||
r == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn compare(a: &[u8], b: &[u8]) -> i32 {
|
||||
assert!(a.len() == b.len());
|
||||
unsafe { libsodium::sodium_compare(a.as_ptr(), b.as_ptr(), a.len()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn increment(v: &mut [u8]) {
|
||||
unsafe {
|
||||
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use libsodium_sys as libsodium;
|
||||
|
||||
macro_rules! sodium_call {
|
||||
($name:ident, $($args:expr),*) => { ::rosenpass_util::attempt!({
|
||||
anyhow::ensure!(unsafe{libsodium::$name($($args),*)} > -1,
|
||||
"Error in libsodium's {}.", stringify!($name));
|
||||
Ok(())
|
||||
})};
|
||||
($name:ident) => { sodium_call!($name, ) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn init() -> anyhow::Result<()> {
|
||||
log::trace!("initializing libsodium");
|
||||
sodium_call!(sodium_init)
|
||||
}
|
||||
|
||||
pub mod aead;
|
||||
pub mod alloc;
|
||||
pub mod hash;
|
||||
pub mod helpers;
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-to"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Flexible destination parameters"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
[dev-dependencies]
|
||||
doc-comment = { workspace = true }
|
||||
477
to/README.md
477
to/README.md
@@ -1,477 +0,0 @@
|
||||
# The To Crate – Patterns for dealing with destination parameters in rust functions
|
||||
|
||||
<!-- The code blocks in this file double as tests. -->
|
||||
|
||||

|
||||

|
||||
|
||||
The To Crate provides a pattern for declaring and dealing with destination parameters in rust functions. It improves over stock rust by providing an interface that allows the caller to choose whether to place the destination parameter first – through a `to(dest, copy(source))` function – or last – through a chained function `copy(source).to(dest)`.
|
||||
|
||||
The crate provides chained functions to simplify allocating the destination parameter on the fly and it provides well defined patterns for dealing with error handling and destination parameters.
|
||||
|
||||
For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may.
|
||||
|
||||
```rust
|
||||
use std::ops::BitXorAssign;
|
||||
use rosenpass_to::{To, to, with_destination};
|
||||
use rosenpass_to::ops::copy_array;
|
||||
|
||||
// Destination functions return some value that implements the To trait.
|
||||
// Unfortunately dealing with lifetimes is a bit more finicky than it would#
|
||||
// be without destination parameters
|
||||
fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a
|
||||
where T: BitXorAssign + Clone {
|
||||
// Custom implementations of the to trait can be created, but the easiest
|
||||
with_destination(move |dst: &mut [T]| {
|
||||
assert!(src.len() == dst.len());
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
*d ^= s.clone();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let flip0 = b"\xff\x00\x00\x00";
|
||||
let flip1 = b"\x00\xff\x00\x00";
|
||||
let flip01 = b"\xff\xff\x00\x00";
|
||||
|
||||
// You can specify a destination by using the to method
|
||||
let mut dst = [0u8; 4];
|
||||
xor_slice(flip0).to(&mut dst);
|
||||
xor_slice(flip1).to(&mut dst);
|
||||
assert_eq!(&dst[..], &flip01[..]);
|
||||
|
||||
// Or using the to function
|
||||
let mut dst = [0u8; 4];
|
||||
to(&mut dst, xor_slice(flip0));
|
||||
to(&mut dst, xor_slice(flip1));
|
||||
assert_eq!(&dst[..], &flip01[..]);
|
||||
|
||||
// You can pass a function to generate the destination on the fly
|
||||
let dst = xor_slice(flip1).to_this(|| flip0.to_vec());
|
||||
assert_eq!(&dst[..], &flip01[..]);
|
||||
|
||||
// If xor_slice used a return value that could be created using Default::default(),
|
||||
// you could just use `xor_slice(flip01).to_value()` to generate the destination
|
||||
// on the fly. Since [u8] is unsized, it can only be used for references.
|
||||
//
|
||||
// You can however use collect to specify the storage value explicitly.
|
||||
// This works for any type that implements Default::default() and BorrowMut<...> for
|
||||
// the destination value.
|
||||
|
||||
// Collect in an array with a fixed size
|
||||
let dst = xor_slice(flip01).collect::<[u8; 4]>();
|
||||
assert_eq!(&dst[..], &flip01[..]);
|
||||
|
||||
// The builtin function copy_array supports to_value() since its
|
||||
// destination parameter is a fixed size array, which can be allocated
|
||||
// using default()
|
||||
let dst : [u8; 4] = copy_array(flip01).to_value();
|
||||
assert_eq!(&dst, flip01);
|
||||
```
|
||||
|
||||
The to crate really starts to shine when error handling (through result) is combined with destination parameters. See the tutorial below for details.
|
||||
|
||||
## Motivation
|
||||
|
||||
Destination parameters are often used when simply returning the value is undesirable or impossible.
|
||||
|
||||
Using stock rust features, functions can declare destination parameters by accepting mutable references as arguments.
|
||||
This pattern introduces some shortcomings; developers have to make a call on whether to place destination parameters before or after source parameters and they have to enforce consistency across their codebase or accept inconsistencies, leading to hard-to-remember interfaces.
|
||||
|
||||
Functions declared like this are more cumbersome to use when the destination parameter should be allocated on the fly.
|
||||
|
||||
```rust
|
||||
use std::ops::BitXorAssign;
|
||||
|
||||
fn xor_slice<T>(dst: &mut [T], src: &[T])
|
||||
where T: BitXorAssign + Clone {
|
||||
assert!(src.len() == dst.len());
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
*d ^= s.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let flip0 = b"\xff\x00\x00\x00";
|
||||
let flip1 = b"\x00\xff\x00\x00";
|
||||
let flip01 = b"\xff\xff\x00\x00";
|
||||
|
||||
// Copy a slice from src to dest; its unclear whether src or dest should come first
|
||||
let mut dst = [0u8; 4];
|
||||
xor_slice(&mut dst, flip0);
|
||||
xor_slice(&mut dst, flip1);
|
||||
assert_eq!(&dst[..], &flip01[..]);
|
||||
|
||||
// The other examples can not be translated to use the standard rust pattern,
|
||||
// since using mutable references for destination parameters does not allow
|
||||
// for specifying the destination parameter on the right side or allocating
|
||||
// the destination parameter on the fly.
|
||||
```
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Using a function with destination
|
||||
|
||||
There are a couple of ways to use a function with destination:
|
||||
|
||||
```rust
|
||||
use rosenpass_to::{to, To};
|
||||
use rosenpass_to::ops::{copy_array, copy_slice_least};
|
||||
|
||||
let mut dst = b" ".to_vec();
|
||||
|
||||
// Using the to function to have data flowing from the right to the left,
|
||||
// performing something akin to a variable assignment
|
||||
to(&mut dst[..], copy_slice_least(b"Hello World"));
|
||||
assert_eq!(&dst[..], b"Hello World");
|
||||
|
||||
// Using the to method to have information flowing from the left to the right
|
||||
copy_slice_least(b"This is fin").to(&mut dst[..]);
|
||||
assert_eq!(&dst[..], b"This is fin");
|
||||
|
||||
// You can allocate the destination variable on the fly using `.to_this(...)`
|
||||
let tmp = copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned());
|
||||
assert_eq!(&tmp[..], b"This is new---verwritten");
|
||||
|
||||
// You can allocate the destination variable on the fly `.collect(..)` if it implements default
|
||||
let tmp = copy_slice_least(b"This is ad-hoc").collect::<[u8; 16]>();
|
||||
assert_eq!(&tmp[..], b"This is ad-hoc\0\0");
|
||||
|
||||
// Finally, if the destination variable specified by the function implements default,
|
||||
// you can simply use `.to_value()` to allocate it on the fly.
|
||||
let tmp = copy_array(b"Fixed").to_value();
|
||||
assert_eq!(&tmp[..], b"Fixed");
|
||||
```
|
||||
|
||||
### Builtin functions with destination
|
||||
|
||||
The to crate provides basic functions with destination for copying data between slices and arrays.
|
||||
|
||||
```rust
|
||||
use rosenpass_to::{to, To};
|
||||
use rosenpass_to::ops::{copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, try_copy_slice_least_src};
|
||||
|
||||
let mut dst = b" ".to_vec();
|
||||
|
||||
// Copy a slice, source and destination must match exactly
|
||||
to(&mut dst[..], copy_slice(b"Hello World"));
|
||||
assert_eq!(&dst[..], b"Hello World");
|
||||
|
||||
// Copy a slice, destination must be at least as long as the destination
|
||||
to(&mut dst[4..], copy_slice_least_src(b"!!!"));
|
||||
assert_eq!(&dst[..], b"Hell!!!orld");
|
||||
|
||||
// Copy a slice, copying as many bytes as possible
|
||||
to(&mut dst[6..], copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
|
||||
assert_eq!(&dst[..], b"Hell!!xxxxx");
|
||||
|
||||
// Copy a slice, will return None and abort if the sizes do not much
|
||||
assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World")));
|
||||
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
|
||||
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---------------------")));
|
||||
assert_eq!(&dst[..], b"Hello World");
|
||||
|
||||
// Copy a slice, will return None and abort if source is longer than destination
|
||||
assert_eq!(Some(()), to(&mut dst[4..], try_copy_slice_least_src(b"!!!")));
|
||||
assert_eq!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------")));
|
||||
assert_eq!(&dst[..], b"Hell!!!orld");
|
||||
|
||||
// Copy fixed size arrays all at once
|
||||
let mut dst = [0u8; 5];
|
||||
to(&mut dst, copy_array(b"Hello"));
|
||||
assert_eq!(&dst, b"Hello");
|
||||
```
|
||||
|
||||
### Declaring a function with destination
|
||||
|
||||
The easiest way to declare a function with destination is to use the with_destination function.
|
||||
|
||||
```rust
|
||||
use rosenpass_to::{To, to, with_destination};
|
||||
use rosenpass_to::ops::copy_array;
|
||||
|
||||
/// Copy the given slice to the start of a vector, reusing its memory if possible
|
||||
fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a
|
||||
where T: Clone {
|
||||
with_destination(move |dst: &mut Vec<T>| {
|
||||
dst.clear();
|
||||
dst.extend_from_slice(src);
|
||||
})
|
||||
}
|
||||
|
||||
let mut buf = copy_to_vec(b"Hello World, this is a long text.").to_value();
|
||||
assert_eq!(&buf[..], b"Hello World, this is a long text.");
|
||||
|
||||
to(&mut buf, copy_to_vec(b"Avoids allocation"));
|
||||
assert_eq!(&buf[..], b"Avoids allocation");
|
||||
```
|
||||
|
||||
This example also shows of some of the advantages of using To: The function gains a very slight allocate over using `.to_vec()` by reusing memory:
|
||||
|
||||
```rust
|
||||
let mut buf = b"Hello World, this is a long text.".to_vec();
|
||||
buf = b"This allocates".to_vec(); // This uses memory allocation
|
||||
```
|
||||
|
||||
The same pattern can be implemented without `to`, at the cost of being slightly more verbose
|
||||
|
||||
```rust
|
||||
/// Copy the given slice to the start of a vector, reusing its memory if possible
|
||||
fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T])
|
||||
where T: Clone {
|
||||
dst.clear();
|
||||
dst.extend_from_slice(src);
|
||||
}
|
||||
|
||||
let mut buf = Vec::default();
|
||||
copy_to_vec(&mut buf, b"Hello World, this is a long text.");
|
||||
assert_eq!(&buf[..], b"Hello World, this is a long text.");
|
||||
|
||||
copy_to_vec(&mut buf, b"Avoids allocation");
|
||||
assert_eq!(&buf[..], b"Avoids allocation");
|
||||
```
|
||||
|
||||
This usability enhancement might seem minor, but when many functions take destination parameters, manually allocating all of these can really become annoying.
|
||||
|
||||
## Beside values: Functions with destination and return value
|
||||
|
||||
Return values are supported, but `from_this()`, `to_value()`, and `collect()` cannot be used together with return values (unless they implement CondenseBeside – see the next section), since that would erase the return value.
|
||||
|
||||
Alternative functions are returned, that return a `to::Beside` value, containing both the
|
||||
destination variable and the return value.
|
||||
|
||||
```rust
|
||||
use std::cmp::{min, max};
|
||||
use rosenpass_to::{To, to, with_destination, Beside};
|
||||
|
||||
/// Copy an array of floats and calculate the average
|
||||
pub fn copy_and_average<'a>(src: &'a[f64]) -> impl To<[f64], f64> + 'a {
|
||||
with_destination(move |dst: &mut [f64]| {
|
||||
assert!(src.len() == dst.len());
|
||||
let mut sum = 0f64;
|
||||
for (d, s) in dst.iter_mut().zip(src.iter()) {
|
||||
*d = *s;
|
||||
sum = sum + *d;
|
||||
}
|
||||
sum / (src.len() as f64)
|
||||
})
|
||||
}
|
||||
|
||||
let src = [12f64, 13f64, 14f64];
|
||||
|
||||
// `.to()` and `to(...)` function as normal, but return the value now
|
||||
let mut dst = [0f64; 3];
|
||||
let avg = copy_and_average(&src).to(&mut dst);
|
||||
assert_eq!((&dst[..], avg), (&src[..], 13f64));
|
||||
|
||||
let mut dst = [0f64; 3];
|
||||
let avg = to(&mut dst, copy_and_average(&src));
|
||||
assert_eq!((&dst[..], avg), (&src[..], 13f64));
|
||||
|
||||
// Instead of .to_this, .to_value, or .collect variants returning a beside value have to be used
|
||||
|
||||
let Beside(dst, avg) = copy_and_average(&src).to_this_beside(|| [0f64; 3]);
|
||||
assert_eq!((&dst[..], avg), (&src[..], 13f64));
|
||||
|
||||
let Beside(dst, avg) = copy_and_average(&src).collect_beside::<[f64; 3]>();
|
||||
assert_eq!((&dst[..], avg), (&src[..], 13f64));
|
||||
|
||||
// Beside values are simple named tuples
|
||||
|
||||
let b = copy_and_average(&src).collect_beside::<[f64; 3]>();
|
||||
assert_eq!(b, Beside(dst, avg));
|
||||
|
||||
// They can convert from and to tuples
|
||||
let b_tup = (dst, avg);
|
||||
assert_eq!(b, (dst, avg).into());
|
||||
assert_eq!(b, Beside::from(b_tup));
|
||||
|
||||
// Simple accessors for the value and returned value are provided
|
||||
assert_eq!(&dst, b.dest());
|
||||
assert_eq!(&avg, b.ret());
|
||||
|
||||
let mut tmp = b;
|
||||
*tmp.dest_mut() = [42f64; 3];
|
||||
*tmp.ret_mut() = 42f64;
|
||||
assert_eq!(tmp, Beside([42f64; 3], 42f64));
|
||||
```
|
||||
|
||||
## Beside Condensation: Working with destinations and Optional or Result
|
||||
|
||||
When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value.
|
||||
|
||||
```rust
|
||||
use std::result::Result;
|
||||
use rosenpass_to::{Beside};
|
||||
|
||||
assert_eq!((), Beside((), ()).condense());
|
||||
|
||||
assert_eq!(42, Beside(42, ()).condense());
|
||||
assert_eq!(None, Beside(42, None).condense());
|
||||
|
||||
let ok_unit = Result::<(), ()>::Ok(());
|
||||
assert_eq!(Ok(42), Beside(42, ok_unit).condense());
|
||||
|
||||
let err_unit = Result::<(), ()>::Err(());
|
||||
assert_eq!(Err(()), Beside(42, err_unit).condense());
|
||||
```
|
||||
|
||||
When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value:
|
||||
|
||||
```rust
|
||||
use rosenpass_to::To;
|
||||
use rosenpass_to::ops::try_copy_slice;;
|
||||
|
||||
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>();
|
||||
assert_eq!(tmp, Some(*b"Hello World"));
|
||||
|
||||
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 2]>();
|
||||
assert_eq!(tmp, None);
|
||||
|
||||
let tmp = try_copy_slice(b"Hello World").to_this(|| [0u8; 11].to_vec());
|
||||
assert_eq!(tmp, Some(b"Hello World".to_vec()));
|
||||
|
||||
let tmp = try_copy_slice(b"Hello World").to_this(|| [0u8; 2].to_vec());
|
||||
assert_eq!(tmp, None);
|
||||
```
|
||||
|
||||
The same naturally also works for Results, but the example is a bit harder to motivate:
|
||||
|
||||
```rust
|
||||
use std::result::Result;
|
||||
use rosenpass_to::{to, To, with_destination};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Default)]
|
||||
struct InvalidFloat;
|
||||
|
||||
fn check_float(f: f64) -> Result<(), InvalidFloat> {
|
||||
if f.is_normal() || f == 0.0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InvalidFloat)
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_add<'a>(src: f64) -> impl To<f64, Result<(), InvalidFloat>> + 'a {
|
||||
with_destination(move |dst: &mut f64| {
|
||||
check_float(src)?;
|
||||
check_float(*dst)?;
|
||||
*dst += src;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
let mut tmp = 0.0;
|
||||
checked_add(14.0).to(&mut tmp).unwrap();
|
||||
checked_add(12.0).to(&mut tmp).unwrap();
|
||||
assert_eq!(tmp, 26.0);
|
||||
|
||||
assert_eq!(Ok(78.0), checked_add(14.0).to_this(|| 64.0));
|
||||
assert_eq!(Ok(14.0), checked_add(14.0).to_value());
|
||||
assert_eq!(Ok(14.0), checked_add(14.0).collect());
|
||||
|
||||
assert_eq!(Err(InvalidFloat), checked_add(f64::NAN).to_this(|| 64.0));
|
||||
assert_eq!(Err(InvalidFloat), checked_add(f64::INFINITY).to_value());
|
||||
```
|
||||
|
||||
## Custom condensation
|
||||
|
||||
Condensation is implemented through a trait called CondenseBeside ([local](CondenseBeside) | [docs.rs](https://docs.rs/to/latest/rosenpass-to/trait.CondenseBeside.html)). You can implement it for your own types.
|
||||
|
||||
If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules.
|
||||
|
||||
```rust
|
||||
use rosenpass_to::{To, with_destination, Beside, CondenseBeside};
|
||||
use rosenpass_to::ops::copy_slice;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Default)]
|
||||
struct MyTuple<Left, Right>(Left, Right);
|
||||
|
||||
impl<Val, Right> CondenseBeside<Val> for MyTuple<(), Right> {
|
||||
type Condensed = MyTuple<Val, Right>;
|
||||
|
||||
fn condense(self, val: Val) -> MyTuple<Val, Right> {
|
||||
let MyTuple((), right) = self;
|
||||
MyTuple(val, right)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a
|
||||
where T: Copy, U: 'a {
|
||||
with_destination(move |dst: &mut [T]| {
|
||||
copy_slice(src).to(dst);
|
||||
something
|
||||
})
|
||||
}
|
||||
|
||||
let tmp = Beside(42, MyTuple((), 23)).condense();
|
||||
assert_eq!(tmp, MyTuple(42, 23));
|
||||
|
||||
let tmp = copy_slice_and_return_something(b"23", MyTuple((), 42)).collect::<[u8; 2]>();
|
||||
assert_eq!(tmp, MyTuple(*b"23", 42));
|
||||
```
|
||||
|
||||
## Manually implementing the To trait
|
||||
|
||||
Using `with_destination(...)` is convenient, but since it uses closures it results in an type that can not be written down, which is why the `-> impl To<...>` pattern is used everywhere in this tutorial.
|
||||
|
||||
Implementing the ToTrait manual is the right choice for library use cases.
|
||||
|
||||
```rust
|
||||
use rosenpass_to::{to, To, with_destination};
|
||||
|
||||
struct TryCopySliceSource<'a, T: Copy> {
|
||||
src: &'a [T],
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
|
||||
fn to(self, dst: &mut [T]) -> Option<()> {
|
||||
(self.src.len() == dst.len())
|
||||
.then(|| dst.copy_from_slice(self.src))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T>
|
||||
where T: Copy {
|
||||
TryCopySliceSource { src }
|
||||
}
|
||||
|
||||
let mut dst = try_copy_slice(b"Hello World").collect::<[u8; 11]>().unwrap();
|
||||
assert_eq!(&dst[..], b"Hello World");
|
||||
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
|
||||
```
|
||||
|
||||
## Methods with destination
|
||||
|
||||
Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation.
|
||||
|
||||
```rust
|
||||
use std::borrow::Borrow;
|
||||
use rosenpass_to::{to, To, with_destination};
|
||||
|
||||
struct TryCopySliceSource<'a, T: Copy> {
|
||||
src: &'a [T],
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
|
||||
fn to(self, dst: &mut [T]) -> Option<()> {
|
||||
(self.src.len() == dst.len())
|
||||
.then(|| dst.copy_from_slice(self.src))
|
||||
}
|
||||
}
|
||||
|
||||
trait TryCopySliceExt<'a, T: Copy> {
|
||||
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>;
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref {
|
||||
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
|
||||
TryCopySliceSource {
|
||||
src: self.borrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dst = b"Hello World".try_copy_slice().collect::<[u8; 11]>().unwrap();
|
||||
assert_eq!(&dst[..], b"Hello World");
|
||||
assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice()));
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
|
||||
|
||||
#[cfg(doctest)]
|
||||
doc_comment::doctest!("../README.md");
|
||||
|
||||
// Core implementation
|
||||
mod to;
|
||||
pub use crate::to::{
|
||||
beside::Beside, condense::CondenseBeside, dst_coercion::DstCoercion, to_function::to,
|
||||
to_trait::To, with_destination::with_destination,
|
||||
};
|
||||
|
||||
// Example use cases
|
||||
pub mod ops;
|
||||
@@ -1,80 +0,0 @@
|
||||
//! Functions with destination copying data between slices and arrays.
|
||||
|
||||
use crate::{with_destination, To};
|
||||
|
||||
/// Function with destination that copies data from
|
||||
/// origin into the destination.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if the two slices have different lengths.
|
||||
pub fn copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T]| out.copy_from_slice(origin))
|
||||
}
|
||||
|
||||
/// Function with destination that copies all data from
|
||||
/// origin into the destination.
|
||||
///
|
||||
/// Destination may be longer than origin.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if destination is shorter than origin.
|
||||
pub fn copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T]| copy_slice(origin).to(&mut out[..origin.len()]))
|
||||
}
|
||||
|
||||
/// Function with destination that copies as much data as possible from origin to the
|
||||
/// destination.
|
||||
///
|
||||
/// Copies as much data as is present in the shorter slice.
|
||||
pub fn copy_slice_least<'a, T>(origin: &'a [T]) -> impl To<[T], ()> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T]| {
|
||||
let len = std::cmp::min(origin.len(), out.len());
|
||||
copy_slice(&origin[..len]).to(&mut out[..len])
|
||||
})
|
||||
}
|
||||
|
||||
/// Function with destination that attempts to copy data from origin into the destination.
|
||||
///
|
||||
/// Will return None if the slices are of different lengths.
|
||||
pub fn try_copy_slice<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T]| {
|
||||
(origin.len() == out.len()).then(|| copy_slice(origin).to(out))
|
||||
})
|
||||
}
|
||||
|
||||
/// Function with destination that tries to copy all data from
|
||||
/// origin into the destination.
|
||||
///
|
||||
/// Destination may be longer than origin.
|
||||
///
|
||||
/// Will return None if the destination is shorter than origin.
|
||||
pub fn try_copy_slice_least_src<'a, T>(origin: &'a [T]) -> impl To<[T], Option<()>> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T]| {
|
||||
(origin.len() <= out.len()).then(|| copy_slice_least_src(origin).to(out))
|
||||
})
|
||||
}
|
||||
|
||||
/// Function with destination that copies all data between two array references.
|
||||
pub fn copy_array<'a, T, const N: usize>(origin: &'a [T; N]) -> impl To<[T; N], ()> + 'a
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
with_destination(|out: &mut [T; N]| out.copy_from_slice(origin))
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use crate::CondenseBeside;
|
||||
|
||||
/// Named tuple holding the return value and the output from a function with destinations.
|
||||
#[derive(Debug, PartialEq, Eq, Default, PartialOrd, Ord, Copy, Clone)]
|
||||
pub struct Beside<Val, Ret>(pub Val, pub Ret);
|
||||
|
||||
impl<Val, Ret> Beside<Val, Ret> {
|
||||
pub fn dest(&self) -> &Val {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn ret(&self) -> &Ret {
|
||||
&self.1
|
||||
}
|
||||
|
||||
pub fn dest_mut(&mut self) -> &mut Val {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
pub fn ret_mut(&mut self) -> &mut Ret {
|
||||
&mut self.1
|
||||
}
|
||||
|
||||
/// Perform beside condensation. See [CondenseBeside]
|
||||
pub fn condense(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Ret: CondenseBeside<Val>,
|
||||
{
|
||||
self.1.condense(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Val, Ret> From<(Val, Ret)> for Beside<Val, Ret> {
|
||||
fn from(tuple: (Val, Ret)) -> Self {
|
||||
let (val, ret) = tuple;
|
||||
Self(val, ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Val, Ret> From<Beside<Val, Ret>> for (Val, Ret) {
|
||||
fn from(beside: Beside<Val, Ret>) -> Self {
|
||||
let Beside(val, ret) = beside;
|
||||
(val, ret)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/// Beside condensation.
|
||||
///
|
||||
/// This trait can be used to enable the use of [to_this(|| ...)](crate::To::to_this),
|
||||
/// [to_value()](crate::To::to_value), and [collect::<...>()](crate::To::collect) with custom
|
||||
/// types.
|
||||
///
|
||||
/// The function [Beside::condense()](crate::Beside::condense) is a shorthand for using the
|
||||
/// condense trait.
|
||||
pub trait CondenseBeside<Val> {
|
||||
type Condensed;
|
||||
|
||||
fn condense(self, ret: Val) -> Self::Condensed;
|
||||
}
|
||||
|
||||
impl<Val> CondenseBeside<Val> for () {
|
||||
type Condensed = Val;
|
||||
|
||||
fn condense(self, ret: Val) -> Val {
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<Val, Error> CondenseBeside<Val> for Result<(), Error> {
|
||||
type Condensed = Result<Val, Error>;
|
||||
|
||||
fn condense(self, ret: Val) -> Result<Val, Error> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Val> CondenseBeside<Val> for Option<()> {
|
||||
type Condensed = Option<Val>;
|
||||
|
||||
fn condense(self, ret: Val) -> Option<Val> {
|
||||
self.map(|()| ret)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/// Helper performing explicit unsized coercion.
|
||||
/// Used by the [to](crate::to()) function.
|
||||
pub trait DstCoercion<Dst: ?Sized> {
|
||||
fn coerce_dest(&mut self) -> &mut Dst;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> DstCoercion<T> for T {
|
||||
fn coerce_dest(&mut self) -> &mut T {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> DstCoercion<[T]> for [T; N] {
|
||||
fn coerce_dest(&mut self) -> &mut [T] {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//! Module implementing the core function with destination functionality.
|
||||
//!
|
||||
//! Parameter naming scheme
|
||||
//!
|
||||
//! - `Src: impl To<Dst, Ret>` – The value of an instance of something implementing the `To` trait
|
||||
//! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to
|
||||
//! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to
|
||||
//! - `Coercable: ?Sized + DstCoercion<Dst>`; (e.g. `[u8]`, `[u8; 16]`) – Some value that
|
||||
//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of
|
||||
//! `Dst` (e.g. `[u8; 64]`).
|
||||
//! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output
|
||||
//! - `Val: Sized + BorrowMut<Dst>`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst`
|
||||
//! - `Condensed: Sized = CondenseBeside<Val>::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside<Val, Ret>::condense()`/`Ret::condense(v)` for all `v : Val`).
|
||||
|
||||
pub mod beside;
|
||||
pub mod condense;
|
||||
pub mod dst_coercion;
|
||||
pub mod to_function;
|
||||
pub mod to_trait;
|
||||
pub mod with_destination;
|
||||
@@ -1,14 +0,0 @@
|
||||
use crate::{DstCoercion, To};
|
||||
|
||||
/// Alias for [To::to] moving the destination to the left.
|
||||
///
|
||||
/// This provides similar haptics to the let assignment syntax is rust, which also keeps
|
||||
/// the variable to assign to on the left and the generating function on the right.
|
||||
pub fn to<Coercable, Src, Dst, Ret>(dst: &mut Coercable, src: Src) -> Ret
|
||||
where
|
||||
Coercable: ?Sized + DstCoercion<Dst>,
|
||||
Src: To<Dst, Ret>,
|
||||
Dst: ?Sized,
|
||||
{
|
||||
src.to(dst.coerce_dest())
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
use crate::{Beside, CondenseBeside};
|
||||
use std::borrow::BorrowMut;
|
||||
|
||||
// The To trait is the core of the to crate; most functions with destinations will either return
|
||||
// an object that is an instance of this trait or they will return `-> impl To<Destination,
|
||||
// Return_value`.
|
||||
//
|
||||
// A quick way to implement a function with destination is to use the
|
||||
// [with_destination(|param: &mut Type| ...)] higher order function.
|
||||
pub trait To<Dst: ?Sized, Ret>: Sized {
|
||||
fn to(self, out: &mut Dst) -> Ret;
|
||||
|
||||
/// Generate a destination on the fly with a lambda.
|
||||
///
|
||||
/// Calls the provided closure to create a value,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
fn to_this_beside<Val, Fun>(self, fun: Fun) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: BorrowMut<Dst>,
|
||||
Fun: FnOnce() -> Val,
|
||||
{
|
||||
let mut val = fun();
|
||||
let ret = self.to(val.borrow_mut());
|
||||
Beside(val, ret)
|
||||
}
|
||||
|
||||
/// Generate a destination on the fly using default.
|
||||
///
|
||||
/// Uses [Default] to create a value,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
fn to_value_beside(self) -> Beside<Dst, Ret>
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
{
|
||||
self.to_this_beside(|| Dst::default())
|
||||
}
|
||||
|
||||
/// Generate a destination on the fly using default and a custom storage type.
|
||||
///
|
||||
/// Uses [Default] to create a value of the given type,
|
||||
/// calls [crate::to()] to evaluate the function and finally
|
||||
/// returns a [Beside] instance containing the generated destination value and the return
|
||||
/// value.
|
||||
///
|
||||
/// Using collect_beside with an explicit type instead of [Self::to_value_beside] is mainly useful
|
||||
/// when the Destination is unsized.
|
||||
///
|
||||
/// This could be the case when the destination is an `[u8]` for instance.
|
||||
fn collect_beside<Val>(self) -> Beside<Val, Ret>
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
{
|
||||
self.to_this_beside(|| Val::default())
|
||||
}
|
||||
|
||||
/// Generate a destination on the fly with a lambda, condensing the destination and the
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_this_beside] followed by calling [Beside::condense].
|
||||
fn to_this<Val, Fun>(self, fun: Fun) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Ret: CondenseBeside<Val>,
|
||||
Val: BorrowMut<Dst>,
|
||||
Fun: FnOnce() -> Val,
|
||||
{
|
||||
self.to_this_beside(fun).condense()
|
||||
}
|
||||
|
||||
/// Generate a destination on the fly using default, condensing the destination and the
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::to_value_beside] followed by calling [Beside::condense].
|
||||
fn to_value(self) -> <Ret as CondenseBeside<Dst>>::Condensed
|
||||
where
|
||||
Dst: Sized + Default,
|
||||
Ret: CondenseBeside<Dst>,
|
||||
{
|
||||
self.to_value_beside().condense()
|
||||
}
|
||||
|
||||
/// Generate a destination on the fly using default, condensing the destination and the
|
||||
/// return value into one.
|
||||
///
|
||||
/// This is like using [Self::collect_beside] followed by calling [Beside::condense].
|
||||
fn collect<Val>(self) -> <Ret as CondenseBeside<Val>>::Condensed
|
||||
where
|
||||
Val: Default + BorrowMut<Dst>,
|
||||
Ret: CondenseBeside<Val>,
|
||||
{
|
||||
self.collect_beside::<Val>().condense()
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::To;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
struct ToClosure<Dst, Ret, Fun>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
Fun: FnOnce(&mut Dst) -> Ret,
|
||||
{
|
||||
fun: Fun,
|
||||
_val: PhantomData<Box<Dst>>,
|
||||
}
|
||||
|
||||
impl<Dst, Ret, Fun> To<Dst, Ret> for ToClosure<Dst, Ret, Fun>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
Fun: FnOnce(&mut Dst) -> Ret,
|
||||
{
|
||||
fn to(self, out: &mut Dst) -> Ret {
|
||||
(self.fun)(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to create a function with destination.
|
||||
///
|
||||
/// See the tutorial in [readme.me]..
|
||||
pub fn with_destination<Dst, Ret, Fun>(fun: Fun) -> impl To<Dst, Ret>
|
||||
where
|
||||
Dst: ?Sized,
|
||||
Fun: FnOnce(&mut Dst) -> Ret,
|
||||
{
|
||||
ToClosure {
|
||||
fun,
|
||||
_val: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "rosenpass-util"
|
||||
version = "0.1.0"
|
||||
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Rosenpass internal utilities"
|
||||
homepage = "https://rosenpass.eu/"
|
||||
repository = "https://github.com/rosenpass/rosenpass"
|
||||
readme = "readme.md"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
@@ -1,20 +0,0 @@
|
||||
use base64::{
|
||||
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
|
||||
write::EncoderWriter as B64Writer,
|
||||
};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use base64::engine::general_purpose::GeneralPurpose as Base64Engine;
|
||||
const B64ENGINE: Base64Engine = base64::engine::general_purpose::STANDARD;
|
||||
|
||||
pub fn fmt_b64<'a>(payload: &'a [u8]) -> B64Display<'a, 'static, Base64Engine> {
|
||||
B64Display::<'a, 'static>::new(payload, &B64ENGINE)
|
||||
}
|
||||
|
||||
pub fn b64_writer<W: Write>(w: W) -> B64Writer<'static, Base64Engine, W> {
|
||||
B64Writer::new(w, &B64ENGINE)
|
||||
}
|
||||
|
||||
pub fn b64_reader<R: Read>(r: R) -> B64Reader<'static, Base64Engine, R> {
|
||||
B64Reader::new(r, &B64ENGINE)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use anyhow::ensure;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::result::Result;
|
||||
use std::{fs::OpenOptions, path::Path};
|
||||
|
||||
/// Open a file writable
|
||||
pub fn fopen_w<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
||||
Ok(OpenOptions::new()
|
||||
.read(false)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)?)
|
||||
}
|
||||
/// Open a file readable
|
||||
pub fn fopen_r<P: AsRef<Path>>(path: P) -> std::io::Result<File> {
|
||||
Ok(OpenOptions::new()
|
||||
.read(true)
|
||||
.write(false)
|
||||
.create(false)
|
||||
.truncate(false)
|
||||
.open(path)?)
|
||||
}
|
||||
|
||||
pub trait ReadExactToEnd {
|
||||
type Error;
|
||||
|
||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<R: Read> ReadExactToEnd for R {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()> {
|
||||
let mut dummy = [0u8; 8];
|
||||
self.read_exact(buf)?;
|
||||
ensure!(self.read(&mut dummy)? == 0, "File too long!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LoadValue {
|
||||
type Error;
|
||||
|
||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait LoadValueB64 {
|
||||
type Error;
|
||||
|
||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait StoreValue {
|
||||
type Error;
|
||||
|
||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
pub fn mutating<T, F>(mut v: T, f: F) -> T
|
||||
where
|
||||
F: Fn(&mut T),
|
||||
{
|
||||
f(&mut v);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn sideeffect<T, F>(v: T, f: F) -> T
|
||||
where
|
||||
F: Fn(&T),
|
||||
{
|
||||
f(&v);
|
||||
v
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
pub mod b64;
|
||||
pub mod file;
|
||||
pub mod functional;
|
||||
pub mod mem;
|
||||
pub mod ord;
|
||||
pub mod result;
|
||||
pub mod time;
|
||||
@@ -1,33 +0,0 @@
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::cmp::min;
|
||||
|
||||
/// Concatenate two byte arrays
|
||||
// TODO: Zeroize result?
|
||||
#[macro_export]
|
||||
macro_rules! cat {
|
||||
($len:expr; $($toks:expr),+) => {{
|
||||
let mut buf = [0u8; $len];
|
||||
let mut off = 0;
|
||||
$({
|
||||
let tok = $toks;
|
||||
let tr = ::std::borrow::Borrow::<[u8]>::borrow(tok);
|
||||
(&mut buf[off..(off + tr.len())]).copy_from_slice(tr);
|
||||
off += tr.len();
|
||||
})+
|
||||
assert!(off == buf.len(), "Size mismatch in cat!()");
|
||||
buf
|
||||
}}
|
||||
}
|
||||
|
||||
// TODO: consistent inout ordering
|
||||
pub fn cpy<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
dst.borrow_mut().copy_from_slice(src.borrow());
|
||||
}
|
||||
|
||||
/// Copy from `src` to `dst`. If `src` and `dst` are not of equal length, copy as many bytes as possible.
|
||||
pub fn cpy_min<T: BorrowMut<[u8]> + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, dst: &mut T) {
|
||||
let src = src.borrow();
|
||||
let dst = dst.borrow_mut();
|
||||
let len = min(src.len(), dst.len());
|
||||
dst[..len].copy_from_slice(&src[..len]);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// TODO remove this once std::cmp::max becomes const
|
||||
pub const fn max_usize(a: usize, b: usize) -> usize {
|
||||
if a > b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timebase(Instant);
|
||||
|
||||
impl Default for Timebase {
|
||||
fn default() -> Self {
|
||||
Self(Instant::now())
|
||||
}
|
||||
}
|
||||
|
||||
impl Timebase {
|
||||
pub fn now(&self) -> f64 {
|
||||
self.0.elapsed().as_secs_f64()
|
||||
}
|
||||
|
||||
pub fn dur(&self, t: f64) -> Duration {
|
||||
Duration::from_secs_f64(t)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user