feat(proto): add URL parsing and TLS certificate management
This commit is contained in:
340
server/Cargo.lock
generated
340
server/Cargo.lock
generated
@@ -59,14 +59,21 @@ version = "0.1.0"
|
||||
name = "arbiter-proto"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"futures",
|
||||
"hex",
|
||||
"kameo",
|
||||
"miette",
|
||||
"prost",
|
||||
"rand",
|
||||
"rcgen",
|
||||
"rstest",
|
||||
"rustls-pki-types",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-prost",
|
||||
"tonic-prost-build",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -88,6 +95,7 @@ dependencies = [
|
||||
"kameo",
|
||||
"memsafe",
|
||||
"miette",
|
||||
"pem",
|
||||
"rand",
|
||||
"rcgen",
|
||||
"restructed",
|
||||
@@ -109,6 +117,16 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "arbiter-useragent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arbiter-proto",
|
||||
"ed25519-dalek",
|
||||
"kameo",
|
||||
"smlang",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
@@ -859,6 +877,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
@@ -936,6 +963,12 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
@@ -1006,6 +1039,12 @@ version = "0.32.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
@@ -1055,12 +1094,6 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@@ -1195,6 +1228,87 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
@@ -1207,6 +1321,27 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
@@ -1342,6 +1477,12 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -1684,6 +1825,15 @@ version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -1700,6 +1850,15 @@ dependencies = [
|
||||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@@ -1900,6 +2059,12 @@ version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "restructed"
|
||||
version = "0.2.2"
|
||||
@@ -1936,6 +2101,35 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
|
||||
dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.115",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.27"
|
||||
@@ -2206,6 +2400,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "string_morph"
|
||||
version = "0.1.0"
|
||||
@@ -2419,6 +2619,16 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
@@ -2505,6 +2715,18 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.8+spec-1.1.0"
|
||||
@@ -2747,6 +2969,24 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.21.0"
|
||||
@@ -3138,6 +3378,9 @@ name = "winnow"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
@@ -3227,6 +3470,12 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "x25519-dalek"
|
||||
version = "2.0.1"
|
||||
@@ -3266,6 +3515,50 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.115",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.115",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
@@ -3286,6 +3579,39 @@ dependencies = [
|
||||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.115",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
||||
@@ -23,3 +23,12 @@ async-trait = "0.1.89"
|
||||
futures = "0.3.31"
|
||||
tokio-stream = { version = "0.1.18", features = ["full"] }
|
||||
kameo = "0.19.2"
|
||||
x25519-dalek = { version = "2.0.1", features = ["getrandom"] }
|
||||
rstest = "0.26.1"
|
||||
rustls-pki-types = "1.14.0"
|
||||
rcgen = { version = "0.14.7", features = [
|
||||
"aws_lc_rs",
|
||||
"pem",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
], default-features = false }
|
||||
|
||||
@@ -9,12 +9,22 @@ license = "Apache-2.0"
|
||||
tonic.workspace = true
|
||||
tokio.workspace = true
|
||||
futures.workspace = true
|
||||
hex = "0.4.3"
|
||||
tonic-prost = "0.14.3"
|
||||
prost = "0.14.3"
|
||||
kameo.workspace = true
|
||||
url = "2.5.8"
|
||||
miette.workspace = true
|
||||
thiserror.workspace = true
|
||||
rustls-pki-types.workspace = true
|
||||
base64 = "0.22.1"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
tonic-prost-build = "0.14.3"
|
||||
|
||||
[dev-dependencies]
|
||||
rstest.workspace = true
|
||||
rand.workspace = true
|
||||
rcgen.workspace = true
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
pub mod transport;
|
||||
pub mod url;
|
||||
|
||||
use base64::{Engine, prelude::BASE64_STANDARD};
|
||||
|
||||
use crate::proto::auth::AuthChallenge;
|
||||
|
||||
pub mod proto {
|
||||
@@ -8,9 +13,7 @@ pub mod proto {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod transport;
|
||||
|
||||
pub static BOOTSTRAP_TOKEN_PATH: &str = "bootstrap_token";
|
||||
pub static BOOTSTRAP_PATH: &str = "bootstrap_token";
|
||||
|
||||
pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
||||
static ARBITER_HOME: &str = ".arbiter";
|
||||
@@ -26,6 +29,6 @@ pub fn home_path() -> Result<std::path::PathBuf, std::io::Error> {
|
||||
}
|
||||
|
||||
pub fn format_challenge(challenge: &AuthChallenge) -> Vec<u8> {
|
||||
let concat_form = format!("{}:{}", challenge.nonce, hex::encode(&challenge.pubkey));
|
||||
let concat_form = format!("{}:{}", challenge.nonce, BASE64_STANDARD.encode(&challenge.pubkey));
|
||||
concat_form.into_bytes().to_vec()
|
||||
}
|
||||
|
||||
128
server/crates/arbiter-proto/src/url.rs
Normal file
128
server/crates/arbiter-proto/src/url.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use base64::{Engine as _, prelude::BASE64_URL_SAFE};
|
||||
use rustls_pki_types::CertificateDer;
|
||||
|
||||
const ARBITER_URL_SCHEME: &str = "arbiter";
|
||||
const CERT_QUERY_KEY: &str = "cert";
|
||||
const BOOTSTRAP_TOKEN_QUERY_KEY: &str = "bootstrap_token";
|
||||
|
||||
pub struct ArbiterUrl {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub ca_cert: CertificateDer<'static>,
|
||||
pub bootstrap_token: Option<String>,
|
||||
}
|
||||
|
||||
impl Display for ArbiterUrl {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut base = format!(
|
||||
"{ARBITER_URL_SCHEME}://{}:{}?{CERT_QUERY_KEY}={}",
|
||||
self.host,
|
||||
self.port,
|
||||
BASE64_URL_SAFE.encode(self.ca_cert.to_vec())
|
||||
);
|
||||
if let Some(token) = &self.bootstrap_token {
|
||||
base.push_str(&format!("&{BOOTSTRAP_TOKEN_QUERY_KEY}={}", token));
|
||||
}
|
||||
f.write_str(&base)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||
pub enum Error {
|
||||
#[error("Invalid URL scheme, expected '{ARBITER_URL_SCHEME}://'")]
|
||||
#[diagnostic(
|
||||
code(arbiter::url::invalid_scheme),
|
||||
help("The URL must start with '{ARBITER_URL_SCHEME}://'")
|
||||
)]
|
||||
InvalidScheme,
|
||||
#[error("Missing host in URL")]
|
||||
#[diagnostic(
|
||||
code(arbiter::url::missing_host),
|
||||
help("The URL must include a host, e.g., '{ARBITER_URL_SCHEME}://127.0.0.1:<port>'")
|
||||
)]
|
||||
MissingHost,
|
||||
#[error("Missing port in URL")]
|
||||
#[diagnostic(
|
||||
code(arbiter::url::missing_port),
|
||||
help("The URL must include a port, e.g., '{ARBITER_URL_SCHEME}://127.0.0.1:1234'")
|
||||
)]
|
||||
MissingPort,
|
||||
#[error("Missing 'cert' query parameter in URL")]
|
||||
#[diagnostic(
|
||||
code(arbiter::url::missing_cert),
|
||||
help("The URL must include a 'cert' query parameter")
|
||||
)]
|
||||
MissingCert,
|
||||
#[error("Invalid base64 in 'cert' query parameter: {0}")]
|
||||
#[diagnostic(code(arbiter::url::invalid_cert_base64))]
|
||||
InvalidCertBase64(#[from] base64::DecodeError),
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for ArbiterUrl {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let url = url::Url::parse(value).map_err(|_| Error::InvalidScheme)?;
|
||||
|
||||
if url.scheme() != ARBITER_URL_SCHEME {
|
||||
return Err(Error::InvalidScheme);
|
||||
}
|
||||
|
||||
let host = url.host_str().ok_or(Error::MissingHost)?.to_string();
|
||||
let port = url.port().ok_or(Error::MissingPort)?;
|
||||
let cert_str = url
|
||||
.query_pairs()
|
||||
.find(|(k, _)| k == CERT_QUERY_KEY)
|
||||
.ok_or(Error::MissingCert)?
|
||||
.1;
|
||||
|
||||
let cert = BASE64_URL_SAFE.decode(cert_str.as_ref())?;
|
||||
let cert = CertificateDer::from_slice(&cert).into_owned();
|
||||
|
||||
let bootstrap_token = url
|
||||
.query_pairs()
|
||||
.find(|(k, _)| k == BOOTSTRAP_TOKEN_QUERY_KEY)
|
||||
.map(|(_, v)| v.to_string());
|
||||
|
||||
Ok(ArbiterUrl {
|
||||
host,
|
||||
port,
|
||||
ca_cert: cert,
|
||||
bootstrap_token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rcgen::generate_simple_self_signed;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[rstest]
|
||||
|
||||
fn test_parsing_correctness(
|
||||
#[values("127.0.0.1", "localhost", "192.168.1.1", "some.domain.com")] host: &str,
|
||||
|
||||
#[values(None, Some("token123".to_string()))] bootstrap_token: Option<String>,
|
||||
) {
|
||||
let cert = generate_simple_self_signed(&["Arbiter CA".into()]).unwrap();
|
||||
let cert = cert.cert.der();
|
||||
|
||||
let url = ArbiterUrl {
|
||||
host: host.to_string(),
|
||||
port: 1234,
|
||||
ca_cert: cert.clone().into_owned(),
|
||||
bootstrap_token,
|
||||
};
|
||||
let url_str = url.to_string();
|
||||
let parsed_url = ArbiterUrl::try_from(url_str.as_str()).unwrap();
|
||||
assert_eq!(url.host, parsed_url.host);
|
||||
assert_eq!(url.port, parsed_url.port);
|
||||
assert_eq!(url.ca_cert.to_vec(), parsed_url.ca_cert.to_vec());
|
||||
assert_eq!(url.bootstrap_token, parsed_url.bootstrap_token);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ arbiter-proto.path = "../arbiter-proto"
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tonic.workspace = true
|
||||
tonic.features = ["tls-aws-lc"]
|
||||
tokio.workspace = true
|
||||
rustls.workspace = true
|
||||
smlang.workspace = true
|
||||
@@ -30,21 +31,17 @@ futures.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
dashmap = "6.1.0"
|
||||
rand.workspace = true
|
||||
rcgen = { version = "0.14.7", features = [
|
||||
"aws_lc_rs",
|
||||
"pem",
|
||||
"x509-parser",
|
||||
"zeroize",
|
||||
], default-features = false }
|
||||
rcgen.workspace = true
|
||||
chrono.workspace = true
|
||||
memsafe = "0.4.0"
|
||||
zeroize = { version = "1.8.2", features = ["std", "simd"] }
|
||||
kameo.workspace = true
|
||||
x25519-dalek = { version = "2.0.1", features = ["getrandom"] }
|
||||
x25519-dalek.workspace = true
|
||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||
argon2 = { version = "0.5.3", features = ["zeroize"] }
|
||||
restructed = "0.2.2"
|
||||
strum = { version = "0.27.2", features = ["derive"] }
|
||||
pem = "3.0.6"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.46.3"
|
||||
|
||||
@@ -24,14 +24,24 @@ create unique index if not exists uniq_nonce_per_root_key on aead_encrypted (
|
||||
associated_root_key_id
|
||||
);
|
||||
|
||||
create table if not exists tls_history (
|
||||
id INTEGER not null PRIMARY KEY,
|
||||
cert text not null,
|
||||
cert_key text not null, -- PEM Encoded private key
|
||||
ca_cert text not null,
|
||||
ca_key text not null, -- PEM Encoded private key
|
||||
created_at integer not null default(unixepoch ('now'))
|
||||
) STRICT;
|
||||
|
||||
-- This is a singleton
|
||||
create table if not exists arbiter_settings (
|
||||
id INTEGER not null PRIMARY KEY CHECK (id = 1), -- singleton row, id must be 1
|
||||
root_key_id integer references root_key_history (id) on delete RESTRICT, -- if null, means wasn't bootstrapped yet
|
||||
cert_key blob not null,
|
||||
cert blob not null
|
||||
tls_id integer references tls_history (id) on delete RESTRICT
|
||||
) STRICT;
|
||||
|
||||
insert into arbiter_settings (id) values (1) on conflict do nothing; -- ensure singleton row exists
|
||||
|
||||
create table if not exists useragent_client (
|
||||
id integer not null primary key,
|
||||
nonce integer not null default(1), -- used for auth challenge
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
use arbiter_proto::{BOOTSTRAP_TOKEN_PATH, home_path};
|
||||
use arbiter_proto::{BOOTSTRAP_PATH, home_path};
|
||||
use diesel::QueryDsl;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use kameo::{Actor, messages};
|
||||
use miette::Diagnostic;
|
||||
use rand::{RngExt, distr::StandardUniform, make_rng, rngs::StdRng};
|
||||
use rand::{
|
||||
RngExt,
|
||||
distr::{Alphanumeric},
|
||||
make_rng,
|
||||
rngs::StdRng,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tracing::info;
|
||||
|
||||
use crate::db::{self, DatabasePool, schema};
|
||||
|
||||
const TOKEN_LENGTH: usize = 64;
|
||||
|
||||
pub async fn generate_token() -> Result<String, std::io::Error> {
|
||||
let rng: StdRng = make_rng();
|
||||
|
||||
let token: String = rng
|
||||
.sample_iter::<char, _>(StandardUniform)
|
||||
.take(TOKEN_LENGTH)
|
||||
.fold(Default::default(), |mut accum, char| {
|
||||
let token: String = rng.sample_iter(Alphanumeric).take(TOKEN_LENGTH).fold(
|
||||
Default::default(),
|
||||
|mut accum, char| {
|
||||
accum += char.to_string().as_str();
|
||||
accum
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
tokio::fs::write(home_path()?.join(BOOTSTRAP_TOKEN_PATH), token.as_str()).await?;
|
||||
tokio::fs::write(home_path()?.join(BOOTSTRAP_PATH), token.as_str()).await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
@@ -58,10 +61,9 @@ impl Bootstrapper {
|
||||
|
||||
drop(conn);
|
||||
|
||||
|
||||
let token = if row_count == 0 {
|
||||
let token = generate_token().await?;
|
||||
info!(%token, "Generated bootstrap token");
|
||||
tokio::fs::write(home_path()?.join(BOOTSTRAP_TOKEN_PATH), token.as_str()).await?;
|
||||
Some(token)
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -345,30 +345,15 @@ impl KeyHolder {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use diesel::SelectableHelper;
|
||||
use diesel::dsl::insert_into;
|
||||
|
||||
use diesel_async::RunQueryDsl;
|
||||
use memsafe::MemSafe;
|
||||
|
||||
use crate::db::{self, models::ArbiterSetting};
|
||||
use crate::db::{self};
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn seed_settings(pool: &db::DatabasePool) {
|
||||
let mut conn = pool.get().await.unwrap();
|
||||
insert_into(schema::arbiter_settings::table)
|
||||
.values(&ArbiterSetting {
|
||||
id: 1,
|
||||
root_key_id: None,
|
||||
cert_key: vec![],
|
||||
cert: vec![],
|
||||
})
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder {
|
||||
seed_settings(db).await;
|
||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||
let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
||||
actor.bootstrap(seal_key).await.unwrap();
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use diesel::OptionalExtension as _;
|
||||
use diesel_async::RunQueryDsl as _;
|
||||
use miette::Diagnostic;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
actors::GlobalActors,
|
||||
context::tls::{TlsDataRaw, TlsManager},
|
||||
db::{self, models::ArbiterSetting, schema::arbiter_settings},
|
||||
context::tls::TlsManager,
|
||||
db::{self},
|
||||
};
|
||||
|
||||
pub mod tls;
|
||||
@@ -29,7 +27,7 @@ pub enum InitError {
|
||||
|
||||
#[error("TLS initialization failed: {0}")]
|
||||
#[diagnostic(code(arbiter_server::init::tls_init))]
|
||||
Tls(#[from] tls::TlsInitError),
|
||||
Tls(#[from] tls::InitError),
|
||||
|
||||
#[error("Actor spawn failed: {0}")]
|
||||
#[diagnostic(code(arbiter_server::init::actor_spawn))]
|
||||
@@ -57,54 +55,11 @@ impl std::ops::Deref for ServerContext {
|
||||
}
|
||||
|
||||
impl ServerContext {
|
||||
async fn load_tls(
|
||||
db: &mut db::DatabaseConnection,
|
||||
settings: Option<&ArbiterSetting>,
|
||||
) -> Result<TlsManager, InitError> {
|
||||
match &settings {
|
||||
Some(settings) => {
|
||||
let tls_data_raw = TlsDataRaw {
|
||||
cert: settings.cert.clone(),
|
||||
key: settings.cert_key.clone(),
|
||||
};
|
||||
|
||||
Ok(TlsManager::new(Some(tls_data_raw)).await?)
|
||||
}
|
||||
None => {
|
||||
let tls = TlsManager::new(None).await?;
|
||||
let tls_data_raw = tls.bytes();
|
||||
|
||||
diesel::insert_into(arbiter_settings::table)
|
||||
.values(&ArbiterSetting {
|
||||
id: 1,
|
||||
root_key_id: None,
|
||||
cert_key: tls_data_raw.key,
|
||||
cert: tls_data_raw.cert,
|
||||
})
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
Ok(tls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new(db: db::DatabasePool) -> Result<Self, InitError> {
|
||||
let mut conn = db.get().await?;
|
||||
|
||||
let settings = arbiter_settings::table
|
||||
.first::<ArbiterSetting>(&mut conn)
|
||||
.await
|
||||
.optional()?;
|
||||
|
||||
let tls = Self::load_tls(&mut conn, settings.as_ref()).await?;
|
||||
|
||||
drop(conn);
|
||||
|
||||
Ok(Self(Arc::new(_ServerContextInner {
|
||||
actors: GlobalActors::spawn(db.clone()).await?,
|
||||
tls: TlsManager::new(db.clone()).await?,
|
||||
db,
|
||||
tls,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,36 @@
|
||||
use std::string::FromUtf8Error;
|
||||
|
||||
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper as _};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
use miette::Diagnostic;
|
||||
use rcgen::{Certificate, KeyPair};
|
||||
use rustls::pki_types::CertificateDer;
|
||||
use pem::Pem;
|
||||
use rcgen::{
|
||||
BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType,
|
||||
IsCa, Issuer, KeyPair, KeyUsagePurpose,
|
||||
};
|
||||
use rustls::pki_types::{pem::PemObject};
|
||||
use thiserror::Error;
|
||||
use tonic::transport::CertificateDer;
|
||||
|
||||
use crate::db::{
|
||||
self,
|
||||
models::{NewTlsHistory, TlsHistory},
|
||||
schema::{
|
||||
arbiter_settings,
|
||||
tls_history::{self},
|
||||
},
|
||||
};
|
||||
|
||||
const ENCODE_CONFIG: pem::EncodeConfig = {
|
||||
let line_ending = match cfg!(target_family = "windows") {
|
||||
true => pem::LineEnding::CRLF,
|
||||
false => pem::LineEnding::LF,
|
||||
};
|
||||
pem::EncodeConfig::new().set_line_ending(line_ending)
|
||||
};
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
pub enum TlsInitError {
|
||||
pub enum InitError {
|
||||
#[error("Key generation error during TLS initialization: {0}")]
|
||||
#[diagnostic(code(arbiter_server::tls_init::key_generation))]
|
||||
KeyGeneration(#[from] rcgen::Error),
|
||||
@@ -18,68 +42,211 @@ pub enum TlsInitError {
|
||||
#[error("Key deserialization error: {0}")]
|
||||
#[diagnostic(code(arbiter_server::tls_init::key_deserialization))]
|
||||
KeyDeserializationError(rcgen::Error),
|
||||
|
||||
#[error("Database error during TLS initialization: {0}")]
|
||||
#[diagnostic(code(arbiter_server::tls_init::database_error))]
|
||||
DatabaseError(#[from] diesel::result::Error),
|
||||
|
||||
#[error("Pem deserialization error during TLS initialization: {0}")]
|
||||
#[diagnostic(code(arbiter_server::tls_init::pem_deserialization))]
|
||||
PemDeserializationError(#[from] rustls::pki_types::pem::Error),
|
||||
|
||||
#[error("Database pool acquire error during TLS initialization: {0}")]
|
||||
#[diagnostic(code(arbiter_server::tls_init::database_pool_acquire))]
|
||||
DatabasePoolAcquire(#[from] db::PoolError),
|
||||
}
|
||||
|
||||
pub struct TlsData {
|
||||
pub cert: CertificateDer<'static>,
|
||||
pub keypair: KeyPair,
|
||||
pub type PemCert = String;
|
||||
|
||||
pub fn encode_cert_to_pem(cert: &CertificateDer) -> PemCert {
|
||||
pem::encode_config(
|
||||
&Pem::new("CERTIFICATE", cert.to_vec()),
|
||||
ENCODE_CONFIG,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct TlsDataRaw {
|
||||
pub cert: Vec<u8>,
|
||||
pub key: Vec<u8>,
|
||||
#[allow(unused)]
|
||||
struct SerializedTls {
|
||||
cert_pem: PemCert,
|
||||
cert_key_pem: String,
|
||||
}
|
||||
impl TlsDataRaw {
|
||||
pub fn serialize(cert: &TlsData) -> Self {
|
||||
Self {
|
||||
cert: cert.cert.as_ref().to_vec(),
|
||||
key: cert.keypair.serialize_pem().as_bytes().to_vec(),
|
||||
|
||||
struct TlsCa {
|
||||
issuer: Issuer<'static, KeyPair>,
|
||||
cert: CertificateDer<'static>,
|
||||
}
|
||||
|
||||
impl TlsCa {
|
||||
fn generate() -> Result<Self, InitError> {
|
||||
let keypair = KeyPair::generate()?;
|
||||
let mut params = CertificateParams::new(["Arbiter Instance CA".into()])?;
|
||||
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
||||
params.key_usages = vec![
|
||||
KeyUsagePurpose::KeyCertSign,
|
||||
KeyUsagePurpose::CrlSign,
|
||||
KeyUsagePurpose::DigitalSignature,
|
||||
];
|
||||
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(DnType::CommonName, "Arbiter Instance CA");
|
||||
params.distinguished_name = dn;
|
||||
let certified_issuer = CertifiedIssuer::self_signed(params, keypair)?;
|
||||
|
||||
let cert_key_pem = certified_issuer.key().serialize_pem();
|
||||
|
||||
let issuer = Issuer::from_ca_cert_pem(
|
||||
&certified_issuer.pem(),
|
||||
KeyPair::from_pem(cert_key_pem.as_ref()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
issuer,
|
||||
cert: certified_issuer.der().clone(),
|
||||
})
|
||||
}
|
||||
fn generate_leaf(&self) -> Result<TlsCert, InitError> {
|
||||
let cert_key = KeyPair::generate()?;
|
||||
let mut params = CertificateParams::new(["Arbiter Instance Leaf".into()])?;
|
||||
params.is_ca = IsCa::NoCa;
|
||||
params.key_usages = vec![
|
||||
KeyUsagePurpose::DigitalSignature,
|
||||
KeyUsagePurpose::KeyEncipherment,
|
||||
];
|
||||
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(DnType::CommonName, "Arbiter Instance Leaf");
|
||||
params.distinguished_name = dn;
|
||||
|
||||
let new_cert = params.signed_by(&cert_key, &self.issuer)?;
|
||||
|
||||
Ok(TlsCert {
|
||||
cert: new_cert,
|
||||
cert_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn deserialize(&self) -> Result<TlsData, TlsInitError> {
|
||||
let cert = CertificateDer::from_slice(&self.cert).into_owned();
|
||||
#[allow(unused)]
|
||||
fn serialize(&self) -> Result<SerializedTls, InitError> {
|
||||
let cert_key_pem = self.issuer.key().serialize_pem();
|
||||
Ok(SerializedTls {
|
||||
cert_pem: encode_cert_to_pem(&self.cert),
|
||||
cert_key_pem,
|
||||
})
|
||||
}
|
||||
|
||||
let key = String::from_utf8(self.key.clone()).map_err(TlsInitError::KeyInvalidFormat)?;
|
||||
|
||||
let keypair = KeyPair::from_pem(&key).map_err(TlsInitError::KeyDeserializationError)?;
|
||||
|
||||
Ok(TlsData { cert, keypair })
|
||||
#[allow(unused)]
|
||||
fn try_deserialize(cert_pem: &str, cert_key_pem: &str) -> Result<Self, InitError> {
|
||||
let keypair =
|
||||
KeyPair::from_pem(cert_key_pem).map_err(InitError::KeyDeserializationError)?;
|
||||
let issuer = Issuer::from_ca_cert_pem(cert_pem, keypair)?;
|
||||
Ok(Self {
|
||||
issuer,
|
||||
cert: CertificateDer::from_pem_slice(cert_pem.as_bytes())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_cert(key: &KeyPair) -> Result<Certificate, rcgen::Error> {
|
||||
let params =
|
||||
rcgen::CertificateParams::new(vec!["arbiter.local".to_string(), "localhost".to_string()])?;
|
||||
|
||||
params.self_signed(key)
|
||||
struct TlsCert {
|
||||
cert: Certificate,
|
||||
cert_key: KeyPair,
|
||||
}
|
||||
|
||||
// TODO: Implement cert rotation
|
||||
pub struct TlsManager {
|
||||
data: TlsData,
|
||||
cert: CertificateDer<'static>,
|
||||
keypair: KeyPair,
|
||||
ca_cert: CertificateDer<'static>,
|
||||
_db: db::DatabasePool,
|
||||
}
|
||||
|
||||
impl TlsManager {
|
||||
pub async fn new(data: Option<TlsDataRaw>) -> Result<Self, TlsInitError> {
|
||||
match data {
|
||||
Some(raw) => {
|
||||
let tls_data = raw.deserialize()?;
|
||||
Ok(Self { data: tls_data })
|
||||
}
|
||||
None => {
|
||||
let keypair = KeyPair::generate()?;
|
||||
let cert = generate_cert(&keypair)?;
|
||||
let tls_data = TlsData {
|
||||
cert: cert.der().clone(),
|
||||
keypair,
|
||||
pub async fn generate_new(db: &db::DatabasePool) -> Result<Self, InitError> {
|
||||
let ca = TlsCa::generate()?;
|
||||
let new_cert = ca.generate_leaf()?;
|
||||
|
||||
{
|
||||
let mut conn = db.get().await?;
|
||||
conn.transaction(|conn| {
|
||||
Box::pin(async {
|
||||
let new_tls_history = NewTlsHistory {
|
||||
cert: new_cert.cert.pem(),
|
||||
cert_key: new_cert.cert_key.serialize_pem(),
|
||||
ca_cert: encode_cert_to_pem(&ca.cert),
|
||||
ca_key: ca.issuer.key().serialize_pem(),
|
||||
};
|
||||
Ok(Self { data: tls_data })
|
||||
|
||||
let inserted_tls_history: i32 = diesel::insert_into(tls_history::table)
|
||||
.values(&new_tls_history)
|
||||
.returning(tls_history::id)
|
||||
.get_result(conn)
|
||||
.await?;
|
||||
|
||||
diesel::update(arbiter_settings::table)
|
||||
.set(arbiter_settings::tls_id.eq(inserted_tls_history))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Result::<_, diesel::result::Error>::Ok(())
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
cert: new_cert.cert.der().clone(),
|
||||
keypair: new_cert.cert_key,
|
||||
ca_cert: ca.cert,
|
||||
_db: db.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn new(db: db::DatabasePool) -> Result<Self, InitError> {
|
||||
let cert_data: Option<TlsHistory> = {
|
||||
let mut conn = db.get().await?;
|
||||
arbiter_settings::table
|
||||
.left_join(tls_history::table)
|
||||
.select(Option::<TlsHistory>::as_select())
|
||||
.first(&mut conn)
|
||||
.await?
|
||||
};
|
||||
|
||||
match cert_data {
|
||||
Some(data) => {
|
||||
let try_load = || -> Result<_, Box<dyn std::error::Error>> {
|
||||
let keypair = KeyPair::from_pem(&data.cert_key)?;
|
||||
let cert = CertificateDer::from_pem_slice(data.cert.as_bytes())?;
|
||||
let ca_cert = CertificateDer::from_pem_slice(data.ca_cert.as_bytes())?;
|
||||
Ok(Self {
|
||||
cert,
|
||||
keypair,
|
||||
ca_cert,
|
||||
_db: db.clone(),
|
||||
})
|
||||
};
|
||||
match try_load() {
|
||||
Ok(manager) => Ok(manager),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to load existing TLS certs: {e}. Generating new ones.");
|
||||
Self::generate_new(&db).await
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Self::generate_new(&db).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self) -> TlsDataRaw {
|
||||
TlsDataRaw::serialize(&self.data)
|
||||
pub fn cert(&self) -> &CertificateDer<'static> {
|
||||
&self.cert
|
||||
}
|
||||
pub fn ca_cert(&self) -> &CertificateDer<'static> {
|
||||
&self.ca_cert
|
||||
}
|
||||
|
||||
pub fn cert_pem(&self) -> PemCert {
|
||||
encode_cert_to_pem(&self.cert)
|
||||
}
|
||||
pub fn key_pem(&self) -> String {
|
||||
self.keypair.serialize_pem()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,23 +24,23 @@ const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||
#[derive(Error, Diagnostic, Debug)]
|
||||
pub enum DatabaseSetupError {
|
||||
#[error("Failed to determine home directory")]
|
||||
#[diagnostic(code(arbiter::db::home_dir_error))]
|
||||
#[diagnostic(code(arbiter::db::home_dir))]
|
||||
HomeDir(std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(arbiter::db::connection_error))]
|
||||
#[diagnostic(code(arbiter::db::connection))]
|
||||
Connection(diesel::ConnectionError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(arbiter::db::concurrency_error))]
|
||||
#[diagnostic(code(arbiter::db::concurrency))]
|
||||
ConcurrencySetup(diesel::result::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(arbiter::db::migration_error))]
|
||||
#[diagnostic(code(arbiter::db::migration))]
|
||||
Migration(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
#[error(transparent)]
|
||||
#[diagnostic(code(arbiter::db::pool_error))]
|
||||
#[diagnostic(code(arbiter::db::pool))]
|
||||
Pool(#[from] PoolInitError),
|
||||
}
|
||||
|
||||
@@ -91,12 +91,12 @@ fn initialize_database(url: &str) -> Result<(), DatabaseSetupError> {
|
||||
|
||||
#[tracing::instrument(level = "info")]
|
||||
pub async fn create_pool(url: Option<&str>) -> Result<DatabasePool, DatabaseSetupError> {
|
||||
let database_url = url.map(String::from).unwrap_or(format!(
|
||||
"{}?mode=rwc",
|
||||
(database_path()?
|
||||
let database_url = url.map(String::from).unwrap_or(
|
||||
database_path()?
|
||||
.to_str()
|
||||
.expect("database path is not valid UTF-8"))
|
||||
));
|
||||
.expect("database path is not valid UTF-8")
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
initialize_database(&database_url)?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(unused)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
use crate::db::schema::{self, aead_encrypted, arbiter_settings, root_key_history};
|
||||
use crate::db::schema::{self, aead_encrypted, arbiter_settings, root_key_history, tls_history};
|
||||
use diesel::{prelude::*, sqlite::Sqlite};
|
||||
use restructed::Models;
|
||||
|
||||
@@ -46,13 +46,29 @@ pub struct RootKeyHistory {
|
||||
pub salt: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Debug, Insertable)]
|
||||
#[derive(Models, Queryable, Debug, Insertable, Selectable)]
|
||||
#[diesel(table_name = tls_history, check_for_backend(Sqlite))]
|
||||
#[view(
|
||||
NewTlsHistory,
|
||||
derive(Insertable),
|
||||
omit(id, created_at),
|
||||
attributes_with = "deriveless"
|
||||
)]
|
||||
pub struct TlsHistory {
|
||||
pub id: i32,
|
||||
pub cert: String,
|
||||
pub cert_key: String, // PEM Encoded private key
|
||||
pub ca_cert: String, // PEM Encoded certificate for cert signing
|
||||
pub ca_key: String, // PEM Encoded public key for cert signing
|
||||
pub created_at: i32,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Debug, Insertable, Selectable)]
|
||||
#[diesel(table_name = arbiter_settings, check_for_backend(Sqlite))]
|
||||
pub struct ArbiterSetting {
|
||||
pub struct ArbiterSettings {
|
||||
pub id: i32,
|
||||
pub root_key_id: Option<i32>, // references root_key_history.id
|
||||
pub cert_key: Vec<u8>,
|
||||
pub cert: Vec<u8>,
|
||||
pub tls_id: Option<i32>, // references tls_history.id
|
||||
}
|
||||
|
||||
#[derive(Queryable, Debug)]
|
||||
|
||||
@@ -16,8 +16,7 @@ diesel::table! {
|
||||
arbiter_settings (id) {
|
||||
id -> Integer,
|
||||
root_key_id -> Nullable<Integer>,
|
||||
cert_key -> Binary,
|
||||
cert -> Binary,
|
||||
tls_id -> Nullable<Integer>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +42,17 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
tls_history (id) {
|
||||
id -> Integer,
|
||||
cert -> Text,
|
||||
cert_key -> Text,
|
||||
ca_cert -> Text,
|
||||
ca_key -> Text,
|
||||
created_at -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
useragent_client (id) {
|
||||
id -> Integer,
|
||||
@@ -55,11 +65,13 @@ diesel::table! {
|
||||
|
||||
diesel::joinable!(aead_encrypted -> root_key_history (associated_root_key_id));
|
||||
diesel::joinable!(arbiter_settings -> root_key_history (root_key_id));
|
||||
diesel::joinable!(arbiter_settings -> tls_history (tls_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
aead_encrypted,
|
||||
arbiter_settings,
|
||||
program_client,
|
||||
root_key_history,
|
||||
tls_history,
|
||||
useragent_client,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use arbiter_proto::proto::arbiter_service_server::ArbiterServiceServer;
|
||||
use arbiter_server::{Server, context::ServerContext, db};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
||||
use arbiter_server::{Server, actors::bootstrap::GetToken, context::ServerContext, db};
|
||||
use miette::miette;
|
||||
use tonic::transport::{Identity, ServerTlsConfig};
|
||||
use tracing::info;
|
||||
|
||||
const PORT: u16 = 50051;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> miette::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
@@ -13,18 +19,31 @@ async fn main() -> miette::Result<()> {
|
||||
|
||||
info!("Starting arbiter server");
|
||||
|
||||
info!("Initializing database");
|
||||
let db = db::create_pool(None).await?;
|
||||
info!("Database ready");
|
||||
|
||||
info!("Initializing server context");
|
||||
let context = ServerContext::new(db).await?;
|
||||
info!("Server context ready");
|
||||
|
||||
let addr = "[::1]:50051".parse().expect("valid address");
|
||||
let addr: SocketAddr = format!("127.0.0.1:{PORT}").parse().expect("valid address");
|
||||
info!(%addr, "Starting gRPC server");
|
||||
|
||||
let url = ArbiterUrl {
|
||||
host: addr.ip().to_string(),
|
||||
port: addr.port(),
|
||||
ca_cert: context.tls.ca_cert().clone().into_owned(),
|
||||
bootstrap_token: context.actors.bootstrapper.ask(GetToken).await.unwrap(),
|
||||
};
|
||||
|
||||
info!(%url, "Server URL");
|
||||
|
||||
let tls = ServerTlsConfig::new().identity(Identity::from_pem(
|
||||
context.tls.cert_pem(),
|
||||
context.tls.key_pem(),
|
||||
));
|
||||
|
||||
tonic::transport::Server::builder()
|
||||
.tls_config(tls)
|
||||
.map_err(|err| miette!("Faild to setup TLS: {err}"))?
|
||||
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
||||
.serve(addr)
|
||||
.await
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
use arbiter_server::{
|
||||
actors::keyholder::KeyHolder,
|
||||
db::{self, models::ArbiterSetting, schema},
|
||||
db::{self, schema},
|
||||
};
|
||||
use diesel::{QueryDsl, insert_into};
|
||||
use diesel::QueryDsl;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use memsafe::MemSafe;
|
||||
|
||||
pub async fn seed_settings(pool: &db::DatabasePool) {
|
||||
let mut conn = pool.get().await.unwrap();
|
||||
insert_into(schema::arbiter_settings::table)
|
||||
.values(&ArbiterSetting {
|
||||
id: 1,
|
||||
root_key_id: None,
|
||||
cert_key: vec![],
|
||||
cert: vec![],
|
||||
})
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder {
|
||||
seed_settings(db).await;
|
||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||
actor
|
||||
.bootstrap(MemSafe::new(b"test-seal-key".to_vec()).unwrap())
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::common;
|
||||
#[test_log::test]
|
||||
async fn test_bootstrap() {
|
||||
let db = db::create_test_pool().await;
|
||||
common::seed_settings(&db).await;
|
||||
let mut actor = KeyHolder::new(db.clone()).await.unwrap();
|
||||
|
||||
let seal_key = MemSafe::new(b"test-seal-key".to_vec()).unwrap();
|
||||
@@ -53,7 +52,6 @@ async fn test_bootstrap_rejects_double() {
|
||||
#[test_log::test]
|
||||
async fn test_create_new_before_bootstrap_fails() {
|
||||
let db = db::create_test_pool().await;
|
||||
common::seed_settings(&db).await;
|
||||
let mut actor = KeyHolder::new(db).await.unwrap();
|
||||
|
||||
let err = actor
|
||||
@@ -67,7 +65,6 @@ async fn test_create_new_before_bootstrap_fails() {
|
||||
#[test_log::test]
|
||||
async fn test_decrypt_before_bootstrap_fails() {
|
||||
let db = db::create_test_pool().await;
|
||||
common::seed_settings(&db).await;
|
||||
let mut actor = KeyHolder::new(db).await.unwrap();
|
||||
|
||||
let err = actor.decrypt(1).await.unwrap_err();
|
||||
|
||||
@@ -20,7 +20,6 @@ use kameo::actor::Spawn;
|
||||
#[test_log::test]
|
||||
pub async fn test_bootstrap_token_auth() {
|
||||
let db =db::create_test_pool().await;
|
||||
crate::common::seed_settings(&db).await;
|
||||
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap();
|
||||
@@ -67,7 +66,6 @@ pub async fn test_bootstrap_token_auth() {
|
||||
#[test_log::test]
|
||||
pub async fn test_bootstrap_invalid_token_auth() {
|
||||
let db = db::create_test_pool().await;
|
||||
crate::common::seed_settings(&db).await;
|
||||
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
let user_agent =
|
||||
@@ -110,7 +108,6 @@ pub async fn test_bootstrap_invalid_token_auth() {
|
||||
#[test_log::test]
|
||||
pub async fn test_challenge_auth() {
|
||||
let db = db::create_test_pool().await;
|
||||
crate::common::seed_settings(&db).await;
|
||||
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
let user_agent =
|
||||
|
||||
@@ -23,7 +23,6 @@ async fn setup_authenticated_user_agent(
|
||||
seal_key: &[u8],
|
||||
) -> (arbiter_server::db::DatabasePool, ActorRef<UserAgentActor>) {
|
||||
let db = db::create_test_pool().await;
|
||||
crate::common::seed_settings(&db).await;
|
||||
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
actors
|
||||
@@ -167,7 +166,6 @@ pub async fn test_unseal_corrupted_ciphertext() {
|
||||
#[test_log::test]
|
||||
pub async fn test_unseal_start_without_auth_fails() {
|
||||
let db = db::create_test_pool().await;
|
||||
crate::common::seed_settings(&db).await;
|
||||
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
let user_agent =
|
||||
|
||||
@@ -5,3 +5,11 @@ edition = "2024"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
arbiter-proto.path = "../arbiter-proto"
|
||||
kameo.workspace = true
|
||||
tokio = {workspace = true, features = ["net"]}
|
||||
tonic.workspace = true
|
||||
tracing.workspace = true
|
||||
ed25519-dalek.workspace = true
|
||||
smlang.workspace = true
|
||||
x25519-dalek.workspace = true
|
||||
@@ -1,14 +0,0 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user