Compare commits
2 commits
e0cc192841
...
abd8e1b885
| Author | SHA1 | Date | |
|---|---|---|---|
| abd8e1b885 | |||
| 30b7e5fed5 |
29 changed files with 1392 additions and 466 deletions
333
Cargo.lock
generated
333
Cargo.lock
generated
|
|
@ -68,6 +68,23 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.0"
|
||||
|
|
@ -123,10 +140,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
name = "cc"
|
||||
version = "1.2.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
|
|
@ -192,9 +219,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -202,9 +229,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.48"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -214,9 +241,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.47"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -226,9 +253,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
|
|
@ -348,7 +375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -364,10 +391,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
|
|
@ -380,20 +413,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-storage"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
|
||||
dependencies = [
|
||||
"gloo-utils",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -464,6 +527,12 @@ version = "1.70.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.81"
|
||||
|
|
@ -476,9 +545,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.176"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
|
@ -492,6 +561,22 @@ version = "0.4.28"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "minicov"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -634,7 +719,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -643,6 +728,21 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
|
|
@ -689,6 +789,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
|
|
@ -702,14 +815,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sharenet-passport"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bip39",
|
||||
"chacha20poly1305",
|
||||
"ciborium",
|
||||
"ed25519-dalek",
|
||||
"getrandom 0.2.16",
|
||||
"gloo-storage",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"js-sys",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"serde",
|
||||
|
|
@ -718,6 +836,9 @@ dependencies = [
|
|||
"tempfile",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test",
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -734,6 +855,12 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
|
|
@ -783,10 +910,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -867,7 +994,7 @@ version = "1.18.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
|
@ -878,21 +1005,22 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
|
|
@ -929,6 +1057,19 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.104"
|
||||
|
|
@ -962,10 +1103,63 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"minicov",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
|
|
@ -991,7 +1185,16 @@ version = "0.60.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.4",
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1012,19 +1215,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.4"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1035,9 +1238,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
|
|
@ -1047,9 +1250,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
|
|
@ -1059,9 +1262,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
|
|
@ -1071,9 +1274,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
|
|
@ -1083,9 +1286,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
|
|
@ -1095,9 +1298,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
|
|
@ -1107,9 +1310,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
|
|
@ -1119,9 +1322,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
|
|
|
|||
15
libs/sharenet-passport/.cargo/config.toml
Normal file
15
libs/sharenet-passport/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[build]
|
||||
# Default to native target
|
||||
rustflags = ["-C", "target-cpu=native"]
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
# Use wasm-bindgen-test for WASM testing
|
||||
runner = "wasm-bindgen-test-runner"
|
||||
|
||||
[target.wasm32-wasi]
|
||||
# Use wasmtime for WASI testing
|
||||
runner = "wasmtime"
|
||||
|
||||
[env]
|
||||
# Environment variables for development
|
||||
RUST_BACKTRACE = "1"
|
||||
149
libs/sharenet-passport/ARCHITECTURE.md
Normal file
149
libs/sharenet-passport/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# Sharenet Passport Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The Sharenet Passport library provides a unified API for cryptographic operations and file storage that works seamlessly across both native and WASM targets. The architecture uses Rust's conditional compilation to automatically select the appropriate implementation based on the target platform.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Public API Surface │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Bip39MnemonicGenerator, Ed25519KeyDeriver, │ │
|
||||
│ │ XChaCha20FileEncryptor, FileSystemStorage │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Infrastructure Layer │
|
||||
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
|
||||
│ │ Crypto │ │ Storage │ │ RNG │ │
|
||||
│ │ │ │ │ │ Time │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Target-Specific Implementations │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Native (std) │ WASM │ │
|
||||
│ │ • OsRng │ • getrandom(js) │ │
|
||||
│ │ • File system │ • LocalStorage │ │
|
||||
│ │ • SystemTime │ • web-time │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
### Core Modules
|
||||
- **`domain/`**: Domain entities, traits, and error types
|
||||
- **`application/`**: Use cases and application logic
|
||||
- **`infrastructure/`**: Platform-specific implementations
|
||||
|
||||
### Infrastructure Layer
|
||||
- **`infrastructure/crypto/`**: Unified cryptographic API
|
||||
- **`shared/`**: Shared utilities and constants
|
||||
- **`native/`**: Native implementations using std
|
||||
- **`wasm/`**: WASM implementations using browser APIs
|
||||
- **`infrastructure/storage/`**: Unified storage API
|
||||
- **`native/`**: File system storage
|
||||
- **`wasm/`**: Browser LocalStorage
|
||||
- **`infrastructure/rng/`**: Random number generation abstraction
|
||||
- **`infrastructure/time/`**: Time abstraction
|
||||
|
||||
## Target Selection
|
||||
|
||||
The library automatically selects implementations based on the target architecture:
|
||||
|
||||
- **Native targets** (`x86_64`, `aarch64`, etc.): Use native implementations
|
||||
- **WASM targets** (`wasm32-unknown-unknown`): Use WASM implementations
|
||||
|
||||
### Conditional Compilation
|
||||
|
||||
```rust
|
||||
// In infrastructure/crypto/mod.rs
|
||||
#[cfg(any(not(target_arch = "wasm32"), feature = "force-native"))]
|
||||
mod native;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
|
||||
mod wasm;
|
||||
```
|
||||
|
||||
## Optional Override Features
|
||||
|
||||
For testing and special cases, optional features allow manual override:
|
||||
|
||||
- **`force-wasm`**: Use WASM implementation even on native targets
|
||||
- **`force-native`**: Use native implementation even on WASM targets
|
||||
|
||||
These features are mutually exclusive and will trigger a compile error if both are enabled.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Target-Specific Tests
|
||||
- **Native tests**: Located in `src/infrastructure/crypto/native_test.rs`
|
||||
- **WASM tests**: Located in `src/infrastructure/crypto/wasm_test.rs`
|
||||
|
||||
### Test Runners
|
||||
- **Native**: Standard `cargo test`
|
||||
- **WASM**: `wasm-bindgen-test-runner` configured in `.cargo/config.toml`
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Target-Specific Dependencies
|
||||
- **WASM-only**: `gloo-storage`, `web-time`, `getrandom` with JS feature
|
||||
- **Native-only**: `getrandom` with std feature
|
||||
- **Shared**: `bip39`, `ed25519-dalek`, `chacha20poly1305`
|
||||
|
||||
## Public API Consistency
|
||||
|
||||
The public API remains identical regardless of target:
|
||||
|
||||
```rust
|
||||
// Same API on both targets
|
||||
use sharenet_passport::{
|
||||
Bip39MnemonicGenerator,
|
||||
Ed25519KeyDeriver,
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage,
|
||||
};
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Testing Both Targets
|
||||
```bash
|
||||
# Test native
|
||||
cargo test
|
||||
|
||||
# Test WASM
|
||||
cargo test --target wasm32-unknown-unknown
|
||||
|
||||
# Test with override features
|
||||
cargo test --features force-wasm
|
||||
cargo test --features force-native
|
||||
```
|
||||
|
||||
### Building for Different Targets
|
||||
```bash
|
||||
# Build native
|
||||
cargo build
|
||||
|
||||
# Build WASM
|
||||
cargo build --target wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
## Adding New Target-Specific Code
|
||||
|
||||
1. Add shared logic to the appropriate `shared/` module
|
||||
2. Implement native version in `native/` module
|
||||
3. Implement WASM version in `wasm/` module
|
||||
4. Update the aggregator module to export the unified API
|
||||
5. Add target-specific tests
|
||||
|
||||
## Cryptographic Consistency
|
||||
|
||||
All implementations produce identical cryptographic outputs for the same inputs, ensuring cross-platform compatibility.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sharenet-passport"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
publish = ["sharenet-sh-forgejo"] # Set this to whichever Cargo registry you are publishing to
|
||||
edition = "2021"
|
||||
description = "Core library for Sharenet Passport creation and management"
|
||||
|
|
@ -25,16 +25,40 @@ thiserror = "1.0"
|
|||
zeroize = { version = "1.7", features = ["zeroize_derive"] }
|
||||
hex = "0.4"
|
||||
ciborium = "0.2"
|
||||
uuid = { version = "1.8", features = ["v7"] }
|
||||
|
||||
# Core async support
|
||||
async-trait = "0.1"
|
||||
|
||||
# Dependencies needed for WASM implementation (available for all targets)
|
||||
base64 = "0.21"
|
||||
|
||||
# WASM-specific dependencies
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
uuid = { version = "1.10", features = ["v7", "js"] }
|
||||
web-time = "1.1"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
gloo-storage = "0.3"
|
||||
|
||||
# Native dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["std"] }
|
||||
uuid = { version = "1.10", features = ["v7", "rng"] }
|
||||
|
||||
# Dev dependencies for WASM testing
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8"
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"] # Support both native and WASM
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [] # Standard library support
|
||||
default = []
|
||||
std = [] # Standard library support (for native targets)
|
||||
alloc = [] # No-std with alloc support
|
||||
wasm = ["alloc"] # WASM target support
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8"
|
||||
# Optional override features for manual platform selection
|
||||
force-wasm = [] # Force WASM implementation even on native targets
|
||||
force-native = [] # Force native implementation even on WASM targets
|
||||
|
|
@ -18,15 +18,12 @@ A secure Rust library for creating and managing Sharenet Passport files (.spf) f
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
sharenet-passport = { version = "0.1.0", registry = "sharenet-sh-forgejo", features = ["std"] }
|
||||
sharenet-passport = { version = "0.2.0", registry = "sharenet-sh-forgejo" }
|
||||
```
|
||||
|
||||
### For WASM Projects
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sharenet-passport = { version = "0.1.0", registry = "sharenet-sh-forgejo", features = ["wasm"] }
|
||||
```
|
||||
Platform selection is automatic based on your compilation target:
|
||||
- **Native targets** (x86_64, aarch64, etc.): Use native implementations
|
||||
- **WASM targets** (wasm32-unknown-unknown): Use WASM implementations
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -100,11 +97,29 @@ Built with Clean Architecture principles:
|
|||
- **Application Layer**: Use cases (CreatePassport, ImportFromRecovery, SignCard, etc.)
|
||||
- **Infrastructure Layer**: Crypto implementations, file storage
|
||||
|
||||
## Feature Flags
|
||||
## Targets
|
||||
|
||||
- `std` (default): Standard library support for CLI and server applications
|
||||
- `wasm`: WebAssembly support for web applications
|
||||
- `alloc`: No-std with allocator support
|
||||
The library automatically selects the appropriate implementation based on your compilation target:
|
||||
|
||||
### Native Targets
|
||||
- **Platforms**: Linux, macOS, Windows, etc.
|
||||
- **Storage**: File system
|
||||
- **RNG**: System entropy (OsRng)
|
||||
- **Time**: System time
|
||||
|
||||
### WASM Targets
|
||||
- **Platforms**: Web browsers, Node.js
|
||||
- **Storage**: Browser LocalStorage
|
||||
- **RNG**: Web Crypto API via getrandom
|
||||
- **Time**: JavaScript Date API
|
||||
|
||||
### Optional Override Features
|
||||
|
||||
For testing and special cases:
|
||||
- `force-wasm`: Use WASM implementation even on native targets
|
||||
- `force-native`: Use native implementation even on WASM targets
|
||||
|
||||
These features are mutually exclusive and will trigger a compile error if both are enabled.
|
||||
|
||||
## Security Features
|
||||
|
||||
|
|
@ -128,22 +143,25 @@ Sharenet Passport Files (.spf) are encrypted containers that store:
|
|||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
# Test native implementation
|
||||
cargo test
|
||||
|
||||
# Test specific features
|
||||
cargo test --features std
|
||||
cargo test --features wasm
|
||||
# Test WASM implementation
|
||||
cargo test --target wasm32-unknown-unknown
|
||||
|
||||
# Test with override features
|
||||
cargo test --features force-wasm
|
||||
cargo test --features force-native
|
||||
```
|
||||
|
||||
### Building for WASM
|
||||
### Building for Different Targets
|
||||
|
||||
```bash
|
||||
# Install wasm-pack if needed
|
||||
cargo install wasm-pack
|
||||
# Build for native
|
||||
cargo build
|
||||
|
||||
# Build for web
|
||||
wasm-pack build --target web --features wasm
|
||||
# Build for WASM
|
||||
cargo build --target wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
## License
|
||||
|
|
|
|||
|
|
@ -1,5 +1,2 @@
|
|||
pub mod use_cases;
|
||||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
mod use_cases_test;
|
||||
|
|
@ -2,7 +2,7 @@ use crate::domain::entities::*;
|
|||
use crate::domain::traits::*;
|
||||
use crate::application::error::ApplicationError;
|
||||
use ed25519_dalek::Signer;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use crate::infrastructure::time;
|
||||
|
||||
pub struct CreatePassportUseCase<MG, KD, FE, FS>
|
||||
where
|
||||
|
|
@ -425,10 +425,8 @@ where
|
|||
let existing_profile = passport.user_profile_by_id(id)
|
||||
.ok_or_else(|| ApplicationError::UseCaseError("User profile not found".to_string()))?;
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Time error: {}", e)))?
|
||||
.as_secs();
|
||||
let now = time::now_seconds()
|
||||
.map_err(|e| ApplicationError::UseCaseError(format!("Time error: {}", e)))?;
|
||||
|
||||
// Use existing hub_did (cannot change hub_did via update)
|
||||
let profile = UserProfile {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#[cfg(test)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod tests {
|
||||
use crate::application::use_cases::{CreatePassportUseCase, ImportFromRecoveryUseCase, ImportFromFileUseCase, ExportPassportUseCase, SignCardUseCase, CreateUserProfileUseCase, UpdateUserProfileUseCase, DeleteUserProfileUseCase};
|
||||
// Note: These domain entities are used indirectly through the use cases
|
||||
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
use crate::infrastructure::native::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
use crate::infrastructure::storage::FileSystemStorage;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use uuid::Uuid;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
|
||||
use crate::infrastructure::time;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RecoveryPhrase {
|
||||
words: Vec<String>,
|
||||
|
|
@ -266,10 +268,7 @@ impl UserProfile {
|
|||
identity: UserIdentity,
|
||||
preferences: UserPreferences,
|
||||
) -> Self {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let now = time::now_seconds().unwrap_or_default();
|
||||
|
||||
Self {
|
||||
id: Uuid::now_v7().to_string(),
|
||||
|
|
|
|||
32
libs/sharenet-passport/src/infrastructure/crypto/mod.rs
Normal file
32
libs/sharenet-passport/src/infrastructure/crypto/mod.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//! Unified cryptographic API with target-specific implementations
|
||||
|
||||
// Check for mutually exclusive override features
|
||||
#[cfg(all(feature = "force-wasm", feature = "force-native"))]
|
||||
compile_error!("Features 'force-wasm' and 'force-native' are mutually exclusive");
|
||||
|
||||
// Shared helper functions and types
|
||||
mod shared;
|
||||
|
||||
// Platform-specific implementations
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
|
||||
mod native;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
|
||||
mod wasm;
|
||||
|
||||
// Re-export the unified API
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
|
||||
pub use native::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
|
||||
pub use wasm::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
// Target-specific tests
|
||||
#[cfg(all(test, all(not(target_arch = "wasm32"), not(feature = "force-wasm"))))]
|
||||
mod native_test;
|
||||
|
||||
#[cfg(all(test, any(target_arch = "wasm32", feature = "force-wasm")))]
|
||||
mod wasm_test;
|
||||
|
||||
// Re-export shared types if any
|
||||
pub use shared::*;
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
//! Native (std) cryptographic implementations
|
||||
|
||||
use bip39::Mnemonic;
|
||||
use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH};
|
||||
use chacha20poly1305::{aead::{Aead, KeyInit}, XChaCha20Poly1305, Key, XNonce};
|
||||
|
|
@ -7,8 +9,9 @@ use rand::{RngCore, rngs::OsRng};
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::*;
|
||||
use crate::domain::error::DomainError;
|
||||
use crate::domain::traits::*;
|
||||
use super::shared::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bip39MnemonicGenerator;
|
||||
|
|
@ -42,6 +45,8 @@ impl KeyDeriver for Ed25519KeyDeriver {
|
|||
type Error = DomainError;
|
||||
|
||||
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error> {
|
||||
validate_seed_length(seed.as_bytes())?;
|
||||
|
||||
let signing_key = SigningKey::from_bytes(&seed.as_bytes()[..SECRET_KEY_LENGTH].try_into()
|
||||
.map_err(|_| DomainError::CryptographicError("Invalid seed length".to_string()))?);
|
||||
|
||||
|
|
@ -60,7 +65,14 @@ impl KeyDeriver for Ed25519KeyDeriver {
|
|||
|
||||
// Use univ_id as passphrase to bind seed to universe
|
||||
let bip39_seed = bip39_mnemonic.to_seed(univ_id);
|
||||
Ok(Seed::new(bip39_seed.to_vec()))
|
||||
|
||||
// BIP39 produces 64-byte seed, but we only need 32 bytes for Ed25519
|
||||
// Use the first 32 bytes of the BIP39 seed
|
||||
let ed25519_seed: [u8; 32] = bip39_seed[..32]
|
||||
.try_into()
|
||||
.map_err(|_| DomainError::CryptographicError("Failed to extract 32-byte seed from BIP39 seed".to_string()))?;
|
||||
|
||||
Ok(Seed::new(ed25519_seed.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,22 +92,22 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
|||
user_profiles: &[UserProfile],
|
||||
) -> Result<PassportFile, Self::Error> {
|
||||
// Generate salt and nonce
|
||||
let mut salt = [0u8; 32];
|
||||
let mut nonce_bytes = [0u8; 24];
|
||||
let mut salt = [0u8; SALT_LENGTH];
|
||||
let mut nonce_bytes = [0u8; NONCE_LENGTH];
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
OsRng.fill_bytes(&mut nonce_bytes);
|
||||
|
||||
// Derive KEK from password using HKDF
|
||||
let hk = Hkdf::<Sha256>::new(Some(&salt), password.as_bytes());
|
||||
let mut kek = [0u8; 32];
|
||||
hk.expand(b"sharenet-passport-kek", &mut kek)
|
||||
hk.expand(KDF_INFO, &mut kek)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
|
||||
|
||||
// Encrypt seed
|
||||
let cipher = XChaCha20Poly1305::new(Key::from_slice(&kek));
|
||||
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
|
||||
let nonce = XNonce::from_slice(&nonce_bytes);
|
||||
let enc_seed = cipher
|
||||
.encrypt(nonce, seed.as_bytes())
|
||||
.encrypt(&nonce, seed.as_bytes())
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Encryption failed: {}", e)))?;
|
||||
|
||||
// Serialize and encrypt user profiles
|
||||
|
|
@ -103,7 +115,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
|||
let user_profiles_bytes = serde_cbor::to_vec(&user_profiles_vec)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize user profiles: {}", e)))?;
|
||||
let enc_user_profiles = cipher
|
||||
.encrypt(nonce, &*user_profiles_bytes)
|
||||
.encrypt(&nonce, &*user_profiles_bytes)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
|
||||
|
||||
// Get current timestamp
|
||||
|
|
@ -114,8 +126,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
|||
|
||||
Ok(PassportFile {
|
||||
enc_seed,
|
||||
kdf: "HKDF-SHA256".to_string(),
|
||||
cipher: "XChaCha20-Poly1305".to_string(),
|
||||
kdf: KDF_HKDF_SHA256.to_string(),
|
||||
cipher: CIPHER_XCHACHA20_POLY1305.to_string(),
|
||||
salt: salt.to_vec(),
|
||||
nonce: nonce_bytes.to_vec(),
|
||||
public_key: public_key.0.clone(),
|
||||
|
|
@ -133,23 +145,19 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
|||
password: &str,
|
||||
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
|
||||
// Validate file format
|
||||
if file.kdf != "HKDF-SHA256" || file.cipher != "XChaCha20-Poly1305" {
|
||||
return Err(DomainError::InvalidFileFormat(
|
||||
"Unsupported KDF or cipher".to_string(),
|
||||
));
|
||||
}
|
||||
validate_file_format(&file.kdf, &file.cipher)?;
|
||||
|
||||
// Derive KEK from password
|
||||
let hk = Hkdf::<Sha256>::new(Some(&file.salt), password.as_bytes());
|
||||
let mut kek = [0u8; 32];
|
||||
hk.expand(b"sharenet-passport-kek", &mut kek)
|
||||
hk.expand(KDF_INFO, &mut kek)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
|
||||
|
||||
// Decrypt seed
|
||||
let cipher = XChaCha20Poly1305::new(Key::from_slice(&kek));
|
||||
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
|
||||
let nonce = XNonce::from_slice(&file.nonce);
|
||||
let seed_bytes = cipher
|
||||
.decrypt(nonce, &*file.enc_seed)
|
||||
.decrypt(&nonce, &*file.enc_seed)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Decryption failed: {}", e)))?;
|
||||
|
||||
let seed = Seed::new(seed_bytes);
|
||||
|
|
@ -167,7 +175,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
|
|||
|
||||
// Decrypt user profiles
|
||||
let user_profiles_bytes = cipher
|
||||
.decrypt(nonce, &*file.enc_user_profiles)
|
||||
.decrypt(&nonce, &*file.enc_user_profiles)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles decryption failed: {}", e)))?;
|
||||
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//! Native-specific cryptographic tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
|
||||
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
#[test]
|
||||
fn test_native_bip39_generator_creates_valid_mnemonic() {
|
||||
let generator = Bip39MnemonicGenerator;
|
||||
let phrase = generator.generate().unwrap();
|
||||
|
||||
// Should have 24 words
|
||||
assert_eq!(phrase.words().len(), 24);
|
||||
|
||||
// Should be valid BIP-39
|
||||
generator.validate(phrase.words()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_key_deriver_creates_consistent_keys() {
|
||||
let deriver = Ed25519KeyDeriver;
|
||||
|
||||
// Create a test seed
|
||||
let test_seed = Seed::new(vec![1; 32]);
|
||||
|
||||
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
|
||||
|
||||
// Public key should be 32 bytes
|
||||
assert_eq!(public_key.0.len(), 32);
|
||||
|
||||
// Private key should be 32 bytes
|
||||
assert_eq!(private_key.0.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_file_encryptor_round_trip() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
// Encrypt
|
||||
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
|
||||
|
||||
// Verify file structure
|
||||
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
||||
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
|
||||
assert_eq!(encrypted_file.salt.len(), 32);
|
||||
assert_eq!(encrypted_file.nonce.len(), 24);
|
||||
assert_eq!(encrypted_file.public_key, public_key.0);
|
||||
assert_eq!(encrypted_file.did, did.0);
|
||||
|
||||
// Decrypt
|
||||
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
|
||||
|
||||
// Verify decryption
|
||||
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
||||
assert_eq!(decrypted_public_key.0, public_key.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_file_encryptor_wrong_password_fails() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
|
||||
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
|
||||
let public_key = PublicKey(vec![1; 32]);
|
||||
let did = Did::new(&public_key);
|
||||
|
||||
// Encrypt with one password
|
||||
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
|
||||
|
||||
// Try to decrypt with wrong password
|
||||
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
|
||||
|
||||
// Should fail
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
35
libs/sharenet-passport/src/infrastructure/crypto/shared.rs
Normal file
35
libs/sharenet-passport/src/infrastructure/crypto/shared.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//! Shared cryptographic utilities and constants
|
||||
|
||||
/// Cryptographic constants used across implementations
|
||||
pub const SEED_LENGTH: usize = 32;
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
pub const PRIVATE_KEY_LENGTH: usize = 32;
|
||||
pub const SALT_LENGTH: usize = 32;
|
||||
pub const NONCE_LENGTH: usize = 24;
|
||||
|
||||
/// KDF and cipher identifiers
|
||||
pub const KDF_HKDF_SHA256: &str = "HKDF-SHA256";
|
||||
pub const CIPHER_XCHACHA20_POLY1305: &str = "XChaCha20-Poly1305";
|
||||
|
||||
/// HKDF info strings
|
||||
pub const KDF_INFO: &[u8] = b"sharenet-passport-kek";
|
||||
|
||||
/// Helper function to validate seed length
|
||||
pub fn validate_seed_length(seed_bytes: &[u8]) -> Result<(), crate::domain::error::DomainError> {
|
||||
if seed_bytes.len() != SEED_LENGTH {
|
||||
return Err(crate::domain::error::DomainError::CryptographicError(
|
||||
format!("Invalid seed length: expected {}, got {}", SEED_LENGTH, seed_bytes.len())
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to validate file format
|
||||
pub fn validate_file_format(kdf: &str, cipher: &str) -> Result<(), crate::domain::error::DomainError> {
|
||||
if kdf != KDF_HKDF_SHA256 || cipher != CIPHER_XCHACHA20_POLY1305 {
|
||||
return Err(crate::domain::error::DomainError::InvalidFileFormat(
|
||||
"Unsupported KDF or cipher".to_string()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
185
libs/sharenet-passport/src/infrastructure/crypto/wasm.rs
Normal file
185
libs/sharenet-passport/src/infrastructure/crypto/wasm.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
//! WASM-compatible cryptographic implementations
|
||||
|
||||
use bip39::Mnemonic;
|
||||
use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH};
|
||||
use chacha20poly1305::{aead::{Aead, KeyInit}, XChaCha20Poly1305, Key, XNonce};
|
||||
use hkdf::Hkdf;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::error::DomainError;
|
||||
use crate::domain::traits::*;
|
||||
use crate::infrastructure::rng;
|
||||
use crate::infrastructure::time;
|
||||
use super::shared::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bip39MnemonicGenerator;
|
||||
|
||||
impl MnemonicGenerator for Bip39MnemonicGenerator {
|
||||
type Error = DomainError;
|
||||
|
||||
fn generate(&self) -> Result<RecoveryPhrase, Self::Error> {
|
||||
let mut entropy = [0u8; 32];
|
||||
let mut rng = rng::new_rng();
|
||||
rng.fill_bytes(&mut entropy)?;
|
||||
|
||||
let mnemonic = Mnemonic::from_entropy(&entropy)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to generate mnemonic: {}", e)))?;
|
||||
|
||||
let words: Vec<String> = mnemonic.words().into_iter().map(|s| s.to_string()).collect();
|
||||
Ok(RecoveryPhrase::new(words))
|
||||
}
|
||||
|
||||
fn validate(&self, words: &[String]) -> Result<(), Self::Error> {
|
||||
let phrase = words.join(" ");
|
||||
Mnemonic::parse(&phrase)
|
||||
.map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Ed25519KeyDeriver;
|
||||
|
||||
impl KeyDeriver for Ed25519KeyDeriver {
|
||||
type Error = DomainError;
|
||||
|
||||
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error> {
|
||||
validate_seed_length(seed.as_bytes())?;
|
||||
|
||||
let signing_key = SigningKey::from_bytes(&seed.as_bytes()[..SECRET_KEY_LENGTH].try_into()
|
||||
.map_err(|_| DomainError::CryptographicError("Invalid seed length".to_string()))?);
|
||||
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
Ok((
|
||||
PublicKey(verifying_key.to_bytes().to_vec()),
|
||||
PrivateKey(signing_key.to_bytes().to_vec()),
|
||||
))
|
||||
}
|
||||
|
||||
fn derive_from_mnemonic(&self, mnemonic: &RecoveryPhrase, univ_id: &str) -> Result<Seed, Self::Error> {
|
||||
let phrase = mnemonic.words().join(" ");
|
||||
let bip39_mnemonic = Mnemonic::parse(&phrase)
|
||||
.map_err(|e| DomainError::InvalidMnemonic(format!("Invalid mnemonic: {}", e)))?;
|
||||
|
||||
// Use univ_id as passphrase to bind seed to universe
|
||||
let bip39_seed = bip39_mnemonic.to_seed(univ_id);
|
||||
|
||||
// BIP39 produces 64-byte seed, but we only need 32 bytes for Ed25519
|
||||
// Use the first 32 bytes of the BIP39 seed
|
||||
let ed25519_seed: [u8; 32] = bip39_seed[..32]
|
||||
.try_into()
|
||||
.map_err(|_| DomainError::CryptographicError("Failed to extract 32-byte seed from BIP39 seed".to_string()))?;
|
||||
|
||||
Ok(Seed::new(ed25519_seed.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct XChaCha20FileEncryptor;
|
||||
|
||||
impl FileEncryptor for XChaCha20FileEncryptor {
|
||||
type Error = DomainError;
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
seed: &Seed,
|
||||
password: &str,
|
||||
public_key: &PublicKey,
|
||||
did: &Did,
|
||||
univ_id: &str,
|
||||
user_profiles: &[UserProfile],
|
||||
) -> Result<PassportFile, Self::Error> {
|
||||
// Generate salt and nonce using WASM-compatible RNG
|
||||
let mut salt = [0u8; SALT_LENGTH];
|
||||
let mut nonce_bytes = [0u8; NONCE_LENGTH];
|
||||
let mut rng = rng::new_rng();
|
||||
rng.fill_bytes(&mut salt)?;
|
||||
rng.fill_bytes(&mut nonce_bytes)?;
|
||||
|
||||
// Derive KEK from password using HKDF
|
||||
let hk = Hkdf::<Sha256>::new(Some(&salt), password.as_bytes());
|
||||
let mut kek = [0u8; 32];
|
||||
hk.expand(KDF_INFO, &mut kek)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
|
||||
|
||||
// Encrypt seed
|
||||
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
|
||||
let nonce = XNonce::from_slice(&nonce_bytes);
|
||||
let enc_seed = cipher
|
||||
.encrypt(&nonce, seed.as_bytes())
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Encryption failed: {}", e)))?;
|
||||
|
||||
// Serialize and encrypt user profiles
|
||||
let user_profiles_vec: Vec<UserProfile> = user_profiles.to_vec();
|
||||
let user_profiles_bytes = serde_cbor::to_vec(&user_profiles_vec)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize user profiles: {}", e)))?;
|
||||
let enc_user_profiles = cipher
|
||||
.encrypt(&nonce, &*user_profiles_bytes)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
|
||||
|
||||
// Get current timestamp using WASM-compatible time
|
||||
let created_at = time::now_seconds()?;
|
||||
|
||||
Ok(PassportFile {
|
||||
enc_seed,
|
||||
kdf: KDF_HKDF_SHA256.to_string(),
|
||||
cipher: CIPHER_XCHACHA20_POLY1305.to_string(),
|
||||
salt: salt.to_vec(),
|
||||
nonce: nonce_bytes.to_vec(),
|
||||
public_key: public_key.0.clone(),
|
||||
did: did.0.clone(),
|
||||
univ_id: univ_id.to_string(),
|
||||
created_at,
|
||||
version: "1.0.0".to_string(),
|
||||
enc_user_profiles,
|
||||
})
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
file: &PassportFile,
|
||||
password: &str,
|
||||
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
|
||||
// Validate file format
|
||||
validate_file_format(&file.kdf, &file.cipher)?;
|
||||
|
||||
// Derive KEK from password
|
||||
let hk = Hkdf::<Sha256>::new(Some(&file.salt), password.as_bytes());
|
||||
let mut kek = [0u8; 32];
|
||||
hk.expand(KDF_INFO, &mut kek)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("HKDF failed: {}", e)))?;
|
||||
|
||||
// Decrypt seed
|
||||
let cipher = XChaCha20Poly1305::new(&Key::from(kek));
|
||||
let nonce = XNonce::from_slice(&file.nonce);
|
||||
let seed_bytes = cipher
|
||||
.decrypt(&nonce, &*file.enc_seed)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Decryption failed: {}", e)))?;
|
||||
|
||||
let seed = Seed::new(seed_bytes);
|
||||
|
||||
// Re-derive keys from seed to verify
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
let (public_key, private_key) = key_deriver.derive_from_seed(&seed)?;
|
||||
|
||||
// Verify public key matches
|
||||
if public_key.0 != file.public_key {
|
||||
return Err(DomainError::CryptographicError(
|
||||
"Public key mismatch - wrong password?".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Decrypt user profiles
|
||||
let user_profiles_bytes = cipher
|
||||
.decrypt(&nonce, &*file.enc_user_profiles)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("User profiles decryption failed: {}", e)))?;
|
||||
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
|
||||
|
||||
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
|
||||
Ok((seed, public_key, private_key, user_profiles))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
//! WASM-specific cryptographic tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
|
||||
use crate::infrastructure::crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
#[test]
|
||||
fn test_wasm_bip39_generator_creates_valid_mnemonic() {
|
||||
let generator = Bip39MnemonicGenerator;
|
||||
let phrase = generator.generate().unwrap();
|
||||
|
||||
// Should have 24 words
|
||||
assert_eq!(phrase.words().len(), 24);
|
||||
|
||||
// Should be valid BIP-39
|
||||
generator.validate(phrase.words()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wasm_key_deriver_creates_consistent_keys() {
|
||||
let deriver = Ed25519KeyDeriver;
|
||||
|
||||
// Create a test seed
|
||||
let test_seed = Seed::new(vec![1; 32]);
|
||||
|
||||
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
|
||||
|
||||
// Public key should be 32 bytes
|
||||
assert_eq!(public_key.0.len(), 32);
|
||||
|
||||
// Private key should be 32 bytes
|
||||
assert_eq!(private_key.0.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wasm_file_encryptor_round_trip() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
// Encrypt
|
||||
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
|
||||
|
||||
// Verify file structure
|
||||
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
||||
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
|
||||
assert_eq!(encrypted_file.salt.len(), 32);
|
||||
assert_eq!(encrypted_file.nonce.len(), 24);
|
||||
assert_eq!(encrypted_file.public_key, public_key.0);
|
||||
assert_eq!(encrypted_file.did, did.0);
|
||||
|
||||
// Decrypt
|
||||
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
|
||||
|
||||
// Verify decryption
|
||||
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
||||
assert_eq!(decrypted_public_key.0, public_key.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wasm_file_encryptor_wrong_password_fails() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
|
||||
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
|
||||
let public_key = PublicKey(vec![1; 32]);
|
||||
let did = Did::new(&public_key);
|
||||
|
||||
// Encrypt with one password
|
||||
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
|
||||
|
||||
// Try to decrypt with wrong password
|
||||
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
|
||||
|
||||
// Should fail
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
|
||||
use crate::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
|
||||
#[test]
|
||||
fn test_bip39_generator_creates_valid_mnemonic() {
|
||||
let generator = Bip39MnemonicGenerator;
|
||||
let phrase = generator.generate().unwrap();
|
||||
|
||||
// Should have 24 words
|
||||
assert_eq!(phrase.words().len(), 24);
|
||||
|
||||
// Should be valid BIP-39
|
||||
generator.validate(phrase.words()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bip39_generator_validates_correct_mnemonic() {
|
||||
let generator = Bip39MnemonicGenerator;
|
||||
|
||||
// This is a valid test mnemonic
|
||||
let _valid_words = vec![
|
||||
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
|
||||
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
|
||||
"abandon".to_string(), "abandon".to_string(), "abandon".to_string(),
|
||||
"abandon".to_string(), "abandon".to_string(), "about".to_string(),
|
||||
];
|
||||
|
||||
// Note: This test mnemonic is actually invalid (checksum fails)
|
||||
// For a real test, we'd need a properly generated mnemonic
|
||||
// For now, we'll test that invalid mnemonics are rejected
|
||||
let invalid_words = vec!["invalid".to_string(); 12];
|
||||
|
||||
assert!(generator.validate(&invalid_words).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_deriver_creates_consistent_keys() {
|
||||
let deriver = Ed25519KeyDeriver;
|
||||
|
||||
// Create a test seed
|
||||
let test_seed = Seed::new(vec![1; 32]);
|
||||
|
||||
let (public_key, private_key) = deriver.derive_from_seed(&test_seed).unwrap();
|
||||
|
||||
// Public key should be 32 bytes
|
||||
assert_eq!(public_key.0.len(), 32);
|
||||
|
||||
// Private key should be 32 bytes
|
||||
assert_eq!(private_key.0.len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_encryptor_round_trip() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
// Encrypt
|
||||
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "univ:test:crypto", &[]).unwrap();
|
||||
|
||||
// Verify file structure
|
||||
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
||||
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
|
||||
assert_eq!(encrypted_file.salt.len(), 32);
|
||||
assert_eq!(encrypted_file.nonce.len(), 24);
|
||||
assert_eq!(encrypted_file.public_key, public_key.0);
|
||||
assert_eq!(encrypted_file.did, did.0);
|
||||
|
||||
// Decrypt
|
||||
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
|
||||
|
||||
// Verify decryption
|
||||
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
||||
assert_eq!(decrypted_public_key.0, public_key.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_encryptor_wrong_password_fails() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
|
||||
let seed = Seed::new(vec![1, 2, 3, 4, 5]);
|
||||
let public_key = PublicKey(vec![1; 32]);
|
||||
let did = Did::new(&public_key);
|
||||
|
||||
// Encrypt with one password
|
||||
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "univ:test:crypto", &[]).unwrap();
|
||||
|
||||
// Try to decrypt with wrong password
|
||||
let result = encryptor.decrypt(&encrypted_file, "wrong-password");
|
||||
|
||||
// Should fail
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_encryptor_invalid_file_format() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
|
||||
let mut invalid_file = PassportFile {
|
||||
enc_seed: vec![1, 2, 3],
|
||||
kdf: "Invalid-KDF".to_string(),
|
||||
cipher: "Invalid-Cipher".to_string(),
|
||||
salt: vec![0; 32],
|
||||
nonce: vec![0; 24],
|
||||
public_key: vec![1; 32],
|
||||
did: "test-did".to_string(),
|
||||
univ_id: "univ:test:crypto".to_string(),
|
||||
created_at: 0,
|
||||
version: "1.0.0".to_string(),
|
||||
enc_user_profiles: vec![],
|
||||
};
|
||||
|
||||
// Test with invalid KDF
|
||||
let result = encryptor.decrypt(&invalid_file, "password");
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test with invalid cipher
|
||||
invalid_file.kdf = "HKDF-SHA256".to_string();
|
||||
invalid_file.cipher = "Invalid-Cipher".to_string();
|
||||
let result = encryptor.decrypt(&invalid_file, "password");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_encryptor_with_user_profiles() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
// Create test user profiles
|
||||
let user_profiles = vec![
|
||||
UserProfile {
|
||||
id: "test-uuid-default".to_string(),
|
||||
hub_did: None,
|
||||
identity: UserIdentity {
|
||||
handle: Some("defaultuser".to_string()),
|
||||
display_name: Some("Default User".to_string()),
|
||||
first_name: Some("Default".to_string()),
|
||||
last_name: Some("User".to_string()),
|
||||
email: Some("default@example.com".to_string()),
|
||||
avatar_url: None,
|
||||
bio: Some("Default bio".to_string()),
|
||||
},
|
||||
preferences: UserPreferences {
|
||||
theme: Some("dark".to_string()),
|
||||
language: Some("en".to_string()),
|
||||
notifications_enabled: true,
|
||||
auto_sync: true,
|
||||
},
|
||||
created_at: 1234567890,
|
||||
updated_at: 1234567890,
|
||||
},
|
||||
UserProfile {
|
||||
id: "test-uuid-hub".to_string(),
|
||||
hub_did: Some("h:hub1".to_string()),
|
||||
identity: UserIdentity {
|
||||
handle: Some("hubuser".to_string()),
|
||||
display_name: Some("Hub User".to_string()),
|
||||
first_name: Some("Hub".to_string()),
|
||||
last_name: Some("User".to_string()),
|
||||
email: Some("hub@example.com".to_string()),
|
||||
avatar_url: Some("https://example.com/avatar.png".to_string()),
|
||||
bio: Some("Hub bio".to_string()),
|
||||
},
|
||||
preferences: UserPreferences {
|
||||
theme: Some("light".to_string()),
|
||||
language: Some("fr".to_string()),
|
||||
notifications_enabled: false,
|
||||
auto_sync: false,
|
||||
},
|
||||
created_at: 1234567891,
|
||||
updated_at: 1234567892,
|
||||
},
|
||||
];
|
||||
|
||||
// Encrypt
|
||||
let encrypted_file = encryptor.encrypt(
|
||||
&seed,
|
||||
password,
|
||||
&public_key,
|
||||
&did,
|
||||
"univ:test:crypto",
|
||||
&user_profiles
|
||||
).unwrap();
|
||||
|
||||
// Verify file structure includes user profiles
|
||||
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
|
||||
assert_eq!(encrypted_file.cipher, "XChaCha20-Poly1305");
|
||||
assert!(!encrypted_file.enc_user_profiles.is_empty());
|
||||
|
||||
// Decrypt
|
||||
let (decrypted_seed, decrypted_public_key, _, decrypted_profiles) =
|
||||
encryptor.decrypt(&encrypted_file, password).unwrap();
|
||||
|
||||
// Verify decryption
|
||||
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
|
||||
assert_eq!(decrypted_public_key.0, public_key.0);
|
||||
|
||||
// Verify user profiles
|
||||
assert_eq!(decrypted_profiles.len(), 2);
|
||||
|
||||
// Verify default profile
|
||||
let default_profile = decrypted_profiles.iter().find(|p| p.is_default()).unwrap();
|
||||
assert_eq!(default_profile.identity.handle, Some("defaultuser".to_string()));
|
||||
assert_eq!(default_profile.identity.display_name, Some("Default User".to_string()));
|
||||
assert_eq!(default_profile.identity.email, Some("default@example.com".to_string()));
|
||||
assert_eq!(default_profile.preferences.theme, Some("dark".to_string()));
|
||||
|
||||
// Verify hub profile
|
||||
let hub_profile = decrypted_profiles.iter().find(|p| p.hub_did == Some("h:hub1".to_string())).unwrap();
|
||||
assert_eq!(hub_profile.identity.handle, Some("hubuser".to_string()));
|
||||
assert_eq!(hub_profile.identity.display_name, Some("Hub User".to_string()));
|
||||
assert_eq!(hub_profile.identity.email, Some("hub@example.com".to_string()));
|
||||
assert_eq!(hub_profile.preferences.language, Some("fr".to_string()));
|
||||
assert!(!hub_profile.preferences.notifications_enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_encryptor_with_empty_user_profiles() {
|
||||
let encryptor = XChaCha20FileEncryptor;
|
||||
let key_deriver = Ed25519KeyDeriver;
|
||||
|
||||
let seed = Seed::new(vec![1; 32]);
|
||||
let (public_key, _) = key_deriver.derive_from_seed(&seed).unwrap();
|
||||
let did = Did::new(&public_key);
|
||||
let password = "test-password";
|
||||
|
||||
// Encrypt with empty user profiles
|
||||
let encrypted_file = encryptor.encrypt(
|
||||
&seed,
|
||||
password,
|
||||
&public_key,
|
||||
&did,
|
||||
"univ:test:crypto",
|
||||
&[]
|
||||
).unwrap();
|
||||
|
||||
// Decrypt
|
||||
let (_, _, _, decrypted_profiles) = encryptor.decrypt(&encrypted_file, password).unwrap();
|
||||
|
||||
// Should have empty profiles
|
||||
assert_eq!(decrypted_profiles.len(), 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
#[cfg(feature = "std")]
|
||||
// Core abstractions for all platforms
|
||||
pub mod traits;
|
||||
pub mod rng;
|
||||
pub mod time;
|
||||
pub mod crypto;
|
||||
#[cfg(feature = "std")]
|
||||
pub mod storage;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use crypto::{Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor};
|
||||
#[cfg(feature = "std")]
|
||||
pub use storage::FileSystemStorage;
|
||||
// Export platform-appropriate implementations
|
||||
pub use crypto::*;
|
||||
pub use storage::*;
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod crypto_test;
|
||||
// Re-export traits for convenience
|
||||
pub use traits::*;
|
||||
|
||||
#[cfg(all(test, feature = "std"))]
|
||||
mod storage_test;
|
||||
49
libs/sharenet-passport/src/infrastructure/rng.rs
Normal file
49
libs/sharenet-passport/src/infrastructure/rng.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! Random number generation abstraction for WASM compatibility
|
||||
|
||||
use crate::domain::error::DomainError;
|
||||
|
||||
/// Random number generator trait
|
||||
pub trait RngCore {
|
||||
/// Fill a buffer with random bytes
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError>;
|
||||
}
|
||||
|
||||
/// Standard library RNG using OsRng
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct StdRng;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl RngCore for StdRng {
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError> {
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
|
||||
OsRng
|
||||
.try_fill_bytes(dest)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("RNG error: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM-compatible RNG using getrandom
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub struct WasmRng;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl RngCore for WasmRng {
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), DomainError> {
|
||||
getrandom::getrandom(dest)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("WASM RNG error: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new RNG instance based on the current architecture
|
||||
pub fn new_rng() -> Box<dyn RngCore> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Box::new(StdRng)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Box::new(WasmRng)
|
||||
}
|
||||
}
|
||||
22
libs/sharenet-passport/src/infrastructure/storage/mod.rs
Normal file
22
libs/sharenet-passport/src/infrastructure/storage/mod.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//! Unified storage API with target-specific implementations
|
||||
|
||||
// Platform-specific implementations
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
|
||||
mod native;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
|
||||
mod wasm;
|
||||
|
||||
// Re-export the unified API
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(feature = "force-wasm")))]
|
||||
pub use native::FileSystemStorage;
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
|
||||
pub use wasm::BrowserStorage as FileSystemStorage;
|
||||
|
||||
// Target-specific tests
|
||||
#[cfg(all(test, all(not(target_arch = "wasm32"), not(feature = "force-wasm"))))]
|
||||
mod native_test;
|
||||
|
||||
#[cfg(all(test, any(target_arch = "wasm32", feature = "force-wasm")))]
|
||||
mod wasm_test;
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
//! Native (std) file system storage implementation
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::domain::entities::PassportFile;
|
||||
use crate::domain::traits::FileStorage;
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::error::DomainError;
|
||||
use crate::domain::traits::*;
|
||||
|
||||
/// Native file system storage
|
||||
#[derive(Clone)]
|
||||
pub struct FileSystemStorage;
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
//! Native-specific storage tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::FileStorage;
|
||||
use crate::infrastructure::storage::FileSystemStorage;
|
||||
|
||||
#[test]
|
||||
fn test_native_storage_save_and_load() {
|
||||
let storage = FileSystemStorage;
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let file_path = temp_dir.path().join("test-passport.spf");
|
||||
|
||||
// Create a test passport file
|
||||
let test_file = PassportFile {
|
||||
kdf: "HKDF-SHA256".to_string(),
|
||||
cipher: "XChaCha20-Poly1305".to_string(),
|
||||
salt: vec![1; 32],
|
||||
nonce: vec![2; 24],
|
||||
enc_seed: vec![3; 32],
|
||||
public_key: vec![4; 32],
|
||||
did: "did:sharenet:test".to_string(),
|
||||
univ_id: "u:Test Universe:12345678-1234-1234-1234-123456789012".to_string(),
|
||||
created_at: 1234567890,
|
||||
version: "1.0.0".to_string(),
|
||||
enc_user_profiles: vec![],
|
||||
};
|
||||
|
||||
// Save the file
|
||||
storage.save(&test_file, file_path.to_str().unwrap()).unwrap();
|
||||
|
||||
// Load the file
|
||||
let loaded_file = storage.load(file_path.to_str().unwrap()).unwrap();
|
||||
|
||||
// Verify the loaded file matches the original
|
||||
assert_eq!(loaded_file.kdf, test_file.kdf);
|
||||
assert_eq!(loaded_file.cipher, test_file.cipher);
|
||||
assert_eq!(loaded_file.salt, test_file.salt);
|
||||
assert_eq!(loaded_file.nonce, test_file.nonce);
|
||||
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
|
||||
assert_eq!(loaded_file.public_key, test_file.public_key);
|
||||
assert_eq!(loaded_file.did, test_file.did);
|
||||
assert_eq!(loaded_file.univ_id, test_file.univ_id);
|
||||
assert_eq!(loaded_file.created_at, test_file.created_at);
|
||||
assert_eq!(loaded_file.version, test_file.version);
|
||||
assert_eq!(loaded_file.enc_user_profiles, test_file.enc_user_profiles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_native_storage_load_nonexistent_file_fails() {
|
||||
let storage = FileSystemStorage;
|
||||
let result = storage.load("/nonexistent/path/file.spf");
|
||||
|
||||
// Should fail
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
110
libs/sharenet-passport/src/infrastructure/storage/wasm.rs
Normal file
110
libs/sharenet-passport/src/infrastructure/storage/wasm.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//! WASM-compatible storage using browser LocalStorage
|
||||
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::error::DomainError;
|
||||
use crate::domain::traits::*;
|
||||
|
||||
/// WASM storage using browser LocalStorage
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserStorage;
|
||||
|
||||
// Mock storage for testing on native targets
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::collections::HashMap;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
static MOCK_STORAGE: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
|
||||
|
||||
impl FileStorage for BrowserStorage {
|
||||
type Error = DomainError;
|
||||
|
||||
fn save(&self, file: &PassportFile, path: &str) -> Result<(), Self::Error> {
|
||||
// Real implementation for WASM targets
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use base64::Engine;
|
||||
use gloo_storage::Storage;
|
||||
|
||||
// Serialize to CBOR
|
||||
let data = serde_cbor::to_vec(file)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to serialize file: {}", e)))?;
|
||||
|
||||
// Convert to base64 for storage
|
||||
let base64_data = base64::engine::general_purpose::STANDARD.encode(&data);
|
||||
|
||||
// Store in browser localStorage using gloo-storage (synchronous)
|
||||
gloo_storage::LocalStorage::set(path, base64_data)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to store in localStorage: {}", e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Mock implementation for testing on native targets
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
// For testing purposes, we'll use a simple in-memory storage
|
||||
// In a real browser environment, this would use actual localStorage
|
||||
use base64::Engine;
|
||||
|
||||
// Serialize to CBOR
|
||||
let data = serde_cbor::to_vec(file)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to serialize file: {}", e)))?;
|
||||
|
||||
// Convert to base64 for storage
|
||||
let base64_data = base64::engine::general_purpose::STANDARD.encode(&data);
|
||||
|
||||
// Store in mock storage
|
||||
let mock_storage = MOCK_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
mock_storage.lock().unwrap().insert(path.to_string(), base64_data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn load(&self, path: &str) -> Result<PassportFile, Self::Error> {
|
||||
// Real implementation for WASM targets
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use base64::Engine;
|
||||
use gloo_storage::Storage;
|
||||
|
||||
// Load from browser localStorage (synchronous)
|
||||
let base64_data: String = gloo_storage::LocalStorage::get(path)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to load from localStorage: {}", e)))?;
|
||||
|
||||
// Decode from base64
|
||||
let data = base64::engine::general_purpose::STANDARD.decode(&base64_data)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to decode base64: {}", e)))?;
|
||||
|
||||
// Deserialize from CBOR
|
||||
let file = serde_cbor::from_slice(&data)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to deserialize file: {}", e)))?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
// Mock implementation for testing on native targets
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
use base64::Engine;
|
||||
|
||||
// Load from mock storage
|
||||
let mock_storage = MOCK_STORAGE.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
let mock_storage = mock_storage.lock().unwrap();
|
||||
let base64_data = mock_storage.get(path)
|
||||
.ok_or_else(|| DomainError::InvalidFileFormat(format!("Key not found: {}", path)))?;
|
||||
|
||||
// Decode from base64
|
||||
let data = base64::engine::general_purpose::STANDARD.decode(base64_data)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to decode base64: {}", e)))?;
|
||||
|
||||
// Deserialize from CBOR
|
||||
let file = serde_cbor::from_slice(&data)
|
||||
.map_err(|e| DomainError::InvalidFileFormat(format!("Failed to deserialize file: {}", e)))?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
//! WASM-specific storage tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::traits::FileStorage;
|
||||
use crate::infrastructure::storage::FileSystemStorage;
|
||||
|
||||
#[test]
|
||||
fn test_wasm_storage_save_and_load() {
|
||||
let storage = FileSystemStorage;
|
||||
let test_key = "test-passport-key";
|
||||
|
||||
// Create a test passport file
|
||||
let test_file = PassportFile {
|
||||
kdf: "HKDF-SHA256".to_string(),
|
||||
cipher: "XChaCha20-Poly1305".to_string(),
|
||||
salt: vec![1; 32],
|
||||
nonce: vec![2; 24],
|
||||
enc_seed: vec![3; 32],
|
||||
public_key: vec![4; 32],
|
||||
did: "did:sharenet:test".to_string(),
|
||||
univ_id: "u:Test Universe:12345678-1234-1234-1234-123456789012".to_string(),
|
||||
created_at: 1234567890,
|
||||
version: "1.0.0".to_string(),
|
||||
enc_user_profiles: vec![],
|
||||
};
|
||||
|
||||
// Save the file
|
||||
storage.save(&test_file, test_key).unwrap();
|
||||
|
||||
// Load the file
|
||||
let loaded_file = storage.load(test_key).unwrap();
|
||||
|
||||
// Verify the loaded file matches the original
|
||||
assert_eq!(loaded_file.kdf, test_file.kdf);
|
||||
assert_eq!(loaded_file.cipher, test_file.cipher);
|
||||
assert_eq!(loaded_file.salt, test_file.salt);
|
||||
assert_eq!(loaded_file.nonce, test_file.nonce);
|
||||
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
|
||||
assert_eq!(loaded_file.public_key, test_file.public_key);
|
||||
assert_eq!(loaded_file.did, test_file.did);
|
||||
assert_eq!(loaded_file.univ_id, test_file.univ_id);
|
||||
assert_eq!(loaded_file.created_at, test_file.created_at);
|
||||
assert_eq!(loaded_file.version, test_file.version);
|
||||
assert_eq!(loaded_file.enc_user_profiles, test_file.enc_user_profiles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wasm_storage_load_nonexistent_key_fails() {
|
||||
let storage = FileSystemStorage;
|
||||
let result = storage.load("nonexistent-key");
|
||||
|
||||
// Should fail
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tempfile::NamedTempFile;
|
||||
use crate::domain::traits::FileStorage;
|
||||
use crate::{FileSystemStorage, PassportFile};
|
||||
|
||||
#[test]
|
||||
fn test_file_storage_round_trip() {
|
||||
let storage = FileSystemStorage;
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let file_path = temp_file.path().to_str().unwrap();
|
||||
|
||||
let test_file = PassportFile {
|
||||
enc_seed: vec![1, 2, 3, 4, 5],
|
||||
kdf: "HKDF-SHA256".to_string(),
|
||||
cipher: "XChaCha20-Poly1305".to_string(),
|
||||
salt: vec![0; 32],
|
||||
nonce: vec![0; 24],
|
||||
public_key: vec![1; 32],
|
||||
did: "test-did".to_string(),
|
||||
univ_id: "univ:test:storage".to_string(),
|
||||
created_at: 1234567890,
|
||||
version: "1.0.0".to_string(),
|
||||
enc_user_profiles: vec![],
|
||||
};
|
||||
|
||||
// Save file
|
||||
storage.save(&test_file, file_path).unwrap();
|
||||
|
||||
// Load file
|
||||
let loaded_file = storage.load(file_path).unwrap();
|
||||
|
||||
// Verify data integrity
|
||||
assert_eq!(loaded_file.enc_seed, test_file.enc_seed);
|
||||
assert_eq!(loaded_file.kdf, test_file.kdf);
|
||||
assert_eq!(loaded_file.cipher, test_file.cipher);
|
||||
assert_eq!(loaded_file.salt, test_file.salt);
|
||||
assert_eq!(loaded_file.nonce, test_file.nonce);
|
||||
assert_eq!(loaded_file.public_key, test_file.public_key);
|
||||
assert_eq!(loaded_file.did, test_file.did);
|
||||
assert_eq!(loaded_file.created_at, test_file.created_at);
|
||||
assert_eq!(loaded_file.version, test_file.version);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_storage_nonexistent_file() {
|
||||
let storage = FileSystemStorage;
|
||||
let result = storage.load("/nonexistent/path/file.spf");
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_storage_invalid_cbor() {
|
||||
let storage = FileSystemStorage;
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let file_path = temp_file.path().to_str().unwrap();
|
||||
|
||||
// Write invalid CBOR data
|
||||
std::fs::write(file_path, b"invalid cbor data").unwrap();
|
||||
|
||||
let result = storage.load(file_path);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
52
libs/sharenet-passport/src/infrastructure/time.rs
Normal file
52
libs/sharenet-passport/src/infrastructure/time.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! Time abstraction for WASM compatibility
|
||||
|
||||
use crate::domain::error::DomainError;
|
||||
|
||||
/// Time provider trait for abstracting time operations
|
||||
pub trait TimeProvider {
|
||||
/// Get current timestamp in seconds since Unix epoch
|
||||
fn now_seconds() -> Result<u64, DomainError>;
|
||||
}
|
||||
|
||||
/// Standard library time provider
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub struct StdTimeProvider;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl TimeProvider for StdTimeProvider {
|
||||
fn now_seconds() -> Result<u64, DomainError> {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| DomainError::CryptographicError(format!("Time error: {}", e)))
|
||||
.map(|d| d.as_secs())
|
||||
}
|
||||
}
|
||||
|
||||
/// WASM time provider
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub struct WasmTimeProvider;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl TimeProvider for WasmTimeProvider {
|
||||
fn now_seconds() -> Result<u64, DomainError> {
|
||||
// Use JavaScript Date API via js_sys
|
||||
// This will work when compiled to WASM
|
||||
let timestamp = js_sys::Date::now() / 1000.0; // Convert from milliseconds to seconds
|
||||
Ok(timestamp as u64)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current timestamp using the appropriate time provider
|
||||
pub fn now_seconds() -> Result<u64, DomainError> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
StdTimeProvider::now_seconds()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
WasmTimeProvider::now_seconds()
|
||||
}
|
||||
}
|
||||
65
libs/sharenet-passport/src/infrastructure/traits.rs
Normal file
65
libs/sharenet-passport/src/infrastructure/traits.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//! Core abstractions for platform-agnostic cryptography and storage
|
||||
|
||||
use crate::domain::entities::*;
|
||||
use crate::domain::error::DomainError;
|
||||
|
||||
/// Mnemonic generation trait
|
||||
pub trait MnemonicGenerator {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
fn generate(&self) -> Result<RecoveryPhrase, Self::Error>;
|
||||
fn validate(&self, words: &[String]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Key derivation trait
|
||||
pub trait KeyDeriver {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
fn derive_from_seed(&self, seed: &Seed) -> Result<(PublicKey, PrivateKey), Self::Error>;
|
||||
fn derive_from_mnemonic(&self, mnemonic: &RecoveryPhrase, univ_id: &str) -> Result<Seed, Self::Error>;
|
||||
}
|
||||
|
||||
/// File encryption trait
|
||||
pub trait FileEncryptor {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
fn encrypt(
|
||||
&self,
|
||||
seed: &Seed,
|
||||
password: &str,
|
||||
public_key: &PublicKey,
|
||||
did: &Did,
|
||||
univ_id: &str,
|
||||
user_profiles: &[UserProfile],
|
||||
) -> Result<PassportFile, Self::Error>;
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
file: &PassportFile,
|
||||
password: &str,
|
||||
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error>;
|
||||
}
|
||||
|
||||
/// Storage trait for passport files
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait(?Send))]
|
||||
pub trait FileStorage {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
async fn save(&self, file: &PassportFile, path: &str) -> Result<(), Self::Error>;
|
||||
async fn load(&self, path: &str) -> Result<PassportFile, Self::Error>;
|
||||
}
|
||||
|
||||
/// Random number generation trait
|
||||
pub trait RngCore {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Time provider trait
|
||||
pub trait TimeProvider {
|
||||
type Error: Into<DomainError>;
|
||||
|
||||
fn now_seconds(&self) -> Result<u64, Self::Error>;
|
||||
}
|
||||
|
|
@ -21,9 +21,10 @@ pub use application::use_cases::{
|
|||
};
|
||||
pub use application::error::ApplicationError;
|
||||
|
||||
// Re-export infrastructure implementations (automatically selected by target)
|
||||
pub use infrastructure::{
|
||||
Bip39MnemonicGenerator,
|
||||
Ed25519KeyDeriver,
|
||||
XChaCha20FileEncryptor,
|
||||
FileSystemStorage
|
||||
FileSystemStorage,
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
use sharenet_passport::application::use_cases::*;
|
||||
use sharenet_passport::domain::traits::*;
|
||||
use sharenet_passport::infrastructure::crypto::*;
|
||||
use sharenet_passport::infrastructure::storage::*;
|
||||
use sharenet_passport::{
|
||||
application::use_cases::*,
|
||||
Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, FileSystemStorage,
|
||||
FileStorage,
|
||||
};
|
||||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use sharenet_passport::{
|
||||
application::use_cases::*,
|
||||
domain::entities::{UserIdentity, UserPreferences},
|
||||
infrastructure::*,
|
||||
ApplicationError,
|
||||
FileStorage,
|
||||
Bip39MnemonicGenerator, Ed25519KeyDeriver, XChaCha20FileEncryptor, FileSystemStorage,
|
||||
ApplicationError, FileStorage,
|
||||
};
|
||||
use rpassword::prompt_password;
|
||||
use hex;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue