Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b83fdd34af | |||
| 356cd2eeac | |||
| 0f6ec8610e | |||
| c3b8c4b2f7 | |||
| 271b9e545c | |||
| 9641f01670 | |||
| a7bb3e8d91 | |||
| 79ce611d21 | |||
| 960e817b8f | |||
| 8b03e66ba7 | |||
| 6a35581299 | |||
| ce124a29a7 | |||
| f62d8813e0 | |||
| a9cf016af2 | |||
| a8403b585b | |||
| e9a7b48da0 | |||
| 66792f57ed | |||
| f8d863249e | |||
| 7d59a2477a | |||
| eca88b0fa4 | |||
| b80a4c8614 | |||
| ec5d342e3a | |||
| 6565655861 | |||
| 5aba886f14 | |||
| 3ee73d541e | |||
| 4588a3036e | |||
| 6194ac07b8 | |||
| a7fcfef5a3 | |||
| fa927b82d8 | |||
| f724478763 | |||
| 040f4f2500 | |||
| 63002e784a | |||
| 4a0b683b70 | |||
| 9e84b1f0c1 | |||
| bf031ad6de | |||
| 933769401c | |||
| ddd85704bb | |||
| 17860c809f | |||
| 2d00fcd934 | |||
| c2c3df98cb | |||
| f429092e21 | |||
| d7ef68d8d1 | |||
| 1a334b575d | |||
| 2126736aff | |||
| a69aa23609 | |||
| 8a2d98e3ce | |||
| 9c4243914e | |||
| 143ede268d | |||
| 81bddb5f6d | |||
| 247ebb7c43 | |||
| 01c052e9a4 | |||
| 3880971e57 | |||
| 6bd31b9521 | |||
| 430c33eb04 | |||
| d45d1eb313 | |||
| 3cb3ebd79b | |||
| b42e5b063e | |||
| f6b30d0db6 | |||
| c33e4c0836 | |||
| be92ccf0da | |||
| 35bf49c48c | |||
| 7335a3dad4 | |||
| 698883c03f | |||
| 8ddef08c72 | |||
| 0d8b3abc6f | |||
| aa2f336904 | |||
| eacaf84430 | |||
| c284b1e8b1 | |||
| 7785d085cf | |||
| bb5b2eafcf | |||
| 09ea406c02 |
@@ -100,7 +100,6 @@ jobs:
|
||||
cp target/release/nymvisor $OUTPUT_DIR
|
||||
cp target/release/nym-node $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
|
||||
cp target/debian/*.deb $OUTPUT_DIR
|
||||
fi
|
||||
|
||||
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.4-dorina-patched] (2025-03-06)
|
||||
|
||||
- use legacy crypto for constructing SURB headers ([#5579])
|
||||
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
- chore: additional logs when attempting to load ecash keys ([#5567])
|
||||
- add full response body to error message upon decoding failure ([#5566])
|
||||
- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
|
||||
- feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
[#5567]: https://github.com/nymtech/nym/pull/5567
|
||||
[#5566]: https://github.com/nymtech/nym/pull/5566
|
||||
[#5565]: https://github.com/nymtech/nym/pull/5565
|
||||
[#5563]: https://github.com/nymtech/nym/pull/5563
|
||||
[#5561]: https://github.com/nymtech/nym/pull/5561
|
||||
|
||||
## [2025.4-dorina] (2025-03-04)
|
||||
|
||||
- fixed sphinx version metrics registration ([#5546])
|
||||
|
||||
Generated
+105
-62
@@ -816,9 +816,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"pairing",
|
||||
@@ -902,9 +902,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -941,7 +941,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -955,7 +955,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1587,7 +1587,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"crossterm_winapi",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -1799,7 +1799,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -1814,7 +1814,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -2433,7 +2433,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.47"
|
||||
version = "1.1.48"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -2527,9 +2527,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.6.1",
|
||||
@@ -4144,6 +4144,12 @@ version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -4780,7 +4786,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.51"
|
||||
version = "1.1.54"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4841,10 +4847,10 @@ dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest 0.12.4",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tendermint 0.40.1",
|
||||
@@ -4890,6 +4896,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"tendermint 0.40.1",
|
||||
"tendermint-rpc",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"ts-rs",
|
||||
@@ -5029,7 +5036,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5112,7 +5119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5324,7 +5331,7 @@ dependencies = [
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"itertools 0.14.0",
|
||||
@@ -5333,7 +5340,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
"serde",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"subtle 2.6.1",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
@@ -5606,7 +5613,7 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -5874,8 +5881,11 @@ name = "nym-http-api-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"hickory-resolver",
|
||||
"http 1.2.0",
|
||||
"mime",
|
||||
"nym-bin-common",
|
||||
"once_cell",
|
||||
"reqwest 0.12.4",
|
||||
@@ -6052,7 +6062,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde_repr",
|
||||
@@ -6153,7 +6163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.50"
|
||||
version = "1.1.52"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6204,7 +6214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6231,6 +6241,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-gateway",
|
||||
"nym-gateway-stats-storage",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-ip-packet-router",
|
||||
"nym-metrics",
|
||||
@@ -6253,7 +6264,7 @@ dependencies = [
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"si-scale",
|
||||
@@ -6345,6 +6356,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-explorer-client",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
@@ -6489,6 +6501,7 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6536,6 +6549,7 @@ dependencies = [
|
||||
"reqwest 0.12.4",
|
||||
"serde",
|
||||
"tap",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -6587,7 +6601,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7192,7 +7206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.14"
|
||||
version = "0.1.16"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7222,7 +7236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nyx-chain-watcher"
|
||||
version = "0.1.11"
|
||||
version = "0.1.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -7232,15 +7246,12 @@ dependencies = [
|
||||
"nym-bin-common",
|
||||
"nym-config",
|
||||
"nym-network-defaults",
|
||||
"nym-node-requests",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"nyxd-scraper",
|
||||
"reqwest 0.12.4",
|
||||
"rocket",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
@@ -8405,9 +8416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.9"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -8594,9 +8605,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rs_merkle"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
|
||||
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
|
||||
dependencies = [
|
||||
"sha2 0.10.8",
|
||||
]
|
||||
@@ -8698,7 +8709,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8710,7 +8721,20 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -8986,9 +9010,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -9001,9 +9025,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -9041,18 +9065,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
|
||||
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9155,9 +9179,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9428,9 +9452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.4.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec6b227c9167ef91b9c426d7a894e9937d30efbfab0b22f31ff1c28c78c5b6f6"
|
||||
checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
@@ -9439,6 +9463,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"chacha",
|
||||
"ctr",
|
||||
"curve25519-dalek 4.1.3",
|
||||
"digest 0.10.7",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
@@ -9948,15 +9973,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -10084,7 +10109,7 @@ dependencies = [
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
@@ -10244,9 +10269,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -10262,15 +10287,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -10313,9 +10338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
version = "1.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -11240,6 +11265,24 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator-status-check"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"nym-bin-common",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.26.3",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
@@ -12040,8 +12083,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+14
-13
@@ -137,7 +137,7 @@ members = [
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
@@ -209,7 +209,7 @@ blake3 = "1.6.1"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.7.2"
|
||||
bytes = "1.10.1"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -241,6 +241,7 @@ doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
encoding_rs = "0.8.35"
|
||||
env_logger = "0.11.6"
|
||||
envy = "0.4"
|
||||
etherparse = "0.13.0"
|
||||
@@ -307,12 +308,12 @@ reqwest = { version = "0.12.4", default-features = false }
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
rs_merkle = "1.4.2"
|
||||
rs_merkle = "1.5.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.16"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.140"
|
||||
serde_json_path = "0.7.2"
|
||||
@@ -321,7 +322,7 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "=0.4.0" # make sure to use version 0.4.0 (or higher) that has removed backwards compatibility
|
||||
sphinx-packet = "=0.3.2"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -330,10 +331,10 @@ syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tempfile = "3.15"
|
||||
tempfile = "3.18"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.43"
|
||||
time = "0.3.39"
|
||||
tokio = "1.44"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
@@ -369,9 +370,9 @@ prometheus = { version = "0.13.0" }
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.1", default-features = false }
|
||||
subtle = "2.5.0"
|
||||
|
||||
# cosmwasm-related
|
||||
@@ -446,4 +447,4 @@ dbg_macro = "deny"
|
||||
exit = "deny"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
unreachable = "deny"
|
||||
unreachable = "deny"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -6,14 +6,15 @@ pub mod v1;
|
||||
pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
pub mod v5;
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v4 as latest;
|
||||
pub use v5 as latest;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 4;
|
||||
pub const CURRENT_VERSION: u8 = 5;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
|
||||
@@ -8,8 +8,8 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
v1, v2, v3,
|
||||
v4::{self, registration::IpPair},
|
||||
v1, v2, v3, v4,
|
||||
v5::{self, registration::IpPair},
|
||||
Error,
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ pub enum AuthenticatorVersion {
|
||||
V2,
|
||||
V3,
|
||||
V4,
|
||||
V5,
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
@@ -34,6 +35,8 @@ impl From<Protocol> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
} else if value.version == v4::VERSION {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -68,6 +71,12 @@ impl InitMessage for v4::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl InitMessage for v5::registration::InitMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
@@ -138,6 +147,24 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.gateway_client.private_ips.into()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.gateway_client.private_ips
|
||||
}
|
||||
@@ -182,29 +209,39 @@ impl TopUpMessage for v4::topup::TopUpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpMessage for v5::topup::TopUpMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn credential(&self) -> CredentialSpendingData {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AuthenticatorRequest {
|
||||
Initial {
|
||||
msg: Box<dyn InitMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Recipient,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
Final {
|
||||
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Recipient,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
QueryBandwidth {
|
||||
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Recipient,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
TopUpBandwidth {
|
||||
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
reply_to: Recipient,
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
}
|
||||
@@ -218,7 +255,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
|
||||
@@ -227,7 +264,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
@@ -237,7 +274,7 @@ impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
version: value.version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -251,20 +288,20 @@ impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -278,20 +315,20 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -299,7 +336,7 @@ impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -313,20 +350,20 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
@@ -334,7 +371,42 @@ impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: value.reply_to,
|
||||
reply_to: Some(value.reply_to),
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v5::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
use crate::{v4, v5};
|
||||
|
||||
impl From<v4::request::AuthenticatorRequest> for v5::request::AuthenticatorRequest {
|
||||
fn from(authenticator_request: v4::request::AuthenticatorRequest) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: authenticator_request.data.into(),
|
||||
request_id: authenticator_request.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::request::AuthenticatorRequestData> for v5::request::AuthenticatorRequestData {
|
||||
fn from(authenticator_request_data: v4::request::AuthenticatorRequestData) -> Self {
|
||||
match authenticator_request_data {
|
||||
v4::request::AuthenticatorRequestData::Initial(init_msg) => {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_msg.into())
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::Final(final_msg) => {
|
||||
v5::request::AuthenticatorRequestData::Final(Box::new((*final_msg).into()))
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => {
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
}
|
||||
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::InitMessage> for v5::registration::InitMessage {
|
||||
fn from(init_msg: v4::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::FinalMessage> for v5::registration::FinalMessage {
|
||||
fn from(final_msg: v4::registration::FinalMessage) -> Self {
|
||||
Self {
|
||||
gateway_client: final_msg.gateway_client.into(),
|
||||
credential: final_msg.credential,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::GatewayClient> for v5::registration::GatewayClient {
|
||||
fn from(gateway_client: v4::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::GatewayClient> for v4::registration::GatewayClient {
|
||||
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::ClientMac> for v5::registration::ClientMac {
|
||||
fn from(client_mac: v4::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::ClientMac> for v4::registration::ClientMac {
|
||||
fn from(client_mac: v5::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v4::topup::TopUpMessage>> for Box<v5::topup::TopUpMessage> {
|
||||
fn from(top_up_message: Box<v4::topup::TopUpMessage>) -> Self {
|
||||
Box::new(v5::topup::TopUpMessage {
|
||||
pub_key: top_up_message.pub_key,
|
||||
credential: top_up_message.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::AuthenticatorResponse> for v5::response::AuthenticatorResponse {
|
||||
fn from(value: v4::response::AuthenticatorResponse) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
version: 5,
|
||||
service_provider_type: value.protocol.service_provider_type,
|
||||
},
|
||||
data: value.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::AuthenticatorResponseData> for v5::response::AuthenticatorResponseData {
|
||||
fn from(authenticator_response_data: v4::response::AuthenticatorResponseData) -> Self {
|
||||
match authenticator_response_data {
|
||||
v4::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_response.into(),
|
||||
)
|
||||
}
|
||||
v4::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
v5::response::AuthenticatorResponseData::Registered(registered_response.into())
|
||||
}
|
||||
v4::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
),
|
||||
v4::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
|
||||
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RegisteredResponse> for v5::response::RegisteredResponse {
|
||||
fn from(value: v4::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::PendingRegistrationResponse> for v5::response::PendingRegistrationResponse {
|
||||
fn from(value: v4::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistrationData> for v5::registration::RegistrationData {
|
||||
fn from(value: v4::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegistrationData> for v4::registration::RegistrationData {
|
||||
fn from(value: v5::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::RemainingBandwidthResponse> for v5::response::RemainingBandwidthResponse {
|
||||
fn from(value: v4::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidthResponse {
|
||||
fn from(value: v4::response::TopUpBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RemainingBandwidthData> for v5::registration::RemainingBandwidthData {
|
||||
fn from(value: v4::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::IpPair> for v5::registration::IpPair {
|
||||
fn from(value: v4::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::IpPair> for v4::registration::IpPair {
|
||||
fn from(value: v5::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
util::tests::{CREDENTIAL_BYTES, RECIPIENT},
|
||||
v4,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn upgrade_initial_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) = v4::request::AuthenticatorRequest::new_initial_request(
|
||||
v4::registration::InitMessage::new(pub_key),
|
||||
reply_to,
|
||||
);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::Initial(v5::registration::InitMessage {
|
||||
pub_key
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_final_req() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let gateway_client = v4::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let credential = Some(CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap());
|
||||
let final_message = v4::registration::FinalMessage {
|
||||
gateway_client: gateway_client.clone(),
|
||||
credential: credential.clone(),
|
||||
};
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) =
|
||||
v4::request::AuthenticatorRequest::new_final_request(final_message, reply_to);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::Final(Box::new(
|
||||
v5::registration::FinalMessage {
|
||||
gateway_client: v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v5::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
credential
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_query_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let (msg, _) = v4::request::AuthenticatorRequest::new_query_request(pub_key, reply_to);
|
||||
let upgraded_msg = v5::request::AuthenticatorRequest::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_pending_reg_resp() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let wg_port = 51822;
|
||||
let gateway_data = v4::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let registration_data = v4::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data,
|
||||
request_id,
|
||||
reply_to,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(
|
||||
v5::response::PendingRegistrationResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data: v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v5::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
wg_port
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_registered_resp() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_registered(
|
||||
registred_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_remaining_bandwidth_resp() {
|
||||
let available_bandwidth = 42;
|
||||
let remaining_bandwidth_data = Some(v4::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
});
|
||||
let request_id = 123;
|
||||
let reply_to = Recipient::try_from_base58_string(RECIPIENT).unwrap();
|
||||
|
||||
let msg = v4::response::AuthenticatorResponse::new_remaining_bandwidth(
|
||||
remaining_bandwidth_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v5::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.protocol,
|
||||
Protocol {
|
||||
version: 5,
|
||||
service_provider_type: ServiceProviderType::Authenticator
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
v5::response::RemainingBandwidthResponse {
|
||||
request_id,
|
||||
reply: Some(v5::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
|
||||
pub const VERSION: u8 = 5;
|
||||
@@ -0,0 +1,287 @@
|
||||
// -2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::encryption::PrivateKey;
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
|
||||
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpPair {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
let (before_last_byte, last_byte) = match value {
|
||||
std::net::IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
std::net::IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
before_last_byte,
|
||||
last_byte,
|
||||
);
|
||||
let ipv6 = Ipv6Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
|
||||
last_bytes,
|
||||
);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<CredentialSpendingData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IPs (v4 and v6)
|
||||
pub private_ips: IpPair,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ips: IpPair,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes());
|
||||
let local_public: x25519_dalek::PublicKey = (&static_secret).into();
|
||||
|
||||
let dh = static_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public),
|
||||
private_ips,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
|
||||
#[allow(clippy::expect_used)]
|
||||
let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes());
|
||||
|
||||
let dh = static_secret.diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ClientMac(Vec<u8>);
|
||||
|
||||
impl fmt::Display for ClientMac {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", general_purpose::STANDARD.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMac {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(mac: Vec<u8>) -> Self {
|
||||
ClientMac(mac)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ClientMac {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
|
||||
serializer.serialize_str(&encoded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ClientMac {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let encoded_key = String::deserialize(deserializer)?;
|
||||
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
|
||||
#[test]
|
||||
fn create_ip_pair() {
|
||||
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
|
||||
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
|
||||
|
||||
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let gateway_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
let client_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = 5;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol {
|
||||
version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registred_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
version: VERSION,
|
||||
},
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RemainingBandwidthData,
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -50,7 +50,7 @@ const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
|
||||
|
||||
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
|
||||
|
||||
@@ -658,6 +658,9 @@ pub struct ReplySurbs {
|
||||
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
|
||||
/// the default value is used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbs {
|
||||
@@ -675,6 +678,7 @@ impl Default for ReplySurbs {
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
surb_mix_hops: None,
|
||||
fresh_sender_tags: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
-- make aes256gcm column non-nullable and drop any gateways that still use the legacy keys
|
||||
-- (since they'd be unusable after this change)
|
||||
CREATE TABLE remote_gateway_details_tmp
|
||||
(
|
||||
gateway_id_bs58 TEXT NOT NULL UNIQUE PRIMARY KEY REFERENCES registered_gateway (gateway_id_bs58),
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL,
|
||||
gateway_owner_address TEXT,
|
||||
gateway_listener TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO remote_gateway_details_tmp (gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address,
|
||||
gateway_listener)
|
||||
SELECT gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener
|
||||
FROM remote_gateway_details
|
||||
WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE remote_gateway_details;
|
||||
ALTER TABLE remote_gateway_details_tmp
|
||||
RENAME TO remote_gateway_details;
|
||||
@@ -156,26 +156,48 @@ impl StorageManager {
|
||||
|
||||
pub(crate) async fn set_remote_gateway_details(
|
||||
&self,
|
||||
gateway_id_bs58: String,
|
||||
derived_aes256_gcm_siv_key: &[u8],
|
||||
gateway_owner_address: Option<String>,
|
||||
gateway_listener: String,
|
||||
remote: &RawRemoteGatewayDetails,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
VALUES (?, ?, ?, ?)
|
||||
INSERT INTO remote_gateway_details(gateway_id_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key, gateway_owner_address, gateway_listener)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
"#,
|
||||
gateway_id_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address,
|
||||
gateway_listener,
|
||||
remote.gateway_id_bs58,
|
||||
remote.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
remote.derived_aes256_gcm_siv_key,
|
||||
remote.gateway_owner_address,
|
||||
remote.gateway_listener,
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn update_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE remote_gateway_details
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE gateway_id_bs58 = ?
|
||||
"#,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_id_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_remote_gateway_details(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{
|
||||
use async_trait::async_trait;
|
||||
use manager::StorageManager;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod error;
|
||||
@@ -118,16 +119,9 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
|
||||
match &details.details {
|
||||
GatewayDetails::Remote(remote_details) => {
|
||||
let raw_details = remote_details.into();
|
||||
self.manager
|
||||
.set_remote_gateway_details(
|
||||
remote_details.gateway_id.to_base58_string(),
|
||||
remote_details.shared_key.as_bytes(),
|
||||
remote_details
|
||||
.gateway_owner_address
|
||||
.as_ref()
|
||||
.map(|o| o.to_string()),
|
||||
remote_details.gateway_listener.to_string(),
|
||||
)
|
||||
.set_remote_gateway_details(&raw_details)
|
||||
.await?;
|
||||
}
|
||||
GatewayDetails::Custom(custom_details) => {
|
||||
@@ -140,6 +134,21 @@ impl GatewaysDetailsStore for OnDiskGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: ed25519::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.manager
|
||||
.update_remote_gateway_key(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ideally all of those should be run under a storage tx to ensure storage consistency,
|
||||
// but at that point it's fine
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::{ActiveGateway, GatewayRegistration};
|
||||
use crate::{BadGateway, GatewaysDetailsStore};
|
||||
use crate::{BadGateway, GatewayDetails, GatewaysDetailsStore};
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_requests::{SharedGatewayKey, SharedSymmetricKey};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
@@ -94,6 +96,29 @@ impl GatewaysDetailsStore for InMemGatewaysDetails {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
if let Some(target) = guard.gateways.get_mut(&gateway_id.to_string()) {
|
||||
let GatewayDetails::Remote(details) = &mut target.details else {
|
||||
return Ok(());
|
||||
};
|
||||
assert_eq!(Arc::strong_count(&details.shared_key), 1);
|
||||
|
||||
// eh. that's nasty, but it's only ever used for ephemeral clients so should be fine for now...
|
||||
details.shared_key = Arc::new(SharedGatewayKey::Current(
|
||||
SharedSymmetricKey::try_from_bytes(updated_key.as_bytes()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
let mut guard = self.inner.write().await;
|
||||
if let Some(active) = guard.active_gateway.as_ref() {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::SharedSymmetricKey;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod backend;
|
||||
@@ -61,6 +62,12 @@ pub trait GatewaysDetailsStore {
|
||||
details: &GatewayRegistration,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: identity::PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// Remove given gateway details from the underlying store.
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
use crate::BadGateway;
|
||||
use cosmrs::AccountId;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
@@ -64,7 +65,7 @@ impl From<GatewayDetails> for GatewayRegistration {
|
||||
impl GatewayDetails {
|
||||
pub fn new_remote(
|
||||
gateway_id: identity::PublicKey,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
gateway_owner_address: Option<AccountId>,
|
||||
gateway_listener: Url,
|
||||
) -> Self {
|
||||
@@ -87,7 +88,7 @@ impl GatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<&SharedSymmetricKey> {
|
||||
pub fn shared_key(&self) -> Option<&SharedGatewayKey> {
|
||||
match self {
|
||||
GatewayDetails::Remote(details) => Some(&details.shared_key),
|
||||
GatewayDetails::Custom(_) => None,
|
||||
@@ -167,7 +168,8 @@ pub struct RegisteredGateway {
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct RawRemoteGatewayDetails {
|
||||
pub gateway_id_bs58: String,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub gateway_owner_address: Option<String>,
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
@@ -184,11 +186,35 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
})?;
|
||||
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
let shared_key =
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => {
|
||||
return Err(BadGateway::MissingSharedKey {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
})
|
||||
}
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key =
|
||||
SharedSymmetricKey::try_from_bytes(aes256gcm_siv).map_err(|source| {
|
||||
BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
SharedGatewayKey::Current(current_key)
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| BadGateway::MalformedSharedKeys {
|
||||
gateway_id: value.gateway_id_bs58.clone(),
|
||||
source,
|
||||
})?;
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
}
|
||||
};
|
||||
|
||||
let gateway_owner_address = value
|
||||
.gateway_owner_address
|
||||
@@ -221,11 +247,29 @@ impl TryFrom<RawRemoteGatewayDetails> for RemoteGatewayDetails {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a RemoteGatewayDetails> for RawRemoteGatewayDetails {
|
||||
fn from(value: &'a RemoteGatewayDetails) -> Self {
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match value.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
|
||||
RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id.to_base58_string(),
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address.as_ref().map(|o| o.to_string()),
|
||||
gateway_listener: value.gateway_listener.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemoteGatewayDetails {
|
||||
pub gateway_id: identity::PublicKey,
|
||||
|
||||
pub shared_key: Arc<SharedSymmetricKey>,
|
||||
pub shared_key: Arc<SharedGatewayKey>,
|
||||
|
||||
pub gateway_owner_address: Option<AccountId>,
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ where
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
|
||||
@@ -187,6 +187,8 @@ where
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
|
||||
@@ -394,6 +394,7 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -402,6 +403,7 @@ where
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
let managed_keys = initialisation_result.client_keys;
|
||||
let GatewayDetails::Remote(details) = initialisation_result.gateway_registration.details
|
||||
@@ -456,13 +458,31 @@ where
|
||||
// we need to:
|
||||
// - perform handshake (reg or auth)
|
||||
// - check for key upgrade
|
||||
// - maybe perform another upgrade handshake
|
||||
// - check for bandwidth
|
||||
// - start background tasks
|
||||
let _auth_res = gateway_client
|
||||
let auth_res = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
if auth_res.requires_key_upgrade {
|
||||
// drop the shared_key arc because we don't need it and we can't hold it for the purposes of upgrade
|
||||
drop(auth_res);
|
||||
|
||||
let updated_key = gateway_client
|
||||
.upgrade_key_authenticated()
|
||||
.await
|
||||
.map_err(gateway_failure)?;
|
||||
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
|
||||
gateway_client
|
||||
.claim_initial_bandwidth()
|
||||
.await
|
||||
@@ -481,6 +501,7 @@ where
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
bandwidth_controller: Option<BandwidthController<C, S::CredentialStore>>,
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
@@ -489,6 +510,7 @@ where
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
// if we have setup custom gateway sender and persisted details agree with it, return it
|
||||
if let Some(mut custom_gateway_transceiver) = custom_gateway_transceiver {
|
||||
@@ -511,6 +533,7 @@ where
|
||||
config,
|
||||
initialisation_result,
|
||||
bandwidth_controller,
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
@@ -721,7 +744,8 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (reply_storage_backend, credential_store, _) = self.client_store.into_runtime_stores();
|
||||
let (reply_storage_backend, credential_store, details_store) =
|
||||
self.client_store.into_runtime_stores();
|
||||
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -802,6 +826,7 @@ where
|
||||
&self.config,
|
||||
init_res,
|
||||
bandwidth_controller,
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
@@ -2,11 +2,24 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1_1_33 {
|
||||
use crate::client::base_client::{
|
||||
non_wasm_helpers::setup_fs_gateways_storage,
|
||||
storage::helpers::{set_active_gateway, store_gateway_details},
|
||||
};
|
||||
use crate::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use crate::config::disk_persistence::CommonClientPaths;
|
||||
use crate::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use crate::error::ClientCoreError;
|
||||
use nym_client_core_gateways_storage::{
|
||||
CustomGatewayDetails, GatewayDetails, GatewayRegistration, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_requests::shared_key::LegacySharedKeys;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{digest::Digest, Sha256};
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
mod base64 {
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
@@ -44,18 +57,155 @@ pub mod v1_1_33 {
|
||||
details: OldGatewayEndpointConfigV1_1_33,
|
||||
}
|
||||
|
||||
impl PersistedGatewayConfig {
|
||||
fn verify(&self, shared_key: &LegacySharedKeys) -> bool {
|
||||
let key_bytes = Zeroizing::new(shared_key.to_bytes());
|
||||
|
||||
let mut key_hasher = Sha256::new();
|
||||
key_hasher.update(&key_bytes);
|
||||
let key_hash = key_hasher.finalize();
|
||||
|
||||
self.key_hash == key_hash.deref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct PersistedCustomGatewayDetails {
|
||||
gateway_id: String,
|
||||
}
|
||||
|
||||
fn load_shared_key<P: AsRef<Path>>(path: P) -> Result<LegacySharedKeys, ClientCoreError> {
|
||||
// the shared key was a simple pem file
|
||||
Ok(nym_pemstore::load_key(path)?)
|
||||
}
|
||||
|
||||
fn gateway_details_from_raw(
|
||||
gateway_id: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
gateway_shared_key: LegacySharedKeys,
|
||||
) -> Result<GatewayDetails, ClientCoreError> {
|
||||
Ok(GatewayDetails::Remote(RemoteGatewayDetails {
|
||||
gateway_id: gateway_id
|
||||
.parse()
|
||||
.map_err(|err| ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
})?,
|
||||
shared_key: Arc::new(gateway_shared_key.into()),
|
||||
gateway_owner_address: Some(gateway_owner.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway owner address was malformed: {err}"),
|
||||
}
|
||||
})?),
|
||||
gateway_listener: gateway_listener.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway listener address was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
}))
|
||||
}
|
||||
|
||||
// helper to extract shared key and gateway details into the new GatewayRegistration
|
||||
fn extract_gateway_registration(
|
||||
storage_paths: &CommonClientPathsV1_1_33,
|
||||
) -> Result<GatewayRegistration, ClientCoreError> {
|
||||
let details_file = std::fs::File::open(&storage_paths.gateway_details).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to open gateway details file at {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
// in v1.1.33 of the clients, the gateway details struct was saved as json
|
||||
let details: PersistedGatewayDetails =
|
||||
serde_json::from_reader(details_file).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!(
|
||||
"failed to deserialize gateway details from {}: {err}",
|
||||
storage_paths.gateway_details.display()
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
let details = match details {
|
||||
PersistedGatewayDetails::Default(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
if !config.verify(&gateway_shared_key) {
|
||||
return Err(ClientCoreError::UpgradeFailure {
|
||||
message: "failed to verify consistency of the existing gateway details"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
gateway_details_from_raw(
|
||||
config.details.gateway_id,
|
||||
config.details.gateway_owner,
|
||||
config.details.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
}
|
||||
PersistedGatewayDetails::Custom(custom) => {
|
||||
GatewayDetails::Custom(CustomGatewayDetails {
|
||||
gateway_id: custom.gateway_id.parse().map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("the stored gateway id was malformed: {err}"),
|
||||
}
|
||||
})?,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(details.into())
|
||||
}
|
||||
|
||||
// it's responsibility of the caller to ensure this is called **after** new registration has already been saved
|
||||
fn remove_old_gateway_details(storage_paths: &CommonClientPathsV1_1_33) -> std::io::Result<()> {
|
||||
std::fs::remove_file(&storage_paths.gateway_details)?;
|
||||
|
||||
if storage_paths.keys.gateway_shared_key_file.exists() {
|
||||
std::fs::remove_file(&storage_paths.keys.gateway_shared_key_file)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migrate_gateway_details(
|
||||
_old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
_new_storage_paths: &CommonClientPaths,
|
||||
_preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
old_storage_paths: &CommonClientPathsV1_1_33,
|
||||
new_storage_paths: &CommonClientPaths,
|
||||
preloaded_config: Option<OldGatewayEndpointConfigV1_1_33>,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
Err(ClientCoreError::UnsupportedMigration(
|
||||
"migration of legacy keys has been removed and is no longer supported".into(),
|
||||
))
|
||||
let gateway_registration = match preloaded_config {
|
||||
Some(config) => {
|
||||
let gateway_shared_key =
|
||||
load_shared_key(&old_storage_paths.keys.gateway_shared_key_file)?;
|
||||
gateway_details_from_raw(
|
||||
config.gateway_id,
|
||||
config.gateway_owner,
|
||||
config.gateway_listener,
|
||||
gateway_shared_key,
|
||||
)?
|
||||
.into()
|
||||
}
|
||||
None => extract_gateway_registration(old_storage_paths)?,
|
||||
};
|
||||
|
||||
// since we're migrating to a brand new store, the store should be empty
|
||||
// and thus set the 'new' gateway as the active one
|
||||
let details_store =
|
||||
setup_fs_gateways_storage(&new_storage_paths.gateway_registrations).await?;
|
||||
store_gateway_details(&details_store, &gateway_registration).await?;
|
||||
set_active_gateway(
|
||||
&details_store,
|
||||
&gateway_registration.details.gateway_id().to_base58_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
remove_old_gateway_details(old_storage_paths).map_err(|err| {
|
||||
ClientCoreError::UpgradeFailure {
|
||||
message: format!("failed to remove old data: {err}"),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use nym_crypto::{
|
||||
asymmetric::{encryption, identity},
|
||||
hkdf::{DerivationMaterial, InvalidLength},
|
||||
};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::sync::Arc;
|
||||
@@ -106,5 +106,7 @@ fn _assert_keys_zeroize_on_drop() {
|
||||
_assert_zeroize_on_drop::<identity::KeyPair>();
|
||||
_assert_zeroize_on_drop::<encryption::KeyPair>();
|
||||
_assert_zeroize_on_drop::<AckKey>();
|
||||
_assert_zeroize_on_drop::<LegacySharedKeys>();
|
||||
_assert_zeroize_on_drop::<SharedSymmetricKey>();
|
||||
_assert_zeroize_on_drop::<SharedGatewayKey>();
|
||||
}
|
||||
|
||||
@@ -33,10 +33,12 @@ pub enum PreparationError {
|
||||
#[error(transparent)]
|
||||
NymTopologyError(#[from] NymTopologyError),
|
||||
|
||||
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")]
|
||||
#[error("message too long for a single SURB, splitting into {fragments} fragments.")]
|
||||
MessageTooLongForSingleSurb { fragments: usize },
|
||||
|
||||
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")]
|
||||
#[error(
|
||||
"not enough reply SURBs to send the message, available: {available} required: {required}."
|
||||
)]
|
||||
NotEnoughSurbs { available: usize, required: usize },
|
||||
}
|
||||
|
||||
|
||||
@@ -746,7 +746,7 @@ where
|
||||
.request_additional_reply_surbs(target, request_size)
|
||||
.await
|
||||
{
|
||||
warn!("failed to request additional surbs... - {err}")
|
||||
info!("{err}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[error("could not perform the state migration: {0}")]
|
||||
UnsupportedMigration(String),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
@@ -313,9 +315,15 @@ pub(super) async fn register_with_gateway(
|
||||
gateway_id: identity::PublicKey,
|
||||
gateway_listener: Url,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let mut gateway_client =
|
||||
GatewayClient::new_init(gateway_listener, gateway_id, our_identity.clone());
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway_listener,
|
||||
gateway_id,
|
||||
our_identity.clone(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
);
|
||||
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
@@ -335,6 +343,14 @@ pub(super) async fn register_with_gateway(
|
||||
}
|
||||
})?;
|
||||
|
||||
// this should NEVER happen, if it did, it means the function was misused,
|
||||
// because for any fresh **registration**, the derived key is always up to date
|
||||
if auth_response.requires_key_upgrade {
|
||||
return Err(ClientCoreError::UnexpectedKeyUpgrade {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(RegistrationResult {
|
||||
shared_keys: auth_response.initial_shared_key,
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
|
||||
@@ -23,6 +23,8 @@ use nym_topology::node::RoutingNode;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::Serialize;
|
||||
#[cfg(unix)]
|
||||
use std::{os::fd::RawFd, sync::Arc};
|
||||
|
||||
pub mod helpers;
|
||||
pub mod types;
|
||||
@@ -53,6 +55,7 @@ async fn setup_new_gateway<K, D>(
|
||||
details_store: &D,
|
||||
selection_specification: GatewaySelectionSpecification,
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
@@ -108,9 +111,14 @@ where
|
||||
// if we're using a 'normal' gateway setup, do register
|
||||
let our_identity = client_keys.identity_keypair();
|
||||
|
||||
let registration =
|
||||
helpers::register_with_gateway(gateway_id, gateway_listener.clone(), our_identity)
|
||||
.await?;
|
||||
let registration = helpers::register_with_gateway(
|
||||
gateway_id,
|
||||
gateway_listener.clone(),
|
||||
our_identity,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
GatewayDetails::new_remote(
|
||||
gateway_id,
|
||||
@@ -203,9 +211,19 @@ where
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
} => {
|
||||
log::debug!("GatewaySetup::New with spec: {specification:?}");
|
||||
setup_new_gateway(key_store, details_store, specification, available_gateways).await
|
||||
setup_new_gateway(
|
||||
key_store,
|
||||
details_store,
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await
|
||||
}
|
||||
GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
|
||||
@@ -11,13 +11,15 @@ use nym_client_core_gateways_storage::{
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::client::InitGatewayClient;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
@@ -96,7 +98,7 @@ impl SelectedGateway {
|
||||
/// - shared keys derived between ourselves and the node
|
||||
/// - an authenticated handle of an ephemeral handle created for the purposes of registration
|
||||
pub struct RegistrationResult {
|
||||
pub shared_keys: Arc<SharedSymmetricKey>,
|
||||
pub shared_keys: Arc<SharedGatewayKey>,
|
||||
pub authenticated_ephemeral_client: InitGatewayClient,
|
||||
}
|
||||
|
||||
@@ -208,6 +210,10 @@ pub enum GatewaySetup {
|
||||
|
||||
// TODO: seems to be a bit inefficient to pass them by value
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
|
||||
/// Callback useful for allowing initial connection to gateway
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
@@ -231,6 +237,8 @@ impl Debug for GatewaySetup {
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: _,
|
||||
} => f
|
||||
.debug_struct("GatewaySetup::New")
|
||||
.field("specification", specification)
|
||||
@@ -270,6 +278,8 @@ impl GatewaySetup {
|
||||
additional_data: None,
|
||||
},
|
||||
available_gateways: vec![],
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -52,7 +52,10 @@ impl Backend {
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
database_path: P,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
@@ -118,6 +121,9 @@ impl Backend {
|
||||
if days > 2 {
|
||||
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
|
||||
manager.delete_all_tags().await?;
|
||||
} else if fresh_sender_tags {
|
||||
debug!("starting with fresh sender tags");
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
|
||||
@@ -20,8 +20,9 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt, ServerResponse,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
SensitiveServerResponse, ServerResponse, SharedGatewayKey, SharedSymmetricKey,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -46,6 +47,7 @@ use std::os::raw::c_int as RawFd;
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasmtimer::tokio::sleep;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub mod config;
|
||||
|
||||
@@ -80,7 +82,8 @@ impl GatewayConfig {
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct AuthenticationResponse {
|
||||
pub initial_shared_key: Arc<SharedSymmetricKey>,
|
||||
pub initial_shared_key: Arc<SharedGatewayKey>,
|
||||
pub requires_key_upgrade: bool,
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
@@ -92,7 +95,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
connection: SocketState,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
@@ -116,7 +119,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
gateway_config: GatewayConfig,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
// TODO: make it mandatory. if you don't want to pass it, use `new_init`
|
||||
shared_key: Option<Arc<SharedSymmetricKey>>,
|
||||
shared_key: Option<Arc<SharedGatewayKey>>,
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
@@ -146,7 +149,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedSymmetricKey>> {
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
||||
self.shared_key.clone()
|
||||
}
|
||||
|
||||
@@ -268,7 +271,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.shared_key() {
|
||||
let encrypted = message.encrypt(&shared_key)?;
|
||||
let encrypted = message.encrypt(&*shared_key)?;
|
||||
Box::pin(self.send_websocket_message(encrypted)).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -407,39 +410,49 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gateway_protocol(&self, gateway_protocol: u8) -> Result<(), GatewayClientError> {
|
||||
fn check_gateway_protocol(
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// client should reject any gateways that do not indicate they support auth v2 or aes256gcm-siv
|
||||
if !gateway_protocol.supports_authenticate_v2()
|
||||
|| !gateway_protocol.supports_aes256_gcm_siv()
|
||||
{
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gateway_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
}
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
|
||||
// we can't handle gateways with higher protocol than ours
|
||||
if gateway_protocol <= CURRENT_PROTOCOL_VERSION {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
} else {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gateway_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn register(&mut self) -> Result<(), GatewayClientError> {
|
||||
async fn register(
|
||||
&mut self,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
!derive_aes256_gcm_siv_key
|
||||
);
|
||||
|
||||
// it's fine to instantiate it here as it's only used once (during authentication or registration)
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
let mut rng = OsRng;
|
||||
@@ -451,6 +464,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.task_client.clone(),
|
||||
)
|
||||
@@ -478,11 +492,77 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = Some(gateway_protocol);
|
||||
self.negotiated_protocol = gateway_protocol;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upgrade_key_authenticated(
|
||||
&mut self,
|
||||
) -> Result<Zeroizing<SharedSymmetricKey>, GatewayClientError> {
|
||||
info!("*** STARTING AES128CTR-HMAC KEY UPGRADE INTO AES256GCM-SIV***");
|
||||
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
}
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
if !shared_key.is_legacy() {
|
||||
return Err(GatewayClientError::KeyAlreadyUpgraded);
|
||||
}
|
||||
|
||||
// make sure we have the only reference, so we could safely swap it
|
||||
if Arc::strong_count(shared_key) != 1 {
|
||||
return Err(GatewayClientError::KeyAlreadyInUse);
|
||||
}
|
||||
|
||||
assert!(shared_key.is_legacy());
|
||||
let legacy_key = shared_key.unwrap_legacy();
|
||||
let (updated_key, hkdf_salt) = legacy_key.upgrade();
|
||||
let derived_key_digest = updated_key.digest();
|
||||
|
||||
let upgrade_request = ClientRequest::UpgradeKey {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
}
|
||||
.encrypt(legacy_key)?;
|
||||
|
||||
info!("sending upgrade request and awaiting the acknowledgement back");
|
||||
let (ciphertext, nonce) = match self.send_websocket_message(upgrade_request).await? {
|
||||
ServerResponse::EncryptedResponse { ciphertext, nonce } => (ciphertext, nonce),
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
// attempt to decrypt it using NEW key
|
||||
let Ok(response) = SensitiveServerResponse::decrypt(&ciphertext, &nonce, &updated_key)
|
||||
else {
|
||||
return Err(GatewayClientError::FatalKeyUpgradeFailure);
|
||||
};
|
||||
|
||||
match response {
|
||||
SensitiveServerResponse::KeyUpgradeAck { .. } => {
|
||||
info!("received key upgrade acknowledgement")
|
||||
}
|
||||
_ => return Err(GatewayClientError::FatalKeyUpgradeFailure),
|
||||
}
|
||||
|
||||
// perform in memory swap and make a copy for updating storage
|
||||
let zeroizing_updated_key = updated_key.zeroizing_clone();
|
||||
self.shared_key = Some(Arc::new(updated_key.into()));
|
||||
|
||||
Ok(zeroizing_updated_key)
|
||||
}
|
||||
|
||||
async fn send_authenticate_request_and_handle_response(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
@@ -497,7 +577,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.authenticated = status;
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
|
||||
self.negotiated_protocol = Some(protocol_version);
|
||||
self.negotiated_protocol = protocol_version;
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
|
||||
self.task_client.send_status_msg(Box::new(
|
||||
@@ -510,6 +590,27 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn authenticate_v1(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v1 authentication");
|
||||
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let self_address = self
|
||||
.local_identity
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
@@ -521,13 +622,17 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(&mut self) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
self.authenticate_v2().await
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to either call register or authenticate based on self.shared_key value
|
||||
@@ -545,26 +650,24 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// 1. check gateway's protocol version
|
||||
// if we failed to get this request resolved, it means the gateway is on an old version
|
||||
// that definitely does not support auth v2 or aes256gcm, so we bail
|
||||
let gw_protocol = self.get_gateway_protocol().await?;
|
||||
let gw_protocol = match self.get_gateway_protocol().await {
|
||||
Ok(protocol) => Some(protocol),
|
||||
Err(_) => {
|
||||
// if we failed to send the request, it means the gateway is running the old binary,
|
||||
// so it has reset our connection - we have to reconnect
|
||||
self.establish_connection().await?;
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
error!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
}
|
||||
if !supports_aes_gcm_siv {
|
||||
error!("this gateway is on an old version that doesn't support authentication v2");
|
||||
}
|
||||
|
||||
if !supports_auth_v2 || !supports_aes_gcm_siv {
|
||||
// we can't continue
|
||||
return Err(GatewayClientError::IncompatibleProtocol {
|
||||
gateway: gw_protocol,
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
});
|
||||
if !supports_auth_v2 {
|
||||
warn!("this gateway is on an old version that doesn't support authentication v2")
|
||||
}
|
||||
|
||||
if self.authenticated {
|
||||
@@ -572,6 +675,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return if let Some(shared_key) = &self.shared_key {
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: shared_key.is_legacy() && supports_aes_gcm_siv,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailureWithPreexistingSharedKey)
|
||||
@@ -579,20 +683,23 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate().await?;
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade,
|
||||
})
|
||||
} else {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
}
|
||||
} else {
|
||||
self.register().await?;
|
||||
self.register(supports_aes_gcm_siv).await?;
|
||||
|
||||
// if registration didn't return an error, we MUST have an associated shared key
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
@@ -601,6 +708,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// so no upgrades are required
|
||||
Ok(AuthenticationResponse {
|
||||
initial_shared_key: Arc::clone(shared_key),
|
||||
requires_key_upgrade: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -908,7 +1016,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate().await?;
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
@@ -956,6 +1065,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
gateway_listener: Url,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Self {
|
||||
log::trace!("Initialising gateway client");
|
||||
use futures::channel::mpsc;
|
||||
@@ -981,7 +1091,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ pub(crate) async fn connect_async(
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!("Resolve error {err}"))?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
@@ -49,20 +50,27 @@ pub(crate) async fn connect_async(
|
||||
address: endpoint.to_owned(),
|
||||
});
|
||||
for sock_addr in sock_addrs {
|
||||
tracing::info!("Trying with {sock_addr}");
|
||||
let socket = if sock_addr.is_ipv4() {
|
||||
TcpSocket::new_v4()
|
||||
} else {
|
||||
TcpSocket::new_v6()
|
||||
}
|
||||
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
.map_err(|err| {
|
||||
tracing::error!("Couldn't create the socket");
|
||||
GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
tracing::info!("Preparing to call callback");
|
||||
#[cfg(unix)]
|
||||
if let Some(callback) = connection_fd_callback.as_ref() {
|
||||
tracing::info!("Calling callback");
|
||||
callback.as_ref()(socket.as_raw_fd());
|
||||
}
|
||||
tracing::info!("Preparing to connect");
|
||||
|
||||
match socket.connect(sock_addr).await {
|
||||
Ok(s) => {
|
||||
|
||||
@@ -114,7 +114,7 @@ pub enum GatewayClientError {
|
||||
MixnetMsgSenderFailedToSend,
|
||||
|
||||
#[error("Attempted to negotiate connection with gateway using incompatible protocol version. Ours is {current} and the gateway reports {gateway:?}")]
|
||||
IncompatibleProtocol { gateway: u8, current: u8 },
|
||||
IncompatibleProtocol { gateway: Option<u8>, current: u8 },
|
||||
|
||||
#[error(
|
||||
"The packet router hasn't been set - are you sure you started up the client correctly?"
|
||||
|
||||
@@ -7,7 +7,9 @@ use tracing::{error, warn};
|
||||
use tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
pub use client::{config::GatewayClientConfig, GatewayClient, GatewayConfig};
|
||||
pub use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
pub use nym_gateway_requests::shared_key::{
|
||||
LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey,
|
||||
};
|
||||
pub use packet_router::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
|
||||
PacketRouter,
|
||||
@@ -45,7 +47,7 @@ pub(crate) fn cleanup_socket_messages(
|
||||
|
||||
pub(crate) fn try_decrypt_binary_message(
|
||||
bin_msg: Vec<u8>,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
) -> Option<Vec<u8>> {
|
||||
match BinaryResponse::try_from_encrypted_tagged_bytes(bin_msg, shared_keys) {
|
||||
Ok(bin_response) => match bin_response {
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{cleanup_socket_messages, try_decrypt_binary_message};
|
||||
use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::TaskClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
@@ -63,7 +63,7 @@ pub(crate) struct PartiallyDelegatedHandle {
|
||||
|
||||
struct PartiallyDelegatedRouter {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
|
||||
stream_return: SplitStreamSender,
|
||||
@@ -73,7 +73,7 @@ struct PartiallyDelegatedRouter {
|
||||
impl PartiallyDelegatedRouter {
|
||||
fn new(
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
stream_return: SplitStreamSender,
|
||||
stream_return_requester: oneshot::Receiver<()>,
|
||||
@@ -253,7 +253,7 @@ impl PartiallyDelegatedHandle {
|
||||
pub(crate) fn split_and_listen_for_mixnet_messages(
|
||||
conn: WsConn,
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedSymmetricKey>,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
|
||||
@@ -83,6 +83,12 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
|
||||
let url_address = Url::parse(&share.announce_address)?;
|
||||
|
||||
// The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because
|
||||
// this EcashApiClient is used by both client and non-client applications.
|
||||
//
|
||||
// In non-client applications this resolver can cause warning logs about H2 connection
|
||||
// failure. This indicates that the long lived https connection was closed by the remote
|
||||
// peer and the resolver will have to reconnect. It should not impact actual functionality
|
||||
Ok(EcashApiClient {
|
||||
api_client: NymApiClient::new(url_address),
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
|
||||
@@ -12,8 +12,9 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
|
||||
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
|
||||
PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
@@ -69,6 +70,19 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::BUILD_INFORMATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
@@ -1043,6 +1057,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
|
||||
@@ -49,6 +49,8 @@ pub mod nym_nodes {
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||
pub const HEALTH: &str = "health";
|
||||
pub const BUILD_INFORMATION: &str = "build-information";
|
||||
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
pub const GATEWAY: &str = "gateway";
|
||||
pub const NYM_NODES: &str = "nym-nodes";
|
||||
@@ -70,4 +72,5 @@ pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
pub const DETAILS: &str = "details";
|
||||
pub const CHAIN_STATUS: &str = "chain-status";
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
@@ -62,6 +62,7 @@ pub use cw3;
|
||||
pub use cw4;
|
||||
pub use cw_controllers;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use prost::Name;
|
||||
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
pub use tendermint_rpc::{
|
||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||
|
||||
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
rand_core = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
+96
-2
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
|
||||
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
||||
let mut output = vec![Scalar::zero(); n];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
|
||||
}
|
||||
|
||||
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
||||
@@ -112,3 +112,97 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
|
||||
G2Projective::from_bytes(&encoding).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bls12_381::G2Affine;
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected1
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected2
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_g2() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G2Affine::from_compressed(&[
|
||||
175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18,
|
||||
11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36,
|
||||
214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168,
|
||||
219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171,
|
||||
26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G2Affine::from_compressed(&[
|
||||
183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83,
|
||||
255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246,
|
||||
121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34,
|
||||
104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123,
|
||||
218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137,
|
||||
197,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G2Affine::from_compressed(&[
|
||||
151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46,
|
||||
231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117,
|
||||
239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11,
|
||||
217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176,
|
||||
207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102,
|
||||
91,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1);
|
||||
assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2);
|
||||
assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::shared_key::{SharedGatewayKey, SharedKeyUsageError};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Replacement for what used to be an `AuthToken`.
|
||||
///
|
||||
/// Replacement for what used to be an `AuthToken`. We used to be generating an `AuthToken` based on
|
||||
/// local secret and remote address in order to allow for authentication. Due to changes in registration
|
||||
/// and the fact we are deriving a shared key, we are encrypting remote's address with the previously
|
||||
/// derived shared key. If the value is as expected, then authentication is successful.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
// this is no longer constant size due to the differences in ciphertext between aes128ctr and aes256gcm-siv (inclusion of tag)
|
||||
pub struct EncryptedAddressBytes(Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for EncryptedAddressBytes {
|
||||
fn from(encrypted_address: Vec<u8>) -> Self {
|
||||
EncryptedAddressBytes(encrypted_address)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncryptedAddressConversionError {
|
||||
#[error("Failed to decode the encrypted address - {0}")]
|
||||
DecodeError(#[from] bs58::decode::Error),
|
||||
}
|
||||
|
||||
impl EncryptedAddressBytes {
|
||||
pub fn new(
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> Result<Self, SharedKeyUsageError> {
|
||||
let ciphertext = key.encrypt_naive(address.as_bytes_ref(), Some(nonce))?;
|
||||
|
||||
Ok(EncryptedAddressBytes(ciphertext))
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
&self,
|
||||
address: &DestinationAddressBytes,
|
||||
key: &SharedGatewayKey,
|
||||
nonce: &[u8],
|
||||
) -> bool {
|
||||
let Ok(reconstructed) = Self::new(address, key, nonce) else {
|
||||
return false;
|
||||
};
|
||||
self == &reconstructed
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn try_from_base58_string<S: Into<String>>(
|
||||
val: S,
|
||||
) -> Result<Self, EncryptedAddressConversionError> {
|
||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
||||
Ok(EncryptedAddressBytes(decoded))
|
||||
}
|
||||
|
||||
pub fn to_base58_string(self) -> String {
|
||||
bs58::encode(self.0).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncryptedAddressBytes> for String {
|
||||
fn from(val: EncryptedAddressBytes) -> Self {
|
||||
val.to_base58_string()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod encrypted_address;
|
||||
@@ -2,15 +2,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use nym_crypto::generic_array;
|
||||
use nym_crypto::OutputSizeUser;
|
||||
use nym_sphinx::params::GatewayIntegrityHmacAlgorithm;
|
||||
|
||||
pub use types::*;
|
||||
|
||||
pub mod authentication;
|
||||
pub mod models;
|
||||
pub mod registration;
|
||||
pub mod shared_key;
|
||||
pub mod types;
|
||||
|
||||
pub use shared_key::{SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey};
|
||||
pub use shared_key::helpers::SymmetricKey;
|
||||
pub use shared_key::legacy::{LegacySharedKeySize, LegacySharedKeys};
|
||||
pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
@@ -26,17 +33,23 @@ pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for u8 {
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedSymmetricKey;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use futures::{Sink, Stream};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
@@ -15,12 +15,12 @@ impl<S, R> State<'_, S, R> {
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
// 1. generate initiator salt for kdf
|
||||
let hkdf_salt = self.generate_initiator_salt();
|
||||
// 1. if we're using non-legacy, i.e. aes256gcm-siv derivation, generate initiator salt for kdf
|
||||
let maybe_hkdf_salt = self.maybe_generate_initiator_salt();
|
||||
|
||||
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt
|
||||
// 1. send ed25519 pubkey alongside ephemeral x25519 pubkey and a hkdf salt if we're using non-legacy client
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
||||
let init_message = self.init_message(hkdf_salt.clone());
|
||||
let init_message = self.init_message(maybe_hkdf_salt.clone());
|
||||
self.send_handshake_data(init_message).await?;
|
||||
|
||||
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
||||
@@ -31,7 +31,7 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
// 3. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, &hkdf_salt);
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
|
||||
|
||||
// 4. verify the received signature using the locally derived keys
|
||||
self.verify_remote_key_material(&mid_res.materials, &mid_res.ephemeral_dh)?;
|
||||
@@ -49,7 +49,7 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
pub(crate) async fn perform_client_handshake(
|
||||
mut self,
|
||||
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::SharedSymmetricKey;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use futures::{Sink, Stream};
|
||||
use tungstenite::Message as WsMessage;
|
||||
@@ -18,14 +18,18 @@ impl<S, R> State<'_, S, R> {
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and initiator salt
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || INITIATOR_SALT
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
|
||||
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
||||
self.update_remote_identity(init_message.identity);
|
||||
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
|
||||
|
||||
// 2. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(&init_message.ephemeral_dh, &init_message.initiator_salt);
|
||||
self.derive_shared_key(
|
||||
&init_message.ephemeral_dh,
|
||||
init_message.initiator_salt.as_deref(),
|
||||
);
|
||||
|
||||
// 3. send ephemeral x25519 pubkey alongside the encrypted signature
|
||||
// g^y || AES(k, sig(gate_priv, (g^y || g^x))
|
||||
@@ -50,7 +54,7 @@ impl<S, R> State<'_, S, R> {
|
||||
pub(crate) async fn perform_gateway_handshake(
|
||||
mut self,
|
||||
raw_init_message: Vec<u8>,
|
||||
) -> Result<SharedSymmetricKey, HandshakeError>
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::KDF_SALT_LENGTH;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_crypto::symmetric::aead::{nonce_size, tag_size, Nonce};
|
||||
use nym_crypto::symmetric::aead::{nonce_size, tag_size};
|
||||
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
||||
|
||||
// it is vital nobody changes the serialisation implementation unless you have an EXTREMELY good reason,
|
||||
@@ -21,13 +21,20 @@ pub trait HandshakeMessage {
|
||||
pub struct Initialisation {
|
||||
pub identity: ed25519::PublicKey,
|
||||
pub ephemeral_dh: x25519::PublicKey,
|
||||
pub initiator_salt: Vec<u8>,
|
||||
pub initiator_salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Initialisation {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.initiator_salt.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MaterialExchange {
|
||||
pub signature_ciphertext: Vec<u8>,
|
||||
pub nonce: Nonce<GatewayEncryptionAlgorithm>,
|
||||
pub nonce: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl MaterialExchange {
|
||||
@@ -65,12 +72,17 @@ impl HandshakeMessage for Initialisation {
|
||||
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
||||
// initializer's identity from another source.
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
self.identity
|
||||
let bytes = self
|
||||
.identity
|
||||
.to_bytes()
|
||||
.into_iter()
|
||||
.chain(self.ephemeral_dh.to_bytes())
|
||||
.chain(self.initiator_salt)
|
||||
.collect()
|
||||
.chain(self.ephemeral_dh.to_bytes());
|
||||
|
||||
if let Some(salt) = self.initiator_salt {
|
||||
bytes.chain(salt).collect()
|
||||
} else {
|
||||
bytes.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// this will need to be adjusted when REMOTE_ID_PUBKEY is removed
|
||||
@@ -78,8 +90,9 @@ impl HandshakeMessage for Initialisation {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let current_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE + KDF_SALT_LENGTH;
|
||||
if bytes.len() != current_len {
|
||||
let legacy_len = ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE;
|
||||
let current_len = legacy_len + KDF_SALT_LENGTH;
|
||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
||||
return Err(HandshakeError::MalformedRequest);
|
||||
}
|
||||
|
||||
@@ -88,13 +101,14 @@ impl HandshakeMessage for Initialisation {
|
||||
|
||||
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
let ephemeral_dh = x25519::PublicKey::from_bytes(
|
||||
&bytes
|
||||
[ed25519::PUBLIC_KEY_LENGTH..ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE],
|
||||
)
|
||||
.unwrap();
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
|
||||
|
||||
let initiator_salt = bytes[ed25519::PUBLIC_KEY_LENGTH + x25519::PUBLIC_KEY_SIZE..].to_vec();
|
||||
let initiator_salt = if bytes.len() == legacy_len {
|
||||
None
|
||||
} else {
|
||||
Some(bytes[legacy_len..].to_vec())
|
||||
};
|
||||
|
||||
Ok(Initialisation {
|
||||
identity,
|
||||
@@ -107,31 +121,43 @@ impl HandshakeMessage for Initialisation {
|
||||
impl HandshakeMessage for MaterialExchange {
|
||||
// AES(k, SIG(PRIV_GATE, G^y || G^x))
|
||||
fn into_bytes(self) -> Vec<u8> {
|
||||
self.signature_ciphertext
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.nonce)
|
||||
.collect()
|
||||
if let Some(nonce) = self.nonce {
|
||||
self.signature_ciphertext
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(nonce)
|
||||
.collect()
|
||||
} else {
|
||||
self.signature_ciphertext.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_bytes(bytes: &[u8]) -> Result<Self, HandshakeError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// we expect to receive either:
|
||||
// LEGACY: ed25519 signature ciphertext (64 bytes)
|
||||
// CURRENT: ed25519 signature ciphertext (+ tag) + AES256-GCM-SIV nonce (76 bytes)
|
||||
let current_len = ed25519::SIGNATURE_LENGTH
|
||||
let legacy_len = ed25519::SIGNATURE_LENGTH;
|
||||
let current_len = legacy_len
|
||||
+ tag_size::<GatewayEncryptionAlgorithm>()
|
||||
+ nonce_size::<GatewayEncryptionAlgorithm>();
|
||||
|
||||
if bytes.len() != current_len {
|
||||
if bytes.len() != legacy_len && bytes.len() != current_len {
|
||||
return Err(HandshakeError::MalformedResponse);
|
||||
}
|
||||
|
||||
let ciphertext_len = ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
||||
let signature_ciphertext = bytes[..ciphertext_len].to_vec();
|
||||
|
||||
// SAFETY: we know the bytes have correct length
|
||||
let nonce = Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(&bytes[ciphertext_len..]);
|
||||
let (signature_ciphertext, nonce) = if bytes.len() == current_len {
|
||||
let ciphertext_len =
|
||||
ed25519::SIGNATURE_LENGTH + tag_size::<GatewayEncryptionAlgorithm>();
|
||||
(
|
||||
bytes[..ciphertext_len].to_vec(),
|
||||
Some(bytes[ciphertext_len..].to_vec()),
|
||||
)
|
||||
} else {
|
||||
(bytes.to_vec(), None)
|
||||
};
|
||||
|
||||
Ok(MaterialExchange {
|
||||
signature_ciphertext,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use self::error::HandshakeError;
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::SharedSymmetricKey;
|
||||
use crate::SharedGatewayKey;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Sink, Stream};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
@@ -34,11 +34,11 @@ pub const KDF_SALT_LENGTH: usize = 16;
|
||||
// we do not need to worry about that.
|
||||
|
||||
pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedSymmetricKey, HandshakeError>>,
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<SharedSymmetricKey, HandshakeError>;
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.handshake_future).poll(cx)
|
||||
@@ -51,6 +51,7 @@ pub fn client_handshake<'a, S, R>(
|
||||
identity: &'a identity::KeyPair,
|
||||
gateway_pubkey: identity::PublicKey,
|
||||
expects_credential_usage: bool,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: TaskClient,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
@@ -65,7 +66,8 @@ where
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
)
|
||||
.with_credential_usage(expects_credential_usage);
|
||||
.with_credential_usage(expects_credential_usage)
|
||||
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
|
||||
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_client_handshake()),
|
||||
|
||||
@@ -5,9 +5,12 @@ use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::{WsItem, KDF_SALT_LENGTH};
|
||||
use crate::registration::handshake::{SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::shared_key::SharedKeySize;
|
||||
use crate::{types, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION};
|
||||
use crate::{
|
||||
types, LegacySharedKeySize, LegacySharedKeys, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_crypto::symmetric::aead::random_nonce;
|
||||
@@ -49,7 +52,7 @@ pub(crate) struct State<'a, S, R> {
|
||||
ephemeral_keypair: x25519::KeyPair,
|
||||
|
||||
/// The derived shared key using the ephemeral keys of both parties.
|
||||
derived_shared_keys: Option<SharedSymmetricKey>,
|
||||
derived_shared_keys: Option<SharedGatewayKey>,
|
||||
|
||||
/// The known or received public identity key of the remote.
|
||||
/// Ideally it would always be known before the handshake was initiated.
|
||||
@@ -59,6 +62,9 @@ pub(crate) struct State<'a, S, R> {
|
||||
// in order to establish correct protocol for backwards compatibility reasons
|
||||
expects_credential_usage: bool,
|
||||
|
||||
/// Specifies whether the end product should be an AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
|
||||
// channel to receive shutdown signal
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown: TaskClient,
|
||||
@@ -85,6 +91,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
derived_shared_keys: None,
|
||||
// later on this should become the default
|
||||
expects_credential_usage: false,
|
||||
derive_aes256_gcm_siv_key: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
}
|
||||
@@ -95,24 +102,38 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_aes256_gcm_siv_key(mut self, derive_aes256_gcm_siv_key: bool) -> Self {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn set_aes256_gcm_siv_key_derivation(&mut self, derive_aes256_gcm_siv_key: bool) {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn local_ephemeral_key(&self) -> &encryption::PublicKey {
|
||||
self.ephemeral_keypair.public_key()
|
||||
}
|
||||
|
||||
pub(crate) fn generate_initiator_salt(&mut self) -> Vec<u8>
|
||||
pub(crate) fn maybe_generate_initiator_salt(&mut self) -> Option<Vec<u8>>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
salt
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
Some(salt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_SALT
|
||||
// Eventually the ID_PUBKEY prefix will get removed and recipient will know
|
||||
// initializer's identity from another source.
|
||||
pub(crate) fn init_message(&self, initiator_salt: Vec<u8>) -> Initialisation {
|
||||
pub(crate) fn init_message(&self, initiator_salt: Option<Vec<u8>>) -> Initialisation {
|
||||
Initialisation {
|
||||
identity: *self.identity.public_key(),
|
||||
ephemeral_dh: *self.ephemeral_keypair.public_key(),
|
||||
@@ -130,27 +151,37 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
pub(crate) fn derive_shared_key(
|
||||
&mut self,
|
||||
remote_ephemeral_key: &encryption::PublicKey,
|
||||
initiator_salt: &[u8],
|
||||
initiator_salt: Option<&[u8]>,
|
||||
) {
|
||||
let dh_result = self
|
||||
.ephemeral_keypair
|
||||
.private_key()
|
||||
.diffie_hellman(remote_ephemeral_key);
|
||||
|
||||
let key_size = SharedKeySize::to_usize();
|
||||
let key_size = if self.derive_aes256_gcm_siv_key {
|
||||
SharedKeySize::to_usize()
|
||||
} else {
|
||||
LegacySharedKeySize::to_usize()
|
||||
};
|
||||
|
||||
// there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(initiator_salt),
|
||||
initiator_salt,
|
||||
&dh_result,
|
||||
None,
|
||||
key_size,
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let shared_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
|
||||
let shared_key = if self.derive_aes256_gcm_siv_key {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Current(current_key)
|
||||
} else {
|
||||
let legacy_key = LegacySharedKeys::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
};
|
||||
self.derived_shared_keys = Some(shared_key)
|
||||
}
|
||||
|
||||
@@ -169,15 +200,19 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.collect();
|
||||
let signature = self.identity.private_key().sign(plaintext);
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let nonce = random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng);
|
||||
let nonce = if self.derive_aes256_gcm_siv_key {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
let signature_ciphertext = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!")
|
||||
.encrypt(&signature.to_bytes(), &nonce)?;
|
||||
.encrypt_naive(&signature.to_bytes(), nonce.as_deref())?;
|
||||
|
||||
Ok(MaterialExchange {
|
||||
signature_ciphertext,
|
||||
@@ -196,10 +231,15 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!");
|
||||
|
||||
// if the [client] init message contained non-legacy flag, the associated nonce MUST be present
|
||||
if self.derive_aes256_gcm_siv_key && remote_response.nonce.is_none() {
|
||||
return Err(HandshakeError::MissingNonceForCurrentKey);
|
||||
}
|
||||
|
||||
// first decrypt received data
|
||||
let decrypted_signature = derived_shared_key.decrypt(
|
||||
let decrypted_signature = derived_shared_key.decrypt_naive(
|
||||
&remote_response.signature_ciphertext,
|
||||
&remote_response.nonce,
|
||||
remote_response.nonce.as_deref(),
|
||||
)?;
|
||||
|
||||
// now verify signature itself
|
||||
@@ -327,7 +367,13 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_handshake_data<M>(
|
||||
@@ -352,7 +398,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
|
||||
/// Finish the handshake, yielding the derived shared key and implicitly dropping all borrowed
|
||||
/// values.
|
||||
pub(crate) fn finalize_handshake(self) -> SharedSymmetricKey {
|
||||
pub(crate) fn finalize_handshake(self) -> SharedGatewayKey {
|
||||
self.derived_shared_keys.unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{LegacySharedKeys, SharedGatewayKey, SharedKeyUsageError, SharedSymmetricKey};
|
||||
use nym_crypto::symmetric::aead::random_nonce;
|
||||
use nym_crypto::symmetric::stream_cipher::random_iv;
|
||||
use nym_sphinx::params::{GatewayEncryptionAlgorithm, LegacyGatewayEncryptionAlgorithm};
|
||||
use rand::thread_rng;
|
||||
|
||||
pub trait SymmetricKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8>;
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError>;
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError>;
|
||||
}
|
||||
|
||||
impl SymmetricKey for SharedGatewayKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
self.random_nonce_or_iv()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
self.encrypt(plaintext, nonce)
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
self.decrypt(ciphertext, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymmetricKey for SharedSymmetricKey {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?;
|
||||
self.encrypt(plaintext, &nonce)
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let nonce = SharedGatewayKey::validate_aead_nonce(nonce)?;
|
||||
self.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl SymmetricKey for LegacySharedKeys {
|
||||
fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
random_iv::<LegacyGatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let iv = SharedGatewayKey::validate_cipher_iv(nonce)?;
|
||||
Ok(self.encrypt_and_tag(plaintext, iv))
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let iv = SharedGatewayKey::validate_cipher_iv(nonce)?;
|
||||
self.decrypt_tagged(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::registration::handshake::KDF_SALT_LENGTH;
|
||||
use crate::shared_key::SharedSymmetricKey;
|
||||
use crate::shared_key::{SharedKeyConversionError, SharedKeySize, SharedKeyUsageError};
|
||||
use crate::LegacyGatewayMacSize;
|
||||
use nym_crypto::generic_array::{
|
||||
typenum::{Sum, Unsigned, U16},
|
||||
GenericArray,
|
||||
};
|
||||
use nym_crypto::hkdf;
|
||||
use nym_crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag};
|
||||
use nym_crypto::symmetric::stream_cipher::{self, CipherKey, KeySizeUser, IV};
|
||||
use nym_pemstore::traits::PemStorableKey;
|
||||
use nym_sphinx::params::{
|
||||
GatewayIntegrityHmacAlgorithm, GatewaySharedKeyHkdfAlgorithm, LegacyGatewayEncryptionAlgorithm,
|
||||
};
|
||||
use rand::{thread_rng, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
// shared key is as long as the encryption key and the MAC key combined.
|
||||
pub type LegacySharedKeySize = Sum<EncryptionKeySize, MacKeySize>;
|
||||
|
||||
// we're using 16 byte long key in sphinx, so let's use the same one here
|
||||
type MacKeySize = U16;
|
||||
type EncryptionKeySize = <LegacyGatewayEncryptionAlgorithm as KeySizeUser>::KeySize;
|
||||
|
||||
/// Shared key used when computing MAC for messages exchanged between client and its gateway.
|
||||
pub type MacKey = GenericArray<u8, MacKeySize>;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct LegacySharedKeys {
|
||||
encryption_key: CipherKey<LegacyGatewayEncryptionAlgorithm>,
|
||||
mac_key: MacKey,
|
||||
}
|
||||
|
||||
impl LegacySharedKeys {
|
||||
pub fn upgrade(&self) -> (SharedSymmetricKey, Vec<u8>) {
|
||||
let mut rng = thread_rng();
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
rng.fill_bytes(&mut salt);
|
||||
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(&salt),
|
||||
&legacy_bytes,
|
||||
None,
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
(key, salt)
|
||||
}
|
||||
|
||||
pub fn upgrade_verify(
|
||||
&self,
|
||||
salt: &[u8],
|
||||
expected_digest: &[u8],
|
||||
) -> Option<SharedSymmetricKey> {
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(salt),
|
||||
&legacy_bytes,
|
||||
None,
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
if key.digest() != expected_digest {
|
||||
// no need to zeroize that key since it's malformed and we won't be using it anyway
|
||||
None
|
||||
} else {
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SharedKeyConversionError> {
|
||||
if bytes.len() != LegacySharedKeySize::to_usize() {
|
||||
return Err(SharedKeyConversionError::InvalidSharedKeysSize {
|
||||
received: bytes.len(),
|
||||
expected: LegacySharedKeySize::to_usize(),
|
||||
});
|
||||
}
|
||||
|
||||
let encryption_key =
|
||||
GenericArray::clone_from_slice(&bytes[..EncryptionKeySize::to_usize()]);
|
||||
let mac_key = GenericArray::clone_from_slice(&bytes[EncryptionKeySize::to_usize()..]);
|
||||
|
||||
Ok(LegacySharedKeys {
|
||||
encryption_key,
|
||||
mac_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Encrypts the provided data using the optionally provided initialisation vector,
|
||||
/// or a 0 value if nothing was given.
|
||||
/// It does **NOT** attach any integrity macs on the produced ciphertext
|
||||
pub fn encrypt_without_tagging(
|
||||
&self,
|
||||
data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Vec<u8> {
|
||||
match iv {
|
||||
Some(iv) => stream_cipher::encrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
data,
|
||||
),
|
||||
None => {
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
stream_cipher::encrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
&zero_iv,
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypts the provided data using the optionally provided initialisation vector,
|
||||
/// or a 0 value if nothing was given. Then it computes an integrity mac and concatenates it
|
||||
/// with the previously produced ciphertext.
|
||||
pub fn encrypt_and_tag(
|
||||
&self,
|
||||
data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Vec<u8> {
|
||||
let ciphertext = self.encrypt_without_tagging(data, iv);
|
||||
let mac = compute_keyed_hmac::<GatewayIntegrityHmacAlgorithm>(
|
||||
self.mac_key().as_slice(),
|
||||
&ciphertext,
|
||||
);
|
||||
|
||||
mac.into_bytes().into_iter().chain(ciphertext).collect()
|
||||
}
|
||||
|
||||
pub fn decrypt_without_tag(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
let iv = iv.unwrap_or(&zero_iv);
|
||||
Ok(stream_cipher::decrypt::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
ciphertext,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn decrypt_tagged(
|
||||
&self,
|
||||
enc_data: &[u8],
|
||||
iv: Option<&IV<LegacyGatewayEncryptionAlgorithm>>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
let mac_size = LegacyGatewayMacSize::to_usize();
|
||||
if enc_data.len() < mac_size {
|
||||
return Err(SharedKeyUsageError::TooShortRequest);
|
||||
}
|
||||
|
||||
let mac_tag = &enc_data[..mac_size];
|
||||
let message_bytes = &enc_data[mac_size..];
|
||||
|
||||
if !recompute_keyed_hmac_and_verify_tag::<GatewayIntegrityHmacAlgorithm>(
|
||||
self.mac_key().as_slice(),
|
||||
message_bytes,
|
||||
mac_tag,
|
||||
) {
|
||||
return Err(SharedKeyUsageError::InvalidMac);
|
||||
}
|
||||
|
||||
// couldn't have made the first borrow mutable as you can't have an immutable borrow
|
||||
// together with a mutable one
|
||||
let mut message_bytes_mut = message_bytes.to_vec();
|
||||
|
||||
let zero_iv = stream_cipher::zero_iv::<LegacyGatewayEncryptionAlgorithm>();
|
||||
let iv = iv.unwrap_or(&zero_iv);
|
||||
stream_cipher::decrypt_in_place::<LegacyGatewayEncryptionAlgorithm>(
|
||||
self.encryption_key(),
|
||||
iv,
|
||||
&mut message_bytes_mut,
|
||||
);
|
||||
Ok(message_bytes_mut)
|
||||
}
|
||||
|
||||
pub fn encryption_key(&self) -> &CipherKey<LegacyGatewayEncryptionAlgorithm> {
|
||||
&self.encryption_key
|
||||
}
|
||||
|
||||
pub fn mac_key(&self) -> &MacKey {
|
||||
&self.mac_key
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.encryption_key
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(self.mac_key.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn try_from_base58_string<S: Into<String>>(
|
||||
val: S,
|
||||
) -> Result<Self, SharedKeyConversionError> {
|
||||
let decoded = bs58::decode(val.into()).into_vec()?;
|
||||
LegacySharedKeys::try_from_bytes(&decoded)
|
||||
}
|
||||
|
||||
pub fn to_base58_string(&self) -> String {
|
||||
bs58::encode(self.to_bytes()).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacySharedKeys> for String {
|
||||
fn from(keys: LegacySharedKeys) -> Self {
|
||||
keys.to_base58_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl PemStorableKey for LegacySharedKeys {
|
||||
type Error = SharedKeyConversionError;
|
||||
|
||||
fn pem_type() -> &'static str {
|
||||
// TODO: If common\nymsphinx\params\src\lib::GatewayIntegrityHmacAlgorithm changes
|
||||
// the pem type needs updating!
|
||||
"AES-128-CTR + HMAC-BLAKE3 GATEWAY SHARED KEYS"
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::try_from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,213 @@ use nym_crypto::generic_array::{typenum::Unsigned, GenericArray};
|
||||
use nym_crypto::symmetric::aead::{
|
||||
self, nonce_size, random_nonce, AeadError, AeadKey, KeySizeUser, Nonce,
|
||||
};
|
||||
use nym_crypto::symmetric::stream_cipher::{iv_size, random_iv, IV};
|
||||
use nym_pemstore::traits::PemStorableKey;
|
||||
use nym_sphinx::params::GatewayEncryptionAlgorithm;
|
||||
use nym_sphinx::params::{GatewayEncryptionAlgorithm, LegacyGatewayEncryptionAlgorithm};
|
||||
use rand::thread_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
pub use legacy::LegacySharedKeys;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod legacy;
|
||||
|
||||
pub type SharedKeySize = <GatewayEncryptionAlgorithm as KeySizeUser>::KeySize;
|
||||
|
||||
#[derive(Debug, PartialEq, Zeroize, ZeroizeOnDrop)]
|
||||
pub enum SharedGatewayKey {
|
||||
Current(SharedSymmetricKey),
|
||||
Legacy(LegacySharedKeys),
|
||||
}
|
||||
|
||||
impl SharedGatewayKey {
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
matches!(self, SharedGatewayKey::Legacy(..))
|
||||
}
|
||||
|
||||
pub fn aes128_ctr_hmac_bs58(&self) -> Option<Zeroizing<String>> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => None,
|
||||
SharedGatewayKey::Legacy(key) => Some(Zeroizing::new(key.to_base58_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aes256_gcm_siv(&self) -> Option<Zeroizing<Vec<u8>>> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(key) => Some(Zeroizing::new(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_legacy(&self) -> &LegacySharedKeys {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => panic!("expected legacy key"),
|
||||
SharedGatewayKey::Legacy(key) => key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_nonce_or_iv(&self) -> Vec<u8> {
|
||||
let mut rng = thread_rng();
|
||||
|
||||
if self.is_legacy() {
|
||||
random_iv::<LegacyGatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
} else {
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_nonce_or_zero_iv(&self) -> Option<Vec<u8>> {
|
||||
if self.is_legacy() {
|
||||
None
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nonce_size(&self) -> usize {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => nonce_size::<GatewayEncryptionAlgorithm>(),
|
||||
SharedGatewayKey::Legacy(_) => iv_size::<LegacyGatewayEncryptionAlgorithm>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LegacySharedKeys> for SharedGatewayKey {
|
||||
fn from(keys: LegacySharedKeys) -> Self {
|
||||
SharedGatewayKey::Legacy(keys)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SharedSymmetricKey> for SharedGatewayKey {
|
||||
fn from(keys: SharedSymmetricKey) -> Self {
|
||||
SharedGatewayKey::Current(keys)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SharedKeyUsageError {
|
||||
#[error("the request is too short")]
|
||||
TooShortRequest,
|
||||
|
||||
#[error("the provided nonce did not have the expected length or was malformed")]
|
||||
#[error("provided MAC is invalid")]
|
||||
InvalidMac,
|
||||
|
||||
#[error("the provided nonce (or legacy IV) did not have the expected length")]
|
||||
MalformedNonce,
|
||||
|
||||
#[error("did not provide a valid nonce for aead encryption")]
|
||||
MissingAeadNonce,
|
||||
|
||||
#[error("failed to either encrypt or decrypt provided message")]
|
||||
AeadFailure(#[from] AeadError),
|
||||
}
|
||||
|
||||
impl SharedGatewayKey {
|
||||
fn validate_aead_nonce(
|
||||
raw: Option<&[u8]>,
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
let Some(raw) = raw else {
|
||||
return Err(SharedKeyUsageError::MissingAeadNonce);
|
||||
};
|
||||
if raw.len() != nonce_size::<GatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Ok(Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(raw))
|
||||
}
|
||||
|
||||
fn validate_cipher_iv(
|
||||
raw: Option<&[u8]>,
|
||||
) -> Result<Option<&IV<LegacyGatewayEncryptionAlgorithm>>, SharedKeyUsageError> {
|
||||
let Some(raw) = raw else { return Ok(None) };
|
||||
let iv = if raw.is_empty() {
|
||||
None
|
||||
} else {
|
||||
if raw.len() != iv_size::<LegacyGatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Some(IV::<LegacyGatewayEncryptionAlgorithm>::from_slice(raw))
|
||||
};
|
||||
Ok(iv)
|
||||
}
|
||||
|
||||
pub fn encrypt(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.encrypt(plaintext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
Ok(aes_ctr.encrypt_and_tag(plaintext, iv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
aes_ctr.decrypt_tagged(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for the legacy keys do not use integrity MAC
|
||||
pub fn encrypt_naive(
|
||||
&self,
|
||||
plaintext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.encrypt(plaintext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
Ok(aes_ctr.encrypt_without_tagging(plaintext, iv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for the legacy keys do not use integrity MAC
|
||||
pub fn decrypt_naive(
|
||||
&self,
|
||||
ciphertext: &[u8],
|
||||
// the best common denominator for converting into 'IV' and 'Nonce' types
|
||||
raw_nonce: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>, SharedKeyUsageError> {
|
||||
match self {
|
||||
SharedGatewayKey::Current(aes_gcm_siv) => {
|
||||
let nonce = Self::validate_aead_nonce(raw_nonce)?;
|
||||
aes_gcm_siv.decrypt(ciphertext, &nonce)
|
||||
}
|
||||
SharedGatewayKey::Legacy(aes_ctr) => {
|
||||
let iv = Self::validate_cipher_iv(raw_nonce)?;
|
||||
aes_ctr.decrypt_without_tag(ciphertext, iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct SharedSymmetricKey(AeadKey<GatewayEncryptionAlgorithm>);
|
||||
|
||||
@@ -44,36 +230,6 @@ pub enum SharedKeyConversionError {
|
||||
}
|
||||
|
||||
impl SharedSymmetricKey {
|
||||
pub fn random_nonce(&self) -> Nonce<GatewayEncryptionAlgorithm> {
|
||||
let mut rng = thread_rng();
|
||||
random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng)
|
||||
}
|
||||
|
||||
pub fn nonce_size(&self) -> usize {
|
||||
nonce_size::<GatewayEncryptionAlgorithm>()
|
||||
}
|
||||
|
||||
pub fn decode_bs58_nonce<I: AsRef<[u8]>>(
|
||||
raw: I,
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
// 1. decode bytes from encoding
|
||||
let decoded = bs58::decode(raw)
|
||||
.into_vec()
|
||||
.map_err(|_| SharedKeyUsageError::MalformedNonce)?;
|
||||
|
||||
// 2. validate length and convert into the proper type
|
||||
Self::validate_aead_nonce(&decoded)
|
||||
}
|
||||
|
||||
pub fn validate_aead_nonce(
|
||||
raw: &[u8],
|
||||
) -> Result<Nonce<GatewayEncryptionAlgorithm>, SharedKeyUsageError> {
|
||||
if raw.len() != nonce_size::<GatewayEncryptionAlgorithm>() {
|
||||
return Err(SharedKeyUsageError::MalformedNonce);
|
||||
}
|
||||
Ok(Nonce::<GatewayEncryptionAlgorithm>::clone_from_slice(raw))
|
||||
}
|
||||
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, SharedKeyConversionError> {
|
||||
if bytes.len() != KeySize::to_usize() {
|
||||
return Err(SharedKeyConversionError::InvalidSharedKeysSize {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::helpers::BinaryData;
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey};
|
||||
use crate::{GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use strum::FromRepr;
|
||||
use tungstenite::Message;
|
||||
@@ -46,14 +46,14 @@ impl BinaryRequest {
|
||||
|
||||
pub fn try_from_encrypted_tagged_bytes(
|
||||
bytes: Vec<u8>,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
BinaryData::from_raw(&bytes, shared_key)?.into_request(shared_key)
|
||||
}
|
||||
|
||||
pub fn into_encrypted_tagged_bytes(
|
||||
self,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let kind = self.kind();
|
||||
|
||||
@@ -66,7 +66,7 @@ impl BinaryRequest {
|
||||
|
||||
pub fn into_ws_message(
|
||||
self,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Message, GatewayRequestsError> {
|
||||
// all variants are currently encrypted
|
||||
let blob = match self {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::helpers::BinaryData;
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey};
|
||||
use crate::{GatewayRequestsError, SharedGatewayKey};
|
||||
use strum::FromRepr;
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -38,14 +38,14 @@ impl BinaryResponse {
|
||||
|
||||
pub fn try_from_encrypted_tagged_bytes(
|
||||
bytes: Vec<u8>,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
BinaryData::from_raw(&bytes, shared_key)?.into_response(shared_key)
|
||||
}
|
||||
|
||||
pub fn into_encrypted_tagged_bytes(
|
||||
self,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let kind = self.kind();
|
||||
|
||||
@@ -58,7 +58,7 @@ impl BinaryResponse {
|
||||
|
||||
pub fn into_ws_message(
|
||||
self,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Message, GatewayRequestsError> {
|
||||
// all variants are currently encrypted
|
||||
let blob = match self {
|
||||
|
||||
@@ -8,7 +8,9 @@ use nym_sphinx::addressing::nodes::NymNodeRoutingAddressError;
|
||||
use nym_sphinx::forwarding::packet::MixPacketFormattingError;
|
||||
use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::string::FromUtf8Error;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// specific errors (that should not be nested!!) for clients to match on
|
||||
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
|
||||
@@ -50,6 +52,9 @@ pub enum GatewayRequestsError {
|
||||
#[error("the request is too short")]
|
||||
TooShortRequest,
|
||||
|
||||
#[error("provided MAC is invalid")]
|
||||
InvalidMac,
|
||||
|
||||
#[error("address field was incorrectly encoded: {source}")]
|
||||
IncorrectlyEncodedAddress {
|
||||
#[from]
|
||||
@@ -65,15 +70,30 @@ pub enum GatewayRequestsError {
|
||||
]
|
||||
RequestOfInvalidSize(usize),
|
||||
|
||||
#[error("received sphinx packet was malformed")]
|
||||
MalformedSphinxPacket,
|
||||
|
||||
#[error("failed to serialise created sphinx packet: {0}")]
|
||||
SphinxSerialisationFailure(#[from] MixPacketFormattingError),
|
||||
|
||||
#[error("the received encrypted data was malformed")]
|
||||
MalformedEncryption,
|
||||
|
||||
#[error("provided packet mode is invalid")]
|
||||
InvalidPacketMode,
|
||||
|
||||
#[error("failed to deserialize provided credential: {0}")]
|
||||
EcashCredentialDeserializationFailure(#[from] CompactEcashError),
|
||||
|
||||
#[error("failed to deserialize provided credential: EOF")]
|
||||
CredentialDeserializationFailureEOF,
|
||||
|
||||
#[error("failed to deserialize provided credential: malformed string: {0}")]
|
||||
CredentialDeserializationFailureMalformedString(#[from] FromUtf8Error),
|
||||
|
||||
#[error("the provided [v1] credential has invalid number of parameters - {0}")]
|
||||
InvalidNumberOfEmbededParameters(u32),
|
||||
|
||||
#[error("failed to authenticate the client: {0}")]
|
||||
Authentication(#[from] AuthenticationFailure),
|
||||
|
||||
@@ -93,15 +113,15 @@ pub enum AuthenticationFailure {
|
||||
#[error("failed to verify request signature")]
|
||||
InvalidSignature(#[from] SignatureError),
|
||||
|
||||
#[error("provided request timestamp is in the future")]
|
||||
RequestTimestampInFuture,
|
||||
|
||||
#[error("the client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("the provided request is too stale to process")]
|
||||
StaleRequest,
|
||||
#[error("the provided request timestamp is excessively skewed. got {received} whilst the server time is {server}")]
|
||||
ExcessiveTimestampSkew {
|
||||
received: OffsetDateTime,
|
||||
server: OffsetDateTime,
|
||||
},
|
||||
|
||||
#[error("the provided request timestamp is smaller or equal to a one previously used")]
|
||||
#[error("the provided request timestamp is smaller or equal to one previously used")]
|
||||
RequestReuse,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{
|
||||
BinaryRequest, BinaryRequestKind, BinaryResponse, BinaryResponseKind, GatewayRequestsError,
|
||||
SharedSymmetricKey,
|
||||
SharedGatewayKey,
|
||||
};
|
||||
use std::iter::once;
|
||||
|
||||
@@ -22,7 +22,11 @@ pub struct BinaryData<'a> {
|
||||
|
||||
impl<'a> BinaryData<'a> {
|
||||
// serialises possibly encrypted data into bytes to be put on the wire
|
||||
pub fn into_raw(self) -> Vec<u8> {
|
||||
pub fn into_raw(self, legacy: bool) -> Vec<u8> {
|
||||
if legacy {
|
||||
return self.data.to_vec();
|
||||
}
|
||||
|
||||
let i = once(self.kind).chain(once(if self.encrypted { 1 } else { 0 }));
|
||||
if let Some(nonce) = self.maybe_nonce {
|
||||
i.chain(nonce.iter().copied())
|
||||
@@ -36,8 +40,19 @@ impl<'a> BinaryData<'a> {
|
||||
// attempts to perform basic parsing on bytes received from the wire
|
||||
pub fn from_raw(
|
||||
raw: &'a [u8],
|
||||
available_key: &SharedSymmetricKey,
|
||||
available_key: &SharedGatewayKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using legacy key, it's quite simple:
|
||||
// it's always encrypted with no nonce and the request/response kind is always 1
|
||||
if available_key.is_legacy() {
|
||||
return Ok(BinaryData {
|
||||
kind: 1,
|
||||
encrypted: true,
|
||||
maybe_nonce: None,
|
||||
data: raw,
|
||||
});
|
||||
}
|
||||
|
||||
if raw.len() < 2 {
|
||||
return Err(GatewayRequestsError::TooShortRequest);
|
||||
}
|
||||
@@ -68,33 +83,30 @@ impl<'a> BinaryData<'a> {
|
||||
pub fn make_encrypted_blob(
|
||||
kind: u8,
|
||||
plaintext: &[u8],
|
||||
key: &SharedSymmetricKey,
|
||||
key: &SharedGatewayKey,
|
||||
) -> Result<Vec<u8>, GatewayRequestsError> {
|
||||
let nonce = key.random_nonce();
|
||||
let maybe_nonce = key.random_nonce_or_zero_iv();
|
||||
|
||||
let ciphertext = key.encrypt(plaintext, &nonce)?;
|
||||
let ciphertext = key.encrypt(plaintext, maybe_nonce.as_deref())?;
|
||||
Ok(BinaryData {
|
||||
kind,
|
||||
encrypted: true,
|
||||
maybe_nonce: Some(&nonce),
|
||||
maybe_nonce: maybe_nonce.as_deref(),
|
||||
data: &ciphertext,
|
||||
}
|
||||
.into_raw())
|
||||
.into_raw(key.is_legacy()))
|
||||
}
|
||||
|
||||
// attempts to parse previously recovered bytes into a [`BinaryRequest`]
|
||||
pub fn into_request(
|
||||
self,
|
||||
key: &SharedSymmetricKey,
|
||||
key: &SharedGatewayKey,
|
||||
) -> Result<BinaryRequest, GatewayRequestsError> {
|
||||
let kind = BinaryRequestKind::from_repr(self.kind)
|
||||
.ok_or(GatewayRequestsError::UnknownRequestKind { kind: self.kind })?;
|
||||
|
||||
let plaintext = if self.encrypted {
|
||||
let raw_nonce = self.maybe_nonce.unwrap_or(&[]);
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(raw_nonce)?;
|
||||
|
||||
&*key.decrypt(self.data, &nonce)?
|
||||
&*key.decrypt(self.data, self.maybe_nonce)?
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
@@ -105,16 +117,13 @@ impl<'a> BinaryData<'a> {
|
||||
// attempts to parse previously recovered bytes into a [`BinaryResponse`]
|
||||
pub fn into_response(
|
||||
self,
|
||||
key: &SharedSymmetricKey,
|
||||
key: &SharedGatewayKey,
|
||||
) -> Result<BinaryResponse, GatewayRequestsError> {
|
||||
let kind = BinaryResponseKind::from_repr(self.kind)
|
||||
.ok_or(GatewayRequestsError::UnknownResponseKind { kind: self.kind })?;
|
||||
|
||||
let plaintext = if self.encrypted {
|
||||
let raw_nonce = self.maybe_nonce.unwrap_or(&[]);
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(raw_nonce)?;
|
||||
|
||||
&*key.decrypt(self.data, &nonce)?
|
||||
&*key.decrypt(self.data, self.maybe_nonce)?
|
||||
} else {
|
||||
self.data
|
||||
};
|
||||
|
||||
@@ -76,7 +76,25 @@ mod tests {
|
||||
protocol_version,
|
||||
data,
|
||||
} => {
|
||||
assert_eq!(protocol_version, 42);
|
||||
assert_eq!(protocol_version, Some(42));
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
|
||||
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
|
||||
protocol_version: None,
|
||||
data: handshake_data.clone(),
|
||||
};
|
||||
let serialized = serde_json::to_string(&handshake_payload_without_protocol).unwrap();
|
||||
let deserialized = ClientControlRequest::try_from(serialized).unwrap();
|
||||
|
||||
match deserialized {
|
||||
ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version,
|
||||
data,
|
||||
} => {
|
||||
assert!(protocol_version.is_none());
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedSymmetricKey};
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
@@ -21,7 +21,7 @@ pub struct AuthenticateRequest {
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
let content = AuthenticateRequestContent::new(
|
||||
@@ -38,13 +38,22 @@ impl AuthenticateRequest {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
|
||||
pub fn verify_timestamp(
|
||||
&self,
|
||||
max_request_timestamp_skew: Duration,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if self.content.request_timestamp() + max_request_age < now {
|
||||
return Err(AuthenticationFailure::StaleRequest);
|
||||
if self.content.request_timestamp() < now - max_request_timestamp_skew {
|
||||
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
|
||||
received: self.content.request_timestamp(),
|
||||
server: now,
|
||||
});
|
||||
}
|
||||
if self.content.request_timestamp() > now {
|
||||
return Err(AuthenticationFailure::RequestTimestampInFuture);
|
||||
if self.content.request_timestamp() - max_request_timestamp_skew > now {
|
||||
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
|
||||
received: self.content.request_timestamp(),
|
||||
server: now,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -61,14 +70,14 @@ impl AuthenticateRequest {
|
||||
|
||||
pub fn verify_ciphertext(
|
||||
&self,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let expected = shared_key.encrypt(
|
||||
self.content
|
||||
.client_identity
|
||||
.derive_destination_address()
|
||||
.as_bytes_ref(),
|
||||
&SharedSymmetricKey::validate_aead_nonce(&self.content.nonce)?,
|
||||
Some(&self.content.nonce),
|
||||
)?;
|
||||
|
||||
if !bool::from(expected.ct_eq(&self.content.address_ciphertext)) {
|
||||
@@ -106,19 +115,20 @@ pub struct AuthenticateRequestContent {
|
||||
impl AuthenticateRequestContent {
|
||||
fn new(
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
client_identity: ed25519::PublicKey,
|
||||
) -> Result<AuthenticateRequestContent, GatewayRequestsError> {
|
||||
let nonce = shared_key.random_nonce();
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let destination_address = client_identity.derive_destination_address();
|
||||
|
||||
let address_ciphertext = shared_key.encrypt(destination_address.as_bytes_ref(), &nonce)?;
|
||||
let address_ciphertext =
|
||||
shared_key.encrypt(destination_address.as_bytes_ref(), Some(&nonce))?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
Ok(AuthenticateRequestContent {
|
||||
protocol_version,
|
||||
client_identity,
|
||||
address_ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
nonce,
|
||||
request_unix_timestamp: now.unix_timestamp() as u64, // SAFETY: we're running this in post 1970...
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@
|
||||
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey, AUTHENTICATE_V2_PROTOCOL_VERSION};
|
||||
use crate::{
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use tungstenite::Message;
|
||||
@@ -16,13 +21,20 @@ pub mod authenticate;
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientRequest {
|
||||
ForgetMe { client: bool, stats: bool },
|
||||
UpgradeKey {
|
||||
hkdf_salt: Vec<u8>,
|
||||
derived_key_digest: Vec<u8>,
|
||||
},
|
||||
ForgetMe {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
pub fn encrypt(
|
||||
pub fn encrypt<S: SymmetricKey>(
|
||||
&self,
|
||||
key: &SharedSymmetricKey,
|
||||
key: &S,
|
||||
) -> Result<ClientControlRequest, GatewayRequestsError> {
|
||||
// we're using json representation for few reasons:
|
||||
// - ease of re-implementation in other languages (compared to for example bincode)
|
||||
@@ -31,21 +43,17 @@ impl ClientRequest {
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce();
|
||||
let ciphertext = key.encrypt(&plaintext, &nonce)?;
|
||||
Ok(ClientControlRequest::EncryptedRequest {
|
||||
ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
Ok(ClientControlRequest::EncryptedRequest { ciphertext, nonce })
|
||||
}
|
||||
|
||||
pub fn decrypt(
|
||||
pub fn decrypt<S: SymmetricKey>(
|
||||
ciphertext: &[u8],
|
||||
nonce: &[u8],
|
||||
key: &SharedSymmetricKey,
|
||||
key: &S,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, &nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, Some(nonce))?;
|
||||
serde_json::from_slice(&plaintext)
|
||||
.map_err(|source| GatewayRequestsError::MalformedRequest { source })
|
||||
}
|
||||
@@ -56,28 +64,8 @@ impl ClientRequest {
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub enum ClientControlRequest {
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
protocol_version: u8,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
|
||||
EcashCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
#[serde(alias = "iv")]
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
EncryptedRequest {
|
||||
ciphertext: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
SupportedProtocol {},
|
||||
// if you're adding new variants here, consider putting them inside `ClientRequest` instead
|
||||
|
||||
// NO LONGER SUPPORTED:
|
||||
// TODO: should this also contain a MAC considering that at this point we already
|
||||
// have the shared key derived?
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
@@ -86,6 +74,14 @@ pub enum ClientControlRequest {
|
||||
iv: String,
|
||||
},
|
||||
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
BandwidthCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
@@ -94,15 +90,52 @@ pub enum ClientControlRequest {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
EcashCredential {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
EncryptedRequest {
|
||||
ciphertext: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
SupportedProtocol {},
|
||||
// if you're adding new variants here, consider putting them inside `ClientRequest` instead
|
||||
}
|
||||
|
||||
impl ClientControlRequest {
|
||||
pub fn new_authenticate(
|
||||
address: DestinationAddressBytes,
|
||||
shared_key: &SharedGatewayKey,
|
||||
uses_credentials: bool,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're encrypting with non-legacy key, the remote must support AES256-GCM-SIV
|
||||
let protocol_version = if !shared_key.is_legacy() {
|
||||
Some(AES_GCM_SIV_PROTOCOL_VERSION)
|
||||
} else if uses_credentials {
|
||||
Some(CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION)
|
||||
} else {
|
||||
// if we're not going to be using credentials, advertise lower protocol version to allow connection
|
||||
// to wider range of gateways
|
||||
Some(INITIAL_PROTOCOL_VERSION)
|
||||
};
|
||||
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let ciphertext = shared_key.encrypt_naive(address.as_bytes_ref(), Some(&nonce))?;
|
||||
|
||||
Ok(ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address: address.as_base58_string(),
|
||||
enc_address: bs58::encode(&ciphertext).into_string(),
|
||||
iv: bs58::encode(&nonce).into_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
// (which also implicitly implies usage of AES256-GCM-SIV
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
@@ -132,27 +165,26 @@ impl ClientControlRequest {
|
||||
|
||||
pub fn new_enc_ecash_credential(
|
||||
credential: CredentialSpendingData,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
shared_key: &SharedGatewayKey,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let cred = CredentialSpendingRequest::new(credential);
|
||||
let serialized_credential = cred.to_bytes();
|
||||
|
||||
let nonce = shared_key.random_nonce();
|
||||
let enc_credential = shared_key.encrypt(&serialized_credential, &nonce)?;
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let enc_credential = shared_key.encrypt(&serialized_credential, Some(&nonce))?;
|
||||
|
||||
Ok(ClientControlRequest::EcashCredential {
|
||||
enc_credential,
|
||||
nonce: nonce.to_vec(),
|
||||
iv: nonce,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_from_enc_ecash_credential(
|
||||
enc_credential: Vec<u8>,
|
||||
shared_key: &SharedSymmetricKey,
|
||||
nonce: Vec<u8>,
|
||||
shared_key: &SharedGatewayKey,
|
||||
iv: Vec<u8>,
|
||||
) -> Result<CredentialSpendingRequest, GatewayRequestsError> {
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(&nonce)?;
|
||||
let credential_bytes = shared_key.decrypt(&enc_credential, &nonce)?;
|
||||
let credential_bytes = shared_key.decrypt(&enc_credential, Some(&iv))?;
|
||||
CredentialSpendingRequest::try_from_bytes(credential_bytes.as_slice())
|
||||
.map_err(|_| GatewayRequestsError::MalformedEncryption)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{GatewayRequestsError, SharedSymmetricKey, SimpleGatewayRequestsError};
|
||||
use crate::{GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -15,9 +15,9 @@ pub enum SensitiveServerResponse {
|
||||
}
|
||||
|
||||
impl SensitiveServerResponse {
|
||||
pub fn encrypt(
|
||||
pub fn encrypt<S: SymmetricKey>(
|
||||
&self,
|
||||
key: &SharedSymmetricKey,
|
||||
key: &S,
|
||||
) -> Result<ServerResponse, GatewayRequestsError> {
|
||||
// we're using json representation for few reasons:
|
||||
// - ease of re-implementation in other languages (compared to for example bincode)
|
||||
@@ -26,21 +26,17 @@ impl SensitiveServerResponse {
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce();
|
||||
let ciphertext = key.encrypt(&plaintext, &nonce)?;
|
||||
Ok(ServerResponse::EncryptedResponse {
|
||||
ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
Ok(ServerResponse::EncryptedResponse { ciphertext, nonce })
|
||||
}
|
||||
|
||||
pub fn decrypt(
|
||||
pub fn decrypt<S: SymmetricKey>(
|
||||
ciphertext: &[u8],
|
||||
nonce: &[u8],
|
||||
key: &SharedSymmetricKey,
|
||||
key: &S,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
let nonce = SharedSymmetricKey::validate_aead_nonce(nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, &nonce)?;
|
||||
let plaintext = key.decrypt(ciphertext, Some(nonce))?;
|
||||
serde_json::from_slice(&plaintext)
|
||||
.map_err(|source| GatewayRequestsError::MalformedRequest { source })
|
||||
}
|
||||
@@ -51,12 +47,14 @@ impl SensitiveServerResponse {
|
||||
#[non_exhaustive]
|
||||
pub enum ServerResponse {
|
||||
Authenticate {
|
||||
protocol_version: u8,
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
status: bool,
|
||||
bandwidth_remaining: i64,
|
||||
},
|
||||
Register {
|
||||
protocol_version: u8,
|
||||
#[serde(default)]
|
||||
protocol_version: Option<u8>,
|
||||
status: bool,
|
||||
},
|
||||
EncryptedResponse {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
-- make aes256gcm column non-nullable and drop any clients that still use the legacy keys
|
||||
CREATE TABLE shared_keys_tmp
|
||||
(
|
||||
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients (id),
|
||||
client_address_bs58 TEXT NOT NULL UNIQUE,
|
||||
derived_aes256_gcm_siv_key BLOB NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO shared_keys_tmp (client_id, client_address_bs58, derived_aes256_gcm_siv_key)
|
||||
SELECT client_id, client_address_bs58, derived_aes256_gcm_siv_key
|
||||
FROM shared_keys
|
||||
WHERE derived_aes256_gcm_siv_key IS NOT NULL;
|
||||
|
||||
DROP TABLE shared_keys;
|
||||
ALTER TABLE shared_keys_tmp
|
||||
RENAME TO shared_keys;
|
||||
@@ -8,7 +8,7 @@ use models::{
|
||||
VerifiedTicket, WireguardPeer,
|
||||
};
|
||||
use nym_credentials_interface::ClientTicket;
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use shared_keys::SharedKeysManager;
|
||||
use sqlx::{
|
||||
@@ -152,7 +152,7 @@ impl GatewayStorage {
|
||||
pub async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
shared_keys: &SharedSymmetricKey,
|
||||
shared_keys: &SharedGatewayKey,
|
||||
) -> Result<i64, GatewayStorageError> {
|
||||
let client_address_bs58 = client_address.as_base58_string();
|
||||
let client_id = match self
|
||||
@@ -171,7 +171,8 @@ impl GatewayStorage {
|
||||
.insert_shared_keys(
|
||||
client_id,
|
||||
client_address_bs58,
|
||||
shared_keys.to_bytes().as_ref(),
|
||||
shared_keys.aes128_ctr_hmac_bs58().as_deref(),
|
||||
shared_keys.aes256_gcm_siv().as_deref(),
|
||||
)
|
||||
.await?;
|
||||
Ok(client_id)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::error::GatewayStorageError;
|
||||
use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData};
|
||||
use nym_gateway_requests::shared_key::SharedSymmetricKey;
|
||||
use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey};
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
@@ -18,16 +18,33 @@ pub struct PersistedSharedKeys {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub client_address_bs58: String,
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
pub last_used_authentication: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl TryFrom<PersistedSharedKeys> for SharedSymmetricKey {
|
||||
impl TryFrom<PersistedSharedKeys> for SharedGatewayKey {
|
||||
type Error = GatewayStorageError;
|
||||
|
||||
fn try_from(value: PersistedSharedKeys) -> Result<Self, Self::Error> {
|
||||
SharedSymmetricKey::try_from_bytes(&value.derived_aes256_gcm_siv_key)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))
|
||||
match (
|
||||
&value.derived_aes256_gcm_siv_key,
|
||||
&value.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
) {
|
||||
(None, None) => Err(GatewayStorageError::MissingSharedKey {
|
||||
id: value.client_id,
|
||||
}),
|
||||
(Some(aes256gcm_siv), _) => {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(aes256gcm_siv)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Current(current_key))
|
||||
}
|
||||
(None, Some(aes128ctr_hmac)) => {
|
||||
let legacy_key = LegacySharedKeys::try_from_base58_string(aes128ctr_hmac)
|
||||
.map_err(|source| GatewayStorageError::DataCorruption(source.to_string()))?;
|
||||
Ok(SharedGatewayKey::Legacy(legacy_key))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,27 +37,31 @@ impl SharedKeysManager {
|
||||
///
|
||||
/// * `client_id`: The client id for which the shared keys are stored
|
||||
/// * `client_address_bs58`: base58-encoded address of the client
|
||||
/// * `derived_aes256_gcm_siv_key`: shared encryption (AES256GCM_SIV) derived shared keys to store.
|
||||
/// * `derived_aes128_ctr_blake3_hmac_keys_bs58`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
|
||||
pub(crate) async fn insert_shared_keys(
|
||||
&self,
|
||||
client_id: i64,
|
||||
client_address_bs58: String,
|
||||
derived_aes256_gcm_siv_key: &Vec<u8>,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&String>,
|
||||
derived_aes256_gcm_siv_key: Option<&Vec<u8>>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
// https://stackoverflow.com/a/20310838
|
||||
// we don't want to be using `INSERT OR REPLACE INTO` due to the foreign key on `available_bandwidth` if the entry already exists
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes256_gcm_siv_key) VALUES (?, ?, ?);
|
||||
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) VALUES (?, ?, ?, ?);
|
||||
|
||||
UPDATE shared_keys
|
||||
SET
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58 = ?,
|
||||
derived_aes256_gcm_siv_key = ?
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
client_id,
|
||||
client_address_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
client_address_bs58,
|
||||
).execute(&self.connection_pool).await?;
|
||||
|
||||
@@ -21,6 +21,12 @@ serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
@@ -32,4 +38,4 @@ workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features=["rt", "macros"] }
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! DNS resolver configuration for internal lookups.
|
||||
//!
|
||||
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
||||
@@ -9,6 +12,19 @@
|
||||
//!
|
||||
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||
//! `hickory-resolver` crate
|
||||
//!
|
||||
//!
|
||||
//! Note: The hickory DoH resolver can cause warning logs about H2 connection failure. This
|
||||
//! indicates that the long lived https connection was closed by the remote peer and the resolver
|
||||
//! will have to reconnect. It should not impact actual functionality.
|
||||
//!
|
||||
//! code ref: https://github.com/hickory-dns/hickory-dns/blob/06a8b1ce9bd9322d8e6accf857d30257e1274427/crates/proto/src/h2/h2_client_stream.rs#L534
|
||||
//!
|
||||
//! example log:
|
||||
//!
|
||||
//! ```txt
|
||||
//! WARN /home/ubuntu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-proto-0.24.3/src/h2/h2_client_stream.rs:493: h2 connection failed: unexpected end of file
|
||||
//! ```
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use crate::ClientBuilder;
|
||||
@@ -33,6 +49,13 @@ impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
||||
self.use_secure_dns = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn no_hickory_dns(mut self) -> Self {
|
||||
self.use_secure_dns = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +147,13 @@ use thiserror::Error;
|
||||
use tracing::{instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use http::HeaderMap;
|
||||
pub use reqwest::IntoUrl;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
|
||||
mod user_agent;
|
||||
pub use user_agent::UserAgent;
|
||||
|
||||
@@ -210,6 +210,12 @@ pub enum HttpClientError<E: Display = String> {
|
||||
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
||||
EndpointFailure { status: StatusCode, error: E },
|
||||
|
||||
#[error("failed to decode response body: {source} from {content}")]
|
||||
ResponseDecodeFailure {
|
||||
source: serde_json::Error,
|
||||
content: String,
|
||||
},
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("the request has timed out")]
|
||||
RequestTimeout,
|
||||
@@ -222,6 +228,8 @@ pub struct ClientBuilder {
|
||||
timeout: Option<Duration>,
|
||||
custom_user_agent: bool,
|
||||
reqwest_client_builder: reqwest::ClientBuilder,
|
||||
#[allow(dead_code)] // not dead code, just unused in wasm
|
||||
use_secure_dns: bool,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
@@ -233,37 +241,46 @@ impl ClientBuilder {
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new(alt)
|
||||
} else {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
Ok(Self::new_with_url(url.into_url()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_url(url: Url) -> Self {
|
||||
if !url.scheme().starts_with("http") {
|
||||
warn!("the provided url ('{url}') does not use HTTP / HTTPS scheme");
|
||||
}
|
||||
|
||||
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
|
||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
||||
//
|
||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
||||
// that feature is removed.
|
||||
r.gzip(true)
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder,
|
||||
})
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new();
|
||||
|
||||
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
|
||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
||||
//
|
||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
||||
// that feature is removed.
|
||||
r.gzip(true)
|
||||
};
|
||||
|
||||
ClientBuilder {
|
||||
url,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder,
|
||||
use_secure_dns: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,10 +336,18 @@ impl ClientBuilder {
|
||||
let mut builder = self
|
||||
.reqwest_client_builder
|
||||
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
||||
|
||||
// if no custom user agent was set, use a default
|
||||
if !self.custom_user_agent {
|
||||
builder =
|
||||
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
}
|
||||
|
||||
// unless explicitly disabled use the DoT/DoH enabled resolver
|
||||
if self.use_secure_dns {
|
||||
builder = builder.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
}
|
||||
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
@@ -349,6 +374,9 @@ pub struct Client {
|
||||
impl Client {
|
||||
/// Create a new http `Client`
|
||||
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
//
|
||||
// In order to prevent interference in API requests at the DNS phase we default to a resolver
|
||||
// that uses DoT and DoH.
|
||||
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
@@ -849,6 +877,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use mime::Mime;
|
||||
|
||||
let content_type = headers
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
|
||||
.unwrap_or("utf-8");
|
||||
|
||||
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
|
||||
|
||||
let (text, _, _) = encoding.decode(bytes);
|
||||
text.into_owned()
|
||||
}
|
||||
|
||||
/// Attempt to parse a json object from an HTTP response
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
@@ -864,21 +912,23 @@ where
|
||||
return Err(HttpClientError::EmptyResponse { status });
|
||||
}
|
||||
}
|
||||
let headers = res.headers().clone();
|
||||
tracing::trace!("headers: {:?}", headers);
|
||||
|
||||
if res.status().is_success() {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let text = res.text().await.inspect_err(|err| {
|
||||
tracing::error!("Couldn't even get response text: {err}");
|
||||
})?;
|
||||
tracing::trace!("Result:\n{:#?}", text);
|
||||
|
||||
serde_json::from_str(&text)
|
||||
.map_err(|err| HttpClientError::GenericRequestFailure(err.to_string()))
|
||||
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
|
||||
// (and similarly does the same thing for text())
|
||||
let full = res.bytes().await?;
|
||||
match serde_json::from_slice(&full) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let content = decode_as_text(&full, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
source: err,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(HttpClientError::NotFound)
|
||||
} else {
|
||||
|
||||
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
|
||||
bincode.workspace = true
|
||||
cfg-if.workspace = true
|
||||
itertools = { workspace = true }
|
||||
digest = "0.9"
|
||||
digest = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
@@ -113,17 +113,13 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
|
||||
output[0]
|
||||
}
|
||||
|
||||
@@ -401,4 +397,75 @@ mod tests {
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), expected1);
|
||||
assert_eq!(hash_to_scalar(msg2), expected2);
|
||||
assert_eq!(hash_to_scalar(msg3), expected3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_g1() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G1Affine::from_compressed(&[
|
||||
161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117,
|
||||
150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118,
|
||||
219, 115, 184, 96, 2, 10, 31, 63, 150, 70,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G1Affine::from_compressed(&[
|
||||
135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154,
|
||||
233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249,
|
||||
241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G1Affine::from_compressed(&[
|
||||
184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224,
|
||||
155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123,
|
||||
20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g1(msg1), expected1);
|
||||
assert_eq!(hash_g1(msg2), expected2);
|
||||
assert_eq!(hash_g1(msg3), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ pub type GatewaySharedKeyHkdfAlgorithm = blake3::Hasher;
|
||||
/// Hashing algorithm used when computing digest of a reply SURB encryption key.
|
||||
pub type ReplySurbKeyDigestAlgorithm = blake3::Hasher;
|
||||
|
||||
/// Hashing algorithm used when computing integrity (H)Mac for message exchanged between client and gateway.
|
||||
// TODO: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\legacy_shared_key
|
||||
// needs updating!
|
||||
pub type GatewayIntegrityHmacAlgorithm = blake3::Hasher;
|
||||
|
||||
/// Encryption algorithm used for encrypting acknowledgement messages.
|
||||
// TODO: if updated:
|
||||
// - PacketSize::ACK_PACKET_SIZE needs to be manually updated (if nonce/iv size differs);
|
||||
@@ -51,6 +56,12 @@ pub type ReplySurbKeyDigestAlgorithm = blake3::Hasher;
|
||||
// - the pem type defined in nym\common\nymsphinx\acknowledgements\src\key needs updating!
|
||||
pub type AckEncryptionAlgorithm = Aes128Ctr;
|
||||
|
||||
/// Legacy encryption algorithm used for end-to-end encryption of messages exchanged between clients
|
||||
/// and their gateways.
|
||||
// TODO: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\legacy_shared_key
|
||||
// needs updating!
|
||||
pub type LegacyGatewayEncryptionAlgorithm = Aes128Ctr;
|
||||
|
||||
/// Encryption algorithm used for end-to-end encryption of messages exchanged between clients
|
||||
/// and their gateways.
|
||||
// NOTE: if updated, the pem type defined in gateway\gateway-requests\src\registration\handshake\shared_key
|
||||
|
||||
@@ -182,9 +182,11 @@ impl BlockProcessor {
|
||||
// the ones concerned with individual messages
|
||||
for (index, msg) in block_tx.tx.body.messages.iter().enumerate() {
|
||||
for msg_module in &mut self.msg_modules {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
if msg.type_url == msg_module.type_url() {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,15 @@ pub enum ScraperError {
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("could not parse msg in tx {hash} at index {index} into {type_url}: {source}")]
|
||||
MsgParseFailure {
|
||||
hash: Hash,
|
||||
index: usize,
|
||||
type_url: String,
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")]
|
||||
InvalidSubscriptionEvent { query: String, kind: String },
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ use cosmrs::Any;
|
||||
|
||||
#[async_trait]
|
||||
pub trait MsgModule {
|
||||
fn type_url(&self) -> String;
|
||||
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
index: usize,
|
||||
|
||||
@@ -9,3 +9,4 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
pem = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -6,6 +6,7 @@ use pem::Pem;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::debug;
|
||||
|
||||
pub mod traits;
|
||||
|
||||
@@ -46,6 +47,10 @@ where
|
||||
T: PemStorableKey,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
debug!(
|
||||
"attempting to load key with the following pem type: {}",
|
||||
T::pem_type()
|
||||
);
|
||||
let key_pem = read_pem_file(path)?;
|
||||
|
||||
if T::pem_type() != key_pem.tag {
|
||||
|
||||
@@ -103,4 +103,8 @@ impl LaneQueueLengthsInner {
|
||||
{
|
||||
self.map.entry(*lane).and_modify(f);
|
||||
}
|
||||
|
||||
pub fn total(&self) -> usize {
|
||||
self.map.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +494,9 @@ pub struct ReplySurbsWasm {
|
||||
/// Defines how many mix nodes the reply surb should go through.
|
||||
/// If not set, the default value is going to be used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbsWasm {
|
||||
@@ -525,6 +528,7 @@ impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
reply_surbs.maximum_reply_key_age_ms as u64,
|
||||
),
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,6 +552,7 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,9 @@ pub struct ReplySurbsWasmOverride {
|
||||
|
||||
#[tsify(optional)]
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
@@ -416,6 +419,7 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
.maximum_reply_key_age_ms
|
||||
.unwrap_or(def.maximum_reply_key_age_ms),
|
||||
surb_mix_hops: value.surb_mix_hops,
|
||||
fresh_sender_tags: value.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::key_manager::ClientKeys;
|
||||
use nym_client_core::client::replies::reply_storage::browser_backend;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_crypto::asymmetric::ed25519::PublicKey;
|
||||
use nym_gateway_client::SharedSymmetricKey;
|
||||
use wasm_utils::console_log;
|
||||
|
||||
// temporary until other variants are properly implemented (probably it should get changed into `ClientStorage`
|
||||
@@ -156,6 +158,19 @@ impl GatewaysDetailsStore for ClientStorage {
|
||||
self.store_registered_gateway(&raw_registration).await
|
||||
}
|
||||
|
||||
async fn upgrade_stored_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id: PublicKey,
|
||||
updated_key: &SharedSymmetricKey,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.update_remote_gateway_key(
|
||||
&gateway_id.to_base58_string(),
|
||||
None,
|
||||
Some(updated_key.as_bytes()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn remove_gateway_details(&self, gateway_id: &str) -> Result<(), Self::StorageError> {
|
||||
self.remove_registered_gateway(gateway_id).await
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::{
|
||||
BadGateway, GatewayDetails, GatewayRegistration, RawRemoteGatewayDetails, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_gateway_client::SharedGatewayKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
@@ -16,8 +18,10 @@ pub struct WasmRawRegisteredGateway {
|
||||
#[zeroize(skip)]
|
||||
pub registration_timestamp: OffsetDateTime,
|
||||
|
||||
pub derived_aes128_ctr_blake3_hmac_keys_bs58: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub derived_aes256_gcm_siv_key: Vec<u8>,
|
||||
pub derived_aes256_gcm_siv_key: Option<Vec<u8>>,
|
||||
|
||||
pub gateway_owner_address: Option<String>,
|
||||
|
||||
@@ -31,6 +35,8 @@ impl TryFrom<WasmRawRegisteredGateway> for GatewayRegistration {
|
||||
// offload some parsing to an existing impl
|
||||
let raw_remote = RawRemoteGatewayDetails {
|
||||
gateway_id_bs58: value.gateway_id_bs58,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: value
|
||||
.derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key: value.derived_aes256_gcm_siv_key,
|
||||
gateway_owner_address: value.gateway_owner_address,
|
||||
gateway_listener: value.gateway_listener,
|
||||
@@ -50,11 +56,16 @@ impl<'a> From<&'a GatewayRegistration> for WasmRawRegisteredGateway {
|
||||
panic!("somehow obtained custom gateway registration in wasm!")
|
||||
};
|
||||
|
||||
let derived_aes256_gcm_siv_key = remote_details.shared_key.to_bytes().to_vec();
|
||||
let (derived_aes128_ctr_blake3_hmac_keys_bs58, derived_aes256_gcm_siv_key) =
|
||||
match remote_details.shared_key.deref() {
|
||||
SharedGatewayKey::Current(key) => (None, Some(key.to_bytes())),
|
||||
SharedGatewayKey::Legacy(key) => (Some(key.to_base58_string()), None),
|
||||
};
|
||||
|
||||
WasmRawRegisteredGateway {
|
||||
gateway_id_bs58: remote_details.gateway_id.to_string(),
|
||||
registration_timestamp: value.registration_timestamp,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58,
|
||||
derived_aes256_gcm_siv_key,
|
||||
gateway_listener: remote_details.gateway_listener.to_string(),
|
||||
gateway_owner_address: remote_details
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::error::Error;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_storage::traits::BaseWasmStorage;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// v1 tables
|
||||
pub(crate) mod v1 {
|
||||
@@ -237,6 +238,23 @@ pub trait WasmClientStorage: BaseWasmStorage {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn update_remote_gateway_key(
|
||||
&self,
|
||||
gateway_id_bs58: &str,
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58: Option<&str>,
|
||||
derived_aes256_gcm_siv_key: Option<&[u8]>,
|
||||
) -> Result<(), <Self as WasmClientStorage>::StorageError> {
|
||||
if let Some(mut current) = self.maybe_get_registered_gateway(gateway_id_bs58).await? {
|
||||
current.derived_aes128_ctr_blake3_hmac_keys_bs58 =
|
||||
derived_aes128_ctr_blake3_hmac_keys_bs58.map(|k| k.to_string());
|
||||
current.derived_aes256_gcm_siv_key = derived_aes256_gcm_siv_key.map(|k| k.to_vec());
|
||||
self.store_registered_gateway(¤t).await?;
|
||||
current.zeroize();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_registered_gateway(
|
||||
&self,
|
||||
gateway_id: &str,
|
||||
|
||||
Generated
+50
-12
@@ -1185,6 +1185,7 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1251,6 +1252,12 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.9.0"
|
||||
@@ -1522,18 +1529,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1558,9 +1565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1777,9 +1784,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -1794,15 +1801,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -1840,6 +1847,37 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
@@ -7,4 +7,6 @@ package-lock.json
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
.env
|
||||
|
||||
scratch.md
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- Run synchronization against [pool.ntp.org](https://www.ntppool.org/en/):
|
||||
```bash
|
||||
ntpdate -q pool.ntp.org
|
||||
```
|
||||
- Enable `ntp` service, start and review the status:
|
||||
```bash
|
||||
systemctl enable --now ntp
|
||||
service ntp start
|
||||
service ntp status
|
||||
```
|
||||
+1
-1
@@ -1 +1 @@
|
||||
807_251_217
|
||||
808_623_916
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.64%
|
||||
0.68%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
44.811
|
||||
42.157
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_025_628
|
||||
1_028_488
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
403_625_608
|
||||
404_311_958
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 191_376_083 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 808_623_916 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Wednesday, February 26th 2025, 16:02:47 UTC
|
||||
Tuesday, March 11th 2025, 11:04:18 UTC
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Node Status API
|
||||
|
||||
The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure.
|
||||
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
|
||||
|
||||
<Callout type="info">
|
||||
People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster.
|
||||
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
|
||||
</Callout>
|
||||
|
||||
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ns-api-run-deploy":"Run Instance",
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { RedocStandalone } from 'redoc';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
|
||||
options={{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user