diff --git a/Cargo.lock b/Cargo.lock index a80cd00..7dc4267 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -18,14 +18,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "aead" -version = "0.5.2" +name = "assert_matches" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "autocfg" @@ -48,12 +44,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64ct" version = "1.6.0" @@ -75,6 +65,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.6.0" @@ -93,6 +89,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concat-idents" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -100,15 +106,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "cpufeatures" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", + "libc", ] [[package]] @@ -121,6 +124,33 @@ dependencies = [ "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" @@ -147,17 +177,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "elliptic-curve", - "signature", -] - [[package]] name = "ed25519" version = "2.2.3" @@ -169,32 +188,25 @@ dependencies = [ ] [[package]] -name = "elliptic-curve" -version = "0.13.8" +name = "ed25519-dalek" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", + "curve25519-dalek", + "ed25519", "rand_core", - "sec1", + "serde", + "sha2", "subtle", "zeroize", ] [[package]] -name = "ff" -version = "0.13.0" +name = "fiat-crypto" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "gateway" @@ -212,7 +224,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -232,23 +243,6 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -257,9 +251,15 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -277,6 +277,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.2" @@ -303,6 +309,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -323,25 +339,16 @@ dependencies = [ ] [[package]] -name = "p256" -version = "0.13.2" +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] -name = "p384" -version = "0.13.0" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" @@ -363,7 +370,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -383,22 +390,22 @@ dependencies = [ ] [[package]] -name = "pretty_assertions" -version = "1.4.0" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "diff", - "yansi", + "zerocopy", ] [[package]] -name = "primeorder" -version = "0.13.6" +name = "pretty_assertions" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "elliptic-curve", + "diff", + "yansi", ] [[package]] @@ -420,55 +427,42 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom", + "libc", + "rand_chacha", + "rand_core", ] [[package]] -name = "redox_syscall" -version = "0.5.1" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "bitflags", + "ppv-lite86", + "rand_core", ] [[package]] -name = "ring" -version = "0.17.8" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "cc", - "cfg-if", "getrandom", - "libc", - "spin", - "untrusted", - "windows-sys 0.52.0", ] [[package]] -name = "ring-compat" -version = "0.8.0" +name = "redox_syscall" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccce7bae150b815f0811db41b8312fcb74bffa4cab9cee5429ee00f356dd5bd4" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "aead", - "digest", - "ecdsa", - "ed25519", - "generic-array", - "p256", - "p384", - "pkcs8", - "rand_core", - "ring", - "signature", + "bitflags", ] [[package]] @@ -478,10 +472,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rustversion" -version = "1.0.17" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "ryu" @@ -489,6 +486,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "ryu-js" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad97d4ce1560a5e27cec89519dc8300d1aa6035b099821261c651486a19e44d5" + [[package]] name = "scopeguard" version = "1.2.0" @@ -496,17 +499,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sec1" -version = "0.7.3" +name = "semver" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "subtle", - "zeroize", -] +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" [[package]] name = "serde" @@ -530,15 +526,47 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_json_canonicalizer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18032888bfda612a88f6b9c7c7a12e8168686936702fe584e3f1b1fc7848443a" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -573,12 +601,6 @@ 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" - [[package]] name = "spki" version = "0.7.3" @@ -589,45 +611,28 @@ dependencies = [ "der", ] -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sutera-lib" version = "0.1.0" dependencies = [ + "assert_matches", + "concat-idents", + "ed25519-dalek", "pretty_assertions", + "rand", "rand_core", - "ring-compat", "serde", - "serde_json", - "strum", + "serde_json_canonicalizer", "thiserror", + "tracing", + "tracing-error", + "tracing-subscriber", ] [[package]] @@ -661,6 +666,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.38.0" @@ -691,6 +706,73 @@ dependencies = [ "syn", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + [[package]] name = "typenum" version = "1.17.0" @@ -704,16 +786,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "untrusted" -version = "0.9.0" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -721,6 +803,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" @@ -736,7 +840,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -756,18 +860,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -778,9 +882,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -790,9 +894,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -802,15 +906,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -820,9 +924,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -832,9 +936,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -844,9 +948,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -856,9 +960,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "yansi" @@ -866,6 +970,27 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6230899..0b0392e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.78.0" +channel = "1.83.0" components = ["rust-analyzer"] profile = "default" diff --git a/sutera-lib/Cargo.toml b/sutera-lib/Cargo.toml index 6268048..58a02d0 100644 --- a/sutera-lib/Cargo.toml +++ b/sutera-lib/Cargo.toml @@ -4,12 +4,17 @@ version = "0.1.0" edition = "2021" [dev-dependencies] +assert_matches = "1.5.0" +concat-idents = "1.1.5" pretty_assertions = "1.4.0" -serde_json = "1.0.117" +tracing-subscriber = "0.3.19" [dependencies] -rand_core = { version = "0.6.4", features = ["std"] } -ring-compat = "0.8.0" +ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } +rand = { version = "0.8.4", features = ["std_rng"] } +rand_core = "0.6.4" serde = { version = "1.0.203", features = ["derive"] } -strum = { version = "0.26.2", features = ["derive"] } +serde_json_canonicalizer = "0.3.0" thiserror = "1.0.61" +tracing = "0.1.41" +tracing-error = { version = "0.2.1", features = ["traced-error"] } diff --git a/sutera-lib/src/error/mod.rs b/sutera-lib/src/error/mod.rs new file mode 100644 index 0000000..2380638 --- /dev/null +++ b/sutera-lib/src/error/mod.rs @@ -0,0 +1,91 @@ +use std::fmt::{Debug, Display}; + +use tracing_error::SpanTrace; + +pub trait TraceableError: std::error::Error { + fn trace(&self) -> &SpanTrace; +} + +pub struct CapturedError { + pub error: E, + span_trace: SpanTrace, +} + +impl TraceableError for CapturedError { + fn trace(&self) -> &SpanTrace { + &self.span_trace + } +} + +pub trait ResultTracingUnwrapExt { + fn tracing_unwrap(self) -> T; +} +pub trait ResultCaptureErrExt { + fn capture_err(self) -> Result>; + fn capture_and_unwrap(self) -> T; +} + +impl ResultTracingUnwrapExt for Result { + #[inline(always)] + fn tracing_unwrap(self) -> T { + match self { + Ok(value) => value, + Err(ref error) => { + tracing::error!(error = %error, "called `unwrap()` on an `Err` Value"); + eprintln!("== TRACING =="); + eprintln!("{}", error.trace()); + self.unwrap(); + unreachable!() + } + } + } +} + +impl ResultCaptureErrExt for Result { + #[inline(always)] + fn capture_err(self) -> Result> { + self.map_err(|error| CapturedError { + error, + span_trace: SpanTrace::capture(), + }) + } + #[inline(always)] + fn capture_and_unwrap(self) -> T { + self.capture_err().tracing_unwrap() + } +} + +impl Display for CapturedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.error, f) + } +} + +impl Debug for CapturedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.error, f) + } +} + +impl PartialEq for CapturedError { + fn eq(&self, other: &Self) -> bool { + self.error == other.error + } +} + +impl Eq for CapturedError {} + +impl std::error::Error for CapturedError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.error.source() + } +} + +impl From for CapturedError { + fn from(error: E) -> Self { + Self { + error, + span_trace: SpanTrace::capture(), + } + } +} diff --git a/sutera-lib/src/lib.rs b/sutera-lib/src/lib.rs index 4917bfb..087afef 100644 --- a/sutera-lib/src/lib.rs +++ b/sutera-lib/src/lib.rs @@ -1 +1,3 @@ +pub mod error; pub mod signature; +pub mod util; diff --git a/sutera-lib/src/signature/algorithms/ed25519.rs b/sutera-lib/src/signature/algorithms/ed25519.rs new file mode 100644 index 0000000..8dbd475 --- /dev/null +++ b/sutera-lib/src/signature/algorithms/ed25519.rs @@ -0,0 +1,94 @@ +use std::str::FromStr; + +use crate::error::ResultCaptureErrExt; +use crate::signature::SuteraPrivateKey; +use crate::util::hex::{from_hex, to_hex, FromHexError, HexString}; + +use super::{SigningAlgorithm, SigningAlgorithmKind}; +use ed25519_dalek::ed25519::signature::SignerMut; +use ed25519_dalek::{Signature, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; +use rand_core::CryptoRngCore; +use thiserror::Error; +use tracing::instrument; + +pub struct Ed25519 {} + +#[derive(Debug, Error)] +enum DecodeKeyError { + #[error("unable to decode hex: {0}")] + UnableToDecodeHex(#[from] FromHexError), + #[error("invalid key length: {actual} bytes, expected {expected}")] + KeyLengthMismatch { actual: usize, expected: usize }, + #[error(transparent)] + SignatureError(#[from] ed25519_dalek::SignatureError), +} + +#[instrument("parse_verifying_key")] +fn parse_verifying_key(value: &str) -> Result { + let bytes = from_hex(value)?; + if bytes.len() != PUBLIC_KEY_LENGTH { + return Err(DecodeKeyError::KeyLengthMismatch { + actual: bytes.len(), + expected: PUBLIC_KEY_LENGTH, + }); + } + Ok(VerifyingKey::from_bytes( + &<[u8; PUBLIC_KEY_LENGTH]>::try_from(bytes).unwrap(), + )?) +} + +#[instrument("parse_signing_key")] +fn parse_signing_key(value: &T) -> Result { + let bytes = from_hex(value)?; + if bytes.len() != SECRET_KEY_LENGTH { + return Err(DecodeKeyError::KeyLengthMismatch { + actual: bytes.len(), + expected: SECRET_KEY_LENGTH, + }); + } + Ok(SigningKey::from_bytes( + &<[u8; SECRET_KEY_LENGTH]>::try_from(bytes).unwrap(), + )) +} + +impl SigningAlgorithm for Ed25519 { + fn is_capable(kind: &SigningAlgorithmKind) -> bool { + kind.0 == "ed25519" + } + + #[instrument("sign", skip_all)] + fn sign(data: &[u8], private_key: &SuteraPrivateKey) -> Result { + let mut private_key = parse_signing_key(&private_key.key) + .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; + Ok(private_key.try_sign(data).capture_and_unwrap().to_string()) + } + + #[instrument("to_public_key", skip_all)] + fn to_public_key(private_key: &SuteraPrivateKey) -> Result { + let private_key = parse_signing_key(&private_key.key) + .map_err(|e| super::SignatureError::PrivateKey(Box::new(e)))?; + Ok(to_hex(&private_key.verifying_key().to_bytes())) + } + + #[instrument("generate_private_key", skip_all)] + fn generate_private_key(rng: &mut R) -> SuteraPrivateKey { + let private_key = SigningKey::generate(rng); + SuteraPrivateKey { + key: to_hex(&private_key.to_bytes()).into(), + algorithm: SigningAlgorithmKind("ed25519".into()), + } + } + + #[instrument("verify", skip_all)] + fn verify( + data: &[u8], + signature: &str, + public_key: &str, + ) -> Result { + let public_key = parse_verifying_key(public_key) + .map_err(|e| super::SignatureError::PublicKey(Box::new(e)))?; + let signature = Signature::from_str(signature) + .map_err(|e| super::SignatureError::Signature(Box::new(e)))?; + Ok(public_key.verify_strict(data, &signature).is_ok()) + } +} diff --git a/sutera-lib/src/signature/algorithms/macros.rs b/sutera-lib/src/signature/algorithms/macros.rs new file mode 100644 index 0000000..1654f7e --- /dev/null +++ b/sutera-lib/src/signature/algorithms/macros.rs @@ -0,0 +1,26 @@ +macro_rules! algorithm_action { + ($kind:expr => $e:expr) => { + if $crate::signature::algorithms::ed25519::Ed25519::is_capable($kind) { + type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; + Some($e) + } else { + None + } + }; +} +pub(crate) use algorithm_action; + +#[cfg(test)] +macro_rules! algorithm_tests { + ($(#[$attr:meta])* fn $name:ident $args:tt $b:block) => { + ::concat_idents::concat_idents!(test_name = $name, _ed25519 { + $(#[$attr])* + fn test_name $args { + type Algorithm = $crate::signature::algorithms::ed25519::Ed25519; + $b + } + }); + }; +} +#[cfg(test)] +pub(crate) use algorithm_tests; diff --git a/sutera-lib/src/signature/algorithms/mod.rs b/sutera-lib/src/signature/algorithms/mod.rs new file mode 100644 index 0000000..03f38cf --- /dev/null +++ b/sutera-lib/src/signature/algorithms/mod.rs @@ -0,0 +1,54 @@ +pub mod ed25519; +mod macros; + +#[cfg(test)] +mod tests; + +use rand_core::CryptoRngCore; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use self::macros::algorithm_action; + +use super::{Signature, SuteraIdentity, SuteraPrivateKey}; + +#[derive(Error, Debug)] +pub enum SignatureError { + #[error("Algorithm not supported: {0:?}")] + AlgorithmNotSupported(SigningAlgorithmKind), + #[error("Invalid signature: {0}")] + Signature(Box), + #[error("Invalid public key: {0}")] + PublicKey(Box), + #[error("Invalid private key: {0}")] + PrivateKey(Box), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SigningAlgorithmKind(String); + +#[allow(dead_code)] +pub trait SigningAlgorithm { + fn is_capable(kind: &SigningAlgorithmKind) -> bool; + fn sign(data: &[u8], private_key: &SuteraPrivateKey) -> Result; + fn to_public_key(private_key: &SuteraPrivateKey) -> Result; + fn verify(data: &[u8], signature: &str, public_key: &str) -> Result; + fn generate_private_key(rng: &mut R) -> SuteraPrivateKey; +} + +impl SuteraIdentity { + pub fn verify(&self, data: &[u8], signature: &str) -> Result { + algorithm_action!(&self.algorithm => { + Algorithm::verify(data, signature, &self.public_key) + }) + .ok_or(SignatureError::AlgorithmNotSupported( + self.algorithm.clone(), + ))? + } +} + +impl Signature { + pub fn verify(&self, data: &[u8]) -> Result { + self.identity.verify(data, &self.signature) + } +} diff --git a/sutera-lib/src/signature/algorithms/tests.rs b/sutera-lib/src/signature/algorithms/tests.rs new file mode 100644 index 0000000..803a1e6 --- /dev/null +++ b/sutera-lib/src/signature/algorithms/tests.rs @@ -0,0 +1,55 @@ +use crate::signature::algorithms::SigningAlgorithm as _; + +use super::macros::algorithm_tests; +use assert_matches::assert_matches; +use rand::rngs::StdRng; +use rand::SeedableRng; + +algorithm_tests! { + #[test] + fn verify_correct_sign() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &public_key), Ok(true)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_content() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(b"broken content", &signature, &public_key), Ok(false)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_signature() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let public_key = Algorithm::to_public_key(&private_key).unwrap(); + let data = b"hello, world!"; + let fake_data = b"original!"; + let signature = Algorithm::sign(fake_data, &private_key).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &public_key), Ok(false)); + } +} + +algorithm_tests! { + #[test] + fn verify_unmatched_public_key() { + let mut rng = StdRng::seed_from_u64(0); + let private_key = Algorithm::generate_private_key(&mut rng); + let data = b"hello, world!"; + let signature = Algorithm::sign(data, &private_key).unwrap(); + let fake_public_key = Algorithm::to_public_key(&Algorithm::generate_private_key(&mut rng)).unwrap(); + assert_matches!(Algorithm::verify(data, &signature, &fake_public_key), Ok(false)); + } +} diff --git a/sutera-lib/src/signature/identity.rs b/sutera-lib/src/signature/identity.rs deleted file mode 100644 index 9cc5ead..0000000 --- a/sutera-lib/src/signature/identity.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::str::FromStr; - -use ring_compat::signature::ed25519; -use thiserror::Error; - -/// An error that occurs when parsing a Sutera identity string. -/// Sutera-identity-stringをパースする際に起きるエラー。 -#[derive(Debug, Error, PartialEq, Eq)] -pub enum SuteraIdentityStringParseError { - #[error("invalid identity string")] - InvalidFormat, - #[error("invalid identity string, version {0} is not supported")] - VersionMismatch(String), - #[error("invalid identity string, kind {0} is not supported")] - UnsupportedKind(String), -} - -#[derive(Debug, PartialEq, Eq, Clone, strum::EnumString, strum::AsRefStr)] -pub enum SuteraIdentityKind { - #[strum(serialize = "user")] - User, -} - -/// A struct representing an identity in the Sutera network. -/// Suteraネットワークにおけるidentityを表す構造体。 -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SuteraIdentity { - /// The kind of object treated in the Sutera network (e.g., user, server, world etc.) - /// Suteraネットワークで扱うオブジェクトの種類 (例: ユーザー、サーバー、ワールドなど) - pub kind: SuteraIdentityKind, - - /// The display name of the identity. - /// This is only designated for human-readable purposes and plays no role in authentication. - /// display_name can only contain alphanumeric characters (0-9, a-z) - /// identityの表示名。 - /// これは人間の理解を促進するためだけに定義されており、認証プロセスにおいて何の役目も果たしません。 - /// 表示名には英数字(0-9, a-z)のみを利用することができます。 - pub display_name: Option, - - /// The ed25519 public key of the identity. - /// This is used to verify the signature of the identity. - /// identityのed25519公開鍵です。 - /// identityの署名を検証されるために使用されます。 - pub pub_signature: ed25519::VerifyingKey, -} - -/// Convert SuteraIdentity to String. -/// The format is `{type}@{display_name}.sutera-identity-v1.{pub_signature}`. -/// Because pub_signature is 32byte, so the part `{pub_signature}` is 64 letters hexadecimal string. -/// SuteraIdentityを文字列に変換します。 -/// 形式は `{type}@{display_name}.sutera-identity-v1.{pub_signature}` です。 -/// pub_signatureは32バイトなので、`{pub_signature}`は64文字の16進数文字列になります。 -/// -/// ## Example -/// ```no_test -/// user.sutera-identity-v1.fffffff..... -/// user@alice.sutera-identity-v1.fffffff..... -/// ``` -impl From for String { - fn from(identity: SuteraIdentity) -> String { - format!( - "{}.sutera-identity-v1.{}", - match identity.display_name { - Some(display_name) => format!("{}@{}", identity.kind.as_ref(), display_name), - None => identity.kind.as_ref().to_string(), - }, - identity - .pub_signature - .0 - .iter() - .fold(String::with_capacity(64), |acc, byte| acc - + &format!("{:02x}", byte)) - ) - } -} - -impl TryFrom for SuteraIdentity { - type Error = SuteraIdentityStringParseError; - - fn try_from(value: String) -> Result { - let parts: Vec<&str> = value.split('.').collect(); - if parts.len() != 3 { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - let (kind, version, pub_key) = (parts[0].to_string(), parts[1].to_string(), parts[2]); - - if kind.is_empty() { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - if version != "sutera-identity-v1" { - return Err(SuteraIdentityStringParseError::VersionMismatch(version)); - } - - if pub_key.len() != 64 { - return Err(SuteraIdentityStringParseError::InvalidFormat); - } - - let (kind, display_name) = match kind.find('@') { - Some(index) => ( - SuteraIdentityKind::from_str(&kind[..index]).map_err(|_| { - SuteraIdentityStringParseError::UnsupportedKind(kind[..index].to_string()) - })?, - Some(kind[index + 1..].to_string()), - ), - None => ( - SuteraIdentityKind::from_str(&kind).map_err(|_| { - SuteraIdentityStringParseError::UnsupportedKind(kind.to_string()) - })?, - None, - ), - }; - - let pub_key_bytes = parts[2] - .as_bytes() - .chunks(2) - .map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16)) - .collect::, std::num::ParseIntError>>() - .or(Err(SuteraIdentityStringParseError::InvalidFormat))?; - - Ok(SuteraIdentity { - kind, - display_name, - pub_signature: ed25519::VerifyingKey(pub_key_bytes.try_into().unwrap()), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use pretty_assertions::assert_eq; - use ring_compat::signature::ed25519; - - #[test] - fn sutera_identity_string() { - // ダミーの公開鍵(全てのビットが0)のSuteraIdentityを作成, 文字列に変換 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: Some("see2et".to_string()), - pub_signature: ed25519::VerifyingKey([0; 32]), - }; - - let identity_str: String = identity.clone().into(); - assert_eq!( - identity_str, - "user@see2et.sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000" - ); - - // 変換した文字列をSuteraIdentityに戻し, オリジナルのSuteraIdentityと一致するか検証 - let parsed_identity: SuteraIdentity = identity_str.try_into().unwrap(); - assert_eq!(identity, parsed_identity); - } - - #[test] - fn sutera_identity_string_without_name() { - // ダミーの公開鍵(全てのビットが0)のSuteraIdentityを作成, 文字列に変換 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: None, - pub_signature: ed25519::VerifyingKey([0; 32]), - }; - - let identity_str: String = identity.clone().into(); - assert_eq!( - identity_str, - "user.sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000" - ); - - // 変換した文字列をSuteraIdentityに戻し, オリジナルのSuteraIdentityと一致するか検証 - let parsed_identity: SuteraIdentity = identity_str.try_into().unwrap(); - assert_eq!(identity, parsed_identity); - } - - #[test] - fn sutera_identity_string_version_mismatch() { - // バージョンが異なる文字列をSuteraIdentityに変換しようとした場合のエラーを検証 - let invalid_identity_str = "see2et.sutera-identity-v2.xxx"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::VersionMismatch( - "sutera-identity-v2".to_string() - )) - ); - } - - #[test] - fn sutera_identity_string_invalid() { - // 不正な文字列をSuteraIdentityに変換しようとした場合にエラーにちゃんとなるか検証 - - let invalid_identity_str = "user.sutera-identity-v1"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "user.sutera-identity-v1.abc"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "user.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = "unknown.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::UnsupportedKind( - "unknown".to_string() - )) - ); - let invalid_identity_str = "unknown@hello.sutera-identity-v1.x000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::UnsupportedKind( - "unknown".to_string() - )) - ); - let invalid_identity_str = - "sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - let invalid_identity_str = - ".sutera-identity-v1.0000000000000000000000000000000000000000000000000000000000000000"; - assert_eq!( - SuteraIdentity::try_from(invalid_identity_str.to_string()), - Err(SuteraIdentityStringParseError::InvalidFormat) - ); - } -} diff --git a/sutera-lib/src/signature/message.rs b/sutera-lib/src/signature/message.rs deleted file mode 100644 index cf04af0..0000000 --- a/sutera-lib/src/signature/message.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::str::FromStr; - -use super::identity::SuteraIdentity; -use ring_compat::signature::{ed25519, Signer, Verifier}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -/// A common structure of message exchanged within the Sutera network. -/// Suteraネットワークにおけるメッセージの送受信に使用される構造体です。 -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SuteraSignedMessage { - pub author: SuteraIdentity, - pub message: String, - pub signature: ed25519::Signature, -} - -/// An error that occurs when signing a message. -/// メッセージの署名時に起きるエラー。 -#[derive(Debug, Error)] -pub enum SuteraMessageSigningError { - #[error("the signing key does not match the author's verifying key")] - SigningKeyMismatch, -} - -impl SuteraSignedMessage { - /// Sign a message with the [`author`]'s signing key. - /// [`author`]の署名鍵でメッセージを署名する。 - /// - /// ## Parameters / 引数 - /// - `author`: The author of the message. / メッセージの署名者。 - /// - `message`: The content of the message. / メッセージの内容。 - /// - `signer`: The signing key of the author. / 署名者の署名鍵。 - /// - /// ## Returns / 戻り値 - /// A new signed message. - /// if the signing key does not match the author's verifying key, return `Err`. - /// 新たに署名されたメッセージが返却されます。 - /// 署名鍵が署名者の認証鍵と合致しない場合、`Err`が返却されます。 - pub fn new( - author: SuteraIdentity, - message: String, - signer: ed25519::SigningKey, - ) -> Result { - let verifying_key = signer.verifying_key(); - if verifying_key != author.pub_signature { - return Err(SuteraMessageSigningError::SigningKeyMismatch); - } - - let signature = signer.sign(message.as_bytes()); - - Ok(SuteraSignedMessage { - author, - message, - signature, - }) - } - - /// Check if the signature is valid. - /// 署名が有効かどうかを確認します。 - /// - /// **SuteraSignedMessage should be verified before processing the message.** - /// **SuteraSignedMessageはメッセージが処理される前に検証されなければいけません。** - /// - /// **The signature is once checked at the time of creation in normal scenario,** - /// **so this method is used as a assertion.** - /// **署名は通常の場合、作成されたタイミングで一度検証されます。** - /// **そのため、このメソッドはアサーションとして利用されます。** - /// - /// ## Return / 戻り値 - /// `true` if the signature is valid, otherwise `false`. - /// 署名が有効な場合は`true`、そうでない場合は`false`を返します。 - pub fn verify(&self) -> bool { - self.author - .pub_signature - .verify(self.message.as_bytes(), &self.signature) - .is_ok() - } -} - -impl Serialize for SuteraSignedMessage { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let payload = SuteraSignedMessagePayload { - author: self.author.clone().into(), - message: self.message.clone(), - signature: self.signature.to_string(), - }; - payload.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for SuteraSignedMessage { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let payload = SuteraSignedMessagePayload::deserialize(deserializer)?; - let author = SuteraIdentity::try_from(payload.author).map_err(serde::de::Error::custom)?; - let signature = - ed25519::Signature::from_str(&payload.signature).map_err(serde::de::Error::custom)?; - - Ok(SuteraSignedMessage { - author, - message: payload.message, - signature, - }) - } -} - -#[derive(Serialize, Deserialize)] -struct SuteraSignedMessagePayload { - pub author: String, - pub message: String, - pub signature: String, -} - -#[cfg(test)] -mod tests { - use crate::signature::identity::SuteraIdentityKind; - - use super::*; - use pretty_assertions::assert_eq; - use rand_core::{OsRng, RngCore}; - - fn test_signed_message() -> SuteraSignedMessage { - // ランダムな秘密鍵を生成 - let mut ed25519_seed = [0u8; 32]; - OsRng.fill_bytes(&mut ed25519_seed); - let secret = ed25519::SigningKey::from_bytes(&ed25519_seed); - - // 秘密鍵からSuteraIdentityを生成 - let identity = SuteraIdentity { - kind: SuteraIdentityKind::User, - display_name: Some("see2et".to_string()), - pub_signature: secret.verifying_key(), - }; - - // 適当なStringをメッセージとして用意し,署名する - let message = "Hello, Sutera!"; - SuteraSignedMessage::new(identity, message.to_string(), secret).unwrap() - } - - #[test] - fn sign_message() { - // ランダムな秘密鍵で署名されたメッセージを生成 - let mut signed_message = test_signed_message(); - - // 署名検証を通過することを確認 - assert!(signed_message.verify()); - - // 内容を変更し,改竄されたメッセージとして署名検証に失敗することを確認 - signed_message.message = "Hello, Sutera?".to_string(); - assert!(!signed_message.verify()); - } - - #[test] - fn signed_message_serializable() { - // ランダムな秘密鍵で署名されたメッセージを生成 - let signed_message = test_signed_message(); - - // シリアライズ -> デシリアライズしても内容が変わらないことを確認 - let serialized = serde_json::to_string(&signed_message).unwrap(); - let deserialized: SuteraSignedMessage = serde_json::from_str(&serialized).unwrap(); - - assert_eq!(signed_message, deserialized); - } -} diff --git a/sutera-lib/src/signature/mod.rs b/sutera-lib/src/signature/mod.rs index 674a14b..6801018 100644 --- a/sutera-lib/src/signature/mod.rs +++ b/sutera-lib/src/signature/mod.rs @@ -1,2 +1,32 @@ -pub mod identity; -pub mod message; +pub mod algorithms; +pub mod private_key_masked; +use self::algorithms::SigningAlgorithmKind; +use self::private_key_masked::PrivateKeyMasked; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct Signed { + #[serde(bound(deserialize = "T: DeserializeOwned", serialize = "T: Serialize"))] + payload: T, + signature: Signature, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct Signature { + identity: SuteraIdentity, + signature: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SuteraIdentity { + display_name: String, + algorithm: SigningAlgorithmKind, + public_key: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SuteraPrivateKey { + algorithm: SigningAlgorithmKind, + key: PrivateKeyMasked, +} diff --git a/sutera-lib/src/signature/private_key_masked.rs b/sutera-lib/src/signature/private_key_masked.rs new file mode 100644 index 0000000..d316d5d --- /dev/null +++ b/sutera-lib/src/signature/private_key_masked.rs @@ -0,0 +1,106 @@ +use std::cmp::Ordering; +use std::fmt::Debug; +use std::fmt::{self, Display}; + +pub struct PrivateKeyMasked(T); + +impl PrivateKeyMasked { + /// Get the inner value. + /// + /// Do not use this for error messages. + /// **Do not use this in argument or return type to any function.** + /// (tracing-subscriber might expose the inner value) + pub fn get_raw(&self) -> &T { + &self.0 + } + + /// Get the mutable reference to the inner value. + /// + /// Do not use this for error messages. + /// **Do not use this in argument or return type to any function.** + /// (tracing-subscriber might expose the inner value) + pub fn get_raw_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl PrivateKeyMasked {} + +const SECRET_IN_LOG_WARNING_LEFT: &str = "(!WARNING[SECRET-IN-LOG] Following angle bracket contains information from which a private key may be derived. Please be sure to mask this log when sharing it with others! / 警告! 続く山括弧の中には, あなたの秘密鍵を推測する材料となり得る情報が含まれます。 このログを他者と共有する際には, 該当部分を必ず隠してください。<"; +const SECRET_IN_LOG_WARNING_RIGHT: &str = ">)"; + +impl Debug for PrivateKeyMasked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(SECRET_IN_LOG_WARNING_LEFT)?; + ::fmt(&self.0, f)?; + f.write_str(SECRET_IN_LOG_WARNING_RIGHT) + } +} + +impl Display for PrivateKeyMasked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(SECRET_IN_LOG_WARNING_LEFT)?; + ::fmt(&self.0, f)?; + f.write_str(SECRET_IN_LOG_WARNING_RIGHT) + } +} + +impl From for PrivateKeyMasked { + fn from(t: T) -> Self { + Self(t) + } +} + +impl Clone for PrivateKeyMasked { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl PartialEq for PrivateKeyMasked { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for PrivateKeyMasked {} + +impl PartialOrd for PrivateKeyMasked { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for PrivateKeyMasked { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn debug_print_should_masked_string() { + assert_eq!( + format!("{:?}", PrivateKeyMasked("SUPERSECRET")), + format!( + "{}{}{}", + SECRET_IN_LOG_WARNING_LEFT, "\"SUPERSECRET\"", SECRET_IN_LOG_WARNING_RIGHT + ) + ); + } + + #[test] + fn display_print_should_masked_string() { + assert_eq!( + format!("{}", PrivateKeyMasked("SUPERSECRET")), + format!( + "{}{}{}", + SECRET_IN_LOG_WARNING_LEFT, "SUPERSECRET", SECRET_IN_LOG_WARNING_RIGHT + ) + ); + } +} diff --git a/sutera-lib/src/util/hex.rs b/sutera-lib/src/util/hex.rs new file mode 100644 index 0000000..605b88a --- /dev/null +++ b/sutera-lib/src/util/hex.rs @@ -0,0 +1,137 @@ +use std::fmt::Debug; +use std::fmt::Write; +use std::str::Utf8Error; +use tracing_error::SpanTrace; + +use thiserror::Error; +use tracing::instrument; + +use crate::error::{CapturedError, ResultCaptureErrExt, TraceableError}; +use crate::signature::private_key_masked::PrivateKeyMasked; + +pub(crate) trait HexString: Debug { + fn content(&self) -> &str; +} + +impl HexString for str { + fn content(&self) -> &str { + self + } +} + +impl HexString for String { + fn content(&self) -> &str { + self + } +} + +impl HexString for PrivateKeyMasked { + fn content(&self) -> &str { + self.get_raw().content() + } +} + +trait Binary: Debug { + fn content(&self) -> &[u8]; +} + +impl Binary for [u8; N] { + fn content(&self) -> &[u8] { + self + } +} + +impl Binary for Vec { + fn content(&self) -> &[u8] { + self + } +} + +impl Binary for PrivateKeyMasked { + fn content(&self) -> &[u8] { + self.get_raw().content() + } +} + +#[instrument("to_hex")] +pub(crate) fn to_hex(data: &impl Binary) -> String { + data.content().iter().fold(String::new(), |mut acc, byte| { + write!(acc, "{byte:02x}").unwrap(); + acc + }) +} + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum FromHexError { + #[error(transparent)] + Utf8(#[from] CapturedError), + + #[error(transparent)] + ParseInt(#[from] CapturedError), +} + +impl TraceableError for FromHexError { + fn trace(&self) -> &SpanTrace { + match self { + FromHexError::Utf8(e) => e.trace(), + FromHexError::ParseInt(e) => e.trace(), + } + } +} + +#[instrument("from_hex")] +pub(crate) fn from_hex(data: &H) -> Result, FromHexError> { + data.content() + .as_bytes() + .chunks(2) + .map(|chunk| { + Ok(u8::from_str_radix(std::str::from_utf8(chunk).capture_err()?, 16).capture_err()?) + }) + .collect::, FromHexError>>() +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_matches::assert_matches; + use pretty_assertions::assert_eq; + + #[test] + fn from_hex_after_to_hex_should_do_nothing() { + assert_eq!( + from_hex(&to_hex(&[0x01, 0x02, 0x03, 0x04])), + Ok(vec![0x01, 0x02, 0x03, 0x04]) + ); + } + + #[test] + fn to_hex_after_from_hex_should_do_nothing() { + assert_eq!( + from_hex("faceb00c").map(|hex| to_hex(&hex)), + Ok("faceb00c".to_string()) + ); + } + + #[test] + fn from_hex_should_handle_odd_length_strings() { + assert_eq!(from_hex("f"), Ok(vec![0xf])); + assert_eq!(from_hex("fa"), Ok(vec![0xfa])); + assert_eq!(from_hex("fac"), Ok(vec![0xfa, 0xc])); + assert_eq!(from_hex("face"), Ok(vec![0xfa, 0xce])); + } + + #[test] + fn to_hex_should_handle_empty_input() { + assert_eq!(to_hex(&[]), ""); + } + + #[test] + fn from_hex_should_handle_empty_input() { + assert_eq!(from_hex(""), Ok(vec![])); + } + + #[test] + fn from_hex_should_fail_for_invalid_characters() { + assert_matches!(from_hex("g"), Err(FromHexError::ParseInt(_))); + } +} diff --git a/sutera-lib/src/util/mod.rs b/sutera-lib/src/util/mod.rs new file mode 100644 index 0000000..ce02e67 --- /dev/null +++ b/sutera-lib/src/util/mod.rs @@ -0,0 +1 @@ +pub mod hex;