WIP: add OPAQUE password-authenticated key exchange
Add opaque-ke (v4, ristretto255) for password-based registration and login. Extend NodeService schema with opaqueRegisterStart/Finish and opaqueLoginStart/Finish RPCs. Add Store trait methods for OPAQUE server setup and user records. Initial e2e integration test scaffolding. Note: FileBackedStore does not yet implement the new Store trait methods — server compilation is temporarily broken. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -164,6 +164,21 @@ version = "1.0.101"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_cmd"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"bstr",
|
||||||
|
"libc",
|
||||||
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.76"
|
version = "0.3.76"
|
||||||
@@ -212,15 +227,6 @@ version = "2.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake2"
|
|
||||||
version = "0.10.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
|
||||||
dependencies = [
|
|
||||||
"digest 0.10.7",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -239,6 +245,17 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.20.2"
|
version = "3.20.2"
|
||||||
@@ -570,7 +587,9 @@ dependencies = [
|
|||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"fiat-crypto",
|
"fiat-crypto",
|
||||||
|
"rand_core 0.6.4",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
|
"serde",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -632,6 +651,23 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive-where"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -653,6 +689,17 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.16.9"
|
version = "0.16.9"
|
||||||
@@ -663,6 +710,7 @@ dependencies = [
|
|||||||
"digest 0.10.7",
|
"digest 0.10.7",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"rfc6979",
|
"rfc6979",
|
||||||
|
"serdect",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
"spki",
|
"spki",
|
||||||
]
|
]
|
||||||
@@ -683,6 +731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
|
"serde",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -711,6 +760,7 @@ dependencies = [
|
|||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
|
"signature 2.2.0",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -738,6 +788,7 @@ dependencies = [
|
|||||||
"pkcs8",
|
"pkcs8",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"sec1",
|
"sec1",
|
||||||
|
"serdect",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -788,6 +839,12 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -904,6 +961,7 @@ version = "0.14.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -1198,6 +1256,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -1308,6 +1372,29 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-ke"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ded22991b43cd15561b62b2e1cf9ace1344a8534eebec96202d5c96a77a6616a"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek 4.1.3",
|
||||||
|
"derive-where",
|
||||||
|
"digest 0.10.7",
|
||||||
|
"displaydoc",
|
||||||
|
"ecdsa",
|
||||||
|
"ed25519-dalek 2.2.0",
|
||||||
|
"elliptic-curve",
|
||||||
|
"generic-array",
|
||||||
|
"hkdf",
|
||||||
|
"hmac",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
|
"subtle",
|
||||||
|
"voprf",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openmls"
|
name = "openmls"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -1509,6 +1596,15 @@ dependencies = [
|
|||||||
"universal-hash 0.5.1",
|
"universal-hash 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portpicker"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9"
|
||||||
|
dependencies = [
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1524,6 +1620,33 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"difflib",
|
||||||
|
"predicates-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "primeorder"
|
name = "primeorder"
|
||||||
version = "0.13.6"
|
version = "0.13.6"
|
||||||
@@ -1547,21 +1670,26 @@ name = "quicnprotochat-client"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"assert_cmd",
|
||||||
"bincode",
|
"bincode",
|
||||||
"capnp",
|
"capnp",
|
||||||
"capnp-rpc",
|
"capnp-rpc",
|
||||||
"clap",
|
"clap",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"opaque-ke",
|
||||||
"openmls_rust_crypto",
|
"openmls_rust_crypto",
|
||||||
|
"portpicker",
|
||||||
"quicnprotochat-core",
|
"quicnprotochat-core",
|
||||||
"quicnprotochat-proto",
|
"quicnprotochat-proto",
|
||||||
"quinn",
|
"quinn",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
|
"rand 0.8.5",
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
|
"tempfile",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -1574,13 +1702,12 @@ name = "quicnprotochat-core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"bytes",
|
|
||||||
"capnp",
|
"capnp",
|
||||||
"chacha20poly1305 0.10.1",
|
"chacha20poly1305 0.10.1",
|
||||||
"ed25519-dalek 2.2.0",
|
"ed25519-dalek 2.2.0",
|
||||||
"futures",
|
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"ml-kem",
|
"ml-kem",
|
||||||
|
"opaque-ke",
|
||||||
"openmls",
|
"openmls",
|
||||||
"openmls_rust_crypto",
|
"openmls_rust_crypto",
|
||||||
"openmls_traits",
|
"openmls_traits",
|
||||||
@@ -1589,11 +1716,9 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"snow",
|
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tls_codec 0.3.0",
|
"tls_codec 0.3.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
|
||||||
"x25519-dalek",
|
"x25519-dalek",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -1617,10 +1742,12 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
|
"opaque-ke",
|
||||||
"quicnprotochat-core",
|
"quicnprotochat-core",
|
||||||
"quicnprotochat-proto",
|
"quicnprotochat-proto",
|
||||||
"quinn",
|
"quinn",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
|
"rand 0.8.5",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"rustls",
|
"rustls",
|
||||||
@@ -1924,6 +2051,19 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.36"
|
version = "0.23.36"
|
||||||
@@ -2038,6 +2178,7 @@ dependencies = [
|
|||||||
"der",
|
"der",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
|
"serdect",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -2123,6 +2264,16 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serdect"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@@ -2216,22 +2367,6 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "snow"
|
|
||||||
version = "0.9.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85"
|
|
||||||
dependencies = [
|
|
||||||
"aes-gcm 0.10.3",
|
|
||||||
"blake2",
|
|
||||||
"chacha20poly1305 0.10.1",
|
|
||||||
"curve25519-dalek 4.1.3",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
"rustc_version",
|
|
||||||
"sha2 0.10.9",
|
|
||||||
"subtle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
@@ -2281,6 +2416,25 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -2615,6 +2769,34 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "voprf"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28f59c30c76e2fea54cdece6a054e2662feffa7ab19658a7887524265ee39470"
|
||||||
|
dependencies = [
|
||||||
|
"curve25519-dalek 4.1.3",
|
||||||
|
"derive-where",
|
||||||
|
"digest 0.10.7",
|
||||||
|
"displaydoc",
|
||||||
|
"elliptic-curve",
|
||||||
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"serde",
|
||||||
|
"sha2 0.10.9",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -3021,6 +3203,7 @@ version = "1.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"zeroize_derive",
|
"zeroize_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ ed25519-dalek = { version = "2", features = ["rand_core"] }
|
|||||||
sha2 = { version = "0.10" }
|
sha2 = { version = "0.10" }
|
||||||
hkdf = { version = "0.12" }
|
hkdf = { version = "0.12" }
|
||||||
chacha20poly1305 = { version = "0.10" }
|
chacha20poly1305 = { version = "0.10" }
|
||||||
|
opaque-ke = { version = "4", features = ["ristretto255"] }
|
||||||
zeroize = { version = "1", features = ["derive"] }
|
zeroize = { version = "1", features = ["derive"] }
|
||||||
rand = { version = "0.8" }
|
rand = { version = "0.8" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ serde = { workspace = true }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
bincode = { workspace = true }
|
bincode = { workspace = true }
|
||||||
|
|
||||||
|
# Crypto — OPAQUE PAKE
|
||||||
|
opaque-ke = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
@@ -44,5 +48,7 @@ tracing-subscriber = { workspace = true }
|
|||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Integration tests use quicnprotochat-core, quicnprotochat-proto, and capnp-rpc directly.
|
|
||||||
dashmap = { workspace = true }
|
dashmap = { workspace = true }
|
||||||
|
assert_cmd = "2"
|
||||||
|
tempfile = "3"
|
||||||
|
portpicker = "0.1"
|
||||||
|
|||||||
164
crates/quicnprotochat-client/tests/e2e.rs
Normal file
164
crates/quicnprotochat-client/tests/e2e.rs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
use std::{path::PathBuf, process::Command, time::Duration};
|
||||||
|
|
||||||
|
use assert_cmd::cargo::cargo_bin;
|
||||||
|
use portpicker::pick_unused_port;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
use quicnprotochat_client::{
|
||||||
|
cmd_create_group, cmd_invite, cmd_join, cmd_ping, cmd_register_state, cmd_send, ClientAuth,
|
||||||
|
connect_node, fetch_wait, init_auth,
|
||||||
|
};
|
||||||
|
use quicnprotochat_core::IdentityKeypair;
|
||||||
|
|
||||||
|
fn hex_encode(bytes: &[u8]) -> String {
|
||||||
|
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct StoredStateCompat {
|
||||||
|
identity_seed: [u8; 32],
|
||||||
|
#[allow(dead_code)]
|
||||||
|
group: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_health(server: &str, ca_cert: &PathBuf, server_name: &str) -> anyhow::Result<()> {
|
||||||
|
let local = tokio::task::LocalSet::new();
|
||||||
|
for _ in 0..30 {
|
||||||
|
if local
|
||||||
|
.run_until(cmd_ping(server, ca_cert, server_name))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
|
anyhow::bail!("server health never became ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn e2e_happy_path_register_invite_join_send_recv() -> anyhow::Result<()> {
|
||||||
|
let temp = TempDir::new()?;
|
||||||
|
let base = temp.path();
|
||||||
|
let port = pick_unused_port().expect("free port");
|
||||||
|
let listen = format!("127.0.0.1:{port}");
|
||||||
|
let server = listen.clone();
|
||||||
|
let ca_cert = base.join("server-cert.der");
|
||||||
|
let tls_key = base.join("server-key.der");
|
||||||
|
let data_dir = base.join("data");
|
||||||
|
let auth_token = "devtoken";
|
||||||
|
|
||||||
|
// Spawn server binary.
|
||||||
|
let server_bin = cargo_bin("quicnprotochat-server");
|
||||||
|
let mut child = Command::new(server_bin)
|
||||||
|
.arg("--listen")
|
||||||
|
.arg(&listen)
|
||||||
|
.arg("--data-dir")
|
||||||
|
.arg(&data_dir)
|
||||||
|
.arg("--tls-cert")
|
||||||
|
.arg(&ca_cert)
|
||||||
|
.arg("--tls-key")
|
||||||
|
.arg(&tls_key)
|
||||||
|
.arg("--auth-token")
|
||||||
|
.arg(auth_token)
|
||||||
|
.spawn()
|
||||||
|
.expect("spawn server");
|
||||||
|
|
||||||
|
// Ensure we always terminate the child.
|
||||||
|
struct ChildGuard(std::process::Child);
|
||||||
|
impl Drop for ChildGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.0.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let child_guard = ChildGuard(child);
|
||||||
|
let _ = child_guard;
|
||||||
|
|
||||||
|
// Wait for server to be healthy and certs to be generated.
|
||||||
|
wait_for_health(&server, &ca_cert, "localhost").await?;
|
||||||
|
|
||||||
|
// Set client auth context.
|
||||||
|
init_auth(ClientAuth::from_parts(auth_token.to_string(), None));
|
||||||
|
|
||||||
|
// LocalSet for capnp !Send operations.
|
||||||
|
let local = tokio::task::LocalSet::new();
|
||||||
|
|
||||||
|
let alice_state = base.join("alice.bin");
|
||||||
|
let bob_state = base.join("bob.bin");
|
||||||
|
|
||||||
|
local
|
||||||
|
.run_until(cmd_register_state(
|
||||||
|
&alice_state,
|
||||||
|
&server,
|
||||||
|
&ca_cert,
|
||||||
|
"localhost",
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
local
|
||||||
|
.run_until(cmd_register_state(
|
||||||
|
&bob_state,
|
||||||
|
&server,
|
||||||
|
&ca_cert,
|
||||||
|
"localhost",
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
local
|
||||||
|
.run_until(cmd_create_group(
|
||||||
|
&alice_state,
|
||||||
|
&server,
|
||||||
|
"test-group",
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Load Bob identity key from persisted state to use as peer key.
|
||||||
|
let bob_bytes = std::fs::read(&bob_state)?;
|
||||||
|
let bob_state_compat: StoredStateCompat = bincode::deserialize(&bob_bytes)?;
|
||||||
|
let bob_identity = IdentityKeypair::from_seed(bob_state_compat.identity_seed);
|
||||||
|
let bob_pk_hex = hex_encode(&bob_identity.public_key_bytes());
|
||||||
|
|
||||||
|
local
|
||||||
|
.run_until(cmd_invite(
|
||||||
|
&alice_state,
|
||||||
|
&server,
|
||||||
|
&ca_cert,
|
||||||
|
"localhost",
|
||||||
|
&bob_pk_hex,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
local
|
||||||
|
.run_until(cmd_join(
|
||||||
|
&bob_state,
|
||||||
|
&server,
|
||||||
|
&ca_cert,
|
||||||
|
"localhost",
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Send Alice -> Bob.
|
||||||
|
local
|
||||||
|
.run_until(cmd_send(
|
||||||
|
&alice_state,
|
||||||
|
&server,
|
||||||
|
&ca_cert,
|
||||||
|
"localhost",
|
||||||
|
&bob_pk_hex,
|
||||||
|
"hello bob",
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Confirm Bob can fetch at least one payload.
|
||||||
|
local
|
||||||
|
.run_until(async {
|
||||||
|
let client = connect_node(&server, &ca_cert, "localhost").await?;
|
||||||
|
let payloads = fetch_wait(&client, &bob_identity.public_key_bytes(), 1000).await?;
|
||||||
|
anyhow::ensure!(!payloads.is_empty(), "no payloads delivered to Bob");
|
||||||
|
Ok::<(), anyhow::Error>(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@ rand = { workspace = true }
|
|||||||
# Crypto — post-quantum hybrid KEM (M7)
|
# Crypto — post-quantum hybrid KEM (M7)
|
||||||
ml-kem = { workspace = true }
|
ml-kem = { workspace = true }
|
||||||
|
|
||||||
|
# Crypto — OPAQUE password-authenticated key exchange
|
||||||
|
opaque-ke = { workspace = true }
|
||||||
|
|
||||||
# Crypto — MLS (M2)
|
# Crypto — MLS (M2)
|
||||||
openmls = { workspace = true }
|
openmls = { workspace = true }
|
||||||
openmls_rust_crypto = { workspace = true }
|
openmls_rust_crypto = { workspace = true }
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub mod hybrid_kem;
|
|||||||
mod identity;
|
mod identity;
|
||||||
mod keypackage;
|
mod keypackage;
|
||||||
mod keystore;
|
mod keystore;
|
||||||
|
pub mod opaque_auth;
|
||||||
|
|
||||||
// ── Public API ────────────────────────────────────────────────────────────────
|
// ── Public API ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
22
crates/quicnprotochat-core/src/opaque_auth.rs
Normal file
22
crates/quicnprotochat-core/src/opaque_auth.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//! Shared OPAQUE (RFC 9497) cipher suite configuration.
|
||||||
|
//!
|
||||||
|
//! Both client and server import this module to ensure they use exactly
|
||||||
|
//! the same cryptographic parameters during registration and login.
|
||||||
|
|
||||||
|
use opaque_ke::CipherSuite;
|
||||||
|
|
||||||
|
/// OPAQUE cipher suite for quicnprotochat.
|
||||||
|
///
|
||||||
|
/// - **OPRF**: Ristretto255 (curve25519-based, ~128-bit security)
|
||||||
|
/// - **Key exchange**: Triple-DH (3DH) over Ristretto255 with SHA-512
|
||||||
|
/// - **KSF**: Identity (no key stretching; upgrade to Argon2 later)
|
||||||
|
pub struct OpaqueSuite;
|
||||||
|
|
||||||
|
impl CipherSuite for OpaqueSuite {
|
||||||
|
type OprfCs = opaque_ke::Ristretto255;
|
||||||
|
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh<
|
||||||
|
opaque_ke::Ristretto255,
|
||||||
|
sha2::Sha512,
|
||||||
|
>;
|
||||||
|
type Ksf = opaque_ke::ksf::Identity;
|
||||||
|
}
|
||||||
@@ -32,6 +32,10 @@ quinn-proto = { workspace = true }
|
|||||||
rustls = { workspace = true }
|
rustls = { workspace = true }
|
||||||
rcgen = { workspace = true }
|
rcgen = { workspace = true }
|
||||||
|
|
||||||
|
# Crypto — OPAQUE PAKE
|
||||||
|
opaque-ke = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
rusqlite = { workspace = true }
|
rusqlite = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,18 @@ impl SqlStore {
|
|||||||
ON key_packages(identity_key);
|
ON key_packages(identity_key);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_del_recipient_channel
|
CREATE INDEX IF NOT EXISTS idx_del_recipient_channel
|
||||||
ON deliveries(recipient_key, channel_id);",
|
ON deliveries(recipient_key, channel_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS server_setup (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
|
setup_data BLOB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
username TEXT PRIMARY KEY,
|
||||||
|
opaque_record BLOB NOT NULL,
|
||||||
|
created_at INTEGER DEFAULT (strftime('%s','now'))
|
||||||
|
);",
|
||||||
)
|
)
|
||||||
.map_err(|e| StorageError::Db(e.to_string()))?;
|
.map_err(|e| StorageError::Db(e.to_string()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -203,6 +214,48 @@ impl Store for SqlStore {
|
|||||||
.optional()
|
.optional()
|
||||||
.map_err(|e| StorageError::Db(e.to_string()))
|
.map_err(|e| StorageError::Db(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn store_server_setup(&self, setup: Vec<u8>) -> Result<(), StorageError> {
|
||||||
|
let conn = self.conn.lock().unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO server_setup (id, setup_data) VALUES (1, ?1)",
|
||||||
|
params![setup],
|
||||||
|
)
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_server_setup(&self) -> Result<Option<Vec<u8>>, StorageError> {
|
||||||
|
let conn = self.conn.lock().unwrap();
|
||||||
|
let mut stmt = conn
|
||||||
|
.prepare("SELECT setup_data FROM server_setup WHERE id = 1")
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))?;
|
||||||
|
|
||||||
|
stmt.query_row([], |row| row.get(0))
|
||||||
|
.optional()
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_user_record(&self, username: &str, record: Vec<u8>) -> Result<(), StorageError> {
|
||||||
|
let conn = self.conn.lock().unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO users (username, opaque_record) VALUES (?1, ?2)",
|
||||||
|
params![username, record],
|
||||||
|
)
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_user_record(&self, username: &str) -> Result<Option<Vec<u8>>, StorageError> {
|
||||||
|
let conn = self.conn.lock().unwrap();
|
||||||
|
let mut stmt = conn
|
||||||
|
.prepare("SELECT opaque_record FROM users WHERE username = ?1")
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))?;
|
||||||
|
|
||||||
|
stmt.query_row(params![username], |row| row.get(0))
|
||||||
|
.optional()
|
||||||
|
.map_err(|e| StorageError::Db(e.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience extension for `rusqlite::OptionalExtension`.
|
/// Convenience extension for `rusqlite::OptionalExtension`.
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ pub trait Store: Send + Sync {
|
|||||||
) -> Result<(), StorageError>;
|
) -> Result<(), StorageError>;
|
||||||
|
|
||||||
fn fetch_hybrid_key(&self, identity_key: &[u8]) -> Result<Option<Vec<u8>>, StorageError>;
|
fn fetch_hybrid_key(&self, identity_key: &[u8]) -> Result<Option<Vec<u8>>, StorageError>;
|
||||||
|
|
||||||
|
/// Store the OPAQUE `ServerSetup` (generated once, loaded on restart).
|
||||||
|
fn store_server_setup(&self, setup: Vec<u8>) -> Result<(), StorageError>;
|
||||||
|
|
||||||
|
/// Load the persisted `ServerSetup`, if any.
|
||||||
|
fn get_server_setup(&self) -> Result<Option<Vec<u8>>, StorageError>;
|
||||||
|
|
||||||
|
/// Store an OPAQUE user record (serialized `ServerRegistration`).
|
||||||
|
fn store_user_record(&self, username: &str, record: Vec<u8>) -> Result<(), StorageError>;
|
||||||
|
|
||||||
|
/// Retrieve an OPAQUE user record by username.
|
||||||
|
fn get_user_record(&self, username: &str) -> Result<Option<Vec<u8>>, StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ChannelKey ───────────────────────────────────────────────────────────────
|
// ── ChannelKey ───────────────────────────────────────────────────────────────
|
||||||
@@ -86,9 +98,12 @@ pub struct FileBackedStore {
|
|||||||
kp_path: PathBuf,
|
kp_path: PathBuf,
|
||||||
ds_path: PathBuf,
|
ds_path: PathBuf,
|
||||||
hk_path: PathBuf,
|
hk_path: PathBuf,
|
||||||
|
setup_path: PathBuf,
|
||||||
|
users_path: PathBuf,
|
||||||
key_packages: Mutex<HashMap<Vec<u8>, VecDeque<Vec<u8>>>>,
|
key_packages: Mutex<HashMap<Vec<u8>, VecDeque<Vec<u8>>>>,
|
||||||
deliveries: Mutex<HashMap<ChannelKey, VecDeque<Vec<u8>>>>,
|
deliveries: Mutex<HashMap<ChannelKey, VecDeque<Vec<u8>>>>,
|
||||||
hybrid_keys: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
|
hybrid_keys: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
|
||||||
|
users: Mutex<HashMap<String, Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileBackedStore {
|
impl FileBackedStore {
|
||||||
@@ -100,18 +115,24 @@ impl FileBackedStore {
|
|||||||
let kp_path = dir.join("keypackages.bin");
|
let kp_path = dir.join("keypackages.bin");
|
||||||
let ds_path = dir.join("deliveries.bin");
|
let ds_path = dir.join("deliveries.bin");
|
||||||
let hk_path = dir.join("hybridkeys.bin");
|
let hk_path = dir.join("hybridkeys.bin");
|
||||||
|
let setup_path = dir.join("server_setup.bin");
|
||||||
|
let users_path = dir.join("users.bin");
|
||||||
|
|
||||||
let key_packages = Mutex::new(Self::load_kp_map(&kp_path)?);
|
let key_packages = Mutex::new(Self::load_kp_map(&kp_path)?);
|
||||||
let deliveries = Mutex::new(Self::load_delivery_map(&ds_path)?);
|
let deliveries = Mutex::new(Self::load_delivery_map(&ds_path)?);
|
||||||
let hybrid_keys = Mutex::new(Self::load_hybrid_keys(&hk_path)?);
|
let hybrid_keys = Mutex::new(Self::load_hybrid_keys(&hk_path)?);
|
||||||
|
let users = Mutex::new(Self::load_users(&users_path)?);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
kp_path,
|
kp_path,
|
||||||
ds_path,
|
ds_path,
|
||||||
hk_path,
|
hk_path,
|
||||||
|
setup_path,
|
||||||
|
users_path,
|
||||||
key_packages,
|
key_packages,
|
||||||
deliveries,
|
deliveries,
|
||||||
hybrid_keys,
|
hybrid_keys,
|
||||||
|
users,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +222,29 @@ impl FileBackedStore {
|
|||||||
}
|
}
|
||||||
fs::write(path, bytes).map_err(|e| StorageError::Io(e.to_string()))
|
fs::write(path, bytes).map_err(|e| StorageError::Io(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_users(path: &Path) -> Result<HashMap<String, Vec<u8>>, StorageError> {
|
||||||
|
if !path.exists() {
|
||||||
|
return Ok(HashMap::new());
|
||||||
|
}
|
||||||
|
let bytes = fs::read(path).map_err(|e| StorageError::Io(e.to_string()))?;
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Ok(HashMap::new());
|
||||||
|
}
|
||||||
|
bincode::deserialize(&bytes).map_err(|_| StorageError::Serde)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_users(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
map: &HashMap<String, Vec<u8>>,
|
||||||
|
) -> Result<(), StorageError> {
|
||||||
|
let bytes = bincode::serialize(map).map_err(|_| StorageError::Serde)?;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|e| StorageError::Io(e.to_string()))?;
|
||||||
|
}
|
||||||
|
fs::write(path, bytes).map_err(|e| StorageError::Io(e.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store for FileBackedStore {
|
impl Store for FileBackedStore {
|
||||||
@@ -272,4 +316,33 @@ impl Store for FileBackedStore {
|
|||||||
let map = self.hybrid_keys.lock().unwrap();
|
let map = self.hybrid_keys.lock().unwrap();
|
||||||
Ok(map.get(identity_key).cloned())
|
Ok(map.get(identity_key).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn store_server_setup(&self, setup: Vec<u8>) -> Result<(), StorageError> {
|
||||||
|
if let Some(parent) = self.setup_path.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|e| StorageError::Io(e.to_string()))?;
|
||||||
|
}
|
||||||
|
fs::write(&self.setup_path, setup).map_err(|e| StorageError::Io(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_server_setup(&self) -> Result<Option<Vec<u8>>, StorageError> {
|
||||||
|
if !self.setup_path.exists() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let bytes = fs::read(&self.setup_path).map_err(|e| StorageError::Io(e.to_string()))?;
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_user_record(&self, username: &str, record: Vec<u8>) -> Result<(), StorageError> {
|
||||||
|
let mut map = self.users.lock().unwrap();
|
||||||
|
map.insert(username.to_string(), record);
|
||||||
|
self.flush_users(&self.users_path, &*map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_user_record(&self, username: &str) -> Result<Option<Vec<u8>>, StorageError> {
|
||||||
|
let map = self.users.lock().unwrap();
|
||||||
|
Ok(map.get(username).cloned())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,22 @@ interface NodeService {
|
|||||||
|
|
||||||
# Fetch a peer's hybrid public key (for post-quantum envelope encryption).
|
# Fetch a peer's hybrid public key (for post-quantum envelope encryption).
|
||||||
fetchHybridKey @7 (identityKey :Data) -> (hybridPublicKey :Data);
|
fetchHybridKey @7 (identityKey :Data) -> (hybridPublicKey :Data);
|
||||||
|
|
||||||
|
# ── OPAQUE password-authenticated registration ──────────────────────────
|
||||||
|
|
||||||
|
# Start OPAQUE registration: client sends blinded password element.
|
||||||
|
opaqueRegisterStart @8 (username :Text, request :Data) -> (response :Data);
|
||||||
|
|
||||||
|
# Finish OPAQUE registration: client uploads sealed credential envelope.
|
||||||
|
opaqueRegisterFinish @9 (username :Text, upload :Data) -> (success :Bool);
|
||||||
|
|
||||||
|
# ── OPAQUE password-authenticated login ─────────────────────────────────
|
||||||
|
|
||||||
|
# Start OPAQUE login: client sends credential request.
|
||||||
|
opaqueLoginStart @10 (username :Text, request :Data) -> (response :Data);
|
||||||
|
|
||||||
|
# Finish OPAQUE login: client sends credential finalization, receives session token.
|
||||||
|
opaqueLoginFinish @11 (username :Text, finalization :Data) -> (sessionToken :Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Auth {
|
struct Auth {
|
||||||
|
|||||||
Reference in New Issue
Block a user