Compare commits

..

2 Commits

Author SHA1 Message Date
Karolin Varner
7632fa047f chore: Add Prabhpreet to the list of whitepper authors 2023-12-01 12:02:33 +01:00
Prabhpreet Dua
3739042d25 feat: Implement the wireguard cookie mechanism 2023-12-01 11:54:48 +01:00
53 changed files with 1818 additions and 1612 deletions

View File

@@ -141,10 +141,8 @@ jobs:
run: cargo install cargo-fuzz
- name: Run fuzzing
run: |
cargo fuzz run fuzz_aead_enc_into -- -max_total_time=5
cargo fuzz run fuzz_blake2b -- -max_total_time=5
cargo fuzz run fuzz_handle_msg -- -max_total_time=5
ulimit -s 8192000 && RUST_MIN_STACK=33554432000 && cargo fuzz run fuzz_kyber_encaps -- -max_total_time=5
cargo fuzz run fuzz_mceliece_encaps -- -max_total_time=5
cargo fuzz run fuzz_box_sodium_alloc -- -max_total_time=5
cargo fuzz run fuzz_vec_sodium_alloc -- -max_total_time=5
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

269
Cargo.lock generated
View File

@@ -44,12 +44,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anes"
version = "0.1.6"
@@ -91,7 +85,7 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -101,7 +95,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -299,9 +293,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.10"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
dependencies = [
"clap_builder",
"clap_derive",
@@ -309,9 +303,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.9"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
dependencies = [
"anstream",
"anstyle",
@@ -498,12 +492,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@@ -515,7 +509,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -530,9 +524,9 @@ dependencies = [
[[package]]
name = "form_urlencoded"
version = "1.2.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
@@ -583,9 +577,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.3"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
[[package]]
name = "heck"
@@ -614,7 +608,7 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -625,9 +619,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "idna"
version = "0.5.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -650,7 +644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
"hashbrown 0.14.2",
]
[[package]]
@@ -661,7 +655,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi 0.3.3",
"rustix",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -762,9 +756,9 @@ dependencies = [
[[package]]
name = "libsodium-sys-stable"
version = "1.20.4"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1d164bc6f9139c5f95efb4f0be931b2bd5a9edf7e4e3c945d26b95ab8fa669b"
checksum = "cfc31f983531631496f4e621110cd81468ab78b65dee0046cfddea83caa2c327"
dependencies = [
"cc",
"libc",
@@ -779,9 +773,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "log"
@@ -834,7 +828,7 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -909,9 +903,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pkg-config"
@@ -947,12 +941,6 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "prettyplease"
version = "0.2.15"
@@ -965,9 +953,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.70"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
@@ -990,36 +978,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.8.0"
@@ -1080,16 +1038,16 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "ring"
version = "0.17.6"
version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866"
checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b"
dependencies = [
"cc",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.48.0",
"windows-sys",
]
[[package]]
@@ -1103,20 +1061,18 @@ name = "rosenpass"
version = "0.2.1"
dependencies = [
"anyhow",
"clap 4.4.10",
"clap 4.4.8",
"criterion",
"env_logger",
"lazy_static",
"libsodium-sys-stable",
"log",
"memoffset",
"mio",
"oqs-sys",
"paste",
"rand",
"rosenpass-cipher-traits",
"rosenpass-ciphers",
"rosenpass-constant-time",
"rosenpass-lenses",
"rosenpass-secret-memory",
"rosenpass-sodium",
"rosenpass-to",
"rosenpass-util",
@@ -1128,18 +1084,12 @@ 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",
@@ -1160,51 +1110,16 @@ dependencies = [
"arbitrary",
"libfuzzer-sys",
"rosenpass",
"rosenpass-cipher-traits",
"rosenpass-ciphers",
"rosenpass-secret-memory",
"rosenpass-sodium",
"rosenpass-to",
"stacker",
]
[[package]]
name = "rosenpass-lenses"
version = "0.1.0"
dependencies = [
"paste",
"thiserror",
]
[[package]]
name = "rosenpass-oqs"
version = "0.1.0"
dependencies = [
"oqs-sys",
"paste",
"rosenpass-cipher-traits",
"rosenpass-util",
]
[[package]]
name = "rosenpass-secret-memory"
version = "0.1.0"
dependencies = [
"anyhow",
"lazy_static",
"libsodium-sys-stable",
"rand",
"rosenpass-sodium",
"rosenpass-to",
"rosenpass-util",
"zeroize",
]
[[package]]
name = "rosenpass-sodium"
version = "0.1.0"
dependencies = [
"allocator-api2",
"anyhow",
"libsodium-sys-stable",
"log",
@@ -1241,22 +1156,22 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.26"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.21.9"
version = "0.21.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
dependencies = [
"log",
"ring",
@@ -1307,18 +1222,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.193"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
@@ -1406,9 +1321,9 @@ dependencies = [
[[package]]
name = "termcolor"
version = "1.4.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
dependencies = [
"winapi-util",
]
@@ -1533,9 +1448,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.9.1"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97"
checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3"
dependencies = [
"base64",
"log",
@@ -1548,9 +1463,9 @@ dependencies = [
[[package]]
name = "url"
version = "2.5.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
@@ -1657,9 +1572,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.25.3"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "which"
@@ -1710,16 +1625,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets",
]
[[package]]
@@ -1728,28 +1634,13 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
@@ -1758,84 +1649,42 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.5.19"

View File

@@ -3,16 +3,12 @@ resolver = "2"
members = [
"rosenpass",
"cipher-traits",
"ciphers",
"util",
"constant-time",
"sodium",
"oqs",
"to",
"fuzz",
"secret-memory",
"lenses",
]
default-members = [
@@ -28,33 +24,27 @@ rosenpass = { path = "rosenpass" }
rosenpass-util = { path = "util" }
rosenpass-constant-time = { path = "constant-time" }
rosenpass-sodium = { path = "sodium" }
rosenpass-cipher-traits = { path = "cipher-traits" }
rosenpass-ciphers = { path = "ciphers" }
rosenpass-to = { path = "to" }
rosenpass-secret-memory = { path = "secret-memory" }
rosenpass-oqs = { path = "oqs" }
rosenpass-lenses = { path = "lenses" }
criterion = "0.4.0"
test_bin = "0.4.0"
libfuzzer-sys = "0.4"
stacker = "0.1.15"
doc-comment = "0.3.3"
base64 = "0.21.5"
base64 = "0.21.1"
zeroize = "1.7.0"
memoffset = "0.9.0"
lazy_static = "1.4.0"
thiserror = "1.0.50"
paste = "1.0.14"
env_logger = "0.10.1"
toml = "0.7.8"
thiserror = "1.0.40"
paste = "1.0.12"
env_logger = "0.10.0"
toml = "0.7.4"
static_assertions = "1.1.0"
allocator-api2 = "0.2.16"
rand = "0.8.5"
log = { version = "0.4.20" }
clap = { version = "4.4.10", features = ["derive"] }
serde = { version = "1.0.193", features = ["derive"] }
log = { version = "0.4.17" }
clap = { version = "4.3.0", features = ["derive"] }
serde = { version = "1.0.163", features = ["derive"] }
arbitrary = { version = "1.3.2", features = ["derive"] }
anyhow = { version = "1.0.75", features = ["backtrace", "std"] }
mio = { version = "0.8.9", features = ["net", "os-poll"] }
libsodium-sys-stable= { version = "1.20.4", features = ["use-pkg-config"] }
oqs-sys = { version = "0.8", default-features = false, features = ['classic_mceliece', 'kyber'] }
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'] }

View File

@@ -1,12 +0,0 @@
[package]
name = "rosenpass-cipher-traits"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal traits for cryptographic primitives"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]

View File

@@ -1,5 +0,0 @@
# Rosenpass internal libsodium bindings
Rosenpass internal library providing traits for cryptographic primitives.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,47 +0,0 @@
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
//!
//! encapsulation.
//! The [KEM] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM].
use std::result::Result;
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key
/// decapsulation.
pub trait Kem {
type Error;
/// Secrete Key length
const SK_LEN: usize;
/// Public Key length
const PK_LEN: usize;
/// Ciphertext length
const CT_LEN: usize;
/// Shared Secret length
const SHK_LEN: usize;
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
///
/// `keygen() -> sk, pk`
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error>;
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
///
/// `encaps(pk) -> shk, ct`
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error>;
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
/// (`shk`)
///
/// `decaps(sk, ct) -> shk`
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error>;
}

View File

@@ -1,2 +0,0 @@
mod kem;
pub use kem::Kem;

View File

@@ -14,7 +14,5 @@ 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 }

View File

@@ -1,109 +0,0 @@
use anyhow::Result;
use rosenpass_secret_memory::Secret;
use rosenpass_to::To;
use crate::subtle::incorrect_hmac_blake2b as hash;
pub use hash::KEY_LEN;
// TODO Use a proper Dec interface
#[derive(Clone, Debug)]
pub struct HashDomain([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct HashDomainNamespace([u8; KEY_LEN]);
#[derive(Clone, Debug)]
pub struct SecretHashDomain(Secret<KEY_LEN>);
#[derive(Clone, Debug)]
pub struct SecretHashDomainNamespace(Secret<KEY_LEN>);
impl HashDomain {
pub fn zero() -> Self {
Self([0u8; KEY_LEN])
}
pub fn dup(self) -> HashDomainNamespace {
HashDomainNamespace(self.0)
}
pub fn turn_secret(self) -> SecretHashDomain {
SecretHashDomain(Secret::from_slice(&self.0))
}
// TODO: Protocol! Use domain separation to ensure that
pub fn mix(self, v: &[u8]) -> Result<Self> {
Ok(Self(hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?))
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
pub fn into_value(self) -> [u8; KEY_LEN] {
self.0
}
}
impl HashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<HashDomain> {
Ok(HashDomain(
hash::hash(&self.0, v).collect::<[u8; KEY_LEN]>()?,
))
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(&self.0, v.secret())
}
}
impl SecretHashDomain {
pub fn invoke_primitive(k: &[u8], d: &[u8]) -> Result<SecretHashDomain> {
let mut r = SecretHashDomain(Secret::zero());
hash::hash(k, d).to(r.0.secret_mut())?;
Ok(r)
}
pub fn zero() -> Self {
Self(Secret::zero())
}
pub fn dup(self) -> SecretHashDomainNamespace {
SecretHashDomainNamespace(self.0)
}
pub fn danger_from_secret(k: Secret<KEY_LEN>) -> Self {
Self(k)
}
pub fn mix(self, v: &[u8]) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(self, v: Secret<N>) -> Result<SecretHashDomain> {
Self::invoke_primitive(self.0.secret(), v.secret())
}
pub fn into_secret(self) -> Secret<KEY_LEN> {
self.0
}
pub fn into_secret_slice(mut self, v: &[u8], dst: &[u8]) -> Result<()> {
hash::hash(v, dst).to(self.0.secret_mut())
}
}
impl SecretHashDomainNamespace {
pub fn mix(&self, v: &[u8]) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v)
}
pub fn mix_secret<const N: usize>(&self, v: Secret<N>) -> Result<SecretHashDomain> {
SecretHashDomain::invoke_primitive(self.0.secret(), v.secret())
}
// TODO: This entire API is not very nice; we need this for biscuits, but
// it might be better to extract a special "biscuit"
// labeled subkey and reinitialize the chain with this
pub fn danger_into_secret(self) -> Secret<KEY_LEN> {
self.0
}
}

View File

@@ -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_domain::KEY_LEN);
const_assert!(KEY_LEN == hash::KEY_LEN);
/// Authenticated encryption with associated data
pub mod aead {
@@ -21,9 +21,8 @@ pub mod xaead {
};
}
pub mod hash_domain;
pub mod kem {
pub use rosenpass_oqs::ClassicMceliece460896 as StaticKem;
pub use rosenpass_oqs::Kyber512 as EphemeralKem;
pub mod hash {
pub use crate::subtle::incorrect_hmac_blake2b::{
hash, KEY_LEN, KEY_MAX, KEY_MIN, OUT_MAX, OUT_MIN,
};
}

View File

@@ -264,7 +264,6 @@
inherit system;
};
packages = self.packages.${system};
devShells = self.devShells.${system};
in
{
#
@@ -326,28 +325,6 @@
'';
};
#
### A DevContainer attempt
#
packages.dev-container = pkgs.dockerTools.buildImage rec {
name = "rosenpass-dev-container";
tag = "latest";
copyToRoot = pkgs.buildEnv {
name = "image-root";
paths = with pkgs; [
bash
coreutils
curl
gnutar
gzip
openssh
stdenv.cc
]; #++ lib.lists.filter (p: builtins.hasAttr "version" p)
#devShells.default.nativeBuildInputs;
pathsToLink = [ "/bin" "/lib" ];
};
config.Cmd = [ "/bin/bash" ];
};
#
### Devshells ###

View File

@@ -11,10 +11,8 @@ 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 }
@@ -47,15 +45,3 @@ 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

View File

@@ -1,12 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{
alloc::{Alloc as SodiumAlloc, Box as SodiumBox},
init,
};
fuzz_target!(|data: &[u8]| {
let _ = init();
let _ = SodiumBox::new_in(data, SodiumAlloc::new());
});

View File

@@ -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]| {

View File

@@ -4,8 +4,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::EphemeralKem;
use rosenpass::pqkem::{EphemeralKEM, KEM};
#[derive(arbitrary::Arbitrary, Debug)]
pub struct Input {
@@ -16,5 +15,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();
});

View File

@@ -3,13 +3,12 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass::pqkem::{StaticKEM, KEM};
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {
fuzz_target!(|input: &[u8]| {
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);
});

View File

@@ -1,13 +0,0 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
use rosenpass_sodium::{
alloc::{Alloc as SodiumAlloc, Vec as SodiumVec},
init,
};
fuzz_target!(|data: &[u8]| {
let _ = init();
let mut vec = SodiumVec::new_in(SodiumAlloc::new());
vec.extend_from_slice(data);
});

View File

@@ -1,16 +0,0 @@
[package]
name = "rosenpass-lenses"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal library for parsing binary data securely"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
paste = { workspace = true }
thiserror = { workspace = true }

View File

@@ -1,3 +0,0 @@
# Rosenpass internal binary parsing library
This is an internal library; no guarantee is made about its API at this point in time.

View File

@@ -1,206 +0,0 @@
use std::result::Result;
/// Common trait shared by all Lenses
pub trait LenseView {
const LEN: usize;
}
/// Error during lense creation
#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)]
pub enum LenseError {
#[error("buffer size mismatch")]
BufferSizeMismatch,
}
pub type LenseResult<T> = Result<T, LenseError>;
impl LenseError {
pub fn ensure_exact_buffer_size(len: usize, required: usize) -> LenseResult<()> {
(len == required)
.then_some(())
.ok_or(LenseError::BufferSizeMismatch)
}
pub fn ensure_sufficient_buffer_size(len: usize, required: usize) -> LenseResult<()> {
(len >= required)
.then_some(())
.ok_or(LenseError::BufferSizeMismatch)
}
}
/// A macro to create data lenses.
#[macro_export]
macro_rules! lense(
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = lense!(maybe_docstring_link $len)]
/// bytes long
pub fn $field(&self) -> &__ContainerType::Output {
&self.0[$offset .. $offset + $len]
}
/// The bytes until the
#[doc = lense!(maybe_docstring_link Self::$field)]
/// field
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
&self.0[0 .. $offset]
}
// if the tail exits, consume it as well
$(
lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
)?
}
};
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = lense!(maybe_docstring_link $len)]
/// bytes long
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
&mut self.0[$offset .. $offset + $len]
}
// if the tail exits, consume it as well
$(
lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
)?
}
};
// switch that yields literals unchanged, but creates docstring links to
// constants
// TODO the doc string link doesn't work if $x is taken from a generic,
(maybe_docstring_link $x:literal) => (stringify!($x));
(maybe_docstring_link $x:expr) => (stringify!([$x]));
// struct name < optional generics > := optional doc string field name : field length, ...
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
/// A data lense to manipulate byte slices.
///
//// # Fields
///
$(
/// - `
#[doc = stringify!($field)]
/// `:
#[doc = lense!(maybe_docstring_link $len)]
/// bytes
)+
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
__ContainerType,
// The phantom data is required, since all generics declared on a
// type need to be used on the type.
// https://doc.rust-lang.org/stable/error_codes/E0392.html
$( $( ::core::marker::PhantomData<$generic> ),+ )?
);
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
$(
/// Size in bytes of the field `
#[doc = !($field)]
/// `
pub const fn [< $field _len >]() -> usize{
$len
}
)+
/// Verify that `len` exactly holds [Self]
pub fn check_size(len: usize) -> ::rosenpass_lenses::LenseResult<()> {
::rosenpass_lenses::LenseError::ensure_exact_buffer_size(len, $( $len + )+ 0)
}
}
// read-only accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
{
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
}
// mutable accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
{
lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
/// View into all bytes belonging to this Lense
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
&mut self.0[0..Self::LEN]
}
}
// lense trait, allowing us to know the implementing lenses size
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
/// Number of bytes required to store this type in binary format
const LEN: usize = $( $len + )+ 0;
}
/// Extension trait to allow checked creation of a lense over
/// some byte slice that contains a
#[doc = lense!(maybe_docstring_link $type)]
pub trait [< $type Ext >] {
type __ContainerType;
/// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
/// Create a lense to the byte slice, automatically truncating oversized buffers
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >>;
}
impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
let required_size = $( $len + )+ 0;
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
[< $type Ext >]::[< $type:snake >](&self[..required_size])
}
}
impl<'a> [< $type Ext >] for &'a mut [u8] {
type __ContainerType = &'a mut [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> ::rosenpass_lenses::LenseResult< $type<Self::__ContainerType, $( $($generic),+ )? >> {
let required_size = $( $len + )+ 0;
::rosenpass_lenses::LenseError::ensure_sufficient_buffer_size(self.len(), required_size)?;
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
}
}
});
);

View File

@@ -1,16 +0,0 @@
[package]
name = "rosenpass-oqs"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal bindings to liboqs"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
rosenpass-cipher-traits = { workspace = true }
rosenpass-util = { workspace = true }
oqs-sys = { workspace = true }
paste = { workspace = true }

View File

@@ -1,5 +0,0 @@
# Rosenpass internal liboqs bindings
Rosenpass internal library providing bindings to liboqs.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,80 +0,0 @@
macro_rules! oqs_kem {
($name:ident) => { ::paste::paste!{
mod [< $name:snake >] {
use rosenpass_cipher_traits::Kem;
use rosenpass_util::result::Guaranteed;
pub enum [< $name:camel >] {}
/// # Panic & Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
/// slices only identified using raw pointers. It must be ensured that the raw
/// pointers point into byte slices of sufficient length, to avoid UB through
/// overwriting of arbitrary data. This is ensured through assertions in the
/// implementation.
///
/// __Note__: This requirement is stricter than necessary, it would suffice
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl Kem for [< $name:camel >] {
type Error = ::std::convert::Infallible;
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
pk.as_mut_ptr(),
sk.as_mut_ptr()
);
}
Ok(())
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
ct.as_mut_ptr(),
shk.as_mut_ptr(),
pk.as_ptr()
);
}
Ok(())
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],
shk.as_mut_ptr(),
ct.as_ptr(),
sk.as_ptr()
);
}
Ok(())
}
}
}
pub use [< $name:snake >] :: [< $name:camel >];
}}
}

View File

@@ -1,21 +0,0 @@
macro_rules! oqs_call {
($name:path, $($args:expr),*) => {{
use oqs_sys::common::OQS_STATUS::*;
match $name($($args),*) {
OQS_SUCCESS => {}, // nop
OQS_EXTERNAL_LIB_ERROR_OPENSSL => {
panic!("OpenSSL error in liboqs' {}.", stringify!($name));
},
OQS_ERROR => {
panic!("Unknown error in liboqs' {}.", stringify!($name));
}
}
}};
($name:ident) => { oqs_call!($name, ) };
}
#[macro_use]
mod kem_macro;
oqs_kem!(kyber_512);
oqs_kem!(classic_mceliece_460896);

View File

@@ -177,11 +177,7 @@ version={4.0},
\titlehead{\centerline{\includegraphics[width=4cm]{RosenPass-Logo}}}
\title{\inserttitle}
}
\ifx\csname insertauthor\endcsname\relax
\author{}
\else
\author{\parbox{\linewidth}{\centering\insertauthor}}
\fi
\author{\csname insertauthor\endcsname}
\subject{\csname insertsubject\endcsname}
\date{\vspace{-1cm}}
}

View File

@@ -6,6 +6,7 @@ author:
- Benjamin Lipp = Max Planck Institute for Security and Privacy (MPI-SP)
- Wanja Zaeske
- Lisa Schmidt = {Scientific Illustrator \\url{mullana.de}}
- Prabhpreet Dua
abstract: |
Rosenpass is used to create post-quantum-secure VPNs. Rosenpass computes a shared key, WireGuard (WG) [@wg] uses the shared key to establish a secure connection. Rosenpass can also be used without WireGuard, deriving post-quantum-secure symmetric keys for another application. The Rosenpass protocol builds on “Post-quantum WireGuard” (PQWG) [@pqwg] and improves it by using a cookie mechanism to provide security against state disruption attacks.
@@ -428,12 +429,89 @@ The responder code handling InitConf needs to deal with the biscuits and package
ICR5 and ICR6 perform biscuit replay protection using the biscuit number. This is not handled in `load_biscuit()` itself because there is the case that `biscuit_no = biscuit_used` which needs to be dealt with for retransmission handling.
### Denial of Service Mitigation and Cookies
Rosenpass derives its cookie-based DoS mitigation technique for a responder from Wireguard [@wg].
When the responder is under load, it may choose to not process further handshake messages, but instead to respond with a cookie reply message (see Figure \ref{img:MessageTypes}).
The sender of the exchange then uses this cookie in order to resend the message and have it accepted the following time by the reciever.
For an initiator, Rosenpass ignores the message when under load.
#### Cookie Reply Message
The cookie reply message consists of the `sid` of the client under load, a random 24-byte bitstring `nonce` and encrypting `cookie_tau` into a `cookie` reply field which consists of the following (from the perspective of the cookie reply sender):
```
cookie_tau = lhash("cookie-tau",r_m, a_m)[0..16]
cookie = XAEAD(lhash("cookie-key", spkm), nonce, cookie_tau , mac_peer)
```
where `r_m` is a secret variable that changes every two minutes to a random value. `a_m` is a concatenation of the source IP address and UDP source port of the client's peer. `cookie_tau` will result in a truncated 16 byte value from the above hash operation. `mac_peer` is the `mac` field of the peer's handshake message to which message is the reply.
#### Envelope `mac` Field
Similar to `mac.1` in Wireguard handshake messages, the `mac` field of a Rosenpass envelope from a handshake packet sender's point of view consists of the following:
```
mac = lhash("mac", spkt, MAC_WIRE_DATA)[0..16]
```
where `MAC_WIRE_DATA` represents all bytes of msg prior to `mac` field in the envelope.
If a client receives an invalid `mac` value for any message, it will discard the message.
#### Envelope cookie field
The `cookie_tau` value encrypted as part of `cookie` field in the cookie reply message is decrypted by its receiver and stored as the `last_recvd_cookie` for a limited time (120 seconds). This value is then used by the sender to append a `cookie` field to the sender's message envelope to retransmit the handshake message. This is the equivalent of Wireguard's `mac.2` field and is determined as follows:
```
if (is_zero_length_bitstring(last_recvd_cookie) || last_cookie_time_ellapsed >= 120) {
cookie = 0^16; //zeroed out 16 bytes bitstring
}
else {
cookie = lhash("cookie",last_recvd_cookie,COOKIE_WIRE_DATA)
}
```
Here, `last_recvd_cookie` is the last received decrypted data of the `cookie` field from a cookie reply message by a hanshake message sender, `last_cookie_time_ellapsed` is the amount of time in seconds ellapsed since last cookie was received, and `COOKIE_WIRE_DATA` are the message contents of all bytes of the retransmitted message prior to the `cookie` field.
The sender can use an invalid value for the `cookie` value, when the receiver is not under load, and the receiver must ignore this value.
However, when the receiver is under load, it may reject messages with the invalid `cookie` value, and issue a cookie reply message.
### Conditions to trigger DoS Mechanism
Rosenpass implementations are expected to detect conditions in which they are under computational load to trigger the cookie based DoS mitigation mechanism by replying with a cookie reply message.
For the reference implemenation, Rosenpass has derived inspiration from the linux implementation of Wireguard.
This implementation suggests that the reciever keep track of the number of messages it is processing at a given time.
On receiving an incoming message, if the length of the message queue to be processed exceeds a threshold `MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD`, the client is considered under load and its state is stored as under load. In addition, the timestamp of this instant when the client was last under load is stored. When recieving subsequent messages, if the client is still in an under load state, the client will check if the time ellpased since the client was last under load has exceeded `LAST_UNDER_LOAD_WINDOW` seconds. If this is the case, the client will update its state to normal operation, and process the message in a normal fashion.
Currently, the following constants are derived from the Linux kernel implementation of Wireguard:
```
MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD = 4096
LAST_UNDER_LOAD_WINDOW = 1 //seconds
```
## Dealing with Packet Loss
The initiator deals with packet loss by storing the messages it sends to the responder and retransmitting them in randomized, exponentially increasing intervals until they get a response. Receiving RespHello terminates retransmission of InitHello. A Data or EmptyData message serves as acknowledgement of receiving InitConf and terminates its retransmission.
The responder does not need to do anything special to handle RespHello retransmission if the RespHello package is lost, the initiator retransmits InitHello and the responder can generate another RespHello package from that. InitConf retransmission needs to be handled specifically in the responder code because accepting an InitConf retransmission would reset the live session including the nonce counter, which would cause nonce reuse. Implementations must detect the case that `biscuit_no = biscuit_used` in ICR5, skip execution of ICR6 and ICR7, and just transmit another EmptyData package to confirm that the initiator can stop transmitting InitConf.
### Interaction with cookie reply system
The cookie reply system does not interfere with the retransmission logic discussed above.
When the initator is under load, it will ignore processing any incoming messages.
When a responder is under load, a handshake message will be discarded and a cookie reply message is sent. The initiator, then on the reciept of the cookie reply message, will store a decrypted `cookie_tau` value to use when appending a `cookie` to subsequently sent messages. As per the retransmission mechanism above, the initiator will send a retransmitted InitHello or InitConf message with a valid `cookie` value appended. On receiving the retransmitted handshake message, the responder will validate the `cookie` value and resume with the handshake process.
\printbibliography
\setupimage{landscape,fullpage,label=img:HandlingCode}

View File

@@ -18,14 +18,13 @@ 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 }
@@ -34,7 +33,6 @@ serde = { workspace = true }
toml = { workspace = true }
clap = { workspace = true }
mio = { workspace = true }
rand = { workspace = true }
[build-dependencies]
anyhow = { workspace = true }

View File

@@ -3,7 +3,6 @@ use rosenpass::pqkem::KEM;
use rosenpass::{
pqkem::StaticKEM,
protocol::{CryptoServer, HandleMsgResult, MsgBuf, PeerPtr, SPk, SSk, SymKey},
sodium::sodium_init,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
@@ -58,7 +57,7 @@ fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> {
}
fn criterion_benchmark(c: &mut Criterion) {
sodium_init().unwrap();
rosenpass_sodium::init().unwrap();
let (mut a, mut b) = make_server_pair().unwrap();
c.bench_function("cca_secret_alloc", |bench| {
bench.iter(|| {

View File

@@ -22,6 +22,7 @@ use std::process::Stdio;
use std::slice;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use crate::{
config::Verbosity,
@@ -33,6 +34,11 @@ use rosenpass_util::b64::{b64_writer, fmt_b64};
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
// Using values from Linux Kernel implementation
// TODO: Customize values for rosenpass
const MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD: usize = 4096;
const LAST_UNDER_LOAD_WINDOW: Duration = Duration::from_secs(1);
fn ipv4_any_binding() -> SocketAddr {
// addr, port
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
@@ -67,6 +73,12 @@ pub struct WireguardOut {
pub extra_params: Vec<String>,
}
#[derive(Debug)]
pub enum DoSOperation {
UnderLoad { last_under_load: Instant },
Normal,
}
/// Holds the state of the application, namely the external IO
///
/// Responsible for file IO, network IO
@@ -80,6 +92,7 @@ pub struct AppServer {
pub peers: Vec<AppPeer>,
pub verbosity: Verbosity,
pub all_sockets_drained: bool,
pub under_load: DoSOperation,
}
/// A socket pointer is an index assigned to a socket;
@@ -435,6 +448,7 @@ impl AppServer {
events,
mio_poll,
all_sockets_drained: false,
under_load: DoSOperation::Normal,
})
}
@@ -549,7 +563,37 @@ impl AppServer {
}
ReceivedMessage(len, endpoint) => {
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
let msg_result = match self.under_load {
DoSOperation::UnderLoad { last_under_load: _ } => {
//TODO: Lookup peer through addresses (hash)
let index = self
.peers
.iter()
.enumerate()
.find(|(_num, p)| {
if let Some(ep) = p.endpoint() {
ep.addresses() == endpoint.addresses()
} else {
false
}
})
.ok_or(anyhow::anyhow!("Received message from unknown endpoint"))?
.0;
let socket_addr = endpoint
.addresses()
.first()
.copied()
.ok_or(anyhow::anyhow!("No socket address for endpoint"))?;
self.crypt.handle_msg_under_load(
&rx[..len],
&mut *tx,
PeerPtr(index),
socket_addr,
)
}
DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx),
};
match msg_result {
Err(ref e) => {
self.verbose().then(|| {
info!(
@@ -704,11 +748,29 @@ impl AppServer {
// desired on a non-dual-stack OS), thus just checking every socket after any
// readiness event seems to be good enough™ for now.
println!("All sockets drained: {}", self.all_sockets_drained);
// only poll if we drained all sockets before
if self.all_sockets_drained {
self.mio_poll.poll(&mut self.events, Some(timeout))?;
let queue_length = self.events.iter().peekable().count();
println!("queue length: {}", queue_length);
if queue_length > MAX_QUEUED_INCOMING_HANDSHAKES_THRESHOLD {
self.under_load = DoSOperation::UnderLoad {
last_under_load: Instant::now(),
}
}
}
if let DoSOperation::UnderLoad { last_under_load } = self.under_load {
if last_under_load.elapsed() > LAST_UNDER_LOAD_WINDOW {
self.under_load = DoSOperation::Normal;
}
}
// drain all sockets
let mut would_block_count = 0;
for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
match socket.recv_from(buf) {

View File

@@ -1,14 +1,16 @@
use anyhow::{bail, ensure};
use clap::Parser;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
use rosenpass_util::file::{LoadValue, LoadValueB64};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use crate::app_server;
use crate::app_server::AppServer;
use crate::protocol::{SPk, SSk, SymKey};
use crate::{
// app_server::{AppServer, LoadValue, LoadValueB64},
coloring::Secret,
pqkem::{StaticKEM, KEM},
protocol::{SPk, SSk, SymKey},
};
use super::config;
@@ -162,7 +164,7 @@ impl Cli {
// generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
@@ -246,3 +248,14 @@ impl Cli {
srv.event_loop()
}
}
trait StoreSecret {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>;
}
impl<const N: usize> StoreSecret for Secret<N> {
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}

443
rosenpass/src/coloring.rs Normal file
View File

@@ -0,0 +1,443 @@
//! 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(())
}
}

View File

@@ -41,6 +41,10 @@ pub struct RosenpassPeer {
#[serde(default)]
pub key_out: Option<PathBuf>,
// TODO make sure failure does not crash but is logged
#[serde(default)]
pub exchange_command: Vec<String>,
// TODO make this field only available on binary builds, not on library builds
#[serde(flatten)]
pub wg: Option<WireGuard>,
@@ -341,20 +345,28 @@ impl Rosenpass {
/// Generate an example configuration
pub fn example_config() -> Self {
let peer = RosenpassPeer {
public_key: "/path/to/rp-peer-public-key".into(),
public_key: "rp-peer-public-key".into(),
endpoint: Some("my-peer.test:9999".into()),
key_out: Some("/path/to/rp-key-out.txt".into()),
pre_shared_key: Some("additional pre shared key".into()),
wg: Some(WireGuard {
device: "wirgeguard device e.g. wg0".into(),
peer: "wireguard public key".into(),
extra_params: vec!["passed to".into(), "wg set".into()],
}),
exchange_command: [
"wg",
"set",
"wg0",
"peer",
"<PEER_ID>",
"preshared-key",
"/dev/stdin",
]
.into_iter()
.map(|x| x.to_string())
.collect(),
key_out: Some("rp-key-out".into()),
pre_shared_key: None,
wg: None,
};
Self {
public_key: "/path/to/rp-public-key".into(),
secret_key: "/path/to/rp-secret-key".into(),
public_key: "rp-public-key".into(),
secret_key: "rp-secret-key".into(),
peers: vec![peer],
..Self::new("", "")
}

View File

@@ -1,46 +0,0 @@
//! Pseudo Random Functions (PRFs) with a tree-like label scheme which
//! ensures their uniqueness
use anyhow::Result;
use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN};
// TODO Use labels that can serve as identifiers
macro_rules! hash_domain_ns {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<HashDomain> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t)
}
}
}
macro_rules! hash_domain {
($base:ident, $name:ident, $($lbl:expr),* ) => {
pub fn $name() -> Result<[u8; KEY_LEN]> {
let t = $base()?;
$( let t = t.mix($lbl.as_bytes())?; )*
Ok(t.into_value())
}
}
}
pub fn protocol() -> Result<HashDomain> {
HashDomain::zero().mix("Rosenpass v1 mceliece460896 Kyber512 ChaChaPoly1305 BLAKE2s".as_bytes())
}
hash_domain_ns!(protocol, mac, "mac");
hash_domain_ns!(protocol, cookie, "cookie");
hash_domain_ns!(protocol, peerid, "peer id");
hash_domain_ns!(protocol, biscuit_ad, "biscuit additional data");
hash_domain_ns!(protocol, ckinit, "chaining key init");
hash_domain_ns!(protocol, _ckextract, "chaining key extract");
hash_domain!(_ckextract, mix, "mix");
hash_domain!(_ckextract, hs_enc, "handshake encryption");
hash_domain!(_ckextract, ini_enc, "initiator handshake encryption");
hash_domain!(_ckextract, res_enc, "responder handshake encryption");
hash_domain_ns!(_ckextract, _user, "user");
hash_domain_ns!(_user, _rp, "rosenpass.eu");
hash_domain!(_rp, osk, "wireguard psk");

View File

@@ -0,0 +1,50 @@
//! 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, cookie_tau, "cookie-tau");
prflabel!(protocol, cookie_key, "cookie-key");
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");

View File

@@ -1,24 +1,56 @@
use rosenpass_lenses::LenseError;
pub mod coloring;
#[rustfmt::skip]
pub mod labeled_prf;
pub mod app_server;
pub mod cli;
pub mod config;
pub mod hash_domains;
pub mod msgs;
pub mod pqkem;
pub mod prftree;
pub mod protocol;
#[derive(thiserror::Error, Debug)]
pub enum RosenpassError {
#[error("buffer size mismatch")]
BufferSizeMismatch,
#[error("error in OQS")]
Oqs,
#[error("error from external library while calling OQS")]
OqsExternalLib,
#[error("buffer size mismatch, required {required_size} but found {actual_size}")]
BufferSizeMismatch {
required_size: usize,
actual_size: usize,
},
#[error("invalid message type")]
InvalidMessageType(u8),
}
impl From<LenseError> for RosenpassError {
fn from(value: LenseError) -> Self {
match value {
LenseError::BufferSizeMismatch => RosenpassError::BufferSizeMismatch,
impl RosenpassError {
/// Helper function to check a buffer size
fn check_buffer_size(required_size: usize, actual_size: usize) -> Result<(), Self> {
if required_size != actual_size {
Err(Self::BufferSizeMismatch {
required_size,
actual_size,
})
} else {
Ok(())
}
}
}
/// Extension trait to attach function calls to foreign types.
trait RosenpassMaybeError {
/// Checks whether something is an error or not
fn to_rg_error(&self) -> Result<(), RosenpassError>;
}
impl RosenpassMaybeError for oqs_sys::common::OQS_STATUS {
fn to_rg_error(&self) -> Result<(), RosenpassError> {
use oqs_sys::common::OQS_STATUS;
match self {
OQS_STATUS::OQS_SUCCESS => Ok(()),
OQS_STATUS::OQS_ERROR => Err(RosenpassError::Oqs),
OQS_STATUS::OQS_EXTERNAL_LIB_ERROR_OPENSSL => Err(RosenpassError::OqsExternalLib),
}
}
}

View File

@@ -9,15 +9,14 @@
//!
//! # Example
//!
//! The following example uses the [`lense` macro](rosenpass_lenses::lense) to create a lense that
//! The following example uses the [`data_lense` macro](crate::data_lense) to create a lense that
//! might be useful when dealing with UDP headers.
//!
//! ```
//! use rosenpass_lenses::{lense, LenseView};
//! use rosenpass::RosenpassError;
//! use rosenpass::{data_lense, RosenpassError, msgs::LenseView};
//! # fn main() -> Result<(), RosenpassError> {
//!
//! lense! {UdpDatagramHeader :=
//! data_lense! {UdpDatagramHeader :=
//! source_port: 2,
//! dest_port: 2,
//! length: 2,
@@ -45,14 +44,222 @@
//! ```
use super::RosenpassError;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};
use rosenpass_lenses::{lense, LenseView};
use crate::pqkem::*;
use rosenpass_ciphers::{aead, xaead};
pub const MAC_SIZE: usize = 16;
pub const COOKIE_SIZE: usize = 16;
// Macro magic ////////////////////////////////////////////////////////////////
lense! { Envelope<M> :=
/// A macro to create data lenses. Refer to the [`msgs` mod](crate::msgs) for
/// an example and further elaboration
// TODO implement TryFrom<[u8]> and From<[u8; Self::len()]>
#[macro_export]
macro_rules! data_lense(
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_ref @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes long
pub fn $field(&self) -> &__ContainerType::Output {
&self.0[$offset .. $offset + $len]
}
/// The bytes until the
#[doc = data_lense!(maybe_docstring_link Self::$field)]
/// field
pub fn [< until_ $field >](&self) -> &__ContainerType::Output {
&self.0[0 .. $offset]
}
// if the tail exits, consume it as well
$(
data_lense!{token_muncher_ref @ $offset + $len ; $( $tail )+ }
)?
}
};
// prefix @ offset ; optional meta ; field name : field length, ...
(token_muncher_mut @ $offset:expr ; $( $attr:meta )* ; $field:ident : $len:expr $(, $( $tail:tt )+ )?) => {
::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
$( #[ $attr ] )*
///
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes long
pub fn [< $field _mut >](&mut self) -> &mut __ContainerType::Output {
&mut self.0[$offset .. $offset + $len]
}
// if the tail exits, consume it as well
$(
data_lense!{token_muncher_mut @ $offset + $len ; $( $tail )+ }
)?
}
};
// switch that yields literals unchanged, but creates docstring links to
// constants
// TODO the doc string link doesn't work if $x is taken from a generic,
(maybe_docstring_link $x:literal) => (stringify!($x));
(maybe_docstring_link $x:expr) => (stringify!([$x]));
// struct name < optional generics > := optional doc string field name : field length, ...
($type:ident $( < $( $generic:ident ),+ > )? := $( $( #[ $attr:meta ] )* $field:ident : $len:expr ),+) => (::paste::paste!{
#[allow(rustdoc::broken_intra_doc_links)]
/// A data lense to manipulate byte slices.
///
//// # Fields
///
$(
/// - `
#[doc = stringify!($field)]
/// `:
#[doc = data_lense!(maybe_docstring_link $len)]
/// bytes
)+
pub struct $type<__ContainerType $(, $( $generic ),+ )? > (
__ContainerType,
// The phantom data is required, since all generics declared on a
// type need to be used on the type.
// https://doc.rust-lang.org/stable/error_codes/E0392.html
$( $( ::core::marker::PhantomData<$generic> ),+ )?
);
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > $type<__ContainerType $(, $( $generic ),+ )? >{
$(
/// Size in bytes of the field `
#[doc = !($field)]
/// `
pub const fn [< $field _len >]() -> usize{
$len
}
)+
/// Verify that `len` is sufficiently long to hold [Self]
pub fn check_size(len: usize) -> Result<(), RosenpassError>{
let required_size = $( $len + )+ 0;
let actual_size = len;
if required_size != actual_size {
Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
})
}else{
Ok(())
}
}
}
// read-only accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::Index<std::ops::Range<usize>> + ?Sized,
{
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
}
// mutable accessor functions
impl<'a, __ContainerType $(, $( $generic: LenseView ),+ )?> $type<&'a mut __ContainerType $(, $( $generic ),+ )?>
where
__ContainerType: std::ops::IndexMut<std::ops::Range<usize>> + ?Sized,
{
data_lense!{token_muncher_ref @ 0 ; $( $( $attr )* ; $field : $len ),+ }
data_lense!{token_muncher_mut @ 0 ; $( $( $attr )* ; $field : $len ),+ }
/// View into all bytes belonging to this Lense
pub fn all_bytes(&self) -> &__ContainerType::Output {
&self.0[0..Self::LEN]
}
/// View into all bytes belonging to this Lense
pub fn all_bytes_mut(&mut self) -> &mut __ContainerType::Output {
&mut self.0[0..Self::LEN]
}
}
// lense trait, allowing us to know the implementing lenses size
impl<__ContainerType $(, $( $generic: LenseView ),+ )? > LenseView for $type<__ContainerType $(, $( $generic ),+ )? >{
/// Number of bytes required to store this type in binary format
const LEN: usize = $( $len + )+ 0;
}
/// Extension trait to allow checked creation of a lense over
/// some byte slice that contains a
#[doc = data_lense!(maybe_docstring_link $type)]
pub trait [< $type Ext >] {
type __ContainerType;
/// Create a lense to the byte slice
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
/// Create a lense to the byte slice, automatically truncating oversized buffers
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>;
}
impl<'a> [< $type Ext >] for &'a [u8] {
type __ContainerType = &'a [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&self[..required_size])
}
}
impl<'a> [< $type Ext >] for &'a mut [u8] {
type __ContainerType = &'a mut [u8];
fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
$type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?;
Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? ))
}
fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> {
let required_size = $( $len + )+ 0;
let actual_size = self.len();
if actual_size < required_size {
return Err(RosenpassError::BufferSizeMismatch {
required_size,
actual_size,
});
}
[< $type Ext >]::[< $type:snake >](&mut self[..required_size])
}
}
});
);
/// Common trait shared by all Lenses
pub trait LenseView {
const LEN: usize;
}
data_lense! { Envelope<M> :=
/// [MsgType] of this message
msg_type: 1,
/// Reserved for future use
@@ -61,40 +268,40 @@ lense! { Envelope<M> :=
payload: M::LEN,
/// Message Authentication Code (mac) over all bytes until (exclusive)
/// `mac` itself
mac: 16,
/// Currently unused, TODO: do something with this
cookie: 16
mac: MAC_SIZE,
/// Cookie value
cookie: COOKIE_SIZE
}
lense! { InitHello :=
data_lense! { InitHello :=
/// Randomly generated connection id
sidi: 4,
/// Kyber 512 Ephemeral Public Key
epki: EphemeralKem::PK_LEN,
epki: EphemeralKEM::PK_LEN,
/// Classic McEliece Ciphertext
sctr: StaticKem::CT_LEN,
sctr: StaticKEM::CT_LEN,
/// Encryped: 16 byte hash of McEliece initiator static key
pidic: aead::TAG_LEN + 32,
/// Encrypted TAI64N Time Stamp (against replay attacks)
auth: aead::TAG_LEN
}
lense! { RespHello :=
data_lense! { RespHello :=
/// Randomly generated connection id
sidr: 4,
/// Copied from InitHello
sidi: 4,
/// Kyber 512 Ephemeral Ciphertext
ecti: EphemeralKem::CT_LEN,
ecti: EphemeralKEM::CT_LEN,
/// Classic McEliece Ciphertext
scti: StaticKem::CT_LEN,
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
}
lense! { InitConf :=
data_lense! { InitConf :=
/// Copied from InitHello
sidi: 4,
/// Copied from RespHello
@@ -105,7 +312,7 @@ lense! { InitConf :=
auth: aead::TAG_LEN
}
lense! { EmptyData :=
data_lense! { EmptyData :=
/// Copied from RespHello
sid: 4,
/// Nonce
@@ -114,21 +321,23 @@ lense! { EmptyData :=
auth: aead::TAG_LEN
}
lense! { Biscuit :=
data_lense! { Biscuit :=
/// H(spki) Ident ifies the initiator
pidi: KEY_LEN,
pidi: aead::KEY_LEN,
/// The biscuit number (replay protection)
biscuit_no: 12,
/// Chaining key
ck: KEY_LEN
ck: aead::KEY_LEN
}
lense! { DataMsg :=
data_lense! { DataMsg :=
dummy: 4
}
lense! { CookieReply :=
dummy: 4
data_lense! { CookieReply :=
sid: 4,
nonce: xaead::NONCE_LEN,
cookie_encrypted: MAC_SIZE + xaead::TAG_LEN
}
// Traits /////////////////////////////////////////////////////////////////////

168
rosenpass/src/pqkem.rs Normal file
View File

@@ -0,0 +1,168 @@
//! 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()
}
}
}

106
rosenpass/src/prftree.rs Normal file
View File

@@ -0,0 +1,106 @@
//! 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
}
}

View File

@@ -19,9 +19,8 @@
//! [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<()> {
@@ -31,11 +30,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();
@@ -68,20 +67,21 @@
//! # }
//! ```
use crate::{hash_domains, msgs::*};
use crate::{
coloring::*,
labeled_prf::{self as lprf},
msgs::*,
pqkem::*,
prftree::{SecretPrfTree, SecretPrfTreeBranch},
};
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;
use std::net::{IpAddr, SocketAddr};
// CONSTANTS & SETTINGS //////////////////////////
@@ -110,6 +110,16 @@ pub const REKEY_AFTER_TIME_RESPONDER: Timing = 120.0;
pub const REKEY_AFTER_TIME_INITIATOR: Timing = 130.0;
pub const REJECT_AFTER_TIME: Timing = 180.0;
// From the wireguard paper; "under no circumstances send an initiation message more than once every 5 seconds"
pub const REKEY_TIMEOUT: Timing = 5.0;
// Cookie Secret value Rm composing `cookie_tau` in the whitepaper
pub const COOKIE_SECRET_LEN: usize = MAC_SIZE;
pub const COOKIE_SECRET_EXP: Timing = 120.0;
// Peer Cookie Tau expiration
pub const PEER_COOKIE_TAU_EXP: Timing = 120.0;
// Seconds until the biscuit key is changed; we issue biscuits
// using one biscuit key for one epoch and store the biscuit for
// decryption for a second epoch
@@ -140,10 +150,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>;
@@ -185,6 +195,89 @@ pub struct CryptoServer {
// Tick handling
pub peer_poll_off: usize,
// Random state which changes every COOKIE_SECRET_EXP seconds
pub cookie_secret: CookieSecret,
}
#[derive(Debug)]
pub enum CookieSecret {
Some {
value: [u8; COOKIE_SECRET_LEN],
last_updated: Timebase,
},
None,
}
impl CookieSecret {
pub fn new(value: [u8; COOKIE_SECRET_LEN]) -> Self {
Self::Some {
value,
last_updated: Timebase::default(),
}
}
// Get the cookie secret, does not update the value
pub fn get(&self, expiry: Timing) -> Option<&[u8]> {
if let CookieSecret::Some {
value: _value,
last_updated,
} = self
{
if last_updated.now() > expiry {
return None;
}
}
match self {
CookieSecret::Some { value, .. } => Some(value),
CookieSecret::None => None,
}
}
// Get the cookie secret or update the value if it has expired and return the updated value
pub fn get_or_update_ellapsed<F: Fn(&mut [u8])>(
&mut self,
expiry: Timing,
update_fn: F,
) -> &[u8] {
if let CookieSecret::Some {
value,
last_updated,
} = self
{
if last_updated.now() > expiry {
update_fn(value);
*last_updated = Timebase::default();
}
value
} else {
*self = CookieSecret::Some {
value: [0; COOKIE_SECRET_LEN],
last_updated: Timebase::default(),
};
let value = match self {
CookieSecret::Some {
value,
last_updated: _,
} => value,
CookieSecret::None => unreachable!(),
};
update_fn(value);
value
}
}
pub fn is_some(&self) -> bool {
match self {
CookieSecret::Some { .. } => true,
CookieSecret::None => false,
}
}
pub fn is_none(&self) -> bool {
!self.is_some()
}
}
/// A Biscuit is like a fancy cookie. To avoid state disruption attacks,
@@ -212,6 +305,8 @@ pub struct Peer {
pub session: Option<Session>,
pub handshake: Option<InitiatorHandshake>,
pub initiation_requested: bool,
pub cookie_tau: CookieSecret,
pub last_sent_mac: Option<Public<MAC_SIZE>>,
}
impl Peer {
@@ -223,6 +318,8 @@ impl Peer {
session: None,
initiation_requested: false,
handshake: None,
cookie_tau: CookieSecret::None,
last_sent_mac: None,
}
}
}
@@ -234,7 +331,7 @@ pub struct HandshakeState {
/// Session ID of Responder
pub sidr: SessionId,
/// Chaining Key
pub ck: SecretHashDomainNamespace, // TODO: We should probably add an abstr
pub ck: SecretPrfTreeBranch,
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone)]
@@ -286,7 +383,7 @@ pub struct Session {
pub sidt: SessionId,
pub handshake_role: HandshakeRole,
// Crypto
pub ck: SecretHashDomainNamespace,
pub ck: SecretPrfTreeBranch,
/// Key for Transmission ("transmission key mine")
pub txkm: SymKey,
/// Key for Reception ("transmission key theirs")
@@ -450,6 +547,7 @@ impl CryptoServer {
peers: Vec::new(),
index: HashMap::new(),
peer_poll_off: 0,
cookie_secret: CookieSecret::None,
}
}
@@ -461,7 +559,7 @@ impl CryptoServer {
#[rustfmt::skip]
pub fn pidm(&self) -> Result<PeerId> {
Ok(Public::new(
hash_domains::peerid()?
lprf::peerid()?
.mix(self.spkm.secret())?
.into_value()))
}
@@ -483,6 +581,8 @@ impl CryptoServer {
session: None,
handshake: None,
initiation_requested: false,
cookie_tau: CookieSecret::None,
last_sent_mac: None,
};
let peerid = peer.pidt()?;
let peerno = self.peers.len();
@@ -585,13 +685,15 @@ impl Peer {
session: None,
handshake: None,
initiation_requested: false,
cookie_tau: CookieSecret::None,
last_sent_mac: None,
}
}
#[rustfmt::skip]
pub fn pidt(&self) -> Result<PeerId> {
Ok(Public::new(
hash_domains::peerid()?
lprf::peerid()?
.mix(self.spkt.secret())?
.into_value()))
}
@@ -604,7 +706,7 @@ impl Session {
sidm: SessionId::zero(),
sidt: SessionId::zero(),
handshake_role: HandshakeRole::Initiator,
ck: SecretHashDomain::zero().dup(),
ck: SecretPrfTree::zero().dup(),
txkm: SymKey::zero(),
txkt: SymKey::zero(),
txnm: 0,
@@ -754,7 +856,122 @@ pub struct HandleMsgResult {
}
impl CryptoServer {
/// Respond to an incoming message
/// Process a message under load
/// This is one of the main entry point for the protocol.
/// Keeps track of messages processed, and qualifies messages using
/// cookie based DoS mitigation. Dispatches message for further processing
/// to `process_msg` handler if cookie is valid, otherwise sends a cookie reply
/// message for sender to process and verify for messages part of the handshake phase
/// (i.e. InitHello, InitConf messages only). Bails on messages sent by responder and
/// non-handshake messages.
pub fn handle_msg_under_load(
&mut self,
rx_buf: &[u8],
tx_buf: &mut [u8],
peer: PeerPtr,
socket_addr: SocketAddr,
) -> Result<HandleMsgResult> {
//Check cookie value
let mut ip_addr_port = match socket_addr.ip() {
IpAddr::V4(ipv4) => ipv4.octets().to_vec(),
IpAddr::V6(ipv6) => ipv6.octets().to_vec(),
};
ip_addr_port.extend_from_slice(&socket_addr.port().to_be_bytes());
let (rx_bytes_til_cookie, rx_cookie, rx_mac, rx_sid) = match rx_buf[0].try_into() {
Ok(MsgType::InitHello) => {
let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?;
(
msg_in.until_cookie().to_vec(),
msg_in.cookie().to_vec(),
msg_in.mac().to_vec(),
msg_in.payload().init_hello()?.sidi().to_vec(),
)
}
Ok(MsgType::InitConf) => {
let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?;
(
msg_in.until_cookie().to_vec(),
msg_in.cookie().to_vec(),
msg_in.mac().to_vec(),
msg_in.payload().init_conf()?.sidi().to_vec(),
)
}
Ok(_) => {
bail!("Message did not contain cookie or could not be sent a cookie reply message (responder sent-message or non-handshake)")
}
Err(_) => {
bail!("Message type not supported")
}
};
let cookie_tau = lprf::cookie_tau()?
.mix(self.cookie_secret.get_or_update_ellapsed(
COOKIE_SECRET_EXP,
rosenpass_sodium::helpers::randombytes_buf,
))?
.mix(&ip_addr_port)?
.into_value()[..16]
.to_vec();
let expected = lprf::cookie()?
.mix(&cookie_tau)?
.mix(&rx_bytes_til_cookie)?
.into_value()[..16]
.to_vec();
//If valid cookie is found, process message
if rosenpass_sodium::helpers::memcmp(&rx_cookie, &expected) {
let result = self.handle_msg(rx_buf, tx_buf)?;
Ok(result)
}
//Otherwise send cookie reply
else {
let mut msg_out = tx_buf.envelope_truncating::<CookieReply<&mut [u8]>>()?;
let cookie_key = lprf::cookie_key()?.mix(self.spkm.secret())?.into_value();
let mut cookie_reply_lens = msg_out.payload_mut().cookie_reply()?;
let nonce_val = XAEADNonce::random();
// Copy sender's session id to cookie reply message
{
let sid = cookie_reply_lens.sid_mut();
sid.copy_from_slice(&rx_sid[..]);
}
// Generate random nonce, copy it to message and nonce_val
{
let nonce = cookie_reply_lens.nonce_mut();
nonce.copy_from_slice(&nonce_val.value);
}
// Encrypt cookie
{
const COOKIE_LENS_SID_LEN: usize = 4;
let cookie_ciphertext =
&mut cookie_reply_lens.all_bytes_mut()[COOKIE_LENS_SID_LEN..];
xaead::encrypt(
cookie_ciphertext,
&cookie_key,
&nonce_val.value,
&rx_mac,
&cookie_tau,
)?;
}
// length of the response
let len = Some(self.seal_and_commit_msg(peer, MsgType::CookieReply, msg_out)?);
Ok(HandleMsgResult {
exchanged_with: None,
resp: len,
})
}
}
/// Handle an incoming message
/// This is one of the main entry point for the protocol.
///
/// # Overview
///
@@ -838,7 +1055,14 @@ impl CryptoServer {
self.handle_resp_conf(msg_in.payload().empty_data()?)?
}
Ok(MsgType::DataMsg) => bail!("DataMsg handling not implemented!"),
Ok(MsgType::CookieReply) => bail!("CookieReply handling not implemented!"),
Ok(MsgType::CookieReply) => {
let msg_in = rx_buf.envelope::<CookieReply<&[u8]>>()?;
ensure!(msg_in.check_seal(self)?, seal_broken);
let peer = self.handle_cookie_reply(msg_in.payload().cookie_reply()?)?;
len = 0;
peer
}
Err(_) => {
bail!("CookieReply handling not implemented!")
}
@@ -851,7 +1075,8 @@ impl CryptoServer {
}
/// Serialize message to `tx_buf`, generating the `mac` in the process of
/// doing so
/// doing so. If `cookie_secret` is also present, a `cookie` value is also generated
/// and added to the message
///
/// The message type is explicitly required here because it is very easy to
/// forget setting that, which creates subtle but far ranging errors.
@@ -863,6 +1088,15 @@ impl CryptoServer {
) -> Result<usize> {
msg.msg_type_mut()[0] = msg_type as u8;
msg.seal(peer, self)?;
msg.seal_cookie(peer, self)?;
//Store sent mac value as last sent mac (for cookie reply)
let mut mac = [0u8; MAC_SIZE];
mac.copy_from_slice(msg.mac());
peer.get_mut(self)
.last_sent_mac
.replace(Public { value: mac });
Ok(<Envelope<(), M> as LenseView>::LEN)
}
}
@@ -1132,12 +1366,22 @@ impl IniHsPtr {
}
pub fn apply_retransmission(&self, srv: &mut CryptoServer, tx_buf: &mut [u8]) -> Result<usize> {
let ih = self
.get_mut(srv)
.as_mut()
.with_context(|| format!("No current handshake for peer {:?}", self.peer()))?;
cpy_min(&ih.tx_buf[..ih.tx_len], tx_buf);
Ok(ih.tx_len)
let ih_tx_len: usize;
{
let ih = self
.get_mut(srv)
.as_mut()
.with_context(|| format!("No current handshake for peer {:?}", self.peer()))?;
cpy_min(&ih.tx_buf[..ih.tx_len], tx_buf);
ih_tx_len = ih.tx_len;
}
// Add cookie to retransmitted message
let mut envelope = tx_buf.envelope_truncating::<InitHello<&mut [u8]>>()?;
envelope.seal_cookie(self.peer(), srv)?;
Ok(ih_tx_len)
}
pub fn register_retransmission(&self, srv: &mut CryptoServer) -> Result<()> {
@@ -1155,7 +1399,7 @@ impl IniHsPtr {
.min(ih.tx_count as f64),
)
* RETRANSMIT_DELAY_JITTER
* (rand::random::<f64>() + 1.0); // TODO: Replace with the rand crate
* (rosenpass_sodium::helpers::rand_f64() + 1.0); // TODO: Replace with the rand crate
ih.tx_count += 1;
Ok(())
}
@@ -1175,13 +1419,23 @@ where
{
/// Calculate the message authentication code (`mac`)
pub fn seal(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
let mac = hash_domains::mac()?
let mac = lprf::mac()?
.mix(peer.get(srv).spkt.secret())?
.mix(self.until_mac())?;
self.mac_mut()
.copy_from_slice(mac.into_value()[..16].as_ref());
Ok(())
}
/// Calculate and append the cookie value if `cookie_tau` exists (`cookie`)
pub fn seal_cookie(&mut self, peer: PeerPtr, srv: &CryptoServer) -> Result<()> {
if let Some(cookie_tau) = peer.get(srv).cookie_tau.get(PEER_COOKIE_TAU_EXP) {
let cookie = lprf::cookie()?.mix(cookie_tau)?.mix(self.until_cookie())?;
self.cookie_mut()
.copy_from_slice(cookie.into_value()[..16].as_ref());
}
Ok(())
}
}
impl<M> Envelope<&[u8], M>
@@ -1190,9 +1444,7 @@ where
{
/// Check the message authentication code
pub fn check_seal(&self, srv: &CryptoServer) -> Result<bool> {
let expected = hash_domains::mac()?
.mix(srv.spkm.secret())?
.mix(self.until_mac())?;
let expected = lprf::mac()?.mix(srv.spkm.secret())?.mix(self.until_mac())?;
Ok(rosenpass_sodium::helpers::memcmp(
self.mac(),
&expected.into_value()[..16],
@@ -1222,38 +1474,38 @@ impl HandshakeState {
Self {
sidi: SessionId::zero(),
sidr: SessionId::zero(),
ck: SecretHashDomain::zero().dup(),
ck: SecretPrfTree::zero().dup(),
}
}
pub fn erase(&mut self) {
self.ck = SecretHashDomain::zero().dup();
self.ck = SecretPrfTree::zero().dup();
}
pub fn init(&mut self, spkr: &[u8]) -> Result<&mut Self> {
self.ck = hash_domains::ckinit()?.turn_secret().mix(spkr)?.dup();
self.ck = lprf::ckinit()?.mix(spkr)?.into_secret_prf_tree().dup();
Ok(self)
}
pub fn mix(&mut self, a: &[u8]) -> Result<&mut Self> {
self.ck = self.ck.mix(&hash_domains::mix()?)?.mix(a)?.dup();
self.ck = self.ck.mix(&lprf::mix()?)?.mix(a)?.dup();
Ok(self)
}
pub fn encrypt_and_mix(&mut self, ct: &mut [u8], pt: &[u8]) -> Result<&mut Self> {
let k = self.ck.mix(&hash_domains::hs_enc()?)?.into_secret();
let k = self.ck.mix(&lprf::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(&hash_domains::hs_enc()?)?.into_secret();
let k = self.ck.mix(&lprf::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<Error = Infallible>, const SHK_LEN: usize>(
pub fn encaps_and_mix<T: KEM, const SHK_LEN: usize>(
&mut self,
ct: &mut [u8],
pk: &[u8],
@@ -1263,7 +1515,7 @@ impl HandshakeState {
self.mix(pk)?.mix(shk.secret())?.mix(ct)
}
pub fn decaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
pub fn decaps_and_mix<T: KEM, const SHK_LEN: usize>(
&mut self,
sk: &[u8],
pk: &[u8],
@@ -1293,7 +1545,7 @@ impl HandshakeState {
.copy_from_slice(self.ck.clone().danger_into_secret().secret());
// calculate ad contents
let ad = hash_domains::biscuit_ad()?
let ad = lprf::biscuit_ad()?
.mix(srv.spkm.secret())?
.mix(self.sidi.as_slice())?
.mix(self.sidr.as_slice())?
@@ -1328,7 +1580,7 @@ impl HandshakeState {
let bk = BiscuitKeyPtr(((biscuit_ct[0] & 0b1000_0000) >> 7) as usize);
// Calculate additional data fields
let ad = hash_domains::biscuit_ad()?
let ad = lprf::biscuit_ad()?
.mix(srv.spkm.secret())?
.mix(sidi.as_slice())?
.mix(sidr.as_slice())?
@@ -1346,7 +1598,7 @@ impl HandshakeState {
// Reconstruct the biscuit fields
let no = BiscuitId::from_slice(biscuit.biscuit_no());
let ck = SecretHashDomain::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
let ck = SecretPrfTree::danger_from_secret(Secret::from_slice(biscuit.ck())).dup();
let pid = PeerId::from_slice(biscuit.pidi());
// Reconstruct the handshake state
@@ -1373,8 +1625,8 @@ impl HandshakeState {
pub fn enter_live(self, srv: &CryptoServer, role: HandshakeRole) -> Result<Session> {
let HandshakeState { ck, sidi, sidr } = self;
let tki = ck.mix(&hash_domains::ini_enc()?)?.into_secret();
let tkr = ck.mix(&hash_domains::res_enc()?)?.into_secret();
let tki = ck.mix(&lprf::ini_enc()?)?.into_secret();
let tkr = ck.mix(&lprf::res_enc()?)?.into_secret();
let created_at = srv.timebase.now();
let (ntx, nrx) = (0, 0);
let (mysid, peersid, ktx, krx) = match role {
@@ -1405,7 +1657,7 @@ impl CryptoServer {
.get(self)
.as_ref()
.with_context(|| format!("No current session for peer {:?}", peer))?;
Ok(session.ck.mix(&hash_domains::osk()?)?.into_secret())
Ok(session.ck.mix(&lprf::osk()?)?.into_secret())
}
}
@@ -1427,7 +1679,7 @@ impl CryptoServer {
ih.sidi_mut().copy_from_slice(&hs.core.sidi.value);
// IHI3
EphemeralKem::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
EphemeralKEM::keygen(hs.eski.secret_mut(), &mut *hs.epki)?;
ih.epki_mut().copy_from_slice(&hs.epki.value);
// IHI4
@@ -1435,7 +1687,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(),
)?;
@@ -1474,7 +1726,7 @@ impl CryptoServer {
core.mix(ih.sidi())?.mix(ih.epki())?;
// IHR5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
core.decaps_and_mix::<StaticKEM, { StaticKEM::SHK_LEN }>(
self.sskm.secret(),
self.spkm.secret(),
ih.sctr(),
@@ -1504,10 +1756,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(),
)?;
@@ -1572,14 +1824,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(),
@@ -1735,10 +1987,55 @@ impl CryptoServer {
Ok(hs.peer())
}
pub fn handle_cookie_reply(&mut self, cr: CookieReply<&[u8]>) -> Result<PeerPtr> {
let session_id = SessionId::from_slice(cr.sid());
let peer_ptr: Option<PeerPtr> = self
.lookup_session(session_id)
.map(|v| PeerPtr(v.0))
.or_else(|| self.lookup_handshake(session_id).map(|v| PeerPtr(v.0)));
if let Some(peer) = peer_ptr {
if let Some(mac) = peer.get(self).last_sent_mac {
let mut cookie_value = [0u8; COOKIE_SECRET_LEN];
let spkt = peer.get(self).spkt.secret();
let cookie_key = lprf::cookie_key()?.mix(spkt)?.into_value();
xaead::decrypt(
&mut cookie_value,
&cookie_key,
&mac.value,
&cr.all_bytes()[4..],
)?;
peer.get_mut(self).cookie_tau = CookieSecret::Some {
value: cookie_value,
last_updated: Timebase::default(),
};
Ok(peer)
} else {
bail!(
"No last sent message for peer {pidr:?} to decrypt cookie reply.",
pidr = cr.sid()
);
}
} else {
let sids: Vec<_> = self
.peers
.iter()
.map(|p| p.session.as_ref().map(|s| s.sidm))
.collect();
println!("SID: {:?}", sids);
bail!("No such peer {pidr:?}.", pidr = cr.sid());
}
}
}
#[cfg(test)]
mod test {
use std::{net::SocketAddrV4, thread::sleep, time::Duration};
use super::*;
#[test]
@@ -1815,7 +2112,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))
}
@@ -1831,4 +2128,129 @@ mod test {
b.add_peer(Some(psk), pka)?;
Ok((a, b))
}
#[test]
fn cookie_reply_mechanism_responder_under_load() {
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let _ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
//B handles handshake under load, should send cookie reply message with invalid cookie
let HandleMsgResult { resp, .. } = b
.handle_msg_under_load(
&a_to_b_buf.as_slice()[..init_hello_len],
&mut *b_to_a_buf,
PeerPtr(0),
SocketAddr::V4(ip_a),
)
.unwrap();
let cookie_reply_len = resp.unwrap();
//A handles cookie reply message
a.handle_msg(&b_to_a_buf[..cookie_reply_len], &mut *a_to_b_buf)
.unwrap();
assert!(a.peers[0].cookie_tau.is_some());
let expected_cookie_tau = lprf::cookie_tau()
.unwrap()
.mix(b.cookie_secret.get(COOKIE_SECRET_EXP).unwrap())
.unwrap()
.mix(&ip_addr_port_a)
.unwrap()
.into_value()[..16]
.to_vec();
assert_eq!(
a.peers[0].cookie_tau.get(PEER_COOKIE_TAU_EXP),
Some(&expected_cookie_tau[..])
);
let retx_init_hello_len = loop {
match a.poll().unwrap() {
PollResult::SendRetransmission(peer) => {
break (a.retransmit_handshake(peer, &mut *a_to_b_buf).unwrap());
}
PollResult::Sleep(time) => {
sleep(Duration::from_secs_f64(time));
}
_ => {}
}
};
let retx_msg_type: MsgType = a_to_b_buf.value[0].try_into().unwrap();
assert_eq!(retx_msg_type, MsgType::InitHello);
//B handles retransmitted message
let HandleMsgResult { resp, .. } = b
.handle_msg_under_load(
&a_to_b_buf.as_slice()[..retx_init_hello_len],
&mut *b_to_a_buf,
PeerPtr(0),
SocketAddr::V4(ip_a),
)
.unwrap();
let _resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
});
}
#[test]
fn cookie_reply_mechanism_initiator_bails_on_message_under_load() {
stacker::grow(8 * 1024 * 1024, || {
type MsgBufPlus = Public<MAX_MESSAGE_LEN>;
let (mut a, mut b) = make_server_pair().unwrap();
let mut a_to_b_buf = MsgBufPlus::zero();
let mut b_to_a_buf = MsgBufPlus::zero();
let ip_a: SocketAddrV4 = "127.0.0.1:8080".parse().unwrap();
let mut ip_addr_port_a = ip_a.ip().octets().to_vec();
ip_addr_port_a.extend_from_slice(&ip_a.port().to_be_bytes());
let ip_b: SocketAddrV4 = "127.0.0.1:8081".parse().unwrap();
//A initiates handshake
let init_hello_len = a.initiate_handshake(PeerPtr(0), &mut *a_to_b_buf).unwrap();
//B handles InitHello message, should respond with RespHello
let HandleMsgResult { resp, .. } = b
.handle_msg(&a_to_b_buf.as_slice()[..init_hello_len], &mut *b_to_a_buf)
.unwrap();
let resp_hello_len = resp.unwrap();
let resp_msg_type: MsgType = b_to_a_buf.value[0].try_into().unwrap();
assert_eq!(resp_msg_type, MsgType::RespHello);
let sids: Vec<_> = b
.peers
.iter()
.map(|p| p.session.as_ref().map(|s| s.sidt))
.collect();
println!("Peer SIDs: {:?}", sids);
//A handles RespHello message under load, should send cookie reply
assert!(a
.handle_msg_under_load(
&b_to_a_buf[..resp_hello_len],
&mut *a_to_b_buf,
PeerPtr(0),
SocketAddr::V4(ip_b),
)
.is_err());
});
}
}

View File

@@ -1,20 +0,0 @@
[package]
name = "rosenpass-secret-memory"
version = "0.1.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Rosenpass internal utilities for storing secrets in memory"
homepage = "https://rosenpass.eu/"
repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
anyhow = { workspace = true }
rosenpass-to = { workspace = true }
rosenpass-sodium = { workspace = true }
rosenpass-util = { workspace = true }
libsodium-sys-stable = { workspace = true }
lazy_static = { workspace = true }
zeroize = { workspace = true }
rand = { workspace = true }

View File

@@ -1,5 +0,0 @@
# Rosenpass secure memory library
Rosenpass internal library providing utilities for securely storing secret data in memory.
This is an internal library; not guarantee is made about its API at this point in time.

View File

@@ -1,20 +0,0 @@
use std::fmt;
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {
for byte in &v[..32] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
fmt.write_str("")?;
for byte in &v[v.len() - 32..] {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
} else {
for byte in v {
std::fmt::LowerHex::fmt(byte, fmt)?;
}
}
Ok(())
}

View File

@@ -1,7 +0,0 @@
use std::path::Path;
pub trait StoreSecret {
type Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -1,9 +0,0 @@
pub mod debug;
pub mod file;
pub mod rand;
mod public;
pub use crate::public::Public;
mod secret;
pub use crate::secret::Secret;

View File

@@ -1,112 +0,0 @@
use crate::debug::debug_crypto_array;
use rand::{Fill as Randomize, Rng};
use rosenpass_to::{ops::copy_slice, To};
use rosenpass_util::file::{fopen_r, LoadValue, ReadExactToEnd, StoreValue};
use rosenpass_util::functional::mutating;
use std::borrow::{Borrow, BorrowMut};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::path::Path;
/// Contains information in the form of a byte array that may be known to the
/// public
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Public<const N: usize> {
pub value: [u8; N],
}
impl<const N: usize> Public<N> {
/// Create a new [Public] from a byte slice
pub fn from_slice(value: &[u8]) -> Self {
copy_slice(value).to_this(|| Self::zero())
}
/// Create a new [Public] from a byte array
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public]
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public]
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Randomize all bytes in an existing [Public]
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
}
impl<const N: usize> Randomize for Public<N> {
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.value.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for Public<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
type Target = [u8; N];
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.value
}
}
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
Ok(v)
}
}
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
}
}

View File

@@ -1,5 +0,0 @@
pub type Rng = rand::rngs::ThreadRng;
pub fn rng() -> Rng {
rand::thread_rng()
}

View File

@@ -1,237 +0,0 @@
use crate::file::StoreSecret;
use anyhow::Context;
use lazy_static::lazy_static;
use rand::{Fill as Randomize, Rng};
use rosenpass_sodium::alloc::{Alloc as SodiumAlloc, Box as SodiumBox, Vec as SodiumVec};
use rosenpass_util::{
b64::b64_reader,
file::{fopen_r, LoadValue, LoadValueB64, ReadExactToEnd},
functional::mutating,
};
use std::{collections::HashMap, convert::TryInto, fmt, path::Path, sync::Mutex};
use zeroize::{Zeroize, ZeroizeOnDrop};
// This might become a problem in library usage; it's effectively a memory
// leak which probably isn't a problem right now because most memory will
// be reused…
lazy_static! {
static ref SECRET_CACHE: Mutex<SecretMemoryPool> = Mutex::new(SecretMemoryPool::new());
}
/// Pool that stores secret memory allocations
///
/// Allocation of secret memory is expensive. Thus, this struct provides a
/// pool of secret memory, readily available to yield protected, slices of
/// memory.
///
/// Further information about the protection in place can be found in in the
/// [libsodium documentation](https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations)
#[derive(Debug)] // TODO check on Debug derive, is that clever
struct SecretMemoryPool {
pool: HashMap<usize, Vec<SodiumBox<[u8]>>>,
}
impl SecretMemoryPool {
/// Create a new [SecretMemoryPool]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
pool: HashMap::new(),
}
}
/// Return secret back to the pool for future re-use
pub fn release<const N: usize>(&mut self, mut sec: SodiumBox<[u8; N]>) {
sec.zeroize();
// This conversion sequence is weird but at least it guarantees
// that the heap allocation is preserved according to the docs
let sec: SodiumVec<u8> = sec.into();
let sec: SodiumBox<[u8]> = sec.into();
self.pool.entry(N).or_default().push(sec);
}
/// Take protected memory from the pool, allocating new one if no suitable
/// chunk is found in the inventory.
///
/// The secret is guaranteed to be full of nullbytes
pub fn take<const N: usize>(&mut self) -> SodiumBox<[u8; N]> {
let entry = self.pool.entry(N).or_default();
match entry.pop() {
None => SodiumBox::new_in([0u8; N], SodiumAlloc::default()),
Some(sec) => sec.try_into().unwrap(),
}
}
}
/// Storeage for a secret backed by [rosenpass_sodium::alloc::Alloc]
pub struct Secret<const N: usize> {
storage: Option<SodiumBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
pub fn from_slice(slice: &[u8]) -> Self {
let mut new_self = Self::zero();
new_self.secret_mut().copy_from_slice(slice);
new_self
}
/// Returns a new [Secret] that is zero initialized
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// yet it is used in hot loops
Self {
storage: Some(SECRET_CACHE.lock().unwrap().take()),
}
}
/// Returns a new [Secret] that is randomized
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Sets all data an existing secret to random bytes
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
/// Borrows the data
pub fn secret(&self) -> &[u8; N] {
self.storage.as_ref().unwrap()
}
/// Borrows the data mutably
pub fn secret_mut(&mut self) -> &mut [u8; N] {
self.storage.as_mut().unwrap()
}
}
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
fn zeroize(&mut self) {
self.secret_mut().zeroize();
}
}
impl<const N: usize> Randomize for Secret<N> {
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
// Zeroize self first just to make sure the barriers from the zeroize create take
// effect to prevent the compiler from optimizing this away.
// We should at some point replace this with our own barriers.
self.zeroize();
self.secret_mut().try_fill(rng)
}
}
impl<const N: usize> Drop for Secret<N> {
fn drop(&mut self) {
self.storage
.take()
.map(|sec| SECRET_CACHE.lock().unwrap().release(sec));
}
}
impl<const N: usize> Clone for Secret<N> {
fn clone(&self) -> Self {
Self::from_slice(self.secret())
}
}
/// The Debug implementation of [Secret] does not reveal the secret data,
/// instead a placeholder `<SECRET>` is used
impl<const N: usize> fmt::Debug for Secret<N> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<SECRET>")
}
}
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
fopen_r(p)?
.read_exact_to_end(v.secret_mut())
.with_context(|| format!("Could not load file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
fn load_b64<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
use std::io::Read;
let mut v = Self::random();
let p = path.as_ref();
// This might leave some fragments of the secret on the stack;
// in practice this is likely not a problem because the stack likely
// will be overwritten by something else soon but this is not exactly
// guaranteed. It would be possible to remedy this, but since the secret
// data will linger in the Linux page cache anyways with the current
// implementation, going to great length to erase the secret here is
// not worth it right now.
b64_reader(&mut fopen_r(p)?)
.read_exact(v.secret_mut())
.with_context(|| format!("Could not load base64 file {p:?}"))?;
Ok(v)
}
}
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
/// check that we can alloc using the magic pool
#[test]
fn secret_memory_pool_take() {
rosenpass_sodium::init().unwrap();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: SodiumBox<[u8; N]> = pool.take();
assert_eq!(secret.as_ref(), &[0; N]);
}
/// check that a secrete lives, even if its [SecretMemoryPool] is deleted
#[test]
fn secret_memory_pool_drop() {
rosenpass_sodium::init().unwrap();
const N: usize = 0x100;
let mut pool = SecretMemoryPool::new();
let secret: SodiumBox<[u8; N]> = pool.take();
std::mem::drop(pool);
assert_eq!(secret.as_ref(), &[0; N]);
}
/// check that a secrete can be reborn, freshly initialized with zero
#[test]
fn secret_memory_pool_release() {
rosenpass_sodium::init().unwrap();
const N: usize = 1;
let mut pool = SecretMemoryPool::new();
let mut secret: SodiumBox<[u8; N]> = pool.take();
let old_secret_ptr = secret.as_ref().as_ptr();
secret.as_mut()[0] = 0x13;
pool.release(secret);
// now check that we get the same ptr
let new_secret: SodiumBox<[u8; N]> = pool.take();
assert_eq!(old_secret_ptr, new_secret.as_ref().as_ptr());
// and that the secret was zeroized
assert_eq!(new_secret.as_ref(), &[0; N]);
}
}

View File

@@ -15,4 +15,3 @@ rosenpass-to = { workspace = true }
anyhow = { workspace = true }
libsodium-sys-stable = { workspace = true }
log = { workspace = true }
allocator-api2 = { workspace = true }

View File

@@ -1,95 +0,0 @@
use allocator_api2::alloc::{AllocError, Allocator, Layout};
use libsodium_sys as libsodium;
use std::fmt;
use std::os::raw::c_void;
use std::ptr::NonNull;
#[derive(Clone, Default)]
struct AllocatorContents;
/// Memory allocation using sodium_malloc/sodium_free
#[derive(Clone, Default)]
pub struct Alloc {
_dummy_private_data: AllocatorContents,
}
impl Alloc {
pub fn new() -> Self {
Alloc {
_dummy_private_data: AllocatorContents,
}
}
}
unsafe impl Allocator for Alloc {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
// Call sodium allocator
let ptr = unsafe { libsodium::sodium_malloc(layout.size()) };
// Ensure the right allocation is used
let off = ptr.align_offset(layout.align());
if off != 0 {
log::error!("Allocation {layout:?} was requested but libsodium returned allocation \
with offset {off} from the requested alignment. Libsodium always allocates values \
at the end of a memory page for security reasons, custom alignments are not supported. \
You could try allocating an oversized value.");
return Err(AllocError);
}
// Convert to a pointer size
let ptr = core::ptr::slice_from_raw_parts_mut(ptr as *mut u8, layout.size());
// Conversion to a *const u8, then to a &[u8]
match NonNull::new(ptr) {
None => {
log::error!(
"Allocation {layout:?} was requested but libsodium returned a null pointer"
);
Err(AllocError)
}
Some(ret) => Ok(ret),
}
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
unsafe {
libsodium::sodium_free(ptr.as_ptr() as *mut c_void);
}
}
}
impl fmt::Debug for Alloc {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("<libsodium based Rust allocator>")
}
}
#[cfg(test)]
mod test {
use super::*;
/// checks that the can malloc with libsodium
#[test]
fn sodium_allocation() {
crate::init().unwrap();
let alloc = Alloc::new();
sodium_allocation_impl::<0>(&alloc);
sodium_allocation_impl::<7>(&alloc);
sodium_allocation_impl::<8>(&alloc);
sodium_allocation_impl::<64>(&alloc);
sodium_allocation_impl::<999>(&alloc);
}
fn sodium_allocation_impl<const N: usize>(alloc: &Alloc) {
crate::init().unwrap();
let layout = Layout::new::<[u8; N]>();
let mem = alloc.allocate(layout).unwrap();
// https://libsodium.gitbook.io/doc/memory_management#guarded-heap-allocations
// promises us that allocated memory is initialized with the magic byte 0xDB
assert_eq!(unsafe { mem.as_ref() }, &[0xDBu8; N]);
let mem = NonNull::new(mem.as_ptr() as *mut u8).unwrap();
unsafe { alloc.deallocate(mem, layout) };
}
}

View File

@@ -1,10 +0,0 @@
//! Access to sodium_malloc/sodium_free
mod allocator;
pub use allocator::Alloc;
/// A box backed by sodium_malloc
pub type Box<T> = allocator_api2::boxed::Box<T, Alloc>;
/// A vector backed by sodium_malloc
pub type Vec<T> = allocator_api2::vec::Vec<T, Alloc>;

View File

@@ -26,3 +26,27 @@ 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)
}

View File

@@ -16,6 +16,5 @@ pub fn init() -> anyhow::Result<()> {
}
pub mod aead;
pub mod alloc;
pub mod hash;
pub mod helpers;

View File

@@ -1,6 +1,3 @@
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 {
@@ -8,86 +5,3 @@ 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()
}
}