From 3a0ad9b028e6b2a72244235a06e21ba123e47464 Mon Sep 17 00:00:00 2001 From: nazeh Date: Sun, 14 Jul 2024 20:15:52 +0300 Subject: [PATCH] feat(common): pubky authn and authnverifier --- Cargo.lock | 645 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- common/Cargo.toml | 15 + common/src/auth.rs | 217 +++++++++++++ common/src/crypto.rs | 14 + common/src/lib.rs | 4 + common/src/namespaces.rs | 1 + common/src/timestamp.rs | 229 ++++++++++++++ 8 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 common/Cargo.toml create mode 100644 common/src/auth.rs create mode 100644 common/src/crypto.rs create mode 100644 common/src/lib.rs create mode 100644 common/src/namespaces.rs create mode 100644 common/src/timestamp.rs diff --git a/Cargo.lock b/Cargo.lock index 3b0764a..4d51cf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,18 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-trait" version = "0.1.81" @@ -119,12 +131,52 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base32" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1ce0365f4d5fb6646220bb52fe547afd51796d90f914d4063cb0b032ebee088" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "blake3" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d08263faac5cde2a4d52b513dadb80846023aade56fcd8fc99ba73ba8050e92" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "bytes" version = "1.6.1" @@ -143,6 +195,155 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "nanorand", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -158,6 +359,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -165,6 +381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -173,6 +390,40 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" @@ -185,10 +436,39 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", ] [[package]] @@ -289,6 +569,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -301,6 +590,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -317,6 +612,32 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" + +[[package]] +name = "mainline" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b751ffb57303217bcae8f490eee6044a5b40eadf6ca05ff476cad37e7b7970d" +dependencies = [ + "bytes", + "crc", + "ed25519-dalek", + "flume", + "lru", + "rand", + "serde", + "serde_bencode", + "serde_bytes", + "sha1_smol", + "thiserror", + "tracing", +] + [[package]] name = "matchers" version = "0.1.0" @@ -364,6 +685,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -466,6 +796,48 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkarr" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f9e12544b00f5561253bbd3cb72a85ff3bc398483dc1bf82bdf095c774136b" +dependencies = [ + "bytes", + "document-features", + "dyn-clone", + "ed25519-dalek", + "flume", + "futures", + "js-sys", + "lru", + "mainline", + "rand", + "self_cell", + "simple-dns", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "z32", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.86" @@ -475,12 +847,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pubky-common" +version = "0.1.0" +dependencies = [ + "base32", + "blake3", + "ed25519-dalek", + "once_cell", + "pkarr", + "rand", + "thiserror", +] + [[package]] name = "pubky_homeserver" version = "0.1.0" dependencies = [ "anyhow", "axum", + "pkarr", "tokio", "tower-http", "tracing", @@ -496,6 +882,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.2" @@ -555,6 +971,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -573,6 +998,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.204" @@ -582,6 +1019,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bencode" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70dfc7b7438b99896e7f8992363ab8e2c4ba26aa5ec675d32d1c3c2c33d413e" +dependencies = [ + "serde", + "serde_bytes", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.204" @@ -626,6 +1082,23 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -644,6 +1117,33 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "simple-dns" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01607fe2e61894468c6dc0b26103abb073fb08b79a3d9e4b6d76a1a341549958" +dependencies = [ + "bitflags", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -660,6 +1160,31 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.71" @@ -683,6 +1208,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "thiserror" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -830,6 +1375,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -842,12 +1393,94 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1008,3 +1641,15 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "z32" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb37266251c28b03d08162174a91c3a092e3bd4f476f8205ee1c507b78b7bdc" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 1423834..ce5c39f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["homeserver"] +members = [ "common","homeserver"] # See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352 resolver = "2" diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..94ca15d --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pubky-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base32 = "0.5.0" +blake3 = "1.5.1" +ed25519-dalek = "2.1.1" +once_cell = "1.19.0" +pkarr = "2.0.3" +rand = "0.8.5" +thiserror = "1.0.60" diff --git a/common/src/auth.rs b/common/src/auth.rs new file mode 100644 index 0000000..6469d2e --- /dev/null +++ b/common/src/auth.rs @@ -0,0 +1,217 @@ +use std::sync::{Arc, Mutex}; + +use ed25519_dalek::ed25519::SignatureBytes; + +use crate::{ + crypto::{random_hash, Keypair, PublicKey, Signature}, + timestamp::Timestamp, +}; + +// 30 seconds +const TIME_INTERVAL: u64 = 30 * 1_000_000; + +#[derive(Debug, PartialEq)] +pub struct AuthnSignature(Box<[u8]>); + +impl AuthnSignature { + pub fn new(signer: &Keypair, audience: &PublicKey, token: Option<&[u8]>) -> Self { + let mut bytes = Vec::with_capacity(96); + + let time: u64 = Timestamp::now().into(); + let time_step = time / TIME_INTERVAL; + + let token_hash = token.map_or(random_hash(), |t| crate::crypto::hash(t)); + + let signature = signer + .sign(&signable( + &time_step.to_be_bytes(), + &signer.public_key(), + audience, + token_hash.as_bytes(), + )) + .to_bytes(); + + bytes.extend_from_slice(&signature); + bytes.extend_from_slice(token_hash.as_bytes()); + + Self(bytes.into()) + } + + pub fn for_token(keypair: &Keypair, audience: &PublicKey, token: &[u8]) -> Self { + AuthnSignature::new(keypair, audience, Some(token)) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct AuthnVerifier { + audience: PublicKey, + inner: Arc>>, + // TODO: Support permisisons + // token_hashes: HashSet<[u8; 32]>, +} + +impl AuthnVerifier { + pub fn new(audience: PublicKey) -> Self { + Self { + audience, + inner: Arc::new(Mutex::new(Vec::new())), + } + } + + pub fn verify(&self, bytes: &[u8], signer: &PublicKey) -> Result<(), AuthnSignatureError> { + self.gc(); + + if bytes.len() != 96 { + return Err(AuthnSignatureError::InvalidLength(bytes.len())); + } + + let signature_bytes: SignatureBytes = bytes[0..64] + .try_into() + .expect("validate token length on instantiating"); + let signature = Signature::from(signature_bytes); + + let token_hash: [u8; 32] = bytes[64..].try_into().expect("should not be reachable"); + + let now = Timestamp::now().into_inner(); + let past = now - TIME_INTERVAL; + let future = now + TIME_INTERVAL; + + let result = verify_at(now, &self, &signature, signer, &token_hash); + + match result { + Ok(_) => return Ok(()), + Err(AuthnSignatureError::AlreadyUsed) => return Err(AuthnSignatureError::AlreadyUsed), + _ => {} + } + + let result = verify_at(past, &self, &signature, signer, &token_hash); + + match result { + Ok(_) => return Ok(()), + Err(AuthnSignatureError::AlreadyUsed) => return Err(AuthnSignatureError::AlreadyUsed), + _ => {} + } + + verify_at(future, &self, &signature, signer, &token_hash) + } + + // === Private Methods === + + /// Remove all tokens older than two time intervals in the past. + fn gc(&self) { + let threshold = ((Timestamp::now().into_inner() / TIME_INTERVAL) - 2).to_be_bytes(); + + let mut inner = self.inner.lock().unwrap(); + + match inner.binary_search_by(|element| element[0..8].cmp(&threshold)) { + Ok(index) | Err(index) => { + inner.drain(0..index); + } + } + } +} + +fn verify_at( + time: u64, + verifier: &AuthnVerifier, + signature: &Signature, + signer: &PublicKey, + token_hash: &[u8; 32], +) -> Result<(), AuthnSignatureError> { + let time_step = time / TIME_INTERVAL; + let time_step_bytes = time_step.to_be_bytes(); + + let result = signer.verify( + &signable(&time_step_bytes, signer, &verifier.audience, token_hash), + signature, + ); + + if result.is_ok() { + let mut inner = verifier.inner.lock().unwrap(); + + let mut candidate = [0_u8; 40]; + candidate[..8].copy_from_slice(&time_step_bytes); + candidate[8..].copy_from_slice(token_hash); + + match inner.binary_search_by(|element| element.cmp(&candidate)) { + Ok(index) | Err(index) => { + inner.insert(index, candidate); + } + }; + + return Ok(()); + } + + Err(AuthnSignatureError::InvalidSignature) +} + +fn signable( + time_step_bytes: &[u8; 8], + signer: &PublicKey, + audience: &PublicKey, + token_hash: &[u8; 32], +) -> [u8; 115] { + let mut arr = [0; 115]; + + arr[..11].copy_from_slice(crate::namespaces::PUBKY_AUTHN); + arr[11..19].copy_from_slice(time_step_bytes); + arr[19..51].copy_from_slice(signer.as_bytes()); + arr[51..83].copy_from_slice(audience.as_bytes()); + arr[83..].copy_from_slice(token_hash); + + arr +} + +#[derive(thiserror::Error, Debug)] +pub enum AuthnSignatureError { + #[error("AuthnSignature should be 96 bytes long, got {0} bytes instead")] + InvalidLength(usize), + + #[error("Invalid signature")] + InvalidSignature, + + #[error("Authn signature already used")] + AlreadyUsed, +} + +#[cfg(test)] +mod tests { + use crate::crypto::Keypair; + + use super::{AuthnSignature, AuthnVerifier}; + + #[test] + fn sign_verify() { + let keypair = Keypair::random(); + let signer = keypair.public_key(); + let audience = Keypair::random().public_key(); + + let verifier = AuthnVerifier::new(audience.clone()); + + let authn_signature = AuthnSignature::new(&keypair, &audience, None); + + verifier + .verify(authn_signature.as_bytes(), &signer) + .unwrap(); + + { + // Invalid signable + let mut invalid = authn_signature.as_bytes().to_vec(); + invalid[64..].copy_from_slice(&[0; 32]); + + assert!(!verifier.verify(&invalid, &signer).is_ok()) + } + + { + // Invalid signer + let mut invalid = authn_signature.as_bytes().to_vec(); + invalid[0..32].copy_from_slice(&[0; 32]); + + assert!(!verifier.verify(&invalid, &signer).is_ok()) + } + } +} diff --git a/common/src/crypto.rs b/common/src/crypto.rs new file mode 100644 index 0000000..8bb700a --- /dev/null +++ b/common/src/crypto.rs @@ -0,0 +1,14 @@ +use rand::prelude::Rng; + +pub use pkarr::{Keypair, PublicKey}; + +pub use ed25519_dalek::Signature; + +pub type Hash = blake3::Hash; + +pub use blake3::hash; + +pub fn random_hash() -> Hash { + let mut rng = rand::thread_rng(); + Hash::from_bytes(rng.gen()) +} diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..90b246d --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,4 @@ +pub mod auth; +pub mod crypto; +pub mod namespaces; +pub mod timestamp; diff --git a/common/src/namespaces.rs b/common/src/namespaces.rs new file mode 100644 index 0000000..6c951dd --- /dev/null +++ b/common/src/namespaces.rs @@ -0,0 +1 @@ +pub const PUBKY_AUTHN: &[u8; 11] = b"PUBKY:AUTHN"; diff --git a/common/src/timestamp.rs b/common/src/timestamp.rs new file mode 100644 index 0000000..f850661 --- /dev/null +++ b/common/src/timestamp.rs @@ -0,0 +1,229 @@ +//! Monotonic unix timestamp in microseconds + +use std::fmt::Display; +use std::time::SystemTime; +use std::{ + ops::{Add, Sub}, + sync::Mutex, +}; + +use once_cell::sync::Lazy; +use rand::Rng; + +/// ~4% chance of none of 10 clocks have matching id. +const CLOCK_MASK: u64 = (1 << 8) - 1; +const TIME_MASK: u64 = !0 >> 8; + +pub struct TimestampFactory { + clock_id: u64, + last_time: u64, +} + +impl TimestampFactory { + pub fn new() -> Self { + Self { + clock_id: rand::thread_rng().gen::() & CLOCK_MASK, + last_time: system_time() & TIME_MASK, + } + } + + pub fn now(&mut self) -> Timestamp { + // Ensure monotonicity. + self.last_time = (system_time() & TIME_MASK).max(self.last_time + CLOCK_MASK + 1); + + // Add clock_id to the end of the timestamp + Timestamp(self.last_time | self.clock_id) + } +} + +impl Default for TimestampFactory { + fn default() -> Self { + Self::new() + } +} + +static DEFAULT_FACTORY: Lazy> = + Lazy::new(|| Mutex::new(TimestampFactory::default())); + +/// Monotonic timestamp since [SystemTime::UNIX_EPOCH] in microseconds as u64. +/// +/// The purpose of this timestamp is to unique per "user", not globally, +/// it achieves this by: +/// 1. Override the last byte with a random `clock_id`, reducing the probability +/// of two matching timestamps across multiple machines/threads. +/// 2. Gurantee that the remaining 3 bytes are ever increasing (monotonic) within +/// the same thread regardless of the wall clock value +/// +/// This timestamp is also serialized as BE bytes to remain sortable. +/// If a `utf-8` encoding is necessary, it is encoded as [base32::Alphabet::Crockford] +/// to act as a sortable Id. +/// +/// U64 of microseconds is valid for the next 500 thousand years! +#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)] +pub struct Timestamp(u64); + +impl Timestamp { + pub fn now() -> Self { + DEFAULT_FACTORY.lock().unwrap().now() + } + + /// Return big endian bytes + pub fn to_bytes(&self) -> [u8; 8] { + self.0.to_be_bytes() + } + + pub fn difference(&self, rhs: &Timestamp) -> u64 { + self.0.abs_diff(rhs.0) + } + + pub fn into_inner(&self) -> u64 { + self.0 + } +} + +impl Display for Timestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let bytes: [u8; 8] = self.into(); + f.write_str(&base32::encode(base32::Alphabet::Crockford, &bytes)) + } +} + +impl TryFrom for Timestamp { + type Error = TimestampError; + + fn try_from(value: String) -> Result { + match base32::decode(base32::Alphabet::Crockford, &value) { + Some(vec) => { + let bytes: [u8; 8] = vec + .try_into() + .map_err(|_| TimestampError::InvalidEncoding)?; + + Ok(bytes.into()) + } + None => Err(TimestampError::InvalidEncoding), + } + } +} + +impl TryFrom<&[u8]> for Timestamp { + type Error = TimestampError; + + fn try_from(bytes: &[u8]) -> Result { + let bytes: [u8; 8] = bytes + .try_into() + .map_err(|_| TimestampError::InvalidBytesLength(bytes.len()))?; + + Ok(bytes.into()) + } +} + +impl From<&Timestamp> for [u8; 8] { + fn from(timestamp: &Timestamp) -> Self { + timestamp.0.to_be_bytes() + } +} + +impl From<[u8; 8]> for Timestamp { + fn from(bytes: [u8; 8]) -> Self { + Self(u64::from_be_bytes(bytes)) + } +} + +// === U64 conversion === + +impl From for u64 { + fn from(value: Timestamp) -> Self { + value.into_inner() + } +} + +impl Add for &Timestamp { + type Output = Timestamp; + + fn add(self, rhs: u64) -> Self::Output { + Timestamp(self.0 + rhs) + } +} + +impl Sub for &Timestamp { + type Output = Timestamp; + + fn sub(self, rhs: u64) -> Self::Output { + Timestamp(self.0 - rhs) + } +} + +#[cfg(not(target_arch = "wasm32"))] +/// Return the number of microseconds since [SystemTime::UNIX_EPOCH] +fn system_time() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("time drift") + .as_micros() as u64 +} + +#[derive(thiserror::Error, Debug)] +pub enum TimestampError { + #[error("Invalid bytes length, Timestamp should be encoded as 8 bytes, got {0}")] + InvalidBytesLength(usize), + #[error("Invalid timestamp encoding")] + InvalidEncoding, +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + + #[test] + fn monotonic() { + const COUNT: usize = 100; + + let mut set = HashSet::with_capacity(COUNT); + let mut vec = Vec::with_capacity(COUNT); + + for _ in 0..COUNT { + let timestamp = Timestamp::now(); + + set.insert(timestamp.clone()); + vec.push(timestamp); + } + + let mut ordered = vec.clone(); + ordered.sort(); + + assert_eq!(set.len(), COUNT, "unique"); + assert_eq!(ordered, vec, "ordered"); + } + + #[test] + fn strings() { + const COUNT: usize = 100; + + let mut set = HashSet::with_capacity(COUNT); + let mut vec = Vec::with_capacity(COUNT); + + for _ in 0..COUNT { + let string = Timestamp::now().to_string(); + + set.insert(string.clone()); + vec.push(string) + } + + let mut ordered = vec.clone(); + ordered.sort(); + + assert_eq!(set.len(), COUNT, "unique"); + assert_eq!(ordered, vec, "ordered"); + } + + #[test] + fn to_from_string() { + let timestamp = Timestamp::now(); + let string = timestamp.to_string(); + let decoded: Timestamp = string.try_into().unwrap(); + + assert_eq!(decoded, timestamp) + } +}