mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-05 20:40:02 -08:00
major rewrite of application server & frontend
- adds TOML based configuation files - with example configuratios in config-examples - reimplments arcane CLI argument parser as automaton - adds a new CLI focused arround configuration files - moves all file utility stuff from `main.rs` to `util.rs` - moves all AppServer stuff to dedicated `app_server.rs` - add mio for multi-listen-socket support (should fix #27) - consistency: rename private to secret
This commit is contained in:
240
Cargo.lock
generated
240
Cargo.lock
generated
@@ -32,6 +32,46 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"concolor-override",
|
||||||
|
"concolor-query",
|
||||||
|
"is-terminal",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.70"
|
version = "1.0.70"
|
||||||
@@ -186,12 +226,47 @@ checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_lex",
|
"clap_lex 0.2.4",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
"textwrap 0.16.0",
|
"textwrap 0.16.0",
|
||||||
"yaml-rust",
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"bitflags",
|
||||||
|
"clap_lex 0.4.1",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -203,6 +278,12 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.49"
|
version = "0.1.49"
|
||||||
@@ -212,6 +293,21 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concolor-override"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concolor-query"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -429,6 +525,12 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
@@ -590,12 +692,6 @@ dependencies = [
|
|||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked-hash-map"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -656,6 +752,18 @@ dependencies = [
|
|||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -778,18 +886,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.51"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.23"
|
version = "1.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -869,18 +977,21 @@ version = "0.1.2-rc.4"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"clap 3.2.23",
|
"clap 4.2.1",
|
||||||
"criterion",
|
"criterion",
|
||||||
"env_logger 0.10.0",
|
"env_logger 0.10.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libsodium-sys-stable",
|
"libsodium-sys-stable",
|
||||||
"log",
|
"log",
|
||||||
"memoffset 0.6.5",
|
"memoffset 0.6.5",
|
||||||
|
"mio",
|
||||||
"oqs-sys",
|
"oqs-sys",
|
||||||
"paste",
|
"paste",
|
||||||
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"test_bin",
|
"test_bin",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -954,9 +1065,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.152"
|
version = "1.0.160"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_cbor"
|
name = "serde_cbor"
|
||||||
@@ -970,13 +1084,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.152"
|
version = "1.0.160"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -990,6 +1104,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1025,6 +1148,17 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar"
|
name = "tar"
|
||||||
version = "0.4.38"
|
version = "0.4.38"
|
||||||
@@ -1083,7 +1217,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1111,6 +1245,40 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.19.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.10"
|
version = "0.3.10"
|
||||||
@@ -1170,6 +1338,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -1187,6 +1361,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.84"
|
version = "0.2.84"
|
||||||
@@ -1208,7 +1388,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1230,7 +1410,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.109",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@@ -1378,6 +1558,15 @@ version = "0.42.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1387,15 +1576,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yaml-rust"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
|
||||||
dependencies = [
|
|
||||||
"linked-hash-map",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ harness = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.52", features = ["backtrace"] }
|
anyhow = { version = "1.0.52", features = ["backtrace"] }
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
clap = { version = "3.0.0", features = ["yaml"] }
|
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
memoffset = "0.6.5"
|
memoffset = "0.6.5"
|
||||||
libsodium-sys-stable = { version = "1.19.26", features = ["use-pkg-config"] }
|
libsodium-sys-stable = { version = "1.19.26", features = ["use-pkg-config"] }
|
||||||
@@ -26,6 +25,10 @@ thiserror = "1.0.38"
|
|||||||
paste = "1.0.11"
|
paste = "1.0.11"
|
||||||
log = { version = "0.4.17", optional = true }
|
log = { version = "0.4.17", optional = true }
|
||||||
env_logger = { version = "0.10.0", optional = true }
|
env_logger = { version = "0.10.0", optional = true }
|
||||||
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
|
toml = "0.7.3"
|
||||||
|
clap = { version = "4.2.1", features = ["derive"] }
|
||||||
|
mio = { version = "0.8.6", features = ["net", "os-poll"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.70"
|
anyhow = "1.0.70"
|
||||||
|
|||||||
2
config-examples/.gitignore
vendored
Normal file
2
config-examples/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
peer-*-*-key
|
||||||
|
peer-*-out
|
||||||
18
config-examples/peer-a-config.toml
Normal file
18
config-examples/peer-a-config.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
public_key = "peer-a-public-key"
|
||||||
|
secret_key = "peer-a-secret-key"
|
||||||
|
listen = ["[::]:10001"]
|
||||||
|
verbosity = "Quiet"
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "peer-b-public-key"
|
||||||
|
endpoint = "localhost:10002"
|
||||||
|
key_out = "peer-a-rp-out-key"
|
||||||
|
# exchange_command = [
|
||||||
|
# "wg",
|
||||||
|
# "set",
|
||||||
|
# "wg0",
|
||||||
|
# "peer",
|
||||||
|
# "<PEER_ID>",
|
||||||
|
# "preshared-key",
|
||||||
|
# "/dev/stdin",
|
||||||
|
# ]
|
||||||
18
config-examples/peer-b-config.toml
Normal file
18
config-examples/peer-b-config.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
public_key = "peer-b-public-key"
|
||||||
|
secret_key = "peer-b-secret-key"
|
||||||
|
listen = ["[::]:10002"]
|
||||||
|
verbosity = "Quiet"
|
||||||
|
|
||||||
|
[[peers]]
|
||||||
|
public_key = "peer-a-public-key"
|
||||||
|
endpoint = "localhost:10001"
|
||||||
|
key_out = "peer-b-rp-out-key"
|
||||||
|
# exchange_command = [
|
||||||
|
# "wg",
|
||||||
|
# "set",
|
||||||
|
# "wg0",
|
||||||
|
# "peer",
|
||||||
|
# "<PEER_ID>",
|
||||||
|
# "preshared-key",
|
||||||
|
# "/dev/stdin",
|
||||||
|
# ]
|
||||||
442
src/app_server.rs
Normal file
442
src/app_server.rs
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::{error, info};
|
||||||
|
use mio::Interest;
|
||||||
|
use mio::Token;
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::net::Ipv4Addr;
|
||||||
|
use std::net::Ipv6Addr;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::net::SocketAddrV4;
|
||||||
|
use std::net::SocketAddrV6;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::process::Stdio;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::util::fopen_w;
|
||||||
|
use crate::{
|
||||||
|
config::Verbosity,
|
||||||
|
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
||||||
|
util::{b64_writer, fmt_b64},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct AppPeer {
|
||||||
|
pub outfile: Option<PathBuf>,
|
||||||
|
pub outwg: Option<WireguardOut>, // TODO make this a generic command
|
||||||
|
pub tx_addr: Option<SocketAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct WireguardOut {
|
||||||
|
// impl KeyOutput
|
||||||
|
pub dev: String,
|
||||||
|
pub pk: String,
|
||||||
|
pub extra_params: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds the state of the application, namely the external IO
|
||||||
|
///
|
||||||
|
/// Responsible for file IO, network IO
|
||||||
|
// TODO add user control via unix domain socket and stdin/stdout
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppServer {
|
||||||
|
pub crypt: CryptoServer,
|
||||||
|
pub sockets: Vec<mio::net::UdpSocket>,
|
||||||
|
pub events: mio::Events,
|
||||||
|
pub mio_poll: mio::Poll,
|
||||||
|
pub peers: Vec<AppPeer>,
|
||||||
|
pub verbosity: Verbosity,
|
||||||
|
pub all_sockets_drained: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Index based pointer to a Peer
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AppPeerPtr(pub usize);
|
||||||
|
|
||||||
|
impl AppPeerPtr {
|
||||||
|
/// Takes an index based handle and returns the actual peer
|
||||||
|
pub fn lift(p: PeerPtr) -> Self {
|
||||||
|
Self(p.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an index based handle to one Peer
|
||||||
|
pub fn lower(&self) -> PeerPtr {
|
||||||
|
PeerPtr(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
||||||
|
&srv.peers[self.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
||||||
|
&mut srv.peers[self.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AppPollResult {
|
||||||
|
DeleteKey(AppPeerPtr),
|
||||||
|
SendInitiation(AppPeerPtr),
|
||||||
|
SendRetransmission(AppPeerPtr),
|
||||||
|
ReceivedMessage(usize, SocketAddr),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KeyOutputReason {
|
||||||
|
Exchanged,
|
||||||
|
Stale,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppServer {
|
||||||
|
pub fn new(
|
||||||
|
sk: SSk,
|
||||||
|
pk: SPk,
|
||||||
|
addrs: Vec<SocketAddr>,
|
||||||
|
verbosity: Verbosity,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
// setup mio
|
||||||
|
let mio_poll = mio::Poll::new()?;
|
||||||
|
let events = mio::Events::with_capacity(8);
|
||||||
|
|
||||||
|
// bind each SocketAddr to a socket
|
||||||
|
let maybe_sockets: Result<Vec<_>, _> =
|
||||||
|
addrs.into_iter().map(mio::net::UdpSocket::bind).collect();
|
||||||
|
let mut sockets = maybe_sockets?;
|
||||||
|
|
||||||
|
// if there is no socket, just listen to anything
|
||||||
|
if sockets.is_empty() {
|
||||||
|
// port 0 means the OS can pick any free port
|
||||||
|
let port = 0;
|
||||||
|
|
||||||
|
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||||
|
|
||||||
|
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||||
|
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
port,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
|
||||||
|
// bind to IPv4
|
||||||
|
sockets.push(mio::net::UdpSocket::bind(ipv4_any)?);
|
||||||
|
|
||||||
|
// and try to bind to IPv6, just in case
|
||||||
|
match mio::net::UdpSocket::bind(ipv6_any) {
|
||||||
|
Ok(socket) => sockets.push(socket),
|
||||||
|
Err(e) if e.kind() == ErrorKind::AddrInUse => { /* shrugs, seems to be a IPv4/IPv6 dual stack OS */
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register all sockets to mio
|
||||||
|
for (i, socket) in sockets.iter_mut().enumerate() {
|
||||||
|
mio_poll
|
||||||
|
.registry()
|
||||||
|
.register(socket, Token(i), Interest::READABLE)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for linux
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
crypt: CryptoServer::new(sk, pk),
|
||||||
|
peers: Vec::new(),
|
||||||
|
verbosity,
|
||||||
|
sockets,
|
||||||
|
events,
|
||||||
|
mio_poll,
|
||||||
|
all_sockets_drained: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verbose(&self) -> bool {
|
||||||
|
matches!(self.verbosity, Verbosity::Verbose)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_peer(
|
||||||
|
&mut self,
|
||||||
|
psk: Option<SymKey>,
|
||||||
|
pk: SPk,
|
||||||
|
outfile: Option<PathBuf>,
|
||||||
|
outwg: Option<WireguardOut>,
|
||||||
|
tx_addr: Option<SocketAddr>,
|
||||||
|
) -> anyhow::Result<AppPeerPtr> {
|
||||||
|
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
|
||||||
|
assert!(pn == self.peers.len());
|
||||||
|
self.peers.push(AppPeer {
|
||||||
|
outfile,
|
||||||
|
outwg,
|
||||||
|
tx_addr,
|
||||||
|
});
|
||||||
|
Ok(AppPeerPtr(pn))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen_loop(&mut self) -> anyhow::Result<()> {
|
||||||
|
const INIT_SLEEP: f64 = 0.01;
|
||||||
|
const MAX_FAILURES: i32 = 10;
|
||||||
|
let mut failure_cnt = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let msgs_processed = 0usize;
|
||||||
|
let err = match self.event_loop() {
|
||||||
|
Ok(()) => return Ok(()),
|
||||||
|
Err(e) => e,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This should not happen…
|
||||||
|
failure_cnt = if msgs_processed > 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
failure_cnt + 1
|
||||||
|
};
|
||||||
|
let sleep = INIT_SLEEP * 2.0f64.powf(f64::from(failure_cnt - 1));
|
||||||
|
let tries_left = MAX_FAILURES - (failure_cnt - 1);
|
||||||
|
error!(
|
||||||
|
"unexpected error after processing {} messages: {:?} {}",
|
||||||
|
msgs_processed,
|
||||||
|
err,
|
||||||
|
err.backtrace()
|
||||||
|
);
|
||||||
|
if tries_left > 0 {
|
||||||
|
error!("reinitializing networking in {sleep}! {tries_left} tries left.");
|
||||||
|
std::thread::sleep(self.crypt.timebase.dur(sleep));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("too many network failures");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_loop(&mut self) -> anyhow::Result<()> {
|
||||||
|
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
||||||
|
|
||||||
|
/// if socket address for peer is known, call closure
|
||||||
|
/// assumes that closure leaves a message in `tx`
|
||||||
|
/// assumes that closure returns the length of message in bytes
|
||||||
|
macro_rules! tx_maybe_with {
|
||||||
|
($peer:expr, $fn:expr) => {
|
||||||
|
attempt!({
|
||||||
|
let p = $peer.get_app(self);
|
||||||
|
if let Some(addr) = p.tx_addr {
|
||||||
|
let len = $fn()?;
|
||||||
|
self.try_send(&tx[..len], addr)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
use crate::protocol::HandleMsgResult;
|
||||||
|
use AppPollResult::*;
|
||||||
|
use KeyOutputReason::*;
|
||||||
|
match self.poll(&mut *rx)? {
|
||||||
|
SendInitiation(peer) => tx_maybe_with!(peer, || self
|
||||||
|
.crypt
|
||||||
|
.initiate_handshake(peer.lower(), &mut *tx))?,
|
||||||
|
SendRetransmission(peer) => tx_maybe_with!(peer, || self
|
||||||
|
.crypt
|
||||||
|
.retransmit_handshake(peer.lower(), &mut *tx))?,
|
||||||
|
DeleteKey(peer) => self.output_key(peer, Stale, &SymKey::random())?,
|
||||||
|
|
||||||
|
ReceivedMessage(len, addr) => {
|
||||||
|
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
|
||||||
|
Err(ref e) => {
|
||||||
|
self.verbose().then(|| {
|
||||||
|
info!(
|
||||||
|
"error processing incoming message from {:?}: {:?} {}",
|
||||||
|
addr,
|
||||||
|
e,
|
||||||
|
e.backtrace()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HandleMsgResult {
|
||||||
|
resp,
|
||||||
|
exchanged_with,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if let Some(len) = resp {
|
||||||
|
self.try_send(&tx[0..len], addr)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(p) = exchanged_with {
|
||||||
|
let ap = AppPeerPtr::lift(p);
|
||||||
|
ap.get_app_mut(self).tx_addr = Some(addr);
|
||||||
|
|
||||||
|
// TODO: Maybe we should rather call the key "rosenpass output"?
|
||||||
|
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_key(
|
||||||
|
&self,
|
||||||
|
peer: AppPeerPtr,
|
||||||
|
why: KeyOutputReason,
|
||||||
|
key: &SymKey,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let peerid = peer.lower().get(&self.crypt).pidt()?;
|
||||||
|
let ap = peer.get_app(self);
|
||||||
|
|
||||||
|
if self.verbose() {
|
||||||
|
let msg = match why {
|
||||||
|
KeyOutputReason::Exchanged => "Exchanged key with peer",
|
||||||
|
KeyOutputReason::Stale => "Erasing outdated key from peer",
|
||||||
|
};
|
||||||
|
info!("{} {}", msg, fmt_b64(&*peerid));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(of) = ap.outfile.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_writer(fopen_w(of)?).write_all(key.secret())?;
|
||||||
|
let why = match why {
|
||||||
|
KeyOutputReason::Exchanged => "exchanged",
|
||||||
|
KeyOutputReason::Stale => "stale",
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is intentionally writing to stdout instead of stderr, because
|
||||||
|
// it is meant to allow external detection of a succesful key-exchange
|
||||||
|
println!(
|
||||||
|
"output-key peer {} key-file {of:?} {why}",
|
||||||
|
fmt_b64(&*peerid)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(owg) = ap.outwg.as_ref() {
|
||||||
|
let child = Command::new("wg")
|
||||||
|
.arg("set")
|
||||||
|
.arg(&owg.dev)
|
||||||
|
.arg("peer")
|
||||||
|
.arg(&owg.pk)
|
||||||
|
.arg("preshared-key")
|
||||||
|
.arg("/dev/stdin")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.args(&owg.extra_params)
|
||||||
|
.spawn()?;
|
||||||
|
b64_writer(child.stdin.unwrap()).write_all(key.secret())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
|
||||||
|
use crate::protocol::PollResult as C;
|
||||||
|
use AppPollResult as A;
|
||||||
|
loop {
|
||||||
|
return Ok(match self.crypt.poll()? {
|
||||||
|
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
|
||||||
|
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
|
||||||
|
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
|
||||||
|
C::Sleep(timeout) => match self.try_recv(rx_buf, timeout)? {
|
||||||
|
Some((len, addr)) => A::ReceivedMessage(len, addr),
|
||||||
|
None => continue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to receive a new message
|
||||||
|
///
|
||||||
|
/// - might wait for an duration up to `timeout`
|
||||||
|
/// - returns immediately if an error occurs
|
||||||
|
/// - returns immediately if a new message is received
|
||||||
|
pub fn try_recv(
|
||||||
|
&mut self,
|
||||||
|
buf: &mut [u8],
|
||||||
|
timeout: Timing,
|
||||||
|
) -> anyhow::Result<Option<(usize, SocketAddr)>> {
|
||||||
|
let timeout = Duration::from_secs_f64(timeout);
|
||||||
|
|
||||||
|
// if there is no time to wait on IO, well, then, lets not waste any time!
|
||||||
|
if timeout.is_zero() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE when using mio::Poll, there are some finickies (taken from
|
||||||
|
// https://docs.rs/mio/latest/mio/struct.Poll.html):
|
||||||
|
//
|
||||||
|
// - poll() might return readiness, even if nothing is ready
|
||||||
|
// - in this case, a WouldBlock error is returned from actual IO operations
|
||||||
|
// - after receiving readiness for a source, it must be drained until a WouldBlock
|
||||||
|
// is received
|
||||||
|
//
|
||||||
|
// This would ususally require us to maintain the drainage status of each socket;
|
||||||
|
// a socket would only become drained when it returned WouldBlock and only
|
||||||
|
// non-drained when receiving a readiness event from mio for it. Then, only the
|
||||||
|
// ready sockets should be worked on, ideally without requiring an O(n) search
|
||||||
|
// through all sockets for checking their drained status. However, our use-case
|
||||||
|
// is primarily heaving one or two sockets (if IPv4 and IPv6 IF_ANY listen is
|
||||||
|
// desired on a non-dual-stack OS), thus just checking every socket after any
|
||||||
|
// readiness event seems to be good enough™ for now.
|
||||||
|
|
||||||
|
// only poll if we drained all sockets before
|
||||||
|
if self.all_sockets_drained {
|
||||||
|
self.mio_poll.poll(&mut self.events, Some(timeout))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut would_block_count = 0;
|
||||||
|
for socket in &mut self.sockets {
|
||||||
|
match socket.recv_from(buf) {
|
||||||
|
Ok(x) => {
|
||||||
|
// at least one socket was not drained...
|
||||||
|
self.all_sockets_drained = false;
|
||||||
|
return Ok(Some(x));
|
||||||
|
}
|
||||||
|
Err(e) if e.kind() == ErrorKind::WouldBlock => {
|
||||||
|
would_block_count += 1;
|
||||||
|
}
|
||||||
|
// TODO if one socket continuesly returns an error, then we never poll, thus we never wait for a timeout, thus we have a spin-lock
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if each socket returned WouldBlock, then we drained them all at least once indeed
|
||||||
|
self.all_sockets_drained = would_block_count == self.sockets.len();
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to send a message
|
||||||
|
///
|
||||||
|
/// Every available socket is tried once
|
||||||
|
// TODO cache what socket worked last time
|
||||||
|
// TODO cache what socket we received from last time for that addr
|
||||||
|
pub fn try_send(&mut self, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
|
||||||
|
for socket in &self.sockets {
|
||||||
|
return match socket.send_to(&buf, addr) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
|
||||||
|
// TODO replace this by
|
||||||
|
// Err(e) if e.kind() == io::ErrorKind::NetworkUnreachable => continue,
|
||||||
|
// once https://github.com/rust-lang/rust/issues/86442 lands
|
||||||
|
Err(e)
|
||||||
|
if e.to_string()
|
||||||
|
.starts_with("Address family not supported by protocol") =>
|
||||||
|
{
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("none of our sockets matched the address family {}", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
265
src/cli.rs
Normal file
265
src/cli.rs
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
use anyhow::{bail, ensure};
|
||||||
|
use clap::Parser;
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::app_server::AppServer;
|
||||||
|
use crate::util::{LoadValue, LoadValueB64};
|
||||||
|
use crate::{
|
||||||
|
// app_server::{AppServer, LoadValue, LoadValueB64},
|
||||||
|
coloring::Secret,
|
||||||
|
pqkem::{StaticKEM, KEM},
|
||||||
|
protocol::{SPk, SSk, SymKey},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::config;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about)]
|
||||||
|
pub enum Cli {
|
||||||
|
/// Start Rosenpass in server mode and carry on with the key exchange
|
||||||
|
///
|
||||||
|
/// This will parse the configuration file and perform the key exchange
|
||||||
|
/// with the specified peers. If a peer's endpoint is specified, this
|
||||||
|
/// Rosenpass instance will try to initiate a key exchange with the peer,
|
||||||
|
/// otherwise only initiation attempts from the peer will be responded to.
|
||||||
|
ExchangeConfig { config_file: PathBuf },
|
||||||
|
|
||||||
|
/// Start in daemon mode, performing key exchanges
|
||||||
|
///
|
||||||
|
/// The configuration is read from the command line. The `peer` token
|
||||||
|
/// always separates multiple peers, e. g. if the token `peer` appears
|
||||||
|
/// in the WIREGUARD_EXTRA_ARGS it terminates is not put into the
|
||||||
|
/// WireGuard arguments but instead a new peer is created.
|
||||||
|
/* Explanation: `first_arg` and `rest_of_args` are combined into one
|
||||||
|
* `Vec<String>`. They are only used to trick clap into displaying some
|
||||||
|
* guidance on the CLI usage.
|
||||||
|
*/
|
||||||
|
Exchange {
|
||||||
|
/// public-key <PATH> secret-key <PATH> [listen <ADDR>:<PORT>] [verbose]
|
||||||
|
#[clap(value_name = "OWN_CONFIG")]
|
||||||
|
first_arg: String,
|
||||||
|
|
||||||
|
/// peer public-key <PATH> [ENDPOINT] [PSK] [OUTFILE] [WG]
|
||||||
|
///
|
||||||
|
/// ENDPOINT := [endpoint <HOST/IP>:<PORT>]
|
||||||
|
///
|
||||||
|
/// PSK := [preshared-key <PATH>]
|
||||||
|
///
|
||||||
|
/// OUTFILE := [outfile <PATH>]
|
||||||
|
///
|
||||||
|
/// WG := [wireguard <WIREGUARD_DEV> <WIREGUARD_PEER> [WIREGUARD_EXTRA_ARGS]...]
|
||||||
|
#[clap(value_names = [
|
||||||
|
"peer", "public-key", "<PATH>", "[ENDPOINT]" ,"[PSK]", "[OUTFILE]", "[WG]"
|
||||||
|
])]
|
||||||
|
rest_of_args: Vec<String>,
|
||||||
|
|
||||||
|
/// Save the parsed configuration to a file before starting the daemon
|
||||||
|
#[clap(short, long)]
|
||||||
|
config_file: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Generate a demo config file
|
||||||
|
GenConfig {
|
||||||
|
config_file: PathBuf,
|
||||||
|
|
||||||
|
/// Forecefully overwrite existing config file
|
||||||
|
#[clap(short, long)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Generate the keys mentioned in a configFile
|
||||||
|
///
|
||||||
|
/// Generates secret- & public-key to their destination. If a config file
|
||||||
|
/// is provided then the key file destination is taken from there.
|
||||||
|
/// Otherwise the
|
||||||
|
GenKeys {
|
||||||
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// where to write public-key to
|
||||||
|
#[clap(short, long)]
|
||||||
|
public_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// where to write secret-key to
|
||||||
|
#[clap(short, long)]
|
||||||
|
secret_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Forecefully overwrite public- & secret-key file
|
||||||
|
#[clap(short, long)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Validate a configuration
|
||||||
|
Validate { config_files: Vec<PathBuf> },
|
||||||
|
|
||||||
|
/// Show the rosenpass manpage
|
||||||
|
// TODO make this the default, but only after the manpage has been adjusted once the CLI stabilizes
|
||||||
|
Man,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
pub fn run() -> anyhow::Result<()> {
|
||||||
|
let cli = Self::parse();
|
||||||
|
|
||||||
|
use Cli::*;
|
||||||
|
match cli {
|
||||||
|
Man => {
|
||||||
|
let _man_cmd = std::process::Command::new("man")
|
||||||
|
.args(["1", "rosenpass"])
|
||||||
|
.status();
|
||||||
|
}
|
||||||
|
GenConfig { config_file, force } => {
|
||||||
|
ensure!(
|
||||||
|
force || !config_file.exists(),
|
||||||
|
"config file {config_file:?} already exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
config::Rosenpass::example_config().store(config_file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
GenKeys {
|
||||||
|
config_file,
|
||||||
|
public_key,
|
||||||
|
secret_key,
|
||||||
|
force,
|
||||||
|
} => {
|
||||||
|
// figure out where the key file is specified, in the config file or directly as flag?
|
||||||
|
let (pkf, skf) = match (config_file, public_key, secret_key) {
|
||||||
|
(Some(config_file), _, _) => {
|
||||||
|
ensure!(
|
||||||
|
config_file.exists(),
|
||||||
|
"config file {config_file:?} does not exist"
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = config::Rosenpass::load(config_file)?;
|
||||||
|
|
||||||
|
(config.public_key, config.secret_key)
|
||||||
|
}
|
||||||
|
(_, Some(pkf), Some(skf)) => (pkf, skf),
|
||||||
|
_ => {
|
||||||
|
bail!("either a config-file or both public-key and secret-key file are required")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// check that we are not overriding something unintentionally
|
||||||
|
let mut problems = vec![];
|
||||||
|
if !force && pkf.is_file() {
|
||||||
|
problems.push(format!(
|
||||||
|
"public-key file {pkf:?} exist, refusing to overwrite it"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !force && skf.is_file() {
|
||||||
|
problems.push(format!(
|
||||||
|
"secret-key file {skf:?} exist, refusing to overwrite it"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !problems.is_empty() {
|
||||||
|
bail!(problems.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the keys and store them in files
|
||||||
|
let mut ssk = crate::protocol::SSk::random();
|
||||||
|
let mut spk = crate::protocol::SPk::random();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
||||||
|
ssk.store_secret(skf)?;
|
||||||
|
spk.store_secret(pkf)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExchangeConfig { config_file } => {
|
||||||
|
ensure!(
|
||||||
|
config_file.exists(),
|
||||||
|
"config file '{config_file:?}' does not exist"
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = config::Rosenpass::load(config_file)?;
|
||||||
|
config.validate()?;
|
||||||
|
Self::event_loop(config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exchange {
|
||||||
|
first_arg,
|
||||||
|
mut rest_of_args,
|
||||||
|
config_file,
|
||||||
|
} => {
|
||||||
|
rest_of_args.insert(0, first_arg);
|
||||||
|
let args = rest_of_args;
|
||||||
|
let mut config = config::Rosenpass::parse_args(args)?;
|
||||||
|
|
||||||
|
if let Some(p) = config_file {
|
||||||
|
config.store(&p)?;
|
||||||
|
config.config_file_path = p;
|
||||||
|
}
|
||||||
|
config.validate()?;
|
||||||
|
Self::event_loop(config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Validate { config_files } => {
|
||||||
|
for file in config_files {
|
||||||
|
match config::Rosenpass::load(&file) {
|
||||||
|
Ok(config) => {
|
||||||
|
eprintln!("{file:?} is valid TOML and conforms to the expected schema");
|
||||||
|
match config.validate() {
|
||||||
|
Ok(_) => eprintln!("{file:?} is passed all logical checks"),
|
||||||
|
Err(_) => eprintln!("{file:?} contains logical errors"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => eprintln!("{file:?} is not valid: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_loop(config: config::Rosenpass) -> anyhow::Result<()> {
|
||||||
|
// dump config
|
||||||
|
eprintln!("{config:#?}");
|
||||||
|
|
||||||
|
// load own keys
|
||||||
|
let sk = SSk::load(&config.secret_key)?;
|
||||||
|
let pk = SPk::load(&config.public_key)?;
|
||||||
|
|
||||||
|
// start an application server
|
||||||
|
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
||||||
|
sk,
|
||||||
|
pk,
|
||||||
|
config.listen,
|
||||||
|
config.verbosity,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
for cfg_peer in config.peers {
|
||||||
|
let endpoint = cfg_peer
|
||||||
|
.endpoint
|
||||||
|
.as_ref()
|
||||||
|
.map(ToSocketAddrs::to_socket_addrs)
|
||||||
|
.transpose()?
|
||||||
|
.and_then(|mut i| i.next());
|
||||||
|
|
||||||
|
srv.add_peer(
|
||||||
|
// psk, pk, outfile, outwg, tx_addr
|
||||||
|
cfg_peer.pre_shared_key.map(SymKey::load_b64).transpose()?,
|
||||||
|
SPk::load(&cfg_peer.public_key)?,
|
||||||
|
cfg_peer.key_out,
|
||||||
|
None, // TODO remove this argument
|
||||||
|
endpoint,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.event_loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait StoreSecret {
|
||||||
|
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreSecret for Secret<N> {
|
||||||
|
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
|
||||||
|
std::fs::write(path, self.secret())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
449
src/config.rs
Normal file
449
src/config.rs
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
fs,
|
||||||
|
io::Write,
|
||||||
|
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{bail, ensure};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::util::fopen_w;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Rosenpass {
|
||||||
|
pub public_key: PathBuf,
|
||||||
|
|
||||||
|
pub secret_key: PathBuf,
|
||||||
|
|
||||||
|
pub listen: Vec<SocketAddr>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub verbosity: Verbosity,
|
||||||
|
pub peers: Vec<RosenpassPeer>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub config_file_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum Verbosity {
|
||||||
|
Quiet,
|
||||||
|
Verbose,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RosenpassPeer {
|
||||||
|
pub public_key: PathBuf,
|
||||||
|
pub endpoint: Option<String>,
|
||||||
|
pub pre_shared_key: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct WireGuard {
|
||||||
|
device: String,
|
||||||
|
peer: String,
|
||||||
|
extra_params: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rosenpass {
|
||||||
|
/// Load a config file from a file path
|
||||||
|
///
|
||||||
|
/// no validation is conducted
|
||||||
|
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
|
||||||
|
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
|
||||||
|
|
||||||
|
config.config_file_path = p.as_ref().to_owned();
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a config to a file
|
||||||
|
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
|
||||||
|
let serialized_config =
|
||||||
|
toml::to_string_pretty(&self).expect("unable to serialize the default config");
|
||||||
|
fs::write(p, serialized_config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit the configuration to where it came from, overwriting the original file
|
||||||
|
pub fn commit(&self) -> anyhow::Result<()> {
|
||||||
|
let mut f = fopen_w(&self.config_file_path)?;
|
||||||
|
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
|
||||||
|
|
||||||
|
self.store(&self.config_file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a configuration
|
||||||
|
pub fn validate(&self) -> anyhow::Result<()> {
|
||||||
|
// check the public-key file exists
|
||||||
|
ensure!(
|
||||||
|
self.public_key.is_file(),
|
||||||
|
"public-key file {:?} does not exist",
|
||||||
|
self.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check the secret-key file exists
|
||||||
|
ensure!(
|
||||||
|
self.secret_key.is_file(),
|
||||||
|
"secret-key file {:?} does not exist",
|
||||||
|
self.secret_key
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, peer) in self.peers.iter().enumerate() {
|
||||||
|
// check peer's public-key file exists
|
||||||
|
ensure!(
|
||||||
|
peer.public_key.is_file(),
|
||||||
|
"peer {i} public-key file {:?} does not exist",
|
||||||
|
peer.public_key
|
||||||
|
);
|
||||||
|
|
||||||
|
// check endpoint is usable
|
||||||
|
if let Some(addr) = peer.endpoint.as_ref() {
|
||||||
|
ensure!(
|
||||||
|
addr.to_socket_addrs().is_ok(),
|
||||||
|
"peer {i} endpoint {} can not be parsed to a socket address",
|
||||||
|
addr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO warn if neither out_key nor exchange_command is defined
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new configuration
|
||||||
|
pub fn new<P1: AsRef<Path>, P2: AsRef<Path>>(public_key: P1, secret_key: P2) -> Self {
|
||||||
|
Self {
|
||||||
|
public_key: PathBuf::from(public_key.as_ref()),
|
||||||
|
secret_key: PathBuf::from(secret_key.as_ref()),
|
||||||
|
listen: vec![],
|
||||||
|
verbosity: Verbosity::Quiet,
|
||||||
|
peers: vec![],
|
||||||
|
config_file_path: PathBuf::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
|
||||||
|
pub fn add_if_any(&mut self, port: u16) {
|
||||||
|
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
|
||||||
|
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
|
||||||
|
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0),
|
||||||
|
port,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
self.listen.push(ipv4_any);
|
||||||
|
self.listen.push(ipv6_any);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// from chaotic args
|
||||||
|
/// Quest: the grammar is undecideable, what do we do here?
|
||||||
|
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
|
||||||
|
let mut config = Self::new("", "");
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||||
|
enum State {
|
||||||
|
Own,
|
||||||
|
OwnPublicKey,
|
||||||
|
OwnSecretKey,
|
||||||
|
OwnListen,
|
||||||
|
Peer,
|
||||||
|
PeerPsk,
|
||||||
|
PeerPublicKey,
|
||||||
|
PeerEndpoint,
|
||||||
|
PeerOutfile,
|
||||||
|
PeerWireguardDev,
|
||||||
|
PeerWireguardPeer,
|
||||||
|
PeerWireguardExtraArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut already_set = HashSet::new();
|
||||||
|
|
||||||
|
// TODO idea: use config.peers.len() to give index of peer with conflicting argument
|
||||||
|
use State::*;
|
||||||
|
let mut state = Own;
|
||||||
|
let mut current_peer = None;
|
||||||
|
let p_exists = "a peer should exist by now";
|
||||||
|
let wg_exists = "a peer wireguard should exist by now";
|
||||||
|
for arg in args {
|
||||||
|
state = match (state, arg.as_str(), &mut current_peer) {
|
||||||
|
(Own, "public-key", None) => OwnPublicKey,
|
||||||
|
(Own, "secret-key", None) => OwnSecretKey,
|
||||||
|
(Own, "listen", None) => OwnListen,
|
||||||
|
(Own, "verbose", None) => {
|
||||||
|
config.verbosity = Verbosity::Verbose;
|
||||||
|
Own
|
||||||
|
}
|
||||||
|
(Own, "peer", None) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.contains(&OwnPublicKey),
|
||||||
|
"public-key file must be set"
|
||||||
|
);
|
||||||
|
ensure!(
|
||||||
|
already_set.contains(&OwnSecretKey),
|
||||||
|
"secret-key file must be set"
|
||||||
|
);
|
||||||
|
|
||||||
|
already_set.clear();
|
||||||
|
current_peer = Some(RosenpassPeer::default());
|
||||||
|
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(OwnPublicKey, pk, None) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(OwnPublicKey),
|
||||||
|
"public-key was already set"
|
||||||
|
);
|
||||||
|
config.public_key = pk.into();
|
||||||
|
Own
|
||||||
|
}
|
||||||
|
(OwnSecretKey, sk, None) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(OwnSecretKey),
|
||||||
|
"secret-key was already set"
|
||||||
|
);
|
||||||
|
config.secret_key = sk.into();
|
||||||
|
Own
|
||||||
|
}
|
||||||
|
(OwnListen, l, None) => {
|
||||||
|
already_set.insert(OwnListen); // multiple listen directives are allowed
|
||||||
|
for socket_addr in l.to_socket_addrs()? {
|
||||||
|
config.listen.push(socket_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Own
|
||||||
|
}
|
||||||
|
(Peer | PeerWireguardExtraArgs, "peer", maybe_peer @ Some(_)) => {
|
||||||
|
// TODO check current peer
|
||||||
|
// commit current peer, create a new one
|
||||||
|
config.peers.push(maybe_peer.take().expect(p_exists));
|
||||||
|
|
||||||
|
already_set.clear();
|
||||||
|
current_peer = Some(RosenpassPeer::default());
|
||||||
|
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(Peer, "public-key", Some(_)) => PeerPublicKey,
|
||||||
|
(Peer, "endpoint", Some(_)) => PeerEndpoint,
|
||||||
|
(Peer, "preshared-key", Some(_)) => PeerPsk,
|
||||||
|
(Peer, "outfile", Some(_)) => PeerOutfile,
|
||||||
|
(Peer, "wireguard", Some(_)) => PeerWireguardDev,
|
||||||
|
(PeerPublicKey, pk, Some(peer)) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(PeerPublicKey),
|
||||||
|
"public-key was already set"
|
||||||
|
);
|
||||||
|
peer.public_key = pk.into();
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(PeerEndpoint, e, Some(peer)) => {
|
||||||
|
ensure!(already_set.insert(PeerEndpoint), "endpoint was already set");
|
||||||
|
peer.endpoint = Some(e.to_owned());
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(PeerPsk, psk, Some(peer)) => {
|
||||||
|
ensure!(already_set.insert(PeerEndpoint), "peer psk was already set");
|
||||||
|
peer.pre_shared_key = Some(psk.into());
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(PeerOutfile, of, Some(peer)) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(PeerOutfile),
|
||||||
|
"peer outfile was already set"
|
||||||
|
);
|
||||||
|
peer.key_out = Some(of.into());
|
||||||
|
Peer
|
||||||
|
}
|
||||||
|
(PeerWireguardDev, dev, Some(peer)) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(PeerWireguardDev),
|
||||||
|
"peer wireguard-dev was already set"
|
||||||
|
);
|
||||||
|
assert!(peer.wg.is_none());
|
||||||
|
peer.wg = Some(WireGuard {
|
||||||
|
device: dev.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
PeerWireguardPeer
|
||||||
|
}
|
||||||
|
(PeerWireguardPeer, p, Some(peer)) => {
|
||||||
|
ensure!(
|
||||||
|
already_set.insert(PeerWireguardPeer),
|
||||||
|
"peer wireguard-peer was already set"
|
||||||
|
);
|
||||||
|
peer.wg.as_mut().expect(wg_exists).peer = p.to_string();
|
||||||
|
PeerWireguardExtraArgs
|
||||||
|
}
|
||||||
|
(PeerWireguardExtraArgs, arg, Some(peer)) => {
|
||||||
|
peer.wg
|
||||||
|
.as_mut()
|
||||||
|
.expect(wg_exists)
|
||||||
|
.extra_params
|
||||||
|
.push(arg.to_string());
|
||||||
|
PeerWireguardExtraArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// error cases
|
||||||
|
(Own, x, None) => {
|
||||||
|
bail!("unrecognised argument {x}");
|
||||||
|
}
|
||||||
|
(Own | OwnPublicKey | OwnSecretKey | OwnListen, _, Some(_)) => {
|
||||||
|
panic!("current_peer is not None while in Own* state, this must never happen")
|
||||||
|
}
|
||||||
|
|
||||||
|
(State::Peer, arg, Some(_)) => {
|
||||||
|
bail!("unrecongnised argument {arg}");
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Peer
|
||||||
|
| PeerEndpoint
|
||||||
|
| PeerOutfile
|
||||||
|
| PeerPublicKey
|
||||||
|
| PeerPsk
|
||||||
|
| PeerWireguardDev
|
||||||
|
| PeerWireguardPeer
|
||||||
|
| PeerWireguardExtraArgs,
|
||||||
|
_,
|
||||||
|
None,
|
||||||
|
) => {
|
||||||
|
panic!("got peer options but no peer was created")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(p) = current_peer {
|
||||||
|
// TODO ensure peer is propagated with sufficient information
|
||||||
|
config.peers.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rosenpass {
|
||||||
|
/// Generate an example configuration
|
||||||
|
pub fn example_config() -> Self {
|
||||||
|
let peer = RosenpassPeer {
|
||||||
|
public_key: "rp-peer-public-key".into(),
|
||||||
|
endpoint: Some("my-peer.test:9999".into()),
|
||||||
|
exchange_command: [
|
||||||
|
"wg",
|
||||||
|
"set",
|
||||||
|
"wg0",
|
||||||
|
"peer",
|
||||||
|
"<PEER_ID>",
|
||||||
|
"preshared-key",
|
||||||
|
"/dev/stdin",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect(),
|
||||||
|
key_out: Some("rp-key-out".into()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
wg: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
public_key: "rp-public-key".into(),
|
||||||
|
secret_key: "rp-secret-key".into(),
|
||||||
|
peers: vec![peer],
|
||||||
|
..Self::new("", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Verbosity {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Quiet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn split_str(s: &str) -> Vec<String> {
|
||||||
|
s.split(" ").map(|s| s.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_cli_parse() {
|
||||||
|
let args = split_str(
|
||||||
|
"public-key /my/public-key secret-key /my/secret-key verbose \
|
||||||
|
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
|
||||||
|
peer.test:9999 outfile /peer/rp-out",
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = Rosenpass::parse_args(args).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||||
|
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||||
|
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||||
|
assert_eq!(
|
||||||
|
&config.listen,
|
||||||
|
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.peers,
|
||||||
|
vec![RosenpassPeer {
|
||||||
|
public_key: PathBuf::from("/peer/public-key"),
|
||||||
|
endpoint: Some("peer.test:9999".into()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
key_out: Some(PathBuf::from("/peer/rp-out")),
|
||||||
|
..Default::default()
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cli_parse_multiple_peers() {
|
||||||
|
let args = split_str(
|
||||||
|
"public-key /my/public-key secret-key /my/secret-key verbose \
|
||||||
|
peer public-key /peer-a/public-key endpoint \
|
||||||
|
peer.test:9999 outfile /peer-a/rp-out \
|
||||||
|
peer public-key /peer-b/public-key outfile /peer-b/rp-out",
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = Rosenpass::parse_args(args).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.public_key, PathBuf::from("/my/public-key"));
|
||||||
|
assert_eq!(config.secret_key, PathBuf::from("/my/secret-key"));
|
||||||
|
assert_eq!(config.verbosity, Verbosity::Verbose);
|
||||||
|
assert!(&config.listen.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
config.peers,
|
||||||
|
vec![
|
||||||
|
RosenpassPeer {
|
||||||
|
public_key: PathBuf::from("/peer-a/public-key"),
|
||||||
|
endpoint: Some("peer.test:9999".into()),
|
||||||
|
pre_shared_key: None,
|
||||||
|
key_out: Some(PathBuf::from("/peer-a/rp-out")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
RosenpassPeer {
|
||||||
|
public_key: PathBuf::from("/peer-b/public-key"),
|
||||||
|
endpoint: None,
|
||||||
|
pre_shared_key: None,
|
||||||
|
key_out: Some(PathBuf::from("/peer-b/rp-out")),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@ pub mod sodium;
|
|||||||
pub mod coloring;
|
pub mod coloring;
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub mod labeled_prf;
|
pub mod labeled_prf;
|
||||||
|
pub mod app_server;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod config;
|
||||||
pub mod msgs;
|
pub mod msgs;
|
||||||
pub mod pqkem;
|
pub mod pqkem;
|
||||||
pub mod prftree;
|
pub mod prftree;
|
||||||
|
|||||||
657
src/main.rs
657
src/main.rs
@@ -1,261 +1,11 @@
|
|||||||
use anyhow::{bail, ensure, Context, Result};
|
use log::error;
|
||||||
use log::{error, info};
|
use rosenpass::{cli::Cli, sodium::sodium_init};
|
||||||
use rosenpass::{
|
use std::process::exit;
|
||||||
attempt,
|
|
||||||
coloring::{Public, Secret},
|
|
||||||
pqkem::{StaticKEM, KEM},
|
|
||||||
protocol::{CryptoServer, MsgBuf, PeerPtr, SPk, SSk, SymKey, Timing},
|
|
||||||
sodium::sodium_init,
|
|
||||||
util::{b64_reader, b64_writer, fmt_b64},
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
fs::{File, OpenOptions},
|
|
||||||
io::{ErrorKind, Read, Write},
|
|
||||||
net::{SocketAddr, ToSocketAddrs, UdpSocket},
|
|
||||||
path::Path,
|
|
||||||
process::{exit, Command, Stdio},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Open a file writable
|
|
||||||
pub fn fopen_w<P: AsRef<Path>>(path: P) -> Result<File> {
|
|
||||||
Ok(OpenOptions::new()
|
|
||||||
.read(false)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(path)?)
|
|
||||||
}
|
|
||||||
/// Open a file readable
|
|
||||||
pub fn fopen_r<P: AsRef<Path>>(path: P) -> Result<File> {
|
|
||||||
Ok(OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(false)
|
|
||||||
.create(false)
|
|
||||||
.truncate(false)
|
|
||||||
.open(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ReadExactToEnd {
|
|
||||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> ReadExactToEnd for R {
|
|
||||||
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> {
|
|
||||||
let mut dummy = [0u8; 8];
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
ensure!(self.read(&mut dummy)? == 0, "File too long!");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LoadValue {
|
|
||||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LoadValueB64 {
|
|
||||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
trait StoreValue {
|
|
||||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
trait StoreSecret {
|
|
||||||
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: StoreValue> StoreSecret for T {
|
|
||||||
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
||||||
self.store(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> LoadValue for Secret<N> {
|
|
||||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
let mut v = Self::random();
|
|
||||||
let p = path.as_ref();
|
|
||||||
fopen_r(p)?
|
|
||||||
.read_exact_to_end(v.secret_mut())
|
|
||||||
.with_context(|| format!("Could not load file {p:?}"))?;
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> LoadValueB64 for Secret<N> {
|
|
||||||
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
let mut v = Self::random();
|
|
||||||
let p = path.as_ref();
|
|
||||||
// This might leave some fragments of the secret on the stack;
|
|
||||||
// in practice this is likely not a problem because the stack likely
|
|
||||||
// will be overwritten by something else soon but this is not exactly
|
|
||||||
// guaranteed. It would be possible to remedy this, but since the secret
|
|
||||||
// data will linger in the linux page cache anyways with the current
|
|
||||||
// implementation, going to great length to erase the secret here is
|
|
||||||
// not worth it right now.
|
|
||||||
b64_reader(&mut fopen_r(p)?)
|
|
||||||
.read_exact(v.secret_mut())
|
|
||||||
.with_context(|| format!("Could not load base64 file {p:?}"))?;
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> StoreSecret for Secret<N> {
|
|
||||||
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
||||||
std::fs::write(path, self.secret())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> LoadValue for Public<N> {
|
|
||||||
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
let mut v = Self::random();
|
|
||||||
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> StoreValue for Public<N> {
|
|
||||||
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
||||||
std::fs::write(path, **self)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! bail_usage {
|
|
||||||
($args:expr, $($pt:expr),*) => {{
|
|
||||||
error!($($pt),*);
|
|
||||||
cmd_help()?;
|
|
||||||
exit(1);
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! ensure_usage {
|
|
||||||
($args:expr, $ck:expr, $($pt:expr),*) => {{
|
|
||||||
if !$ck {
|
|
||||||
bail_usage!($args, $($pt),*);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! mandatory_opt {
|
|
||||||
($args:expr, $val:expr, $name:expr) => {{
|
|
||||||
ensure_usage!($args, $val.is_some(), "{0} option is mandatory", $name)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArgsWalker {
|
|
||||||
pub argv: Vec<String>,
|
|
||||||
pub off: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgsWalker {
|
|
||||||
pub fn get(&self) -> Option<&str> {
|
|
||||||
self.argv.get(self.off).map(|s| s as &str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev(&mut self) -> Option<&str> {
|
|
||||||
assert!(self.off > 0);
|
|
||||||
self.off -= 1;
|
|
||||||
self.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn next(&mut self) -> Option<&str> {
|
|
||||||
assert!(self.todo() > 0);
|
|
||||||
self.off += 1;
|
|
||||||
self.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opt(&mut self, dst: &mut Option<String>) -> Result<()> {
|
|
||||||
let cmd = &self.argv[self.off - 1];
|
|
||||||
ensure_usage!(&self, self.todo() > 0, "Option {} takes a value", cmd);
|
|
||||||
ensure_usage!(&self, dst.is_none(), "Cannot set {} multiple times.", cmd);
|
|
||||||
*dst = Some(String::from(self.next().unwrap()));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn todo(&self) -> usize {
|
|
||||||
self.argv.len() - self.off
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct WireguardOut {
|
|
||||||
// impl KeyOutput
|
|
||||||
dev: String,
|
|
||||||
pk: String,
|
|
||||||
extra_params: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct AppPeer {
|
|
||||||
pub outfile: Option<String>,
|
|
||||||
pub outwg: Option<WireguardOut>,
|
|
||||||
pub tx_addr: Option<SocketAddr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Verbosity {
|
|
||||||
Quiet,
|
|
||||||
Verbose,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds the state of the application, namely the external IO
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AppServer {
|
|
||||||
pub crypt: CryptoServer,
|
|
||||||
pub sock: UdpSocket,
|
|
||||||
pub peers: Vec<AppPeer>,
|
|
||||||
pub verbosity: Verbosity,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Index based pointer to a Peer
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AppPeerPtr(pub usize);
|
|
||||||
|
|
||||||
impl AppPeerPtr {
|
|
||||||
/// Takes an index based handle and returns the actual peer
|
|
||||||
pub fn lift(p: PeerPtr) -> Self {
|
|
||||||
Self(p.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an index based handle to one Peer
|
|
||||||
pub fn lower(&self) -> PeerPtr {
|
|
||||||
PeerPtr(self.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
|
|
||||||
&srv.peers[self.0]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
|
|
||||||
&mut srv.peers[self.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AppPollResult {
|
|
||||||
DeleteKey(AppPeerPtr),
|
|
||||||
SendInitiation(AppPeerPtr),
|
|
||||||
SendRetransmission(AppPeerPtr),
|
|
||||||
ReceivedMessage(usize, SocketAddr),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum KeyOutputReason {
|
|
||||||
Exchanged,
|
|
||||||
Stale,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Catches errors, prints them through the logger, then exits
|
/// Catches errors, prints them through the logger, then exits
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
match rosenpass_main() {
|
match sodium_init().and_then(|()| Cli::run()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{e}");
|
error!("{e}");
|
||||||
@@ -263,402 +13,3 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entry point to the whole program
|
|
||||||
pub fn rosenpass_main() -> Result<()> {
|
|
||||||
sodium_init()?;
|
|
||||||
|
|
||||||
let mut args = ArgsWalker {
|
|
||||||
argv: std::env::args().collect(),
|
|
||||||
off: 0, // skipping executable path
|
|
||||||
};
|
|
||||||
|
|
||||||
// Command parsing
|
|
||||||
match args.next() {
|
|
||||||
Some("help") | Some("-h") | Some("-help") | Some("--help") => cmd_help()?,
|
|
||||||
Some("keygen") => cmd_keygen(args)?,
|
|
||||||
Some("exchange") => cmd_exchange(args)?,
|
|
||||||
Some(cmd) => bail_usage!(&args, "No such command {}", cmd),
|
|
||||||
None => bail_usage!(&args, "Expected a command!"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print the usage information
|
|
||||||
pub fn cmd_help() -> Result<()> {
|
|
||||||
let man_cmd = Command::new("man").args(["1", "rosenpass"]).status();
|
|
||||||
if man_cmd.is_ok() && man_cmd.unwrap().success() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the compiled manual
|
|
||||||
eprint!(include_str!(env!("ROSENPASS_MAN")));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a keypair
|
|
||||||
pub fn cmd_keygen(mut args: ArgsWalker) -> Result<()> {
|
|
||||||
let mut sf: Option<String> = None;
|
|
||||||
let mut pf: Option<String> = None;
|
|
||||||
|
|
||||||
// Arg parsing
|
|
||||||
loop {
|
|
||||||
match args.next() {
|
|
||||||
Some("private-key") => args.opt(&mut sf)?,
|
|
||||||
Some("public-key") => args.opt(&mut pf)?,
|
|
||||||
Some(opt) => bail_usage!(&args, "Unknown option `{}`", opt),
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mandatory_opt!(&args, sf, "private-key");
|
|
||||||
mandatory_opt!(&args, pf, "private-key");
|
|
||||||
|
|
||||||
// Cmd
|
|
||||||
let (mut ssk, mut spk) = (SSk::random(), SPk::random());
|
|
||||||
unsafe {
|
|
||||||
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
|
|
||||||
ssk.store_secret(sf.unwrap())?;
|
|
||||||
spk.store_secret(pf.unwrap())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_exchange(mut args: ArgsWalker) -> Result<()> {
|
|
||||||
// Argument parsing
|
|
||||||
let mut sf: Option<String> = None;
|
|
||||||
let mut pf: Option<String> = None;
|
|
||||||
let mut listen: Option<String> = None;
|
|
||||||
let mut verbosity = Verbosity::Quiet;
|
|
||||||
|
|
||||||
// Global parameters
|
|
||||||
loop {
|
|
||||||
match args.next() {
|
|
||||||
Some("private-key") => args.opt(&mut sf)?,
|
|
||||||
Some("public-key") => args.opt(&mut pf)?,
|
|
||||||
Some("listen") => args.opt(&mut listen)?,
|
|
||||||
Some("verbose") => {
|
|
||||||
verbosity = Verbosity::Verbose;
|
|
||||||
}
|
|
||||||
Some("peer") => {
|
|
||||||
args.prev();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Some(opt) => bail_usage!(&args, "Unknown option `{}`", opt),
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mandatory_opt!(&args, sf, "private-key");
|
|
||||||
mandatory_opt!(&args, pf, "public-key");
|
|
||||||
|
|
||||||
let mut srv = std::boxed::Box::<AppServer>::new(AppServer::new(
|
|
||||||
// sk, pk, addr
|
|
||||||
SSk::load(&sf.unwrap())?,
|
|
||||||
SPk::load(&pf.unwrap())?,
|
|
||||||
listen.as_deref().unwrap_or("[0::0]:0"),
|
|
||||||
verbosity,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
// Peer parameters
|
|
||||||
'_parseAllPeers: while args.todo() > 0 {
|
|
||||||
let mut pf: Option<String> = None;
|
|
||||||
let mut outfile: Option<String> = None;
|
|
||||||
let mut outwg: Option<WireguardOut> = None;
|
|
||||||
let mut endpoint: Option<String> = None;
|
|
||||||
let mut pskf: Option<String> = None;
|
|
||||||
|
|
||||||
args.next(); // skip "peer" starter itself
|
|
||||||
|
|
||||||
'parseOnePeer: loop {
|
|
||||||
match args.next() {
|
|
||||||
// Done with this peer
|
|
||||||
Some("peer") => {
|
|
||||||
args.prev();
|
|
||||||
break 'parseOnePeer;
|
|
||||||
}
|
|
||||||
None => break 'parseOnePeer,
|
|
||||||
// Options
|
|
||||||
Some("public-key") => args.opt(&mut pf)?,
|
|
||||||
Some("endpoint") => args.opt(&mut endpoint)?,
|
|
||||||
Some("preshared-key") => args.opt(&mut pskf)?,
|
|
||||||
Some("outfile") => args.opt(&mut outfile)?,
|
|
||||||
// Wireguard out
|
|
||||||
Some("wireguard") => {
|
|
||||||
ensure_usage!(
|
|
||||||
&args,
|
|
||||||
outwg.is_none(),
|
|
||||||
"Cannot set wireguard output for the same peer multiple times."
|
|
||||||
);
|
|
||||||
ensure_usage!(&args, args.todo() >= 2, "Option wireguard takes to values");
|
|
||||||
let dev = String::from(args.next().unwrap());
|
|
||||||
let pk = String::from(args.next().unwrap());
|
|
||||||
let wg = outwg.insert(WireguardOut {
|
|
||||||
dev,
|
|
||||||
pk,
|
|
||||||
extra_params: Vec::new(),
|
|
||||||
});
|
|
||||||
'_parseWgOutExtra: loop {
|
|
||||||
match args.next() {
|
|
||||||
Some("peer") => {
|
|
||||||
args.prev();
|
|
||||||
break 'parseOnePeer;
|
|
||||||
}
|
|
||||||
None => break 'parseOnePeer,
|
|
||||||
Some(xtra) => wg.extra_params.push(xtra.to_string()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Invalid
|
|
||||||
Some(opt) => bail_usage!(&args, "Unknown peer option `{}`", opt),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mandatory_opt!(&args, pf, "private-key");
|
|
||||||
ensure_usage!(
|
|
||||||
&args,
|
|
||||||
outfile.is_some() || outwg.is_some(),
|
|
||||||
"Either of the outfile or wireguard option is mandatory"
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx_addr = endpoint
|
|
||||||
.map(|e| {
|
|
||||||
e.to_socket_addrs()?
|
|
||||||
.next()
|
|
||||||
.context("Expected address in endpoint parameter")
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
srv.add_peer(
|
|
||||||
// psk, pk, outfile, outwg, tx_addr
|
|
||||||
pskf.map(SymKey::load_b64).transpose()?,
|
|
||||||
SPk::load(&pf.unwrap())?,
|
|
||||||
outfile,
|
|
||||||
outwg,
|
|
||||||
tx_addr,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.listen_loop()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppServer {
|
|
||||||
pub fn new<A: ToSocketAddrs>(sk: SSk, pk: SPk, addr: A, verbosity: Verbosity) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
crypt: CryptoServer::new(sk, pk),
|
|
||||||
sock: UdpSocket::bind(addr)?,
|
|
||||||
peers: Vec::new(),
|
|
||||||
verbosity,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verbose(&self) -> bool {
|
|
||||||
matches!(self.verbosity, Verbosity::Verbose)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_peer(
|
|
||||||
&mut self,
|
|
||||||
psk: Option<SymKey>,
|
|
||||||
pk: SPk,
|
|
||||||
outfile: Option<String>,
|
|
||||||
outwg: Option<WireguardOut>,
|
|
||||||
tx_addr: Option<SocketAddr>,
|
|
||||||
) -> Result<AppPeerPtr> {
|
|
||||||
let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?;
|
|
||||||
assert!(pn == self.peers.len());
|
|
||||||
self.peers.push(AppPeer {
|
|
||||||
outfile,
|
|
||||||
outwg,
|
|
||||||
tx_addr,
|
|
||||||
});
|
|
||||||
Ok(AppPeerPtr(pn))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn listen_loop(&mut self) -> Result<()> {
|
|
||||||
const INIT_SLEEP: f64 = 0.01;
|
|
||||||
const MAX_FAILURES: i32 = 10;
|
|
||||||
let mut failure_cnt = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let msgs_processed = 0usize;
|
|
||||||
let err = match self.event_loop() {
|
|
||||||
Ok(()) => return Ok(()),
|
|
||||||
Err(e) => e,
|
|
||||||
};
|
|
||||||
|
|
||||||
// This should not happen…
|
|
||||||
failure_cnt = if msgs_processed > 0 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
failure_cnt + 1
|
|
||||||
};
|
|
||||||
let sleep = INIT_SLEEP * 2.0f64.powf(f64::from(failure_cnt - 1));
|
|
||||||
let tries_left = MAX_FAILURES - (failure_cnt - 1);
|
|
||||||
error!(
|
|
||||||
"unexpected error after processing {} messages: {:?} {}",
|
|
||||||
msgs_processed,
|
|
||||||
err,
|
|
||||||
err.backtrace()
|
|
||||||
);
|
|
||||||
if tries_left > 0 {
|
|
||||||
error!("reinitializing networking in {sleep}! {tries_left} tries left.");
|
|
||||||
std::thread::sleep(self.crypt.timebase.dur(sleep));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bail!("too many network failures");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_loop(&mut self) -> Result<()> {
|
|
||||||
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
|
|
||||||
|
|
||||||
/// if socket address for peer is known, call closure
|
|
||||||
/// assumes that closure leaves a message in `tx`
|
|
||||||
/// assumes that closure returns the length of message in bytes
|
|
||||||
macro_rules! tx_maybe_with {
|
|
||||||
($peer:expr, $fn:expr) => {
|
|
||||||
attempt!({
|
|
||||||
let p = $peer.get_app(self);
|
|
||||||
if let Some(addr) = p.tx_addr {
|
|
||||||
let len = $fn()?;
|
|
||||||
self.sock.send_to(&tx[..len], addr)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
use rosenpass::protocol::HandleMsgResult;
|
|
||||||
use AppPollResult::*;
|
|
||||||
use KeyOutputReason::*;
|
|
||||||
match self.poll(&mut *rx)? {
|
|
||||||
SendInitiation(peer) => tx_maybe_with!(peer, || self
|
|
||||||
.crypt
|
|
||||||
.initiate_handshake(peer.lower(), &mut *tx))?,
|
|
||||||
SendRetransmission(peer) => tx_maybe_with!(peer, || self
|
|
||||||
.crypt
|
|
||||||
.retransmit_handshake(peer.lower(), &mut *tx))?,
|
|
||||||
DeleteKey(peer) => self.output_key(peer, Stale, &SymKey::random())?,
|
|
||||||
|
|
||||||
ReceivedMessage(len, addr) => {
|
|
||||||
match self.crypt.handle_msg(&rx[..len], &mut *tx) {
|
|
||||||
Err(ref e) => {
|
|
||||||
self.verbose().then(|| {
|
|
||||||
info!(
|
|
||||||
"error processing incoming message from {:?}: {:?} {}",
|
|
||||||
addr,
|
|
||||||
e,
|
|
||||||
e.backtrace()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HandleMsgResult {
|
|
||||||
resp,
|
|
||||||
exchanged_with,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
if let Some(len) = resp {
|
|
||||||
self.sock.send_to(&tx[0..len], addr)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(p) = exchanged_with {
|
|
||||||
let ap = AppPeerPtr::lift(p);
|
|
||||||
ap.get_app_mut(self).tx_addr = Some(addr);
|
|
||||||
|
|
||||||
// TODO: Maybe we should rather call the key "rosenpass output"?
|
|
||||||
self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_key(&self, peer: AppPeerPtr, why: KeyOutputReason, key: &SymKey) -> Result<()> {
|
|
||||||
let peerid = peer.lower().get(&self.crypt).pidt()?;
|
|
||||||
let ap = peer.get_app(self);
|
|
||||||
|
|
||||||
if self.verbose() {
|
|
||||||
let msg = match why {
|
|
||||||
KeyOutputReason::Exchanged => "Exchanged key with peer",
|
|
||||||
KeyOutputReason::Stale => "Erasing outdated key from peer",
|
|
||||||
};
|
|
||||||
info!("{} {}", msg, fmt_b64(&*peerid));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(of) = ap.outfile.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_writer(fopen_w(of)?).write_all(key.secret())?;
|
|
||||||
let why = match why {
|
|
||||||
KeyOutputReason::Exchanged => "exchanged",
|
|
||||||
KeyOutputReason::Stale => "stale",
|
|
||||||
};
|
|
||||||
println!(
|
|
||||||
"output-key peer {} key-file {} {}",
|
|
||||||
fmt_b64(&*peerid),
|
|
||||||
of,
|
|
||||||
why
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(owg) = ap.outwg.as_ref() {
|
|
||||||
let child = Command::new("wg")
|
|
||||||
.arg("set")
|
|
||||||
.arg(&owg.dev)
|
|
||||||
.arg("peer")
|
|
||||||
.arg(&owg.pk)
|
|
||||||
.arg("preshared-key")
|
|
||||||
.arg("/dev/stdin")
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.args(&owg.extra_params)
|
|
||||||
.spawn()?;
|
|
||||||
b64_writer(child.stdin.unwrap()).write_all(key.secret())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll(&mut self, rx_buf: &mut [u8]) -> Result<AppPollResult> {
|
|
||||||
use rosenpass::protocol::PollResult as C;
|
|
||||||
use AppPollResult as A;
|
|
||||||
loop {
|
|
||||||
return Ok(match self.crypt.poll()? {
|
|
||||||
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
|
|
||||||
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
|
|
||||||
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
|
|
||||||
C::Sleep(timeout) => match self.try_recv(rx_buf, timeout)? {
|
|
||||||
Some((len, addr)) => A::ReceivedMessage(len, addr),
|
|
||||||
None => continue,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_recv(&self, buf: &mut [u8], timeout: Timing) -> Result<Option<(usize, SocketAddr)>> {
|
|
||||||
if timeout == 0.0 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
self.sock
|
|
||||||
.set_read_timeout(Some(Duration::from_secs_f64(timeout)))?;
|
|
||||||
match self.sock.recv_from(buf) {
|
|
||||||
Ok(x) => Ok(Some(x)),
|
|
||||||
Err(e) => match e.kind() {
|
|
||||||
ErrorKind::WouldBlock => Ok(None),
|
|
||||||
ErrorKind::TimedOut => Ok(None),
|
|
||||||
_ => Err(anyhow::Error::new(e)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
//! // always init libsodium before anything
|
//! // always init libsodium before anything
|
||||||
//! rosenpass::sodium::sodium_init().unwrap();
|
//! rosenpass::sodium::sodium_init().unwrap();
|
||||||
//!
|
//!
|
||||||
//! // initialize public and private key for peer a ...
|
//! // initialize secret and public key for peer a ...
|
||||||
//! let (mut peer_a_sk, mut peer_a_pk) = (SSk::zero(), SPk::zero());
|
//! 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())?;
|
||||||
//!
|
//!
|
||||||
@@ -249,18 +249,13 @@ impl HandshakeRole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
#[derive(Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
pub enum HandshakeStateMachine {
|
pub enum HandshakeStateMachine {
|
||||||
|
#[default]
|
||||||
RespHello,
|
RespHello,
|
||||||
RespConf,
|
RespConf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HandshakeStateMachine {
|
|
||||||
fn default() -> Self {
|
|
||||||
HandshakeStateMachine::RespHello
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InitiatorHandshake {
|
pub struct InitiatorHandshake {
|
||||||
pub created_at: Timing,
|
pub created_at: Timing,
|
||||||
@@ -1704,7 +1699,7 @@ mod test {
|
|||||||
// always init libsodium before anything
|
// always init libsodium before anything
|
||||||
crate::sodium::sodium_init().unwrap();
|
crate::sodium::sodium_init().unwrap();
|
||||||
|
|
||||||
// initialize public and private key for the crypto server
|
// initialize secret and public key for the crypto server
|
||||||
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
|
||||||
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
|
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
|
||||||
|
|
||||||
|
|||||||
48
src/usage.md
48
src/usage.md
@@ -1,48 +0,0 @@
|
|||||||
NAME
|
|
||||||
|
|
||||||
{0} – Perform post-quantum secure key exchanges for wireguard and other services.
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
|
|
||||||
{0} [ COMMAND ] [ OPTIONS ]... [ ARGS ]...
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
{0} performs cryptographic key exchanges that are secure against quantum-computers and outputs the keys.
|
|
||||||
These keys can then be passed to various services such as wireguard or other vpn services
|
|
||||||
as pre-shared-keys to achieve security against attackers with quantum computers.
|
|
||||||
|
|
||||||
COMMANDS
|
|
||||||
|
|
||||||
keygen private-key <file-path> public-key <file-path>
|
|
||||||
Generate a keypair to use in the exchange command later. Send the public-key file to your communication partner
|
|
||||||
and keep the private-key file a secret!
|
|
||||||
|
|
||||||
exchange private-key <file-path> public-key <file-path> [ OPTIONS ]... PEER...\n"
|
|
||||||
Start a process to exchange keys with the specified peers. You should specify at least one peer.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
listen <ip>[:<port>]
|
|
||||||
Instructs {0} to listen on the specified interface and port. By default {0} will listen on all interfaces and select a random port.
|
|
||||||
|
|
||||||
verbose
|
|
||||||
Extra logging
|
|
||||||
|
|
||||||
PEER := peer public-key <file-path> [endpoint <ip>[:<port>]] [preshared-key <file-path>] [outfile <file-path>] [wireguard <dev> <peer> <extra_params>]
|
|
||||||
Instructs {0} to exchange keys with the given peer and write the resulting PSK into the given output file.
|
|
||||||
You must either specify the outfile or wireguard output option.
|
|
||||||
|
|
||||||
endpoint <ip>[:<port>]
|
|
||||||
Specifies the address where the peer can be reached. This will be automatically updated after the first successful
|
|
||||||
key exchange with the peer. If this is unspecified, the peer must initiate the connection.
|
|
||||||
|
|
||||||
preshared-key <file-path>
|
|
||||||
You may specify a pre-shared key which will be mixed into the final secret.
|
|
||||||
|
|
||||||
outfile <file-path>
|
|
||||||
You may specify a file to write the exchanged keys to. If this option is specified, {0} will
|
|
||||||
write a notification to standard out every time the key is updated.
|
|
||||||
|
|
||||||
wireguard <dev> <peer> <extra_params>
|
|
||||||
This allows you to directly specify a wireguard peer to deploy the pre-shared-key to.
|
|
||||||
You may specify extra parameters you would pass to `wg set` besides the preshared-key parameter which is used by {0}.
|
|
||||||
This makes it possible to add peers entirely from {0}.
|
|
||||||
117
src/util.rs
117
src/util.rs
@@ -1,5 +1,5 @@
|
|||||||
//! Helper functions and macros
|
//! Helper functions and macros
|
||||||
|
use anyhow::{ensure, Context, Result};
|
||||||
use base64::{
|
use base64::{
|
||||||
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
|
display::Base64Display as B64Display, read::DecoderReader as B64Reader,
|
||||||
write::EncoderWriter as B64Writer,
|
write::EncoderWriter as B64Writer,
|
||||||
@@ -7,10 +7,14 @@ use base64::{
|
|||||||
use std::{
|
use std::{
|
||||||
borrow::{Borrow, BorrowMut},
|
borrow::{Borrow, BorrowMut},
|
||||||
cmp::min,
|
cmp::min,
|
||||||
|
fs::{File, OpenOptions},
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
|
path::Path,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::coloring::{Public, Secret};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn xor_into(a: &mut [u8], b: &[u8]) {
|
pub fn xor_into(a: &mut [u8], b: &[u8]) {
|
||||||
assert!(a.len() == b.len());
|
assert!(a.len() == b.len());
|
||||||
@@ -115,3 +119,114 @@ where
|
|||||||
f(&v);
|
f(&v);
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// load'n store
|
||||||
|
|
||||||
|
/// Open a file writable
|
||||||
|
pub fn fopen_w<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
Ok(OpenOptions::new()
|
||||||
|
.read(false)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(path)?)
|
||||||
|
}
|
||||||
|
/// Open a file readable
|
||||||
|
pub fn fopen_r<P: AsRef<Path>>(path: P) -> Result<File> {
|
||||||
|
Ok(OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(false)
|
||||||
|
.create(false)
|
||||||
|
.truncate(false)
|
||||||
|
.open(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReadExactToEnd {
|
||||||
|
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> ReadExactToEnd for R {
|
||||||
|
fn read_exact_to_end(&mut self, buf: &mut [u8]) -> Result<()> {
|
||||||
|
let mut dummy = [0u8; 8];
|
||||||
|
self.read_exact(buf)?;
|
||||||
|
ensure!(self.read(&mut dummy)? == 0, "File too long!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LoadValue {
|
||||||
|
fn load<P: AsRef<Path>>(path: P) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait LoadValueB64 {
|
||||||
|
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait StoreValue {
|
||||||
|
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait StoreSecret {
|
||||||
|
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: StoreValue> StoreSecret for T {
|
||||||
|
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
self.store(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValue for Secret<N> {
|
||||||
|
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let mut v = Self::random();
|
||||||
|
let p = path.as_ref();
|
||||||
|
fopen_r(p)?
|
||||||
|
.read_exact_to_end(v.secret_mut())
|
||||||
|
.with_context(|| format!("Could not load file {p:?}"))?;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValueB64 for Secret<N> {
|
||||||
|
fn load_b64<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let mut v = Self::random();
|
||||||
|
let p = path.as_ref();
|
||||||
|
// This might leave some fragments of the secret on the stack;
|
||||||
|
// in practice this is likely not a problem because the stack likely
|
||||||
|
// will be overwritten by something else soon but this is not exactly
|
||||||
|
// guaranteed. It would be possible to remedy this, but since the secret
|
||||||
|
// data will linger in the linux page cache anyways with the current
|
||||||
|
// implementation, going to great length to erase the secret here is
|
||||||
|
// not worth it right now.
|
||||||
|
b64_reader(&mut fopen_r(p)?)
|
||||||
|
.read_exact(v.secret_mut())
|
||||||
|
.with_context(|| format!("Could not load base64 file {p:?}"))?;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreSecret for Secret<N> {
|
||||||
|
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
std::fs::write(path, self.secret())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> LoadValue for Public<N> {
|
||||||
|
fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let mut v = Self::random();
|
||||||
|
fopen_r(path)?.read_exact_to_end(&mut *v)?;
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> StoreValue for Public<N> {
|
||||||
|
fn store<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
std::fs::write(path, **self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ fn generate_keys() {
|
|||||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
|
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen");
|
||||||
fs::create_dir_all(&tmpdir).unwrap();
|
fs::create_dir_all(&tmpdir).unwrap();
|
||||||
|
|
||||||
let priv_key_path = tmpdir.join("private-key");
|
let secret_key_path = tmpdir.join("secret-key");
|
||||||
let pub_key_path = tmpdir.join("public-key");
|
let public_key_path = tmpdir.join("public-key");
|
||||||
|
|
||||||
let output = test_bin::get_test_bin(BIN)
|
let output = test_bin::get_test_bin(BIN)
|
||||||
.args(["keygen", "private-key"])
|
.args(["gen-keys", "--secret-key"])
|
||||||
.arg(&priv_key_path)
|
.arg(&secret_key_path)
|
||||||
.arg("public-key")
|
.arg("--public-key")
|
||||||
.arg(&pub_key_path)
|
.arg(&public_key_path)
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to start {BIN}");
|
.expect("Failed to start {BIN}");
|
||||||
|
|
||||||
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
|
||||||
|
|
||||||
assert!(priv_key_path.is_file());
|
assert!(secret_key_path.is_file());
|
||||||
assert!(pub_key_path.is_file());
|
assert!(public_key_path.is_file());
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
fs::remove_dir_all(&tmpdir).unwrap();
|
fs::remove_dir_all(&tmpdir).unwrap();
|
||||||
@@ -46,22 +46,22 @@ fn check_exchange() {
|
|||||||
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange");
|
||||||
fs::create_dir_all(&tmpdir).unwrap();
|
fs::create_dir_all(&tmpdir).unwrap();
|
||||||
|
|
||||||
let priv_key_paths = [tmpdir.join("private-key-0"), tmpdir.join("private-key-1")];
|
let secret_key_paths = [tmpdir.join("secret-key-0"), tmpdir.join("secret-key-1")];
|
||||||
let pub_key_paths = [tmpdir.join("public-key-0"), tmpdir.join("public-key-1")];
|
let public_key_paths = [tmpdir.join("public-key-0"), tmpdir.join("public-key-1")];
|
||||||
let shared_key_paths = [tmpdir.join("shared-key-0"), tmpdir.join("shared-key-1")];
|
let shared_key_paths = [tmpdir.join("shared-key-0"), tmpdir.join("shared-key-1")];
|
||||||
|
|
||||||
// generate key pairs
|
// generate key pairs
|
||||||
for (priv_key_path, pub_key_path) in priv_key_paths.iter().zip(pub_key_paths.iter()) {
|
for (secret_key_path, pub_key_path) in secret_key_paths.iter().zip(public_key_paths.iter()) {
|
||||||
let output = test_bin::get_test_bin(BIN)
|
let output = test_bin::get_test_bin(BIN)
|
||||||
.args(["keygen", "private-key"])
|
.args(["gen-keys", "--secret-key"])
|
||||||
.arg(&priv_key_path)
|
.arg(&secret_key_path)
|
||||||
.arg("public-key")
|
.arg("--public-key")
|
||||||
.arg(&pub_key_path)
|
.arg(&pub_key_path)
|
||||||
.output()
|
.output()
|
||||||
.expect("Failed to start {BIN}");
|
.expect("Failed to start {BIN}");
|
||||||
|
|
||||||
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
|
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
|
||||||
assert!(priv_key_path.is_file());
|
assert!(secret_key_path.is_file());
|
||||||
assert!(pub_key_path.is_file());
|
assert!(pub_key_path.is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,12 +69,12 @@ fn check_exchange() {
|
|||||||
let port = find_udp_socket();
|
let port = find_udp_socket();
|
||||||
let listen_addr = format!("localhost:{port}");
|
let listen_addr = format!("localhost:{port}");
|
||||||
let mut server = test_bin::get_test_bin(BIN)
|
let mut server = test_bin::get_test_bin(BIN)
|
||||||
.args(["exchange", "private-key"])
|
.args(["exchange", "secret-key"])
|
||||||
.arg(&priv_key_paths[0])
|
.arg(&secret_key_paths[0])
|
||||||
.arg("public-key")
|
.arg("public-key")
|
||||||
.arg(&pub_key_paths[0])
|
.arg(&public_key_paths[0])
|
||||||
.args(["listen", &listen_addr, "verbose", "peer", "public-key"])
|
.args(["listen", &listen_addr, "verbose", "peer", "public-key"])
|
||||||
.arg(&pub_key_paths[1])
|
.arg(&public_key_paths[1])
|
||||||
.arg("outfile")
|
.arg("outfile")
|
||||||
.arg(&shared_key_paths[0])
|
.arg(&shared_key_paths[0])
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
@@ -82,14 +82,16 @@ fn check_exchange() {
|
|||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to start {BIN}");
|
.expect("Failed to start {BIN}");
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
|
||||||
// start second process, the client
|
// start second process, the client
|
||||||
let mut client = test_bin::get_test_bin(BIN)
|
let mut client = test_bin::get_test_bin(BIN)
|
||||||
.args(["exchange", "private-key"])
|
.args(["exchange", "secret-key"])
|
||||||
.arg(&priv_key_paths[1])
|
.arg(&secret_key_paths[1])
|
||||||
.arg("public-key")
|
.arg("public-key")
|
||||||
.arg(&pub_key_paths[1])
|
.arg(&public_key_paths[1])
|
||||||
.args(["verbose", "peer", "public-key"])
|
.args(["verbose", "peer", "public-key"])
|
||||||
.arg(&pub_key_paths[0])
|
.arg(&public_key_paths[0])
|
||||||
.args(["endpoint", &listen_addr])
|
.args(["endpoint", &listen_addr])
|
||||||
.arg("outfile")
|
.arg("outfile")
|
||||||
.arg(&shared_key_paths[1])
|
.arg(&shared_key_paths[1])
|
||||||
|
|||||||
Reference in New Issue
Block a user