diff --git a/.gitignore b/.gitignore index ea8c4bf..869df07 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 850aee7..aede346 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,32 +27,32 @@ describe. The atsign-foundation GitHub organization's conventions and configurat ### Prerequisites - - ``` sh - # show how to install the tools needed to work with the code here - ``` - +The following need to be installed: +- `rust` - [Installation instructions](https://doc.rust-lang.org/book/ch01-01-installation.html) +- `openssl` +- `libssl-dev` +- `pkg-config` ### GitHub Repository Clone To prepare your dedicated GitHub repository: -1. Fork in GitHub https://github.com/atsign-foundation/REPO -2. Clone *your forked repository* (e.g., `git clone git@github.com:yourname/REPO`) +1. Fork in GitHub https://github.com/atsign-foundation/at_rust.git +2. Clone *your forked repository* (e.g., `git clone git@github.com:yourname/at_rust.git`) 3. Set your remotes as follows: ```sh cd REPO - git remote add upstream git@github.com:atsign-foundation/REPO.git + git remote add upstream git@github.com:atsign-foundation/at_rust.git git remote set-url upstream --push DISABLED ``` Running `git remote -v` should give something similar to: ```text - origin git@github.com:yourname/REPO.git (fetch) - origin git@github.com:yourname/REPO.git (push) - upstream git@github.com:atsign-foundation/REPO.git (fetch) + origin git@github.com:yourname/at_rust.git (fetch) + origin git@github.com:yourname/at_rust.git (push) + upstream git@github.com:atsign-foundation/at_rust.git (fetch) upstream DISABLED (push) ``` diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 5a3ba47..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,684 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "at_rust" -version = "0.1.0" -dependencies = [ - "native-tls", - "rustls", - "serde_json", - "webpki", - "webpki-roots", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "js-sys" -version = "0.3.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "openssl" -version = "0.10.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "proc-macro2" -version = "1.0.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustls" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-webpki" -version = "0.100.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "ryu" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.163" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" - -[[package]] -name = "serde_json" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "syn" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.45.0", -] - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "wasm-bindgen" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" - -[[package]] -name = "web-sys" -version = "0.3.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125" -dependencies = [ - "rustls-webpki", -] - -[[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.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 4079f65..acaa514 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,18 @@ edition = "2021" [dependencies] serde_json = "1.0.96" -rustls = "0.21.1" -webpki = "0.22.0" -webpki-roots = "0.23.0" native-tls = "0.2.11" +regex = "1.8.3" +rsa = { version = "0.9.2", features = ["sha2"] } +rust-crypto = "0.2.36" +rand = "0.8.5" +base64 = "0.21.0" +der = "0.7.6" +hex = "0.4.3" +block-padding = "0.3.3" +generic-array = "0.14.7" +sha256 = "1.1.3" +log = "0.4.19" + +[dev-dependencies] +env_logger = "0.10.0" diff --git a/README.md b/README.md index 165cc0a..3d9c868 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,59 @@ -# Sample README - -Open with intent - we welcome contributions - we want pull requests and to hear about issues. - -## Who is this for? - -The README should be addressed to somebody who's never seen this before. -But also don't assume that they're a novice. - -### Code user - -Does this repo publish to [pub.dev](https://pub.dev) or similar? -In which case the code user just needs a pointer there - e.g. [at_client on pub.dev](https://pub.dev/packages/at_client) - -### Contributor - -This is the person who we want working with us here. -[CONTRIBUTING.md](CONTRIBUTING.md) is going to have the detailed guidance on how to setup their tools, -tests and how to make a pull request. - -## Why, What, How? - -### Why? - -What is the purpose of this project? - -### What? - -What is needed to get the project and its dependencies installed? - -### How? - -How does this work? How is this used to fulfil its intended purpose? - -## Checklist - -### Writing - -Does the writing flow, with proper grammar and correct spelling? - -### Links - -Are the links to external resources correct? -Are the links to other parts of the project correct -(beware stuff carried over from previous repos where the -project might have lived during earlier development)? - -### Description - -Has the Description field been filled out? - -### Acknowledgement/Attribution - -Have we correctly acknowledged the work of others (and their Trademarks etc.) -where appropriate (per the conditions of their LICENSE? - -### LICENSE - -Which LICENSE are we using? -Is the LICENSE(.md) file present? -Does it have the correct dates, legal entities etc.? - -## Maintainers - -Who created this? - -Do they have complete GitHub profiles? - -How can they be contacted? - -Who is going to respond to pull requests? +# Rust SDK - (⚠️Alpha version⚠️) +This repo contains libraries, tools, samples and examples for developers who wish to work with the atPlatform from Rust code. + +It currently has limited functionality with minimal tests. + +## Requirements +The following need to be installed: +- `rust` - [Installation instructions](https://doc.rust-lang.org/book/ch01-01-installation.html) +- `openssl` +- `libssl-dev` +- `pkg-config` + +## Run examples +### Send data +Send data to an atSign - `cargo run --example send_data_example ` +- `path-to-at-keys` - Absolute path to the `.atKeys` of the sender +- `message` - Text data to send to receiver +- `atSign-of-sender` - The name of the atSign (without `@`) who is sending the data +- `atSign-of-receiver` - The name of the atSign (without `@`) who is receiving the data +#### E.g. +```sh +RUST_LOG=info cargo run --example send_data_example ~/.atsign/keys/@aliens12_key.atKeys hello_there aliens12 virgogigantic64 +``` + +### Fetch data +Fetch data from an atSign - `cargo run --example fetch_data_example ` +- `path-to-at-keys` - Absolute path to the `.atKeys` of the receiver +- `atSign-of-receiver` - The name of the atSign (without `@`) who is receiving the data +- `atSign-of-sender` - The name of the atSign (without `@`) who is sending the data +#### E.g. +```sh +RUST_LOG=info cargo run --example fetch_data_example ~/.atsign/keys/@virgogigantic64_key.atKeys virgogigantic64 aliens12 +``` + +## Structure +- `at_client` - What consumers of the library will mostly interact with +- `at_secrets` - Struct for constructing secrets from a file +- `at_chops` (Cryptographic and Hashing Operations (CHOPS)) + - `utils.rs` - Contains the generic, low level crypto operations + - `at_chops.rs` - Contains the specific combination of crypto operations that the client and verbs can use +- `verbs` - Contains a trait that all verbs have to implement. Verbs execute the atProtocol verbs by taking in arguments from the client. + +## Logging +This library uses the `log` crate. This means implementors of this library can use something like `env_logger` and get info from the library. + +## Contributions welcome! + +All of our software is open with intent. We welcome contributions - we want pull requests, and we want to hear about issues. See also [CONTRIBUTING.md](CONTRIBUTING.md). + +## Steps to Beta +- [ ] Notifications using the `monitor` verb +- [ ] Interoperability with other SDKs + +## Future goals +- [ ] `no_std` implementation +- [ ] Distribute to `crates.io` +- [ ] Support for `async` runtime diff --git a/examples/fetch_data_example.rs b/examples/fetch_data_example.rs new file mode 100644 index 0000000..29cd461 --- /dev/null +++ b/examples/fetch_data_example.rs @@ -0,0 +1,39 @@ +use at_rust::at_client::AtClient; +use at_rust::at_secrets::AtSecrets; +use at_rust::at_sign::AtSign; +use std::env; +use std::fs::File; +use std::io::Read; + +extern crate env_logger; + +fn main() { + env_logger::init(); + // Parse the arguments + let args: Vec = env::args().collect(); + // The location of the file containing the secrets + let file_path = args[1].clone(); + // The atSign of the client device + let host = args[2].clone(); + // The atSign of the sender of the data + let contact = args[3].clone(); + + // Read the contents of the file into a string. + let mut file = File::open(file_path).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + + // Create the secrets object from the file + let secrets = AtSecrets::from_file(&contents).expect("Failed to create secrets"); + + // Create the atSign object for the sender + let contact = AtSign::new(contact); + + // Create the AtClient object + let mut at_client = AtClient::init(secrets, AtSign::new(host), "test").expect("Failed to init"); + + // Read the data using the AtClient + at_client + .read_data(contact, "demo") + .expect("Failed to send data"); +} diff --git a/examples/send_data_example.rs b/examples/send_data_example.rs new file mode 100644 index 0000000..9a90648 --- /dev/null +++ b/examples/send_data_example.rs @@ -0,0 +1,40 @@ +use at_rust::at_client::AtClient; +use at_rust::at_secrets::AtSecrets; +use at_rust::at_sign::AtSign; +use std::env; +use std::fs::File; +use std::io::Read; +extern crate env_logger; + +fn main() { + env_logger::init(); + // Parse the arguments + let args: Vec = env::args().collect(); + // The location of the file containing the secrets + let file_path = args[1].clone(); + // The data to send to another atSign + let data = args[2].clone(); + // The atSign of the client device + let host = args[3].clone(); + // The atSign of the recipient of the data + let contact = args[4].clone(); + + // Read the contents of the file into a string. + let mut file = File::open(file_path).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + + // Create the secrets object from the file + let secrets = AtSecrets::from_file(&contents).expect("Failed to create secrets"); + + // Create the atSign object for the contact + let contact = AtSign::new(contact); + + // Create the AtClient object + let mut at_client = AtClient::init(secrets, AtSign::new(host), "test").expect("Failed to init"); + + // Send the data using the AtClient + at_client + .send_data(&data, contact, "demo") + .expect("Failed to send data"); +} diff --git a/src/at_chops/at_chops.rs b/src/at_chops/at_chops.rs new file mode 100644 index 0000000..e1a972c --- /dev/null +++ b/src/at_chops/at_chops.rs @@ -0,0 +1,138 @@ +use super::utils::{ + base64_decode, base64_encode, construct_aes_key, construct_rsa_private_key, + construct_rsa_public_key, create_new_aes_key, decrypt_data_with_aes_key, + decrypt_symm_key_with_private_key, encrypt_data_with_aes_key, encrypt_with_public_key, + rsa_sign, +}; + +/// Base64 decode the self encryption key. +pub fn decode_self_encryption_key(self_encryption_key: &str) -> Vec { + base64_decode(self_encryption_key) +} + +/// Decrypt the private key using the self encryption key. +pub fn decrypt_private_key( + encrypted_private_key: &str, + decoded_self_encryption_key: &[u8], +) -> String { + let iv: [u8; 16] = [0x00; 16]; + let mut cypher = construct_aes_key(decoded_self_encryption_key, &iv); + let decoded_private_key = base64_decode(&encrypted_private_key); + + let mut output: Vec = vec![0; decoded_private_key.len()]; + cypher.process(&decoded_private_key, &mut output); + + // NOTE: Due to the PKCS#7 type of encryption used (on the keys), the output will have padding + + // NOTE: Might be worth converting to a char type then using .is_ascii_hexdigit() (or similar) + + // Get the last byte, which is the number of padding bytes + let last = output.last().unwrap(); + output.truncate(output.len() - usize::from(*last)); + String::from_utf8(output).expect("Unable to convert to UTF-8") +} + +/// Sign a given challenge with the decrypted private key. +pub fn sign_challenge(challenge: &str, decrypted_private_key: &str) -> String { + let decoded_private_key = base64_decode(&decrypted_private_key); + let rsa_private_key = construct_rsa_private_key(&decoded_private_key); + rsa_sign(rsa_private_key, &challenge.as_bytes()) +} + +/// Cut a new symmetric key to be used when interacting with a new atSign. +pub fn create_new_shared_symmetric_key() -> String { + let key = create_new_aes_key(); + base64_encode(&key) +} + +/// Decrypt the symmetric key with "our" private key. +pub fn decrypt_symmetric_key(encrypted_symmetric_key: &str, decrypted_private_key: &str) -> String { + let decoded_private_key = base64_decode(&decrypted_private_key); + let rsa_private_key = construct_rsa_private_key(&decoded_private_key); + // NOTE: Probs need to do the same as decrypt_symmetric_key_2 + let decoded_symmetric_key = base64_decode(&encrypted_symmetric_key); + let decrypted_symmetric_key = + decrypt_symm_key_with_private_key(&rsa_private_key, &decoded_symmetric_key); + decrypted_symmetric_key +} + +/// Encrypt data with our RSA public key. +pub fn encrypt_data_with_public_key(encoded_public_key: &str, data: &str) -> String { + let decoded_public_key = base64_decode(&encoded_public_key); + let rsa_public_key = construct_rsa_public_key(&decoded_public_key); + + // NOTE: Not sure if I need to decode the data or pass it in as bytes. + + let encrypted_data = encrypt_with_public_key(&rsa_public_key, &base64_decode(&data)); + // let encrypted_data = encrypt_with_public_key(&rsa_public_key, &data.as_bytes()); + encrypted_data +} + +/// Encrypt data with AES symm key. +pub fn encrypt_data_with_shared_symmetric_key(encoded_symmetric_key: &str, data: &str) -> String { + let decoded_symmetric_key = base64_decode(&encoded_symmetric_key); + let iv: [u8; 16] = [0x00; 16]; + let mut cypher = construct_aes_key(&decoded_symmetric_key, &iv); + let encrypted_data = encrypt_data_with_aes_key(&mut cypher, &data.as_bytes()); + base64_encode(&encrypted_data) +} + +/// Decrypt data with an encoded AES symm key. +pub fn decrypt_data_with_shared_symmetric_key(encoded_symmetric_key: &str, data: &str) -> String { + let decoded_symmetric_key = base64_decode(&encoded_symmetric_key); + let iv: [u8; 16] = [0x00; 16]; + let mut cypher = construct_aes_key(&decoded_symmetric_key, &iv); + // let mut encrypted_data = decrypt_data_with_aes_key(&mut cypher, &data.as_bytes()); + let encrypted_data = decrypt_data_with_aes_key(&mut cypher, &base64_decode(&data)); + + // base64_encode(&encrypted_data) + String::from_utf8(encrypted_data).expect("Unable to convert to UTF-8") +} + +#[cfg(test)] +mod test { + + use super::*; + + // ----- Test data ------ + // "Hello World!" in base64 + const TEST_KEY_ENCODED: &str = "SGVsbG8gV29ybGQh"; + // "Hello World!" in bytes + const TEST_KEY_DECODED: [u8; 12] = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + // Self encryption key base64 encoded + const SELF_ENCRYPTION_KEY_ENCODED: &str = "LXgXrG4oWQQTa1EpDvkTs3EE83qsyICgrpoWLVYEwbo="; + // Pkam key base64 encoded with self encryption key (input) + const PKAM_KEY_ENCRYPTED_AND_ENCODED: &str = "W5OfspfR4MNVJfwDt7Iuu7SP1Pjiilfj1spIrot+fu6MopEY9B/NyNLoEUfJoPqin8973dSEhsGm8kZmUtmY48nTDqS38hNqNYRYZoaI+FRRzPMCVzz2WOtiCYWdHhRHRuMcX/rGbNS5lLG28ZW45itPOkA/qR5yre20ThPvx9koXB4WYgQn7DRbJGAYo+UTgd0twZoamG56Kr6qjvO01JChoVXzfC4GfFRgI25jO9Zc35xgLgTfMhaWLpDgg3JlC3oHq9nE92VqfZ2TRnEkD7Dxv3V+BOEq1R6sp0H/R5UYEyoSTSldxJttrngGUeEa/gkdcLXlKTF0/a/usv/HAEclv5n/IqsLO9QYzSRqY+dUGoK2aBfZeP38U76Wdycx4GOCyP7ay4EpJ7St+BoQwZCw3GX+e4UDKYcm0JOnzMnmkgtO5hk8R0yd07wzzgBs369GGt/n0HwuVgysXna/EY6k19rcnwjRD54/NiyJOvhE6sO17ymPvZjq1rqBRN2pEpWkyDS2r1V2di6nPCr7jkbBdcEVdUTZhV5QBVjfdudoV0gg5S4zPNar+lWHaqLFp5hlUXjqYhvgJHo5qmlaZqwoa2uxAOoGvR+kkAjXkvb5RD4jjexRhwdwINnCYTBCtIqTwEPa7YaEZfgt2+82WHxwEWc1/u0h4/U4WrktyMW24fbtdu8biMcZNwQkcNOBnEoUseavN1nIuRq0wyTJN3y4bDlyq6wNc7BHm9TOeJzEE6EAvHD6Fo5Wu4KB193ibcgFWpuMmYAnEhC2yHJHY1JcqH3mWS/foQ14fepeAWDHui0/F/kWszu0cxegW6XWcdNieLslk59Oqg7ubHEY+c2UUgvnsbFLl3qhj7IIK/Rzj7OjuSlpHboTI/XnVDoGKxg7Qc0zO/nXI/GB485wCTgIS0eh+aRCaLzVddrZ5AbbNOQVQNXpv78hPLN4TajPWyF4vIrN8CNvyq8I30NTNpE2aifDsUywhGFzjtzdkp3kQ8yStZaAtFYB6zkolsrQoiVCF363BqHBxMwypM+hrbWOpC9vNFgT2RA0356x9m7vVDywGQQbv6wF189FkuWY6vObNfMSb7Cgj8Sju9RZXyV2TX62i1JVCc/GQ9WxwanrhlbBfAkkS7HXcB47i2yCnIi4YUi4RAEWYbcRGkQAZtTGnEO6ifN0feY23sH7NfUOtegVjKFvTJBBcfeG1blrXyfHVLTb+iK71Zy3yDHV/gqqebarKawifxohSNE9J7KEhm54stZP3y8qclNuONHgJDfzO5t+sUbFx8n2hOtVaSHQFtYIekawh98DowVWotcXEAHizWKsK+0lr7oj5H9HKKJikjcmnmbvwlFuQw9ZqM5OPhXmF/0kxpf5AGMzrNi4NhwfAG7zCqb0IHFmOlckw8HbSpKOXFM2Idqr2A8K5SeRGFxlVNMp9K1ba01hnovv4G83tfZktf3qEdJS8lnzxvTI9KZJbUBnLeKGmpWE9VUl7/4ziEnJLOJOuydCArfLXKUeA1iqCA+lMoRj7bRkbHLJxGgOF9Oin/tv4UC0SlCwbnBZM/EPd/sjV/mrnRLfSG5zEcylShnJTRhvK0Bx/jEBJMP7V3pIqQ5ezDpQQSCh/qVS1kXV0dKhgrmaW/MmLklga+wN24SISIbONa27MT19cWuYQSyICxUd+FzbSbxE5knEycZAGVPcDQ7qJs76bxsk8y2EXdU1sIwQB92bn9oYEyfZL9BZeT31mxcZSH+TgbSs9Y4+FNqyvi4mB23YsNJySEsqF61WU5OZYHh27hbe6wGwMLAr2Dry3WdE5p/4SKEX5DW2A8Q5U5Hq5SEdN0PAw3oaDnv0Fi9Towuo8BLp8ZxUxP8AM+1gi3KsfKH4yOQZk/efQtyJ6geRB1TCEWB5N7L2N3FA0lSEoqWORvzwwcHrhnzo2M75Bh14JTsqXNXugq3MUAQGM7cUfE6uWfTpmRo/KryWkc+Yn072dB/Ox9JRRE6UYfnp4ls++su9Ald1NjDAFmcE2wLB8oF13NJBBigqtm55ieYS5EXhCWGZqX+Ejm4PTpuam8E5DzUtrZqbvNF6JgkH0MvWXBhl8Qprjtu/2y6ESFAhpbv04BjsrPplH87AydAdNh9w8DMn+JKY8/SB+qmz6EsAbnEQnplZh0diGbR3z3DgTAj4"; + // Pkam key decoded and converted to utf-8 from bytes (expected output) + const PKAM_KEY_DECRYPTED_AND_ENCODED: &str = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZw9RGU0SnB7hJz5D38SkZjT2mEYt3LTT7NexdzGnX+qkuSwuS7iqICS8cLb0KW2yNaRbWrG2w5wCBFcQ6LPyyiQWAmod2F3LH3c3k06+yC/hw/EhiJqdYOW1Up3WjG4JRzC3G+dIpwMXtScu9AVFJqARPc0j/w8xTYsTTPfsf5A7gFpPRJd/NBZJEpQxZjW5fCRFP+zKFhVEkPHE5rtyVM5Vz4pK6D2ziFoNJdIbme1gWVC4beS4qqbKpLyzfHkfWGSWy/4gEEJZflnWsKVs+wPjNIljsUMAlEMqNzqmuDW0GFc7KNWAK3cKs8CEEGQuqik0xuTROD4JyMPP6nNGvAgMBAAECggEAFA2hCobjhjEQjLfAPUW7SXTNHHJfUOyZY0W2DMmS6DLti3cIDGJ5M4KXHUKty8L+ljalXtvf9lk6DJutGrUxQ4txJ0N/9Ru7wWsg5f3hhQPgo8OTIRHPc0cSBh9MzTfSOB67vZ5pFT7p0Td1lbGtS0DZRw9O7uQ3KozQBIipzo+4y3SaWsPshohSSMjeyKXfEQcy4RwDC+/bX8+pR7jLzkIqqILUs3vuRNAcPLxs5+FK3S/LJmnkhXV+7+tUuHIpl6mDgfhWDUKaOFzq+0cbzS15EJXW6YYdlPdbrj24JYbkUQHxYSEvxnR2+OOtj5HVQnIaNEPf9s0KW8IMLys7aQKBgQDJTnpdVqtN4M2Zuj8bN8ew1x6LkrqLV8EtlxQbn+kLgzmqqj56v6NVXHaxZh3OaIBoHpAQGPR26UAOBcXT5k1PhQ92RAqXh8/dbV+iBrnBjubQawZ3TK2yjclZMuFCW+/tyB6dOwDi+cO/IfBAh5P+mWHOZOa9y+dL2KjVSzL1TQKBgQDDiq3rjrAlPjtcZcMit4r7p4mZtUI0tCJEzt8J/YK9AC+A+g8Lxz7IKCH1vscFhQNU5E65omatCAYpGo17AQ59hLtbC0f3PJXNQTGLfGYiFHPsmOTf4B9w0c6ge1LPPzbfAG+1fvQ+iaa+4d7yNek3OyuH7KiknUN3AKyiFAo06wKBgAP0BZUlqZGK856sOKcJLmO7pb7p773iyElj6SItvr7aIdzHIRj6AHQhr7cGIVm3VaY1y3B1fP+Ezxw3Ys4pfKUuIMKazXZyVVOs3S7qYOV7L+8x2tum5tZV0Hlu9Vt/QLPztR4zVW4fp4duXDB4OSDL1E7gTmO1yGIF7DLcGjEVAoGBAJFiDEk0v3YRPOVHq7umJylPuRiVEXJJ86ig/mdZGtkWyDrmsEUbkGwUmpsxiptp974oOPf/7ML9UkdBPKuVb4aXJw1b59fELcR7kjCY/v6bokzoqFJjOj0RYMUkq772yv8mPef9Se8tPNJy8OW4e3ra/VSD+ibZ3g0ebTvcFnKdAoGAIGHTlkKJmP8nyRNSDceBnwRvqLPt7AC32kSxTl9mqVgYn8Slz+L33dixiMOL8/T/+JY5XxX/iQepBL/wmuxy6O+QoLxoJBIux7qoyYHyoKuOTaXbVs79dREIh/GHf0uQKGQ2MB6FJAuKgzBKyJYjkCf9t/KA2Ja3gxlYnwHBpcw="; + // Challenge text + const CHALLENGE_TEXT: &str = + "_6e27e164-e45b-4ae1-8714-7545d36b6ed4@aliens12:9ef2ec2c-39d4-4e25-825e-0da05f6e0bb9"; + // Expected result of signing the challenge text + const CHALLENGE_RESULT: &str = "aTY5Pxod1hzv/9uL9FSqxbmmCT73vFEBRv4qA+k+d6U5hcglzYvAl1MJNY2eQLTFLoFIkx/3pgm0YkjI4aS1hBAyBmMIinGrPGbOuR3PebPqITLhNWdeWZamHrlKY8tjvARtb4k0gb2LgauzhNq3zzm5aS7EU7OYaRy22/fR5fCWXw+ZyFdRYhA9qlFcA7ksct3pJwHSvSlQb2R7YuzN210Xfii43yAgtncz4CUZRcxPL7AD4mUg7dSMu0RMVKIQKsecwhNfh7bgy1zFDGMpOP8DQJ8tJfQiut5u+0yAGM4O31FJ+F7/1pvR0pgr7/O0/4K+BdhdRWNVine335u6lg=="; + + #[test] + fn decode_self_encryption_key_test() { + let result = decode_self_encryption_key(TEST_KEY_ENCODED); + assert_eq!(result, TEST_KEY_DECODED); + } + + #[test] + fn decrypt_private_key_test() { + let self_encryption_key = decode_self_encryption_key(SELF_ENCRYPTION_KEY_ENCODED); + let result = decrypt_private_key(&PKAM_KEY_ENCRYPTED_AND_ENCODED, &self_encryption_key); + assert_eq!(result, PKAM_KEY_DECRYPTED_AND_ENCODED); + } + + #[test] + fn sign_challenge_test() { + let result = sign_challenge(CHALLENGE_TEXT, &PKAM_KEY_DECRYPTED_AND_ENCODED); + assert_eq!(result, CHALLENGE_RESULT); + } + + #[test] + fn create_new_shared_symmetric_key_test() { + let result = create_new_shared_symmetric_key(); + assert_eq!(result.len(), 44); + } +} diff --git a/src/at_chops/b64_encoded_string.rs b/src/at_chops/b64_encoded_string.rs new file mode 100644 index 0000000..13cef80 --- /dev/null +++ b/src/at_chops/b64_encoded_string.rs @@ -0,0 +1,63 @@ +// use std::fmt::{Display, Formatter}; +// +// use base64::{engine::general_purpose, Engine as _}; +// +// #[derive(Debug)] +// pub struct Base64EncodedString<'a> { +// pub value: &'a str, +// } +// +// impl<'a> Base64EncodedString<'a> { +// pub fn decode(self) -> Vec { +// general_purpose::STANDARD +// .decode(self.value) +// .expect("Failed to decode base64 text") +// } +// } +// +// impl<'a> Display for Base64EncodedString<'a> { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// write!(f, "{}", self.value) +// } +// } +// +// impl<'a> From<&'a str> for Base64EncodedString<'a> { +// fn from(value: &'a str) -> Self { +// Self { value } +// } +// } +// +// impl<'a> From<&'a String> for Base64EncodedString<'a> { +// fn from(value: &'a String) -> Self { +// Self { value } +// } +// } +// +// impl<'a> Into<&'a str> for Base64EncodedString<'a> { +// fn into(self) -> &'a str { +// self.value +// } +// } +// +// // Probably not required. +// pub trait ToBase64 { +// fn to_base64(&self) -> String; +// } +// +// impl ToBase64 for &[u8] { +// fn to_base64(&self) -> String { +// general_purpose::STANDARD.encode(self) +// } +// } +// +// pub trait FromBase64 { +// fn from_base64(&self) -> Vec; +// } +// +// impl FromBase64 for str { +// fn from_base64(&self) -> Vec { +// general_purpose::STANDARD +// .decode(self) +// .expect("Failed to decode base64 text") +// } +// } diff --git a/src/at_chops/mod.rs b/src/at_chops/mod.rs new file mode 100644 index 0000000..f85f6c7 --- /dev/null +++ b/src/at_chops/mod.rs @@ -0,0 +1,4 @@ +pub mod at_chops; +mod b64_encoded_string; +mod utils; + diff --git a/src/at_chops/utils.rs b/src/at_chops/utils.rs new file mode 100644 index 0000000..d56c8c3 --- /dev/null +++ b/src/at_chops/utils.rs @@ -0,0 +1,206 @@ +use crypto::{aes::KeySize, symmetriccipher::SynchronousStreamCipher}; + +use base64::{engine::general_purpose, Engine as _}; +use rsa::{ + pkcs1v15::SigningKey, + pkcs8::{DecodePrivateKey, DecodePublicKey}, + sha2::Sha256, + signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier}, + Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey, +}; + +/// Convert a base64 encoded string to a vector of bytes. +/// Although this just calls another function, the naming is clearer. +pub fn base64_decode(data: &str) -> Vec { + general_purpose::STANDARD + .decode(data) + .expect("Failed to decode base64 text") +} + +/// Convert a slice of bytes to a base64 encoded string. +/// Although this just calls another function, the naming is clearer. +pub fn base64_encode(data: &[u8]) -> String { + general_purpose::STANDARD.encode(data) +} + +/// Construct an AES key from a key. +pub fn construct_aes_key(data: &[u8], iv: &[u8; 16]) -> Box { + crypto::aes::ctr(KeySize::KeySize256, data, iv) +} + +/// Construct an RSA private key from a decoded key. +pub fn construct_rsa_private_key(data: &[u8]) -> RsaPrivateKey { + let rsa_key = RsaPrivateKey::from_pkcs8_der(&data).expect("Unable to create RSA Private Key"); + rsa_key.validate().expect("Invalid RSA Private Key"); + rsa_key +} + +/// Construct an RSA public key from a decoded key. +pub fn construct_rsa_public_key(data: &[u8]) -> RsaPublicKey { + let rsa_key = + RsaPublicKey::from_public_key_der(&data).expect("Unable to create RSA Public Key"); + rsa_key +} + +/// Sign data using an RSA private key. +/// This returns a base64 encoded string. +pub fn rsa_sign(key: RsaPrivateKey, data: &[u8]) -> String { + let mut rng = rand::thread_rng(); + let signing_key = SigningKey::::new(key); + let verifying_key = signing_key.verifying_key(); + + // Sign + let signature = signing_key.sign_with_rng(&mut rng, &data); + verifying_key + .verify(&data, &signature) + .expect("failed to verify"); + let binding = signature.to_bytes(); + let signature_bytes = binding.as_ref(); + + // Encode signature + let sha256_signature_encoded = base64_encode(&signature_bytes); + sha256_signature_encoded +} + +/// Create a new AES-256 key from scratch. +pub fn create_new_aes_key() -> [u8; 32] { + let key: [u8; 32] = rand::random(); + key +} + +/// Encrypt some data using an RSA public key. +pub fn encrypt_with_public_key(public_key: &RsaPublicKey, data: &[u8]) -> String { + let mut rng = rand::thread_rng(); + let encrypted_symmetric_key = public_key + .encrypt(&mut rng, Pkcs1v15Encrypt, data) + .expect("Failed to encrypt symmetric key"); + base64_encode(&encrypted_symmetric_key) +} + +/// Decrypt an AES key using an RSA private key. +pub fn decrypt_symm_key_with_private_key(private_key: &RsaPrivateKey, symm_key: &[u8]) -> String { + let decrypted_symmetric_key = private_key + .decrypt(Pkcs1v15Encrypt, symm_key) + .expect("Failed to decrypt symmetric key"); + base64_encode(&decrypted_symmetric_key) +} + +/// Encrypt some data using an AES key. +pub fn encrypt_data_with_aes_key( + aes_key: &mut Box, + data: &[u8], +) -> Vec { + let mut output: Vec = vec![0; data.len()]; + aes_key.process(&data, &mut output); + output +} + +/// Decrypt some data using an AES key. +pub fn decrypt_data_with_aes_key( + aes_key: &mut Box, + data: &[u8], +) -> Vec { + let mut output: Vec = vec![0; data.len()]; + aes_key.process(&data, &mut output); + output +} + +#[cfg(test)] +mod test { + + use super::*; + + // ----- Test data ------ + // "Hello World!" in base64 + const TEST_KEY_ENCODED: &str = "SGVsbG8gV29ybGQh"; + // "Hello World!" in bytes + const TEST_KEY_DECODED: [u8; 12] = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + // Self encryption key base64 encoded + const SELF_ENCRYPTION_KEY_ENCODED: &str = "LXgXrG4oWQQTa1EpDvkTs3EE83qsyICgrpoWLVYEwbo="; + // Expected result of decoding "Hello World!" with the self encryption key + const SELF_ENCRYPTION_DECRYPT_RESULT: [u8; 12] = [ + 0x5e, 0xbf, 0xba, 0x9b, 0x8e, 0xa0, 0xfe, 0xee, 0x66, 0xd, 0xd9, 0x6c, + ]; + // Pkam key decoded and converted to utf-8 from bytes (expected output) + const PKAM_KEY_DECRYPTED_AND_ENCODED: &str = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCZw9RGU0SnB7hJz5D38SkZjT2mEYt3LTT7NexdzGnX+qkuSwuS7iqICS8cLb0KW2yNaRbWrG2w5wCBFcQ6LPyyiQWAmod2F3LH3c3k06+yC/hw/EhiJqdYOW1Up3WjG4JRzC3G+dIpwMXtScu9AVFJqARPc0j/w8xTYsTTPfsf5A7gFpPRJd/NBZJEpQxZjW5fCRFP+zKFhVEkPHE5rtyVM5Vz4pK6D2ziFoNJdIbme1gWVC4beS4qqbKpLyzfHkfWGSWy/4gEEJZflnWsKVs+wPjNIljsUMAlEMqNzqmuDW0GFc7KNWAK3cKs8CEEGQuqik0xuTROD4JyMPP6nNGvAgMBAAECggEAFA2hCobjhjEQjLfAPUW7SXTNHHJfUOyZY0W2DMmS6DLti3cIDGJ5M4KXHUKty8L+ljalXtvf9lk6DJutGrUxQ4txJ0N/9Ru7wWsg5f3hhQPgo8OTIRHPc0cSBh9MzTfSOB67vZ5pFT7p0Td1lbGtS0DZRw9O7uQ3KozQBIipzo+4y3SaWsPshohSSMjeyKXfEQcy4RwDC+/bX8+pR7jLzkIqqILUs3vuRNAcPLxs5+FK3S/LJmnkhXV+7+tUuHIpl6mDgfhWDUKaOFzq+0cbzS15EJXW6YYdlPdbrj24JYbkUQHxYSEvxnR2+OOtj5HVQnIaNEPf9s0KW8IMLys7aQKBgQDJTnpdVqtN4M2Zuj8bN8ew1x6LkrqLV8EtlxQbn+kLgzmqqj56v6NVXHaxZh3OaIBoHpAQGPR26UAOBcXT5k1PhQ92RAqXh8/dbV+iBrnBjubQawZ3TK2yjclZMuFCW+/tyB6dOwDi+cO/IfBAh5P+mWHOZOa9y+dL2KjVSzL1TQKBgQDDiq3rjrAlPjtcZcMit4r7p4mZtUI0tCJEzt8J/YK9AC+A+g8Lxz7IKCH1vscFhQNU5E65omatCAYpGo17AQ59hLtbC0f3PJXNQTGLfGYiFHPsmOTf4B9w0c6ge1LPPzbfAG+1fvQ+iaa+4d7yNek3OyuH7KiknUN3AKyiFAo06wKBgAP0BZUlqZGK856sOKcJLmO7pb7p773iyElj6SItvr7aIdzHIRj6AHQhr7cGIVm3VaY1y3B1fP+Ezxw3Ys4pfKUuIMKazXZyVVOs3S7qYOV7L+8x2tum5tZV0Hlu9Vt/QLPztR4zVW4fp4duXDB4OSDL1E7gTmO1yGIF7DLcGjEVAoGBAJFiDEk0v3YRPOVHq7umJylPuRiVEXJJ86ig/mdZGtkWyDrmsEUbkGwUmpsxiptp974oOPf/7ML9UkdBPKuVb4aXJw1b59fELcR7kjCY/v6bokzoqFJjOj0RYMUkq772yv8mPef9Se8tPNJy8OW4e3ra/VSD+ibZ3g0ebTvcFnKdAoGAIGHTlkKJmP8nyRNSDceBnwRvqLPt7AC32kSxTl9mqVgYn8Slz+L33dixiMOL8/T/+JY5XxX/iQepBL/wmuxy6O+QoLxoJBIux7qoyYHyoKuOTaXbVs79dREIh/GHf0uQKGQ2MB6FJAuKgzBKyJYjkCf9t/KA2Ja3gxlYnwHBpcw="; + // Challenge text + const CHALLENGE_TEXT: &str = + "_6e27e164-e45b-4ae1-8714-7545d36b6ed4@aliens12:9ef2ec2c-39d4-4e25-825e-0da05f6e0bb9"; + // Expected result of signing the challenge text + const CHALLENGE_RESULT: &str = "aTY5Pxod1hzv/9uL9FSqxbmmCT73vFEBRv4qA+k+d6U5hcglzYvAl1MJNY2eQLTFLoFIkx/3pgm0YkjI4aS1hBAyBmMIinGrPGbOuR3PebPqITLhNWdeWZamHrlKY8tjvARtb4k0gb2LgauzhNq3zzm5aS7EU7OYaRy22/fR5fCWXw+ZyFdRYhA9qlFcA7ksct3pJwHSvSlQb2R7YuzN210Xfii43yAgtncz4CUZRcxPL7AD4mUg7dSMu0RMVKIQKsecwhNfh7bgy1zFDGMpOP8DQJ8tJfQiut5u+0yAGM4O31FJ+F7/1pvR0pgr7/O0/4K+BdhdRWNVine335u6lg=="; + // Public encryption key base64 encoded and decrypted with self encryption key + const PUBLIC_ENCRYPTION_KEY: &str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgcEdJDDvEzAC92N0dKIvgGIh9ddJ4xccDm5QqdsdnaYXygzygjfWUzdKrEkrisQIQBRJwEQd50jths5Rg46f0fowOT2gg3OTpMo0GaQLQagZoYuMiUcsuho6ig3ahsdPq21vz1tTT92rbI+l7477tsG7y+w5jDbDF6kvKfLYs8Ga73Jbwm55yg3ibNJjsiLGa6bg+5Y9pxXxzggURKn+m5h77PDCgCiTd7zLb4L9vsRm6ijdpnuekVGIgqGZO6xUOEYOonmqDjlw8BQagu31Z5NlvhWoCQv1UUPaDOm34R2uUeWt1PWe/AUih02c3GdtIcUyqK8E1GkCHfhFD27CtwIDAQAB"; + + // ----- Tests ------ + #[test] + fn decode_base64_text_test() { + let actual = base64_decode(TEST_KEY_ENCODED); + assert_eq!(actual, TEST_KEY_DECODED); + } + + #[test] + fn encode_base64_text_test() { + let actual = base64_encode(&TEST_KEY_DECODED); + assert_eq!(actual, TEST_KEY_ENCODED); + } + + #[test] + fn construct_aes_key_test() { + // Arrange + let binding = String::from("Hello World!"); + let input = binding.as_bytes(); + let decoded_key = base64_decode(SELF_ENCRYPTION_KEY_ENCODED); + + // Act + let iv: [u8; 16] = [0x00; 16]; + let mut cipher = construct_aes_key(&decoded_key, &iv); + let mut output: Vec = vec![0; input.len()]; + cipher.process(input, &mut output); + + // Assert + assert_eq!(output, SELF_ENCRYPTION_DECRYPT_RESULT); + } + + #[test] + fn construct_rsa_private_key_test() { + // Arrange + let private_key = base64_decode(&PKAM_KEY_DECRYPTED_AND_ENCODED); + // Act + let _ = construct_rsa_private_key(&private_key); + // Assert it doesn't panic + } + + #[test] + fn construct_rsa_public_key_test() { + // Arrange + let public_key = base64_decode(&PUBLIC_ENCRYPTION_KEY); + // Act + let _ = construct_rsa_public_key(&public_key); + // Assert it doesn't panic + } + + #[test] + fn rsa_sign_test() { + // Arrange + let private_key = base64_decode(&PKAM_KEY_DECRYPTED_AND_ENCODED); + let rsa_key = construct_rsa_private_key(&private_key); + // Act + let decrypted = rsa_sign(rsa_key, CHALLENGE_TEXT.as_bytes()); + // Assert + assert_eq!(decrypted, CHALLENGE_RESULT); + } + + #[test] + fn create_new_aes_key_test() { + let key = create_new_aes_key(); + assert_eq!(key.len(), 32); + } + + #[test] + fn encrypt_with_public_key_test() { + let public_key = base64_decode(&PUBLIC_ENCRYPTION_KEY); + let public_key = construct_rsa_public_key(&public_key); + let _ = encrypt_with_public_key(&public_key, &TEST_KEY_DECODED); + // Assert it doesn't panic. + } +} diff --git a/src/at_client.rs b/src/at_client.rs index f0bc46b..2a6b2ad 100644 --- a/src/at_client.rs +++ b/src/at_client.rs @@ -1,63 +1,186 @@ +use log::info; + +use crate::at_chops::at_chops::{ + create_new_shared_symmetric_key, decrypt_data_with_shared_symmetric_key, decrypt_symmetric_key, + encrypt_data_with_public_key, encrypt_data_with_shared_symmetric_key, +}; +use crate::at_error::{AtError, Result}; use crate::at_secrets::AtSecrets; use crate::at_server_addr::AtServerAddr; -use native_tls::TlsConnector; -use std::io::{BufRead, BufReader, Write}; -use std::net::TcpStream; +use crate::at_sign::AtSign; +use crate::at_tls_client::TLSClient; +use crate::verbs::llookup::{LlookupVerb, LlookupVerbInputs}; +use crate::verbs::lookup::{LookupVerb, LookupVerbInputs}; +use crate::verbs::plookup::{PlookupVerb, PlookupVerbInputs}; +use crate::verbs::update::{UpdateVerb, UpdateVerbInputs}; +use crate::verbs::{from::FromVerb, from::FromVerbInputs, Verb}; pub struct AtClient { secrets: AtSecrets, - at_sign: String, - at_sign_server: AtServerAddr, + at_sign: AtSign, + tls_client: TLSClient, + namespace: String, } impl AtClient { - pub fn init(secrets: AtSecrets, at_sign: String) -> Self { - let server = get_at_sign_server_addr(&at_sign); - AtClient { + pub fn init(secrets: AtSecrets, at_sign: AtSign, namespace: &str) -> Result { + let server = get_at_sign_server_addr(&at_sign.get_at_sign())?; + let tls_client = match TLSClient::new(&server) { + Ok(res) => res, + Err(err) => return Err(AtError::new(err.to_string())), + }; + Ok(AtClient { secrets, at_sign, - at_sign_server: server, + tls_client, + namespace: namespace.to_string(), + }) + } + + pub fn send_data(&mut self, data: &str, receiver: AtSign, record_id: &str) -> Result<()> { + self.authenticate_with_at_server()?; + let response = LlookupVerb::execute( + &mut self.tls_client, + LlookupVerbInputs::new( + &self.at_sign, + &receiver, + "shared_key", + &self.secrets.encrypt_public_key, + ), + )?; + let symm_key: String; + if response.contains("error:AT0015-key not found") { + info!("Creating new symmetric key"); + // Create symm key + let new_key = create_new_shared_symmetric_key(); + symm_key = new_key.clone(); + // Save for our use + let encrypted_encoded_sym_key = + encrypt_data_with_public_key(&self.secrets.encrypt_public_key, &new_key); + let _ = UpdateVerb::execute( + &mut self.tls_client, + UpdateVerbInputs::new( + &self.at_sign, + "shared_key", + &encrypted_encoded_sym_key, + Some(&receiver.get_at_sign()), + None, + None, + ), + )?; + // and share with recipient + let recipient_public_key_encoded = PlookupVerb::execute( + &mut self.tls_client, + PlookupVerbInputs::new(&receiver, "publickey"), + )?; + let symm_key_encrypted_with_recipient_public_key = + encrypt_data_with_public_key(&recipient_public_key_encoded, &new_key); + info!( + "Encrypted symm key: {}", + symm_key_encrypted_with_recipient_public_key + ); + // Send data + let _ = UpdateVerb::execute( + &mut self.tls_client, + UpdateVerbInputs::new( + &self.at_sign, + "shared_key", + &symm_key_encrypted_with_recipient_public_key, + None, + Some(86400), + Some(&receiver), + ), + )?; + } else if response.contains("data") { + info!("Already have a copy of the key"); + // Decrypt symm key + let encrypted_symmetric_key = response.split(":").collect::>()[1]; + symm_key = + decrypt_symmetric_key(&encrypted_symmetric_key, &self.secrets.encrypt_private_key); + info!("Decrypted symmetric key: {}", symm_key); + } else { + return Err(AtError::new(String::from("Unknown response from server"))); } + // Send data encrypted with symm key + let encrypted_data_to_send = encrypt_data_with_shared_symmetric_key(&symm_key, data); + let _ = UpdateVerb::execute( + &mut self.tls_client, + UpdateVerbInputs::new( + &self.at_sign, + // TODO: Pass this in as an option somewhere + &record_id, + &encrypted_data_to_send, + Some(&self.namespace), + None, + Some(&receiver), + ), + ); + Ok(()) } - pub fn lookup(&self) { - println!("Connecting to at server"); - let connector = TlsConnector::new().unwrap(); - let stream = TcpStream::connect(&self.at_sign_server).unwrap(); - let mut stream = connector - .connect(&self.at_sign_server.host, stream) - .unwrap(); - stream - .write_all(format!("from:@{}\n", self.at_sign).as_bytes()) - .unwrap(); - let mut res = vec![]; - let mut reader = BufReader::new(&mut stream); - reader.read_until(b'\n', &mut res).unwrap(); - println!("Response: {}", String::from_utf8_lossy(&res)); + pub fn read_data(&mut self, from: AtSign, record_id: &str) -> Result<()> { + self.authenticate_with_at_server()?; + info!("Fetching data"); + // Fetch data + let response = LookupVerb::execute( + &mut self.tls_client, + LookupVerbInputs::new(&from, record_id, Some(&self.namespace)), + )?; + let encrypted_and_encoded_data = response.split(":").collect::>()[1]; + info!("Fetching symmetric key"); + // Fetch symm key + let response = LookupVerb::execute( + &mut self.tls_client, + LookupVerbInputs::new(&from, "shared_key", None), + )?; + info!("Decrypting symmetric key"); + let encrypted_and_encoded_symm_key = response.split(":").collect::>()[1]; + let symm_key = decrypt_symmetric_key( + &encrypted_and_encoded_symm_key, + &self.secrets.encrypt_private_key, + ); + info!("Decrypted symmetric key: {}", symm_key); + info!("Decrypting data"); + let encoded_data = + decrypt_data_with_shared_symmetric_key(&symm_key, &encrypted_and_encoded_data); + info!("Decrypted data: {}", encoded_data); + + Ok(()) } - fn authenticate_with_at_server(&self) {} + fn authenticate_with_at_server(&mut self) -> Result<()> { + let _ = FromVerb::execute( + &mut self.tls_client, + FromVerbInputs::new(&self.at_sign, &self.secrets.pkam_private_key), + ) + .expect("Failed to authenticate with at server"); + info!("Successfully authenticated with at server"); + Ok(()) + } } /// function to get the at sign server address -fn get_at_sign_server_addr(at_sign: &str) -> AtServerAddr { - println!("Getting at sign server address"); +fn get_at_sign_server_addr(at_sign: &str) -> Result { + info!("Getting at sign server address"); + + let at_server_addr = AtServerAddr::new(String::from("root.atsign.org"), 64); + let mut tls_client = match TLSClient::new(&at_server_addr) { + Ok(res) => res, + Err(_) => return Err(AtError::new(String::from("Unable to connect to at server"))), + }; - let connector = TlsConnector::new().unwrap(); + tls_client.send(format!("{}\n", at_sign))?; + let res = tls_client.read_line()?; + tls_client.close()?; + + if res == "@null" { + return Err(AtError::new(String::from("Unable to find at sign"))); + } - let stream = TcpStream::connect("root.atsign.org:64").unwrap(); - let mut stream = connector.connect("root.atsign.org", stream).unwrap(); + // Removing the first letter from the response as it contains "@" + let addr = res[1..].to_string(); + info!("At server address: {}", &addr); - stream - .write_all(format!("{}\n", at_sign).as_bytes()) - .unwrap(); - let mut res = vec![]; - let mut reader = BufReader::new(&mut stream); - reader.read_until(b'\n', &mut res).unwrap(); - // Removing the first byte from the response as it contains "@" - let addr_u8 = &res[1..res.len()]; - let addr = String::from_utf8_lossy(&addr_u8).to_string(); - println!("At server address: {}", &addr); // Trimming to remove the newline character let addr = addr.trim(); let addr = addr.split(":").collect::>(); @@ -65,5 +188,5 @@ fn get_at_sign_server_addr(at_sign: &str) -> AtServerAddr { let port = addr[1] .parse::() .expect("Unable to parse port to a u16"); - AtServerAddr::new(host, port) + Ok(AtServerAddr::new(host, port)) } diff --git a/src/at_error.rs b/src/at_error.rs new file mode 100644 index 0000000..0643d83 --- /dev/null +++ b/src/at_error.rs @@ -0,0 +1,33 @@ +use std::fmt; +use std::io; +use std::result; + +pub type Result = result::Result; +pub type Error = AtError; + +#[derive(Debug)] +pub struct AtError { + pub message: String, +} + +impl AtError { + pub fn new(message: String) -> AtError { + AtError { message } + } +} + +impl fmt::Display for AtError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "AtError: {}", self.message) + } +} + +impl std::error::Error for AtError {} + +impl From for AtError { + fn from(error: io::Error) -> Self { + AtError { + message: error.to_string(), + } + } +} diff --git a/src/at_secrets.rs b/src/at_secrets.rs index 4267e31..8389b42 100644 --- a/src/at_secrets.rs +++ b/src/at_secrets.rs @@ -1,51 +1,72 @@ +use crate::at_chops::at_chops::{decode_self_encryption_key, decrypt_private_key}; +use crate::at_error::Result; +use log::info; use serde_json::{from_str, Value}; /// Struct to store all the secrets associated with an AtSign account. #[derive(Debug)] pub struct AtSecrets { - /// info - pub aes_pkam_public_key: String, - pub aes_pkam_private_key: String, - pub aes_encrypt_public_key: String, - pub aes_encrypt_private_key: String, + pub pkam_public_key: String, + pub pkam_private_key: String, + pub encrypt_public_key: String, + pub encrypt_private_key: String, pub self_encryption_key: String, } impl AtSecrets { - pub fn new( - aes_pkam_public_key: String, - aes_pkam_private_key: String, - aes_encrypt_public_key: String, - aes_encrypt_private_key: String, - aes_self_encrypt_key: String, + fn new( + pkam_public_key: String, + pkam_private_key: String, + encrypt_public_key: String, + encrypt_private_key: String, + self_encryption_key: String, ) -> AtSecrets { AtSecrets { - aes_pkam_public_key, - aes_pkam_private_key, - aes_encrypt_public_key, - aes_encrypt_private_key, - self_encryption_key: aes_self_encrypt_key, + pkam_public_key, + pkam_private_key, + encrypt_public_key, + encrypt_private_key, + self_encryption_key, } } /// Create AtSecrets from a JSON string which is found inside the `.atKeys` file associated - /// with all AtSign accounts. - // TODO: This should really return a Result instead of panicking. - pub fn from_data(input: &str) -> AtSecrets { + /// with all atSign accounts. + pub fn from_file(input: &str) -> Result { + // Get the info from the file let v: Value = from_str(input).unwrap(); + info!("Extracting keys from file"); + + // Get the keys let aes_pkam_public_key = v["aesPkamPublicKey"].as_str().unwrap().to_owned(); let aes_pkam_private_key = v["aesPkamPrivateKey"].as_str().unwrap().to_owned(); let aes_encrypt_public_key = v["aesEncryptPublicKey"].as_str().unwrap().to_owned(); let aes_encrypt_private_key = v["aesEncryptPrivateKey"].as_str().unwrap().to_owned(); let aes_self_encrypt_key = v["selfEncryptionKey"].as_str().unwrap().to_owned(); - AtSecrets::new( - aes_pkam_public_key, - aes_pkam_private_key, - aes_encrypt_public_key, - aes_encrypt_private_key, + info!("Decoding keys"); + // Decode the self encrypt key from base64 + let decoded_self_encrypted_key = decode_self_encryption_key(&aes_self_encrypt_key); + + // Use the key to decrypt all the other private keys + let pkam_public_key = + decrypt_private_key(&aes_pkam_public_key, &decoded_self_encrypted_key); + let pkam_private_key = + decrypt_private_key(&aes_pkam_private_key, &decoded_self_encrypted_key); + let encrypt_public_key = + decrypt_private_key(&aes_encrypt_public_key, &decoded_self_encrypted_key); + let encrypt_private_key = + decrypt_private_key(&aes_encrypt_private_key, &decoded_self_encrypted_key); + + info!("Keys decoded and decrypted"); + + Ok(AtSecrets::new( + pkam_public_key, + pkam_private_key, + encrypt_public_key, + encrypt_private_key, aes_self_encrypt_key, - ) + )) } } diff --git a/src/at_sign.rs b/src/at_sign.rs new file mode 100644 index 0000000..cc00cb7 --- /dev/null +++ b/src/at_sign.rs @@ -0,0 +1,30 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct AtSign { + /// The atSign of the client device. Without the `@` prefix. + at_sign: String, +} + +impl AtSign { + /// Create a new atSign. `at_sign` should be a string without the `@` prefix. + pub fn new(at_sign: String) -> AtSign { + AtSign { at_sign } + } + + /// Get the name of the atSign without the `@` prefix. + pub fn get_at_sign(&self) -> String { + self.at_sign.to_owned() + } + + /// Get the name of the atSign with the `@` prefix. + pub fn get_at_sign_with_prefix(&self) -> String { + format!("@{}", self.at_sign) + } +} + +impl Display for AtSign { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "@{}", self.at_sign) + } +} diff --git a/src/at_tls_client.rs b/src/at_tls_client.rs index 7e7ac80..540746b 100644 --- a/src/at_tls_client.rs +++ b/src/at_tls_client.rs @@ -1,9 +1,10 @@ -// What does the API for this look like? -// I probably want to include the traits for reader, writer, buff reader, buff writer -// I also probably want to include a trait as for the API itself - -use std::net::TcpStream; +use std::{ + error::Error, + io::{BufRead, BufReader, Write}, + net::TcpStream, +}; +use log::info; use native_tls::{TlsConnector, TlsStream}; use crate::at_server_addr::AtServerAddr; @@ -13,11 +14,31 @@ pub struct TLSClient { } impl TLSClient { - pub fn new(at_server_addr: &AtServerAddr) -> Self { - let connector = TlsConnector::new().unwrap(); + pub fn new(at_server_addr: &AtServerAddr) -> Result> { + let connector = TlsConnector::new()?; + let stream = TcpStream::connect(&at_server_addr)?; + let stream = connector.connect(&at_server_addr.host, stream)?; + info!("Connected to {:?}", &at_server_addr); + Ok(TLSClient { stream }) + } + + pub fn send(&mut self, data: String) -> std::io::Result<()> { + info!("Sending: {}", data.trim()); + self.stream.write_all(data.as_bytes()) + } + + /// Reads a line for the stream and converts it to a String which is trimmed. + pub fn read_line(&mut self) -> std::io::Result { + let mut res = vec![]; + let mut reader = BufReader::new(&mut self.stream); + reader.read_until(b'\n', &mut res)?; + let data = String::from_utf8_lossy(&res).trim().to_owned(); + info!("Received: {}", data); + Ok(data) + } - let stream = TcpStream::connect(&at_server_addr).unwrap(); - let stream = connector.connect(&at_server_addr.host, stream).unwrap(); - TLSClient { stream } + pub fn close(&mut self) -> std::io::Result<()> { + info!("Closing connection"); + self.stream.shutdown() } } diff --git a/src/bin/main.rs b/src/bin/main.rs deleted file mode 100644 index 3e4ad9a..0000000 --- a/src/bin/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -use at_rust::at_client::AtClient; -use at_rust::at_secrets::AtSecrets; -use std::fs::File; -use std::io::Read; -use std::{env, println}; - -fn main() { - // Read the contents of the file into a string. - let args: Vec = env::args().collect(); - let filename = args[1].clone(); - - println!("Reading file"); - let mut file = File::open(filename).unwrap(); - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - - println!("Creating AtSecrets"); - let secrets = AtSecrets::from_data(&contents); - - let at_client = AtClient::init(secrets, "aliens12".to_owned()); - at_client.lookup(); -} diff --git a/src/lib.rs b/src/lib.rs index 74a8ef4..d6076c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,8 @@ +pub mod at_chops; pub mod at_client; +pub mod at_error; pub mod at_secrets; mod at_server_addr; +pub mod at_sign; mod at_tls_client; -mod lookup; +pub mod verbs; diff --git a/src/lookup/lookup_data.rs b/src/lookup/lookup_data.rs deleted file mode 100644 index b6bf5f9..0000000 --- a/src/lookup/lookup_data.rs +++ /dev/null @@ -1,16 +0,0 @@ -struct LookupData { - pub at_sign: String, -} - -enum AtVerbOptions { - From, - Cram, - Pkam, - Pol, - Scan, - Update, - Lookup, - Llookup, - Plookup, - Notify, -} diff --git a/src/lookup/mod.rs b/src/lookup/mod.rs deleted file mode 100644 index ae43a33..0000000 --- a/src/lookup/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod lookup_data; -pub mod secure_socket; diff --git a/src/lookup/secure_socket.rs b/src/lookup/secure_socket.rs deleted file mode 100644 index ffb1ec6..0000000 --- a/src/lookup/secure_socket.rs +++ /dev/null @@ -1,120 +0,0 @@ -// use std::io::{self, BufRead, Write}; -// use std::io::{BufReader, Read}; -// use std::net::TcpStream; -// use std::println; -// use std::sync::Arc; -// -// use rustls::ClientConnection; -// -// pub struct TLSClient { -// socket: TcpStream, -// tls_conn: ClientConnection, -// } -// -// impl TLSClient { -// /// Creates a new TLS connection. -// pub fn new(host: &str, port: u16) -> Self { -// let mut root_store = rustls::RootCertStore::empty(); -// root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { -// rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( -// ta.subject, -// ta.spki, -// ta.name_constraints, -// ) -// })); -// let config = rustls::ClientConfig::builder() -// .with_safe_defaults() -// .with_root_certificates(root_store) -// .with_no_client_auth(); -// let rc_config = Arc::new(config); -// -// // Create a TLS client. -// let root_domain = host.try_into().unwrap(); -// let client = rustls::ClientConnection::new(rc_config, root_domain).unwrap(); -// let socket = match TcpStream::connect(format!("{}:{}", host, port.to_string())) { -// Ok(s) => { -// println!("Connected to root server"); -// s -// } -// Err(_) => panic!("Could not connect to root server"), -// }; -// // let tls = rustls::Stream::new(&mut client, &mut socket); -// -// TLSClient { -// socket, -// tls_conn: client, -// } -// } -// -// pub fn read(&mut self) { -// println!("Reading from socket"); -// let can_read = self.tls_conn.wants_read(); -// println!("Can read: {}", can_read); -// let can_write = self.tls_conn.wants_write(); -// println!("Can write: {}", can_write); -// // let mut buf_reader = BufReader::new(self.tls_conn.reader()); -// // match self.tls_conn.read_tls(&mut self.socket) { -// // Err(_) => panic!("Could not read from socket"), -// // Ok(0) => panic!("Socket closed"), -// // Ok(_) => { -// // println!("Read from socket"); -// // } -// // } -// -// println!("Processing new packets"); -// let io_state = match self.tls_conn.process_new_packets() { -// Err(_) => panic!("Could not process new packets"), -// Ok(data) => data, -// }; -// -// println!("Reading plaintext"); -// if io_state.plaintext_bytes_to_read() > 0 { -// let mut plaintext = Vec::new(); -// plaintext.resize(io_state.plaintext_bytes_to_read(), 0u8); -// self.tls_conn.reader().read_exact(&mut plaintext).unwrap(); -// io::stdout().write_all(&plaintext).unwrap(); -// } else { -// println!("No plaintext to read"); -// } -// } -// -// // TODO: Pass data into this function -// pub fn write(&mut self) { -// let result = self.tls_conn.writer().write_all(b"aliens12\n").unwrap(); -// println!("Wrote to socket successfully"); -// } -// } -// -// fn get_at_sign_address(host: &str, port: u16) { -// // Initialise the TLS config. -// let mut root_store = rustls::RootCertStore::empty(); -// root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { -// rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( -// ta.subject, -// ta.spki, -// ta.name_constraints, -// ) -// })); -// let config = rustls::ClientConfig::builder() -// .with_safe_defaults() -// .with_root_certificates(root_store) -// .with_no_client_auth(); -// let rc_config = Arc::new(config); -// -// // Create a TLS client. -// let root_domain = "root.atsign.org".try_into().unwrap(); -// let mut client = rustls::ClientConnection::new(rc_config, root_domain).unwrap(); -// let mut socket = match TcpStream::connect("root.atsign.org:64") { -// Ok(s) => { -// println!("Connected to root server"); -// s -// } -// Err(_) => panic!("Could not connect to root server"), -// }; -// let mut tls = rustls::Stream::new(&mut client, &mut socket); -// tls.write_all("aliens12\n".as_bytes()).unwrap(); -// let mut challenge = String::new(); -// let mut reader = BufReader::new(&mut tls); -// reader.read_line(&mut challenge).unwrap(); -// println!("Challenge: {}", challenge); -// } diff --git a/src/verbs/from.rs b/src/verbs/from.rs new file mode 100644 index 0000000..05e9baf --- /dev/null +++ b/src/verbs/from.rs @@ -0,0 +1,39 @@ +use log::info; + +use crate::at_chops::at_chops::sign_challenge; + +use super::{prelude::*, Verb}; + +pub struct FromVerbInputs<'a> { + pub at_sign: &'a AtSign, + pub priv_pkam: &'a str, +} + +impl<'a> FromVerbInputs<'a> { + pub fn new(at_sign: &'a AtSign, priv_pkam: &'a str) -> Self { + Self { at_sign, priv_pkam } + } +} + +pub struct FromVerb {} + +impl<'a> Verb<'a> for FromVerb { + type Inputs = FromVerbInputs<'a>; + type Result = String; + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result { + info!("Starting PKAM authentication"); + tls_client.send(format!("from:{}\n", input.at_sign.get_at_sign()))?; + let response = tls_client.read_line()?; + + let (_, data) = response.split_at(6); + info!("Challenge: {}", data); + + let signed_challenge = sign_challenge(&data, input.priv_pkam); + + tls_client.send(format!("pkam:{}\n", signed_challenge))?; + let response = tls_client.read_line()?; + + Ok(response) + } +} diff --git a/src/verbs/llookup.rs b/src/verbs/llookup.rs new file mode 100644 index 0000000..6af4929 --- /dev/null +++ b/src/verbs/llookup.rs @@ -0,0 +1,46 @@ +use super::{prelude::*, Verb}; + +pub struct LlookupVerbInputs<'a> { + /// The atSign of the person who is looking up the key-value pair. + pub from_at_sign: &'a AtSign, + /// The atSign of the person who owns the key-value pair. + pub to_at_sign: &'a AtSign, + /// The identifier of the key-value pair to be looked up. + pub at_id: &'a str, + /// The decrypted public key. + pub public_key: &'a str, +} + +impl<'a> LlookupVerbInputs<'a> { + pub fn new( + from_at_sign: &'a AtSign, + to_at_sign: &'a AtSign, + at_id: &'a str, + public_key: &'a str, + ) -> Self { + Self { + from_at_sign, + to_at_sign, + at_id, + public_key, + } + } +} + +pub struct LlookupVerb {} + +impl<'a> Verb<'a> for LlookupVerb { + type Inputs = LlookupVerbInputs<'a>; + type Result = String; + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result { + tls_client.send(format!( + "llookup:{}.{}@{}\n", + input.at_id, + input.to_at_sign.get_at_sign(), + input.from_at_sign.get_at_sign() + ))?; + let response = tls_client.read_line()?; + Ok(response) + } +} diff --git a/src/verbs/lookup.rs b/src/verbs/lookup.rs new file mode 100644 index 0000000..c125ffd --- /dev/null +++ b/src/verbs/lookup.rs @@ -0,0 +1,40 @@ +use super::{prelude::*, Verb}; + +pub struct LookupVerbInputs<'a> { + /// The atSign of the person who owns the key-value pair. + pub to_at_sign: &'a AtSign, + /// The identifier of the key-value pair to be looked up. + pub at_id: &'a str, + /// The namespace of the data + pub namespace: Option<&'a str>, +} + +impl<'a> LookupVerbInputs<'a> { + pub fn new(to_at_sign: &'a AtSign, at_id: &'a str, namespace: Option<&'a str>) -> Self { + Self { + to_at_sign, + at_id, + namespace, + } + } +} + +pub struct LookupVerb {} + +impl<'a> Verb<'a> for LookupVerb { + type Inputs = LookupVerbInputs<'a>; + type Result = String; + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result { + let mut send_string = String::from("lookup"); + send_string.push_str(&format!(":{}", input.at_id)); + if let Some(namespace) = input.namespace { + send_string.push_str(&format!(".{}", namespace)); + } + send_string.push_str(&format!("@{}", input.to_at_sign.get_at_sign())); + send_string.push_str(&format!("\n")); + tls_client.send(send_string)?; + let response = tls_client.read_line()?; + Ok(response) + } +} diff --git a/src/verbs/mod.rs b/src/verbs/mod.rs new file mode 100644 index 0000000..a309669 --- /dev/null +++ b/src/verbs/mod.rs @@ -0,0 +1,21 @@ +pub mod from; +pub mod llookup; +pub mod lookup; +pub mod plookup; +pub mod update; + +mod prelude { + pub use crate::at_error::{AtError, Error, Result}; + pub use crate::at_sign::AtSign; + pub use crate::at_tls_client::TLSClient; + pub use rsa::{RsaPrivateKey, RsaPublicKey}; +} + +use prelude::*; + +pub trait Verb<'a> { + type Inputs: 'a; + type Result; + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result; +} diff --git a/src/verbs/plookup.rs b/src/verbs/plookup.rs new file mode 100644 index 0000000..86fa379 --- /dev/null +++ b/src/verbs/plookup.rs @@ -0,0 +1,34 @@ +use super::{prelude::*, Verb}; + +pub struct PlookupVerbInputs<'a> { + /// The atSign of the person who owns the key-value pair. + pub to_at_sign: &'a AtSign, + /// The identifier of the key-value pair to be looked up. + pub at_id: &'a str, +} + +impl<'a> PlookupVerbInputs<'a> { + pub fn new(to_at_sign: &'a AtSign, at_id: &'a str) -> Self { + Self { to_at_sign, at_id } + } +} + +pub struct PlookupVerb {} + +impl<'a> Verb<'a> for PlookupVerb { + type Inputs = PlookupVerbInputs<'a>; + type Result = String; + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result { + let send_data = format!( + "plookup:{}@{}\n", + input.at_id, + input.to_at_sign.get_at_sign() + ); + tls_client.send(send_data)?; + let response = tls_client.read_line()?; + let (_, data) = response.split_at(5); + + Ok(data.to_owned()) + } +} diff --git a/src/verbs/update.rs b/src/verbs/update.rs new file mode 100644 index 0000000..cc7078b --- /dev/null +++ b/src/verbs/update.rs @@ -0,0 +1,63 @@ +use super::{prelude::*, Verb}; + +pub struct UpdateVerbInputs<'a> { + /// The atSign of the person who is looking up the key-value pair. + pub from_at_sign: &'a AtSign, + /// The identifier of the key-value pair to be looked up. + pub at_id: &'a str, + /// Data that is base64 encoded. + pub data: &'a str, + /// Namespace + pub namespace: Option<&'a str>, + /// Time to refresh + pub ttr: Option, + /// Recipient's atSign + pub to_at_sign: Option<&'a AtSign>, +} + +impl<'a> UpdateVerbInputs<'a> { + pub fn new( + from_at_sign: &'a AtSign, + at_id: &'a str, + data: &'a str, + namespace: Option<&'a str>, + ttr: Option, + to_at_sign: Option<&'a AtSign>, + ) -> Self { + Self { + from_at_sign, + at_id, + data, + namespace, + ttr, + to_at_sign, + } + } +} + +pub struct UpdateVerb {} + +impl<'a> Verb<'a> for UpdateVerb { + type Inputs = UpdateVerbInputs<'a>; + type Result = (); + + fn execute(tls_client: &mut TLSClient, input: Self::Inputs) -> Result { + let mut send_string = String::from("update"); + if let Some(ttr) = input.ttr { + send_string.push_str(&format!(":ttr:{}", ttr)); + } + if let Some(recipient) = input.to_at_sign { + send_string.push_str(&format!(":@{}", recipient.get_at_sign())); + } + send_string.push_str(&format!(":{}", input.at_id)); + if let Some(namespace) = input.namespace { + send_string.push_str(&format!(".{}", namespace)); + } + send_string.push_str(&format!("@{}", input.from_at_sign.get_at_sign())); + send_string.push_str(&format!(" {}\n", input.data)); + tls_client.send(send_string)?; + let _ = tls_client.read_line()?; + // TODO: Check response is formatted like "data: " and return Error if not. + Ok(()) + } +}