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(())
+ }
+}