mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-10 14:50:31 -08:00
Compare commits
24 Commits
dev/karo/m
...
dev/improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96bc5bfb2e | ||
|
|
d84efa7422 | ||
|
|
61ef5b92bb | ||
|
|
184cff0e5e | ||
|
|
9819148b6f | ||
|
|
3a0ebd2cbc | ||
|
|
1eefb5f263 | ||
|
|
d45e24e9b6 | ||
|
|
972e82b35f | ||
|
|
101c9bf4b3 | ||
|
|
955d57ea49 | ||
|
|
838f700a74 | ||
|
|
5448cdc565 | ||
|
|
77cd8a9fd1 | ||
|
|
0f89ab7976 | ||
|
|
70fa9bd6d7 | ||
|
|
85a61808de | ||
|
|
cf132bca11 | ||
|
|
7bda010a9b | ||
|
|
36089fd37f | ||
|
|
31d43accd5 | ||
|
|
205c301012 | ||
|
|
d014095469 | ||
|
|
7cece82119 |
12
.github/workflows/qc.yaml
vendored
12
.github/workflows/qc.yaml
vendored
@@ -141,8 +141,10 @@ jobs:
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Run fuzzing
|
||||
run: |
|
||||
cargo fuzz run fuzz_aead_enc_into -- -max_total_time=60
|
||||
cargo fuzz run fuzz_blake2b -- -max_total_time=60
|
||||
cargo fuzz run fuzz_handle_msg -- -max_total_time=60
|
||||
cargo fuzz run fuzz_kyber_encaps -- -max_total_time=60
|
||||
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=60
|
||||
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
|
||||
|
||||
276
Cargo.lock
generated
276
Cargo.lock
generated
@@ -44,6 +44,12 @@ 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"
|
||||
@@ -85,7 +91,7 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -95,7 +101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -293,9 +299,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.8"
|
||||
version = "4.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
|
||||
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -303,9 +309,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.8"
|
||||
version = "4.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
|
||||
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -492,12 +498,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.6"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -509,7 +515,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -524,9 +530,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
@@ -577,9 +583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -608,7 +614,7 @@ version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -619,9 +625,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
@@ -644,7 +650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.2",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -655,7 +661,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -756,9 +762,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libsodium-sys-stable"
|
||||
version = "1.20.3"
|
||||
version = "1.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc31f983531631496f4e621110cd81468ab78b65dee0046cfddea83caa2c327"
|
||||
checksum = "d1d164bc6f9139c5f95efb4f0be931b2bd5a9edf7e4e3c945d26b95ab8fa669b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -773,9 +779,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -828,7 +834,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -903,9 +909,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
@@ -941,6 +947,12 @@ 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"
|
||||
@@ -953,9 +965,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -978,6 +990,36 @@ 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"
|
||||
@@ -1038,16 +1080,16 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.5"
|
||||
version = "0.17.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
|
||||
checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1061,18 +1103,20 @@ name = "rosenpass"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.4.8",
|
||||
"clap 4.4.10",
|
||||
"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",
|
||||
@@ -1084,12 +1128,18 @@ 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",
|
||||
@@ -1110,16 +1160,58 @@ 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-log"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[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",
|
||||
@@ -1156,22 +1248,22 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.21"
|
||||
version = "0.38.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.8"
|
||||
version = "0.21.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
|
||||
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
@@ -1222,18 +1314,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.192"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1321,9 +1413,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@@ -1448,9 +1540,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.8.0"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
|
||||
checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
@@ -1463,9 +1555,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -1572,9 +1664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
version = "0.25.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
||||
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
@@ -1625,7 +1717,16 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1634,13 +1735,28 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1649,42 +1765,84 @@ 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"
|
||||
|
||||
35
Cargo.toml
35
Cargo.toml
@@ -3,12 +3,17 @@ resolver = "2"
|
||||
|
||||
members = [
|
||||
"rosenpass",
|
||||
"cipher-traits",
|
||||
"ciphers",
|
||||
"util",
|
||||
"constant-time",
|
||||
"sodium",
|
||||
"oqs",
|
||||
"to",
|
||||
"fuzz",
|
||||
"secret-memory",
|
||||
"lenses",
|
||||
"rosenpass-log",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -24,27 +29,33 @@ 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.1"
|
||||
base64 = "0.21.5"
|
||||
zeroize = "1.7.0"
|
||||
memoffset = "0.9.0"
|
||||
lazy_static = "1.4.0"
|
||||
thiserror = "1.0.40"
|
||||
paste = "1.0.12"
|
||||
env_logger = "0.10.0"
|
||||
toml = "0.7.4"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
env_logger = "0.10.1"
|
||||
toml = "0.7.8"
|
||||
static_assertions = "1.1.0"
|
||||
log = { version = "0.4.17" }
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
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.71", features = ["backtrace"] }
|
||||
mio = { version = "0.8.6", features = ["net", "os-poll"] }
|
||||
libsodium-sys-stable= { version = "1.19.28", features = ["use-pkg-config"] }
|
||||
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
|
||||
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'] }
|
||||
|
||||
12
cipher-traits/Cargo.toml
Normal file
12
cipher-traits/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[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]
|
||||
5
cipher-traits/readme.md
Normal file
5
cipher-traits/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
47
cipher-traits/src/kem.rs
Normal file
47
cipher-traits/src/kem.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
//! 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>;
|
||||
}
|
||||
2
cipher-traits/src/lib.rs
Normal file
2
cipher-traits/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod kem;
|
||||
pub use kem::Kem;
|
||||
@@ -14,5 +14,7 @@ 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 }
|
||||
|
||||
109
ciphers/src/hash_domain.rs
Normal file
109
ciphers/src/hash_domain.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ 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::KEY_LEN);
|
||||
const_assert!(KEY_LEN == hash_domain::KEY_LEN);
|
||||
|
||||
/// Authenticated encryption with associated data
|
||||
pub mod aead {
|
||||
@@ -21,8 +21,9 @@ pub mod xaead {
|
||||
};
|
||||
}
|
||||
|
||||
pub mod hash {
|
||||
pub use crate::subtle::incorrect_hmac_blake2b::{
|
||||
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
|
||||
};
|
||||
pub mod hash_domain;
|
||||
|
||||
pub mod kem {
|
||||
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
|
||||
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
|
||||
}
|
||||
|
||||
@@ -291,7 +291,6 @@
|
||||
];
|
||||
buildPhase = ''
|
||||
export HOME=$(mktemp -d)
|
||||
export OSFONTDIR="$(kpsewhich --var-value TEXMF)/fonts/{opentype/public/nunito,truetype/google/noto}"
|
||||
latexmk -r tex/CI.rc
|
||||
'';
|
||||
installPhase = ''
|
||||
|
||||
@@ -11,8 +11,10 @@ cargo-fuzz = true
|
||||
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 }
|
||||
|
||||
@@ -45,3 +47,15 @@ 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
|
||||
|
||||
12
fuzz/fuzz_targets/box_sodium_alloc.rs
Normal file
12
fuzz/fuzz_targets/box_sodium_alloc.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
#![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());
|
||||
});
|
||||
@@ -3,8 +3,8 @@ extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass::coloring::Secret;
|
||||
use rosenpass::protocol::CryptoServer;
|
||||
use rosenpass_secret_memory::Secret;
|
||||
use rosenpass_sodium::init as sodium_init;
|
||||
|
||||
fuzz_target!(|rx_buf: &[u8]| {
|
||||
|
||||
@@ -4,7 +4,8 @@ extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass::pqkem::{EphemeralKEM, KEM};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::EphemeralKem;
|
||||
|
||||
#[derive(arbitrary::Arbitrary, Debug)]
|
||||
pub struct Input {
|
||||
@@ -15,5 +16,5 @@ 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();
|
||||
EphemeralKem::encaps(&mut shared_secret, &mut ciphertext, &input.pk).unwrap();
|
||||
});
|
||||
|
||||
@@ -3,12 +3,13 @@ extern crate rosenpass;
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
use rosenpass::pqkem::{StaticKEM, KEM};
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::StaticKem;
|
||||
|
||||
fuzz_target!(|input: &[u8]| {
|
||||
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);
|
||||
let _ = StaticKem::encaps(&mut shared_secret, &mut ciphertext, &input);
|
||||
});
|
||||
|
||||
13
fuzz/fuzz_targets/vec_sodium_alloc.rs
Normal file
13
fuzz/fuzz_targets/vec_sodium_alloc.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#![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);
|
||||
});
|
||||
16
lenses/Cargo.toml
Normal file
16
lenses/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[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 }
|
||||
3
lenses/readme.md
Normal file
3
lenses/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Rosenpass internal binary parsing library
|
||||
|
||||
This is an internal library; no guarantee is made about its API at this point in time.
|
||||
206
lenses/src/lib.rs
Normal file
206
lenses/src/lib.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
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])
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
16
oqs/Cargo.toml
Normal file
16
oqs/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[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 }
|
||||
5
oqs/readme.md
Normal file
5
oqs/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
80
oqs/src/kem_macro.rs
Normal file
80
oqs/src/kem_macro.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
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 >];
|
||||
}}
|
||||
}
|
||||
21
oqs/src/lib.rs
Normal file
21
oqs/src/lib.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
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,7 +177,11 @@ version={4.0},
|
||||
\titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}}
|
||||
\title{\inserttitle}
|
||||
}
|
||||
\author{\csname insertauthor\endcsname}
|
||||
\ifx\csname insertauthor\endcsname\relax
|
||||
\author{}
|
||||
\else
|
||||
\author{\parbox{\linewidth}{\centering\insertauthor}}
|
||||
\fi
|
||||
\subject{\csname insertsubject\endcsname}
|
||||
\date{\vspace{-1cm}}
|
||||
}
|
||||
|
||||
9
rosenpass-log/Cargo.toml
Normal file
9
rosenpass-log/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rosenpass-log"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
110
rosenpass-log/src/lib.rs
Normal file
110
rosenpass-log/src/lib.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
#![allow(unused_macros)]
|
||||
/// Whenever a log event occurs, the cause of the event must be decided on. This cause will then
|
||||
/// be used to decide, if an actual log event is to be cause. The goal is to prevent especially
|
||||
/// external, unautherized entities from causing excessive loggin, which otherwise might open the
|
||||
/// door to MITM attacks
|
||||
pub enum Cause {
|
||||
/// An unauthorized entitiy triggered this event via Network
|
||||
///
|
||||
/// Example: a InitHello message in the rosenpass protocol
|
||||
UnauthorizedNetwork,
|
||||
|
||||
/// An authorized entitity triggered this event via Network
|
||||
///
|
||||
/// Example: a handshake was succesful (which asserts the peer is authorized)
|
||||
AuthorizedNetwork,
|
||||
|
||||
/// A local entity like rosenpassctl triggered this event
|
||||
///
|
||||
/// Example: the broker adds a new peer
|
||||
LocalNetwork,
|
||||
|
||||
/// The user caused this event
|
||||
///
|
||||
/// Examples:
|
||||
/// - The process was started
|
||||
/// - Ctrl+C was used to send sig SIGINT
|
||||
User,
|
||||
|
||||
/// The developer wanted this in the log!
|
||||
Developer,
|
||||
}
|
||||
|
||||
// Rational: All events are to be displayed if trace level debugging is configured
|
||||
macro_rules! trace {
|
||||
($cause:expr, $($tail:tt)* ) => {{
|
||||
use crate::Cause::*;
|
||||
match $cause {
|
||||
UnauthorizedNetwork | AuthorizedNetwork | LocalNetwork | User | Developer => {
|
||||
::log::trace!($($tail)*);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// Rational: All events are to be displayed if debug level debugging is configured
|
||||
macro_rules! debug {
|
||||
($cause:expr, $($tail:tt)* ) => {{
|
||||
use crate::Cause::*;
|
||||
match $cause {
|
||||
UnauthorizedNetwork | AuthorizedNetwork | LocalNetwork | User | Developer => {
|
||||
::log::debug!($($tail)*);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// Rational: Only authorized causes shall be able to emit info messages
|
||||
macro_rules! info {
|
||||
($cause:expr, $($tail:tt)* ) => {{
|
||||
use crate::Cause::*;
|
||||
match $cause {
|
||||
UnauthorizedNetwork => {},
|
||||
AuthorizedNetwork | LocalNetwork | User | Developer => {
|
||||
::log::info!($($tail)*);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// Rational: Only authorized causes shall be able to emit info messages
|
||||
macro_rules! warn {
|
||||
($cause:expr, $($tail:tt)* ) => {{
|
||||
use crate::Cause::*;
|
||||
match $cause {
|
||||
UnauthorizedNetwork => {},
|
||||
AuthorizedNetwork | LocalNetwork | User | Developer =>{
|
||||
::log::warn!($($tail)*);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// Rational: Only local sources shall be able to cause errors to be displayed
|
||||
macro_rules! error {
|
||||
($cause:expr, $($tail:tt)* ) => {{
|
||||
use crate::Cause::*;
|
||||
match $cause {
|
||||
UnauthorizedNetwork | AuthorizedNetwork => {},
|
||||
LocalNetwork | User | Developer => {
|
||||
::log::error!($($tail)*);
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn expand_all_macros() {
|
||||
use Cause::*;
|
||||
|
||||
trace!(UnauthorizedNetwork, "beep");
|
||||
debug!(UnauthorizedNetwork, "boop");
|
||||
info!(LocalNetwork, "tock");
|
||||
warn!(LocalNetwork, "möp");
|
||||
error!(User, "knirsch");
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,14 @@ 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 }
|
||||
oqs-sys = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
log = { workspace = true }
|
||||
@@ -33,6 +34,7 @@ serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
mio = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
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::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::app_server;
|
||||
use crate::app_server::AppServer;
|
||||
use crate::{
|
||||
// app_server::{AppServer, LoadValue, LoadValueB64},
|
||||
coloring::Secret,
|
||||
pqkem::{StaticKEM, KEM},
|
||||
protocol::{SPk, SSk, SymKey},
|
||||
};
|
||||
use crate::protocol::{SPk, SSk, SymKey};
|
||||
|
||||
use super::config;
|
||||
|
||||
@@ -89,6 +87,15 @@ pub enum Cli {
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// Deprecated - use gen-keys instead
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
#[allow(rustdoc::invalid_html_tags)]
|
||||
Keygen {
|
||||
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
|
||||
/// public-key <PATH> private-key <PATH>
|
||||
args: Vec<String>,
|
||||
},
|
||||
|
||||
/// Validate a configuration
|
||||
Validate { config_files: Vec<PathBuf> },
|
||||
|
||||
@@ -121,6 +128,40 @@ impl Cli {
|
||||
config::Rosenpass::example_config().store(config_file)?;
|
||||
}
|
||||
|
||||
// Deprecated - use gen-keys instead
|
||||
Keygen { args } => {
|
||||
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
|
||||
|
||||
let mut public_key: Option<PathBuf> = None;
|
||||
let mut secret_key: Option<PathBuf> = None;
|
||||
|
||||
// Manual arg parsing, since clap wants to prefix flags with "--"
|
||||
let mut args = args.into_iter();
|
||||
loop {
|
||||
match (args.next().as_ref().map(String::as_str), args.next()) {
|
||||
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
|
||||
secret_key = Some(opt.into());
|
||||
}
|
||||
(Some("public-key"), Some(opt)) => {
|
||||
public_key = Some(opt.into());
|
||||
}
|
||||
(Some(flag), _) => {
|
||||
bail!("Unknown option `{}`", flag);
|
||||
}
|
||||
(_, _) => break,
|
||||
};
|
||||
}
|
||||
|
||||
if secret_key.is_none() {
|
||||
bail!("private-key is required");
|
||||
}
|
||||
if public_key.is_none() {
|
||||
bail!("public-key is required");
|
||||
}
|
||||
|
||||
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
|
||||
}
|
||||
|
||||
GenKeys {
|
||||
config_file,
|
||||
public_key,
|
||||
@@ -162,12 +203,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())?;
|
||||
|
||||
ssk.store_secret(skf)?;
|
||||
spk.store_secret(pkf)?;
|
||||
generate_and_save_keypair(skf, pkf)?;
|
||||
}
|
||||
|
||||
ExchangeConfig { config_file } => {
|
||||
@@ -249,13 +285,11 @@ impl Cli {
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
/// generate secret and public keys, store in files according to the paths passed as arguments
|
||||
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
|
||||
let mut ssk = crate::protocol::SSk::random();
|
||||
let mut spk = crate::protocol::SPk::random();
|
||||
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
||||
ssk.store_secret(secret_key)?;
|
||||
spk.store_secret(public_key)
|
||||
}
|
||||
|
||||
@@ -1,443 +0,0 @@
|
||||
//! 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 anyhow::Context;
|
||||
use lazy_static::lazy_static;
|
||||
use libsodium_sys as libsodium;
|
||||
use rosenpass_util::{
|
||||
b64::b64_reader,
|
||||
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd, StoreValue},
|
||||
functional::mutating,
|
||||
mem::cpy,
|
||||
};
|
||||
use std::result::Result;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryInto,
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
os::raw::c_void,
|
||||
path::Path,
|
||||
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) {
|
||||
rosenpass_sodium::helpers::memzero(self.secret_mut());
|
||||
}
|
||||
|
||||
/// Sets all data an existing secret to random bytes
|
||||
pub fn randomize(&mut self) {
|
||||
rosenpass_sodium::helpers::randombytes_buf(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) {
|
||||
rosenpass_sodium::helpers::randombytes_buf(&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]);
|
||||
}
|
||||
}
|
||||
|
||||
trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
impl<T: StoreValue> StoreSecret for T {
|
||||
type Error = <T as StoreValue>::Error;
|
||||
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
|
||||
self.store(path)
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,6 @@ 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>,
|
||||
@@ -345,28 +341,20 @@ impl Rosenpass {
|
||||
/// Generate an example configuration
|
||||
pub fn example_config() -> Self {
|
||||
let peer = RosenpassPeer {
|
||||
public_key: "rp-peer-public-key".into(),
|
||||
public_key: "/path/to/rp-peer-public-key".into(),
|
||||
endpoint: Some("my-peer.test:9999".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,
|
||||
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()],
|
||||
}),
|
||||
};
|
||||
|
||||
Self {
|
||||
public_key: "rp-public-key".into(),
|
||||
secret_key: "rp-secret-key".into(),
|
||||
public_key: "/path/to/rp-public-key".into(),
|
||||
secret_key: "/path/to/rp-secret-key".into(),
|
||||
peers: vec![peer],
|
||||
..Self::new("", "")
|
||||
}
|
||||
|
||||
46
rosenpass/src/hash_domains.rs
Normal file
46
rosenpass/src/hash_domains.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! 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");
|
||||
@@ -1,48 +0,0 @@
|
||||
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
|
||||
//! ensures their uniqueness
|
||||
|
||||
|
||||
use crate::prftree::PrfTree;
|
||||
use anyhow::Result;
|
||||
use rosenpass_ciphers::KEY_LEN;
|
||||
|
||||
pub fn protocol() -> Result<PrfTree> {
|
||||
PrfTree::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".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_LEN]> {
|
||||
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 handshake encryption");
|
||||
prflabel_leaf!(_ckextract, res_enc, "responder handshake encryption");
|
||||
|
||||
prflabel!(_ckextract, _user, "user");
|
||||
prflabel!(_user, _rp, "rosenpass.eu");
|
||||
prflabel_leaf!(_rp, osk, "wireguard psk");
|
||||
@@ -1,56 +1,24 @@
|
||||
pub mod coloring;
|
||||
#[rustfmt::skip]
|
||||
pub mod labeled_prf;
|
||||
use rosenpass_lenses::LenseError;
|
||||
|
||||
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("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("buffer size mismatch")]
|
||||
BufferSizeMismatch,
|
||||
#[error("invalid message type")]
|
||||
InvalidMessageType(u8),
|
||||
}
|
||||
|
||||
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),
|
||||
impl From<LenseError> for RosenpassError {
|
||||
fn from(value: LenseError) -> Self {
|
||||
match value {
|
||||
LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::process::exit;
|
||||
|
||||
/// Catches errors, prints them through the logger, then exits
|
||||
pub fn main() {
|
||||
env_logger::init();
|
||||
// default to displaying warning and error log messages only
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
|
||||
|
||||
let res = attempt!({
|
||||
rosenpass_sodium::init()?;
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! The following example uses the [`data_lense` macro](crate::data_lense) to create a lense that
|
||||
//! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that
|
||||
//! might be useful when dealing with UDP headers.
|
||||
//!
|
||||
//! ```
|
||||
//! use rosenpass::{data_lense, RosenpassError, msgs::LenseView};
|
||||
//! use rosenpass_lenses::{lense, LenseView};
|
||||
//! use rosenpass::RosenpassError;
|
||||
//! # fn main() -> Result<(), RosenpassError> {
|
||||
//!
|
||||
//! data_lense! {UdpDatagramHeader :=
|
||||
//! lense! {UdpDatagramHeader :=
|
||||
//! source_port: 2,
|
||||
//! dest_port: 2,
|
||||
//! length: 2,
|
||||
@@ -44,219 +45,14 @@
|
||||
//! ```
|
||||
|
||||
use super::RosenpassError;
|
||||
use crate::pqkem::*;
|
||||
use rosenpass_cipher_traits::Kem;
|
||||
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
|
||||
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
|
||||
use rosenpass_lenses::{lense, LenseView};
|
||||
|
||||
// Macro magic ////////////////////////////////////////////////////////////////
|
||||
|
||||
/// 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> :=
|
||||
lense! { Envelope<M> :=
|
||||
/// [MsgType] of this message
|
||||
msg_type: 1,
|
||||
/// Reserved for future use
|
||||
@@ -270,35 +66,35 @@ data_lense! { Envelope<M> :=
|
||||
cookie: 16
|
||||
}
|
||||
|
||||
data_lense! { InitHello :=
|
||||
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,
|
||||
/// Encrypted TAI64N Time Stamp (against replay attacks)
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
data_lense! { RespHello :=
|
||||
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,
|
||||
scti: StaticKem::CT_LEN,
|
||||
/// Empty encrypted message (just an auth tag)
|
||||
auth: aead::TAG_LEN,
|
||||
/// Responders handshake state in encrypted form
|
||||
biscuit: BISCUIT_CT_LEN
|
||||
}
|
||||
|
||||
data_lense! { InitConf :=
|
||||
lense! { InitConf :=
|
||||
/// Copied from InitHello
|
||||
sidi: 4,
|
||||
/// Copied from RespHello
|
||||
@@ -309,7 +105,7 @@ data_lense! { InitConf :=
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
data_lense! { EmptyData :=
|
||||
lense! { EmptyData :=
|
||||
/// Copied from RespHello
|
||||
sid: 4,
|
||||
/// Nonce
|
||||
@@ -318,7 +114,7 @@ data_lense! { EmptyData :=
|
||||
auth: aead::TAG_LEN
|
||||
}
|
||||
|
||||
data_lense! { Biscuit :=
|
||||
lense! { Biscuit :=
|
||||
/// H(spki) – Ident ifies the initiator
|
||||
pidi: KEY_LEN,
|
||||
/// The biscuit number (replay protection)
|
||||
@@ -327,11 +123,11 @@ data_lense! { Biscuit :=
|
||||
ck: KEY_LEN
|
||||
}
|
||||
|
||||
data_lense! { DataMsg :=
|
||||
lense! { DataMsg :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
data_lense! { CookieReply :=
|
||||
lense! { CookieReply :=
|
||||
dummy: 4
|
||||
}
|
||||
|
||||
|
||||
@@ -1,168 +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 crate::{RosenpassError, RosenpassMaybeError};
|
||||
|
||||
/// Key Encapsulation Mechanism
|
||||
///
|
||||
/// The KEM interface defines three operations: Key generation, key encapsulation and key
|
||||
/// decapsulation.
|
||||
pub trait KEM {
|
||||
/// 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<(), RosenpassError>;
|
||||
|
||||
/// 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<(), RosenpassError>;
|
||||
|
||||
/// 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<(), RosenpassError>;
|
||||
}
|
||||
|
||||
/// A KEM that is secure against Chosen Ciphertext Attacks (CCA).
|
||||
/// In the context of rosenpass this is used for static keys.
|
||||
/// Uses [Classic McEliece](https://classic.mceliece.org/) 460896 from liboqs.
|
||||
///
|
||||
/// Classic McEliece is chosen because of its high security margin and its small
|
||||
/// ciphertexts. The public keys are humongous, but (being static keys) the are never transmitted over
|
||||
/// the wire so this is not a big problem.
|
||||
pub struct StaticKEM;
|
||||
|
||||
/// # 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 checked in the following code before
|
||||
/// the unsafe calls, and an early return with an Err occurs if the byte slice
|
||||
/// size does not match the required size.
|
||||
///
|
||||
/// __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 StaticKEM {
|
||||
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_secret_key as usize;
|
||||
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_public_key as usize;
|
||||
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_ciphertext as usize;
|
||||
const SHK_LEN: usize =
|
||||
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_length_shared_secret as usize;
|
||||
|
||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
|
||||
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_keypair(pk.as_mut_ptr(), sk.as_mut_ptr())
|
||||
.to_rg_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
|
||||
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
|
||||
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_encaps(
|
||||
ct.as_mut_ptr(),
|
||||
shk.as_mut_ptr(),
|
||||
pk.as_ptr(),
|
||||
)
|
||||
.to_rg_error()
|
||||
}
|
||||
}
|
||||
|
||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
|
||||
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
|
||||
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_classic_mceliece_460896_decaps(
|
||||
shk.as_mut_ptr(),
|
||||
ct.as_ptr(),
|
||||
sk.as_ptr(),
|
||||
)
|
||||
.to_rg_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements a KEM that is secure against Chosen Plaintext Attacks (CPA).
|
||||
/// In the context of rosenpass this is used for ephemeral keys.
|
||||
/// Currently the implementation uses
|
||||
/// [Kyber 512](https://openquantumsafe.org/liboqs/algorithms/kem/kyber) from liboqs.
|
||||
///
|
||||
/// This is being used for ephemeral keys; since these are use-once the first post quantum
|
||||
/// wireguard paper claimed that CPA security would be sufficient. Nonetheless we choose kyber
|
||||
/// which provides CCA security since there are no publicly vetted KEMs out there which provide
|
||||
/// only CPA security.
|
||||
pub struct EphemeralKEM;
|
||||
|
||||
/// # 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 checked in the following code before
|
||||
/// the unsafe calls, and an early return with an Err occurs if the byte slice
|
||||
/// size does not match the required size.
|
||||
///
|
||||
/// __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 EphemeralKEM {
|
||||
const SK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_secret_key as usize;
|
||||
const PK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_public_key as usize;
|
||||
const CT_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_ciphertext as usize;
|
||||
const SHK_LEN: usize = oqs_sys::kem::OQS_KEM_kyber_512_length_shared_secret as usize;
|
||||
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
|
||||
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_kyber_512_keypair(pk.as_mut_ptr(), sk.as_mut_ptr()).to_rg_error()
|
||||
}
|
||||
}
|
||||
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
|
||||
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
|
||||
RosenpassError::check_buffer_size(pk.len(), Self::PK_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_kyber_512_encaps(ct.as_mut_ptr(), shk.as_mut_ptr(), pk.as_ptr())
|
||||
.to_rg_error()
|
||||
}
|
||||
}
|
||||
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), RosenpassError> {
|
||||
RosenpassError::check_buffer_size(shk.len(), Self::SHK_LEN)?;
|
||||
RosenpassError::check_buffer_size(sk.len(), Self::SK_LEN)?;
|
||||
RosenpassError::check_buffer_size(ct.len(), Self::CT_LEN)?;
|
||||
unsafe {
|
||||
oqs_sys::kem::OQS_KEM_kyber_512_decaps(shk.as_mut_ptr(), ct.as_ptr(), sk.as_ptr())
|
||||
.to_rg_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
//! Implementation of the tree-like structure used for the label derivation in [labeled_prf](crate::labeled_prf)
|
||||
use crate::coloring::Secret;
|
||||
|
||||
use anyhow::Result;
|
||||
use rosenpass_ciphers::{hash, KEY_LEN};
|
||||
use rosenpass_to::To;
|
||||
|
||||
// TODO Use a proper Dec interface
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrfTree([u8; KEY_LEN]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrfTreeBranch([u8; KEY_LEN]);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretPrfTree(Secret<KEY_LEN>);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SecretPrfTreeBranch(Secret<KEY_LEN>);
|
||||
|
||||
impl PrfTree {
|
||||
pub fn zero() -> Self {
|
||||
Self([0u8; KEY_LEN])
|
||||
}
|
||||
|
||||
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(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
||||
}
|
||||
|
||||
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_LEN] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PrfTreeBranch {
|
||||
pub fn mix(&self, v: &[u8]) -> Result<PrfTree> {
|
||||
Ok(PrfTree(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
|
||||
}
|
||||
|
||||
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());
|
||||
hash::hash(k, d).to(r.0.secret_mut())?;
|
||||
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_LEN>) -> 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_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 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_LEN> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,9 @@
|
||||
//! [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<()> {
|
||||
@@ -30,11 +31,11 @@
|
||||
//!
|
||||
//! // 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();
|
||||
@@ -67,20 +68,20 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::{
|
||||
coloring::*,
|
||||
labeled_prf as lprf,
|
||||
msgs::*,
|
||||
pqkem::*,
|
||||
prftree::{SecretPrfTree, SecretPrfTreeBranch},
|
||||
};
|
||||
use crate::{hash_domains, msgs::*};
|
||||
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 //////////////////////////
|
||||
|
||||
@@ -139,10 +140,10 @@ 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>;
|
||||
@@ -233,7 +234,7 @@ pub struct HandshakeState {
|
||||
/// Session ID of Responder
|
||||
pub sidr: SessionId,
|
||||
/// Chaining Key
|
||||
pub ck: SecretPrfTreeBranch,
|
||||
pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
|
||||
@@ -285,7 +286,7 @@ pub struct Session {
|
||||
pub sidt: SessionId,
|
||||
pub handshake_role: HandshakeRole,
|
||||
// Crypto
|
||||
pub ck: SecretPrfTreeBranch,
|
||||
pub ck: SecretHashDomainNamespace,
|
||||
/// Key for Transmission ("transmission key mine")
|
||||
pub txkm: SymKey,
|
||||
/// Key for Reception ("transmission key theirs")
|
||||
@@ -460,7 +461,7 @@ impl CryptoServer {
|
||||
#[rustfmt::skip]
|
||||
pub fn pidm(&self) -> Result<PeerId> {
|
||||
Ok(Public::new(
|
||||
lprf::peerid()?
|
||||
hash_domains::peerid()?
|
||||
.mix(self.spkm.secret())?
|
||||
.into_value()))
|
||||
}
|
||||
@@ -590,7 +591,7 @@ impl Peer {
|
||||
#[rustfmt::skip]
|
||||
pub fn pidt(&self) -> Result<PeerId> {
|
||||
Ok(Public::new(
|
||||
lprf::peerid()?
|
||||
hash_domains::peerid()?
|
||||
.mix(self.spkt.secret())?
|
||||
.into_value()))
|
||||
}
|
||||
@@ -603,7 +604,7 @@ impl Session {
|
||||
sidm: SessionId::zero(),
|
||||
sidt: SessionId::zero(),
|
||||
handshake_role: HandshakeRole::Initiator,
|
||||
ck: SecretPrfTree::zero().dup(),
|
||||
ck: SecretHashDomain::zero().dup(),
|
||||
txkm: SymKey::zero(),
|
||||
txkt: SymKey::zero(),
|
||||
txnm: 0,
|
||||
@@ -1154,7 +1155,7 @@ impl IniHsPtr {
|
||||
.min(ih.tx_count as f64),
|
||||
)
|
||||
* RETRANSMIT_DELAY_JITTER
|
||||
* (rosenpass_sodium::helpers::rand_f64() + 1.0); // TODO: Replace with the rand crate
|
||||
* (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
|
||||
ih.tx_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1174,7 +1175,7 @@ where
|
||||
{
|
||||
/// Calculate the message authentication code (`mac`)
|
||||
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
|
||||
let mac = lprf::mac()?
|
||||
let mac = hash_domains::mac()?
|
||||
.mix(peer.get(srv).spkt.secret())?
|
||||
.mix(self.until_mac())?;
|
||||
self.mac_mut()
|
||||
@@ -1189,7 +1190,9 @@ where
|
||||
{
|
||||
/// Check the message authentication code
|
||||
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
|
||||
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?;
|
||||
let expected = hash_domains::mac()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(self.until_mac())?;
|
||||
Ok(rosenpass_sodium::helpers::memcmp(
|
||||
self.mac(),
|
||||
&expected.into_value()[..16],
|
||||
@@ -1219,38 +1222,38 @@ impl HandshakeState {
|
||||
Self {
|
||||
sidi: SessionId::zero(),
|
||||
sidr: SessionId::zero(),
|
||||
ck: SecretPrfTree::zero().dup(),
|
||||
ck: SecretHashDomain::zero().dup(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn erase(&mut self) {
|
||||
self.ck = SecretPrfTree::zero().dup();
|
||||
self.ck = SecretHashDomain::zero().dup();
|
||||
}
|
||||
|
||||
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
|
||||
self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup();
|
||||
self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup();
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
|
||||
self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup();
|
||||
self.ck = self.ck.mix(&hash_domains::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(&lprf::hs_enc()?)?.into_secret();
|
||||
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
|
||||
aead::encrypt(ct, k.secret(), &[0u8; aead::NONCE_LEN], &[], pt)?;
|
||||
self.mix(ct)
|
||||
}
|
||||
|
||||
pub fn decrypt_and_mix(&mut self, pt: &mut [u8], ct: &[u8]) -> Result<&mut Self> {
|
||||
let k = self.ck.mix(&lprf::hs_enc()?)?.into_secret();
|
||||
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
|
||||
aead::decrypt(pt, k.secret(), &[0u8; aead::NONCE_LEN], &[], ct)?;
|
||||
self.mix(ct)
|
||||
}
|
||||
|
||||
// I loathe "error: constant expression depends on a generic parameter"
|
||||
pub fn encaps_and_mix<T: KEM, const SHK_LEN: usize>(
|
||||
pub fn encaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
|
||||
&mut self,
|
||||
ct: &mut [u8],
|
||||
pk: &[u8],
|
||||
@@ -1260,7 +1263,7 @@ impl HandshakeState {
|
||||
self.mix(pk)?.mix(shk.secret())?.mix(ct)
|
||||
}
|
||||
|
||||
pub fn decaps_and_mix<T: KEM, const SHK_LEN: usize>(
|
||||
pub fn decaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
|
||||
&mut self,
|
||||
sk: &[u8],
|
||||
pk: &[u8],
|
||||
@@ -1290,7 +1293,7 @@ impl HandshakeState {
|
||||
.copy_from_slice(self.ck.clone().danger_into_secret().secret());
|
||||
|
||||
// calculate ad contents
|
||||
let ad = lprf::biscuit_ad()?
|
||||
let ad = hash_domains::biscuit_ad()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(self.sidi.as_slice())?
|
||||
.mix(self.sidr.as_slice())?
|
||||
@@ -1325,7 +1328,7 @@ impl HandshakeState {
|
||||
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
|
||||
|
||||
// Calculate additional data fields
|
||||
let ad = lprf::biscuit_ad()?
|
||||
let ad = hash_domains::biscuit_ad()?
|
||||
.mix(srv.spkm.secret())?
|
||||
.mix(sidi.as_slice())?
|
||||
.mix(sidr.as_slice())?
|
||||
@@ -1343,7 +1346,7 @@ impl HandshakeState {
|
||||
|
||||
// Reconstruct the biscuit fields
|
||||
let no = BiscuitId::from_slice(biscuit.biscuit_no());
|
||||
let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
|
||||
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
|
||||
let pid = PeerId::from_slice(biscuit.pidi());
|
||||
|
||||
// Reconstruct the handshake state
|
||||
@@ -1370,8 +1373,8 @@ impl HandshakeState {
|
||||
|
||||
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
|
||||
let HandshakeState { ck, sidi, sidr } = self;
|
||||
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret();
|
||||
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret();
|
||||
let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret();
|
||||
let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret();
|
||||
let created_at = srv.timebase.now();
|
||||
let (ntx, nrx) = (0, 0);
|
||||
let (mysid, peersid, ktx, krx) = match role {
|
||||
@@ -1402,7 +1405,7 @@ impl CryptoServer {
|
||||
.get(self)
|
||||
.as_ref()
|
||||
.with_context(|| format!("No current session for peer {:?}", peer))?;
|
||||
Ok(session.ck.mix(&lprf::osk()?)?.into_secret())
|
||||
Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1424,7 +1427,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
|
||||
@@ -1432,7 +1435,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(),
|
||||
)?;
|
||||
@@ -1471,7 +1474,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(),
|
||||
@@ -1501,10 +1504,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(),
|
||||
)?;
|
||||
@@ -1569,14 +1572,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(),
|
||||
@@ -1812,7 +1815,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))
|
||||
}
|
||||
|
||||
|
||||
20
secret-memory/Cargo.toml
Normal file
20
secret-memory/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[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 }
|
||||
5
secret-memory/readme.md
Normal file
5
secret-memory/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
20
secret-memory/src/debug.rs
Normal file
20
secret-memory/src/debug.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
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(())
|
||||
}
|
||||
7
secret-memory/src/file.rs
Normal file
7
secret-memory/src/file.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
pub trait StoreSecret {
|
||||
type Error;
|
||||
|
||||
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
|
||||
}
|
||||
9
secret-memory/src/lib.rs
Normal file
9
secret-memory/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod debug;
|
||||
pub mod file;
|
||||
pub mod rand;
|
||||
|
||||
mod public;
|
||||
pub use crate::public::Public;
|
||||
|
||||
mod secret;
|
||||
pub use crate::secret::Secret;
|
||||
112
secret-memory/src/public.rs
Normal file
112
secret-memory/src/public.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
5
secret-memory/src/rand.rs
Normal file
5
secret-memory/src/rand.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
|
||||
pub fn rng() -> Rng {
|
||||
rand::thread_rng()
|
||||
}
|
||||
237
secret-memory/src/secret.rs
Normal file
237
secret-memory/src/secret.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -15,3 +15,4 @@ rosenpass-to = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
libsodium-sys-stable = { workspace = true }
|
||||
log = { workspace = true }
|
||||
allocator-api2 = { workspace = true }
|
||||
|
||||
95
sodium/src/alloc/allocator.rs
Normal file
95
sodium/src/alloc/allocator.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
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) };
|
||||
}
|
||||
}
|
||||
10
sodium/src/alloc/mod.rs
Normal file
10
sodium/src/alloc/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! 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>;
|
||||
@@ -26,27 +26,3 @@ pub fn increment(v: &mut [u8]) {
|
||||
libsodium::sodium_increment(v.as_mut_ptr(), v.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn randombytes_buf(buf: &mut [u8]) {
|
||||
unsafe { libsodium::randombytes_buf(buf.as_mut_ptr() as *mut c_void, buf.len()) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn memzero(buf: &mut [u8]) {
|
||||
unsafe { libsodium::sodium_memzero(buf.as_mut_ptr() as *mut c_void, buf.len()) };
|
||||
}
|
||||
|
||||
// Choose a fully random u64
|
||||
// TODO: Replace with ::rand::random
|
||||
pub fn rand_u64() -> u64 {
|
||||
let mut buf = [0u8; 8];
|
||||
randombytes_buf(&mut buf);
|
||||
u64::from_le_bytes(buf)
|
||||
}
|
||||
|
||||
// Choose a random f64 in [0; 1] inclusive; quick and dirty
|
||||
// TODO: Replace with ::rand::random
|
||||
pub fn rand_f64() -> f64 {
|
||||
(rand_u64() as f64) / (u64::MAX as f64)
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@ pub fn init() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
pub mod aead;
|
||||
pub mod alloc;
|
||||
pub mod hash;
|
||||
pub mod helpers;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
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 {
|
||||
@@ -5,3 +8,86 @@ macro_rules! attempt {
|
||||
(|| -> ::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 {
|
||||
type Value;
|
||||
|
||||
/// Extract the contained value while being panic-safe, like .unwrap()
|
||||
///
|
||||
/// # Panic Safety
|
||||
///
|
||||
/// Implementations of guaranteed() must not panic.
|
||||
fn guaranteed(self) -> Self::Value;
|
||||
}
|
||||
|
||||
/// A result type that never contains an error.
|
||||
///
|
||||
/// This is mostly useful in generic contexts.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::num::Wrapping;
|
||||
/// use std::result::Result;
|
||||
/// use std::convert::Infallible
|
||||
///
|
||||
/// trait FailableAddition {
|
||||
/// type Error;
|
||||
/// fn failable_addition(&self, other: &Self) -> Result<Self, Self::Error>;
|
||||
/// }
|
||||
///
|
||||
/// struct OverflowError;
|
||||
///
|
||||
/// impl<T> FailableAddition for Wrapping<T> {
|
||||
/// type Error = Infallible;
|
||||
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
|
||||
/// self + other
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<T> FailableAddition for u32 {
|
||||
/// type Error = Infallible;
|
||||
/// fn failable_addition(&self, other: &Self) -> Guaranteed<Self> {
|
||||
/// match self.checked_add(*other) {
|
||||
/// Some(v) => Ok(v),
|
||||
/// None => Err(OverflowError),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn failable_multiply<T>(a: &T, b: u32)
|
||||
/// -> Result<T, T::Error>
|
||||
/// where
|
||||
/// T: FailableAddition<Error> {
|
||||
/// let mut accu = a.failable_addition(a)?;
|
||||
/// for _ in ..(b-1) {
|
||||
/// accu.failable_addition(a)?;
|
||||
/// }
|
||||
/// Ok(accu)
|
||||
/// }
|
||||
///
|
||||
/// // We can use .guaranteed() with Wrapping<u32>, since the operation uses
|
||||
/// // the Infallible 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);
|
||||
///
|
||||
/// // We can not use .guaranteed() with u32, since there can be an error.
|
||||
/// // We can however use unwrap(), which may panic
|
||||
/// 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>;
|
||||
|
||||
impl<T> GuaranteedValue for Guaranteed<T> {
|
||||
type Value = T;
|
||||
fn guaranteed(self) -> Self::Value {
|
||||
self.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user