From 47d90fa27bf7641ec1b1ab175faf0729ac95f21e Mon Sep 17 00:00:00 2001 From: Serguei Oleinik Date: Thu, 8 Sep 2022 22:55:56 +0300 Subject: [PATCH 01/38] HashiCorp feature --- .gitignore | 4 + Cargo.lock | 543 +++++++++++++++--- Cargo.toml | 9 + README.hashicorp.md | 167 ++++++ src/commands.rs | 13 + src/commands/hashicorp.rs | 37 ++ src/commands/hashicorp/test.rs | 78 +++ src/commands/hashicorp/upload.rs | 299 ++++++++++ src/commands/init/config_builder.rs | 10 + .../init/templates/keyring/hashicorp.toml | 9 + src/config/provider.rs | 9 + src/config/provider/hashicorp.rs | 21 + src/keyring.rs | 3 + src/keyring/providers.rs | 10 + src/keyring/providers/hashicorp.rs | 80 +++ src/keyring/providers/hashicorp/client.rs | 490 ++++++++++++++++ src/keyring/providers/hashicorp/error.rs | 53 ++ src/keyring/providers/hashicorp/signer.rs | 28 + src/lib.rs | 5 +- tests/cli/init.rs | 82 +-- tests/integration.rs | 532 ++++++++++++++--- tmkms-hashicorp.toml | 41 ++ 22 files changed, 2338 insertions(+), 185 deletions(-) create mode 100644 README.hashicorp.md create mode 100644 src/commands/hashicorp.rs create mode 100644 src/commands/hashicorp/test.rs create mode 100644 src/commands/hashicorp/upload.rs create mode 100644 src/commands/init/templates/keyring/hashicorp.toml create mode 100644 src/config/provider/hashicorp.rs create mode 100644 src/keyring/providers/hashicorp.rs create mode 100644 src/keyring/providers/hashicorp/client.rs create mode 100644 src/keyring/providers/hashicorp/error.rs create mode 100644 src/keyring/providers/hashicorp/signer.rs create mode 100644 tmkms-hashicorp.toml diff --git a/.gitignore b/.gitignore index e134f360..02a38871 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ tmkms.toml *.swp \.idea/ +/state +/secrets +/.vscode +**/*.bin diff --git a/Cargo.lock b/Cargo.lock index 9f42bda6..f3ebea45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,8 +73,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if 1.0.0", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.11", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-kw" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" +dependencies = [ + "aes", ] [[package]] @@ -168,6 +191,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -203,9 +236,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.7" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -471,11 +504,27 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "const-oid" -version = "0.9.6" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "const-oid" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "core-foundation" @@ -533,6 +582,25 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -572,7 +640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.11", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -616,19 +684,30 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", + "crypto-bigint 0.3.2", + "pem-rfc7468", +] + +[[package]] +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "const-oid", + "const-oid 0.9.5", "zeroize", ] [[package]] name = "deranged" -version = "0.3.11" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", "serde", @@ -650,7 +729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", + "const-oid 0.9.5", "crypto-common", "subtle", ] @@ -661,12 +740,12 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.8", "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", - "spki", + "spki 0.7.3", ] [[package]] @@ -675,7 +754,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", + "pkcs8 0.10.2", "signature", ] @@ -722,18 +801,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", - "crypto-bigint", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff", "generic-array", "group", - "pkcs8", - "rand_core", + "pkcs8 0.10.2", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -820,7 +908,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ - "spin", + "spin 0.9.8", ] [[package]] @@ -928,8 +1016,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -954,7 +1044,28 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", ] [[package]] @@ -976,9 +1087,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -999,13 +1110,30 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +[[package]] +name = "hashicorp_vault" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d829f72fad7263fada3eb758f6ee9af60132c425e470c6a4af45a6cafab16d" +dependencies = [ + "base64 0.13.1", + "chrono", + "log", + "quick-error 2.0.1", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "url 2.5.0", +] + [[package]] name = "headers" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.7", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -1035,6 +1163,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hex" version = "0.3.2" @@ -1075,9 +1209,9 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ "hmac", ] @@ -1104,9 +1238,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -1142,6 +1276,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -1225,8 +1360,14 @@ dependencies = [ ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" @@ -1241,9 +1382,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1274,14 +1415,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.11", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "ledger" @@ -1296,7 +1440,7 @@ dependencies = [ "lazy_static", "libc", "nix", - "quick-error", + "quick-error 1.2.3", ] [[package]] @@ -1356,9 +1500,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "memchr" -version = "2.7.4" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "merlin" @@ -1399,6 +1543,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mockito" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9" +dependencies = [ + "assert-json-diff", + "colored", + "httparse", + "lazy_static", + "log", + "rand", + "regex", + "serde_json", + "serde_urlencoded", + "similar", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1440,8 +1602,25 @@ dependencies = [ ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-derive" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" @@ -1456,6 +1635,27 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1463,13 +1663,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", ] [[package]] name = "object" -version = "0.32.2" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1582,6 +1793,15 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1606,14 +1826,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78f66c04ccc83dd4486fd46c33896f4e17b24a7a3a6400dedc48ed0ddd72320" +dependencies = [ + "der 0.5.1", + "pkcs8 0.8.0", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.8", + "spki 0.7.3", ] [[package]] @@ -1628,9 +1870,21 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.11", + "opaque-debug", + "universal-hash 0.4.1", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures 0.2.11", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] @@ -1740,7 +1994,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -1787,6 +2050,44 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding 2.3.1", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url 2.5.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -1817,6 +2118,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" +dependencies = [ + "byteorder", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8 0.8.0", + "rand_core 0.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rtoolbox" version = "0.0.2" @@ -1867,9 +2188,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" @@ -1912,9 +2233,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.8", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -1954,9 +2275,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] @@ -1972,9 +2293,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] @@ -2004,9 +2325,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", @@ -2019,6 +2340,9 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ + "form_urlencoded", + "itoa", + "ryu", "serde", ] @@ -2029,7 +2353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -2041,7 +2365,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.11", "digest 0.9.0", "opaque-debug", ] @@ -2053,7 +2377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -2094,6 +2418,12 @@ dependencies = [ "syn 2.0.85", ] +[[package]] +name = "similar" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" + [[package]] name = "simple-hyper-client" version = "0.1.3" @@ -2126,7 +2456,17 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ @@ -2134,6 +2474,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -2143,6 +2489,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", +] + [[package]] name = "spki" version = "0.7.3" @@ -2150,7 +2506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.8", ] [[package]] @@ -2324,9 +2680,9 @@ checksum = "21d83875eb543e63a6f4903620af14796c41db1e457478bb7feabf3eac88b09f" [[package]] name = "termcolor" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -2363,9 +2719,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", @@ -2384,9 +2740,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "num-conv", "time-core", @@ -2424,6 +2780,9 @@ name = "tmkms" version = "0.14.0" dependencies = [ "abscissa_core", + "aes-gcm", + "aes-kw", + "base64 0.13.1", "byteorder", "bytes", "chrono", @@ -2433,17 +2792,20 @@ dependencies = [ "ed25519-consensus", "elliptic-curve", "eyre", - "getrandom", + "getrandom 0.2.11", + "hashicorp_vault", "hkd32", "hkdf", "k256", "ledger", + "mockito", "once_cell", "prost", "prost-derive", "rand", "rand_core", "rpassword", + "rsa", "sdkms", "serde", "serde_json", @@ -2474,8 +2836,9 @@ dependencies = [ "bytes", "libc", "mio", + "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.52.0", ] @@ -2648,9 +3011,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "typenum" @@ -2695,6 +3058,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "1.7.2" @@ -2730,7 +3103,7 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ - "getrandom", + "getrandom 0.2.11", "serde", ] @@ -2808,6 +3181,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -2837,6 +3222,16 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2870,11 +3265,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8626e58e..9ebcb761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,16 +54,25 @@ wait-timeout = "0.2" yubihsm = { version = "0.42", features = ["secp256k1", "setup", "usb"], optional = true } zeroize = "1" +hashicorp_vault = { version = "2.1.0", optional = true } +base64 = { version = "0.13.0", optional = true} +aes-kw = { version = "0.2.1", features = ["std"], optional = true} +rsa = { version = "0.6.1", default = true, optional = true} +rand = { version = "0.8", optional = true} +aes-gcm = { version = "0.10.1", optional = true} + [dev-dependencies] abscissa_core = { version = "0.7", features = ["testing"] } byteorder = "1" rand = "0.8" +mockito = "0.31.0" [features] softsign = [] yubihsm-mock = ["yubihsm/mockhsm"] yubihsm-server = ["yubihsm/http-server", "rpassword"] fortanixdsm = ["elliptic-curve", "sdkms", "url", "uuid"] +hashicorp = ["hashicorp_vault", "base64", "aes-kw", "rsa", "rand", "aes-gcm"] # Enable integer overflow checks in release builds for security reasons [profile.release] diff --git a/README.hashicorp.md b/README.hashicorp.md new file mode 100644 index 00000000..b4a212a2 --- /dev/null +++ b/README.hashicorp.md @@ -0,0 +1,167 @@ +# HashiCorp Vault + TMKMS + +HashiCorp Vault's `transit` engine mainly designed for data-in-transit encryption, it also provides additional features (sign and verify data, generate hashes and HMACs of data, and act as a source of random bytes). + +This implementation will use Vault as `signer as a service` where private key will not ever leave Vault + + +This document describes how to configure HashiCorp Vault for production use with Tendermint KMS. + +## Setting up Vault for `signer-as-service` +Start vault instance as per Hashicorp tutorial + +following script sets up Vault's configuration. Script designed for single chain signing... Extend it with additional keys+policies for additional chains. These are steps for `admin` +``` +#!/bin/bash +#login with root token +vault login + +echo "\nenabling transit engine..." +vault secrets enable transit +echo "\nenabling transit's engine sign path..." +vault secrets enable -path=sign transit + +echo "\ncreating cosmoshub signing key..." +vault write transit/keys/cosmoshub-sign-key type=ed25519 + +echo "\ncreating policy..." +cat < vault write transit/sign/<...sign key...> plaintext=$(base64 <<< "some-data") +``` + + +## Compiling `tmkms` with HashiCorp Vault support + +Refer the main README.md for compiling `tmkms` +from source code. You will need the prerequisities mentioned as indicated above. + +There are two ways to install `tmkms` with HashiCorp Vault, you need to pass the `--features=hashicorp` parameter to cargo. + +### Compiling from source code (via git) + +`tmkms` can be compiled directly from the git repository source code using the +following method. + +``` +$ git clone https://github.com/iqlusioninc/tmkms.git && cd tmkms +[...] +$ cargo build --release --features=hashicorp +``` + +If successful, this will produce a `tmkms` executable located at +`./target/release/tmkms` + +### Installing with the `cargo install` command + +With Rust (1.40+) installed, you can install tmkms with the following: + +``` +cargo install tmkms --features=hashicorp +``` + +Or to install a specific version (recommended): + +``` +cargo install tmkms --features=hashicorp --version=0.4.0 +``` + +This command installs `tmkms` directly from packages hosted on Rust's +[crates.io] service. Package authenticity is verified via the +[crates.io index] (itself a git repository) and by SHA-256 digests of +released artifacts. + +However, if newer dependencies are available, it may use newer versions +besides the ones which are "locked" in the source code repository. We +cannot verify those dependencies do not contain malicious code. If you would +like to ensure the dependencies in use are identical to the main repository, +please build from source code instead. + + +to run +``` +cargo run --features=hashicorp -- -c /path/to/tmkms.toml +``` + +## Production HashiCorp Vault setup + +`tmkms` contains support for HashiCorp Vault service, which enables tmkms to access the secure keys, stored in HashiCorp Vault's transit engine. This requires creation of the keys in Vault which can be done by referring to this [guide](https://www.vaultproject.io/docs/secrets/transit). Creating the key for signing and export should enable tmkms to use the keys on HashiCorp Vault. + +### Configuring `tmkms` for initial setup + +In order to perform setup, `tmkms` needs a configuration file which +contains the authentication details needed to authenticate to the HashiCorp Vault with an access token. + +This configuration should be placed in a file called: `tmkms.toml`. +You can specifty the path to the config with either `-c /path/to/tmkms.toml` or else tmkms will look in the current working directory for the same file. + +example: +```toml +[[providers.hashicorp]] +chain_id = "<...chain id...>" +api_endpoint= "https://<...host...>:8200" +access_token="<...token...>" +pk_key_name="<...ed25519 signing key...>" +``` + +You can [get](https://learn.hashicorp.com/tutorials/vault/tokens) the access token from the HashiCorp Vault. + +### Generating keys in HashiCorp Vault, transit engine +1. Enable transit engine +```bash +vault secrets enable transit +``` +2. Enable sign path on transit engine +```bash +vault secrets enable -path=sign transit +``` +3. Create a key +```bash +vault write transit/keys/<..key-name...> type=ed25519 +``` +4. Create a policy for the key + ```bash +vault policy write tmkms-transit-sign-policy - +path "transit/sign/<...key name...>" { + capabilities = [ "update"] +} +#used by HashiCorp API to verify connectivity on startup +path "auth/token/lookup-self" { + capabilities = [ "read" ] +} +``` +5. Create access token for the policy above +```bash +vault token create \ + -policy=tmkms-transit-sign-policy \ + -no-default-policy \ + -non-interactive \ + -renewable=false \ + -period=0 +``` +6. To import an existing tendermint key (this is TODO). +``` diff --git a/src/commands.rs b/src/commands.rs index cff0c176..4b60b474 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,5 +1,7 @@ //! Subcommands of the `tmkms` command-line application +#[cfg(feature = "hashicorp")] +pub mod hashicorp; pub mod init; #[cfg(feature = "ledger")] pub mod ledger; @@ -10,6 +12,8 @@ pub mod version; #[cfg(feature = "yubihsm")] pub mod yubihsm; +#[cfg(feature = "hashicorp")] +pub use self::hashicorp::HashicorpCommand; #[cfg(feature = "ledger")] pub use self::ledger::LedgerCommand; #[cfg(feature = "softsign")] @@ -50,6 +54,11 @@ pub enum KmsCommand { #[cfg(feature = "yubihsm")] #[clap(subcommand)] Yubihsm(YubihsmCommand), + + /// subcommands for HashiCorp + #[cfg(feature = "hashicorp")] + #[clap(subcommand)] + Hashicorp(HashicorpCommand), } impl KmsCommand { @@ -59,6 +68,8 @@ impl KmsCommand { KmsCommand::Start(run) => run.verbose, #[cfg(feature = "yubihsm")] KmsCommand::Yubihsm(yubihsm) => yubihsm.verbose(), + #[cfg(feature = "hashicorp")] + KmsCommand::Hashicorp(hashicorp) => hashicorp.verbose(), _ => false, } } @@ -74,6 +85,8 @@ impl Configurable for KmsCommand { KmsCommand::Yubihsm(yubihsm) => yubihsm.config_path(), #[cfg(feature = "ledger")] KmsCommand::Ledger(ledger) => ledger.config_path(), + #[cfg(feature = "hashicorp")] + KmsCommand::Hashicorp(hashicorp) => hashicorp.config_path(), _ => return None, }; diff --git a/src/commands/hashicorp.rs b/src/commands/hashicorp.rs new file mode 100644 index 00000000..2345decc --- /dev/null +++ b/src/commands/hashicorp.rs @@ -0,0 +1,37 @@ +//! `tmkms hashicorp` CLI (sub)commands + +mod test; +mod upload; + +pub use self::test::TestCommand; +pub use self::upload::UploadCommand; + +use abscissa_core::{Command, Runnable}; +use clap::Subcommand; +use std::path::PathBuf; + +/// `hashicorp` subcommand +#[derive(Command, Debug, Runnable, Subcommand)] +pub enum HashicorpCommand { + /// perform a signing test + Test(TestCommand), + + /// upload priv/pub key + Upload(UploadCommand), +} + +impl HashicorpCommand { + pub(super) fn config_path(&self) -> Option<&PathBuf> { + match self { + HashicorpCommand::Test(init) => init.config.as_ref(), + HashicorpCommand::Upload(init) => init.config.as_ref(), + } + } + + pub(super) fn verbose(&self) -> bool { + match self { + HashicorpCommand::Test(test) => test.verbose, + HashicorpCommand::Upload(test) => test.verbose, + } + } +} diff --git a/src/commands/hashicorp/test.rs b/src/commands/hashicorp/test.rs new file mode 100644 index 00000000..7895a1f3 --- /dev/null +++ b/src/commands/hashicorp/test.rs @@ -0,0 +1,78 @@ +//! Test the Hashicorp is working by performing signatures successively + +use crate::prelude::*; +use abscissa_core::{Command, Runnable}; +use clap::Parser; +use signature::SignerMut; +use std::{path::PathBuf, process, time::Instant}; + +/// The `hashicorp test` subcommand +#[derive(Command, Debug, Default, Parser)] +pub struct TestCommand { + /// path to tmkms.toml + #[clap( + short = 'c', + long = "config", + value_name = "CONFIG", + help = "/path/to/tmkms.toml" + )] + pub config: Option, + /// enable verbose debug logging + #[clap(short = 'v', long = "verbose")] + pub verbose: bool, + + /// Ed25519 signing key ID in Hashicorp Vault + #[clap(help = "Vault's transit secret engine signing key")] + pk_name: String, + + ///test message + #[clap(help = "message to sign")] + test_messsage: String, +} + +impl Runnable for TestCommand { + /// Perform a signing test using the current TMKMS configuration + fn run(&self) { + if self.pk_name.is_empty() { + status_err!("pk_name cannot be empty!"); + process::exit(1); + } + + let config = APP.config(); + + let config = if let Some(c) = config + .providers + .hashicorp + .iter() + .find(|c| c.pk_name == self.pk_name) + { + c + } else { + status_err!("pk_name is not configured in provided \"tmkms.toml\"!"); + process::exit(1); + }; + + let started_at = Instant::now(); + + let app = crate::keyring::providers::hashicorp::client::TendermintValidatorApp::connect( + &config.api_endpoint, + &config.access_token, + &self.pk_name, + ) + .expect(&format!( + "Unable to connect to Vault at {}", + config.api_endpoint + )); + + let mut app = + crate::keyring::providers::hashicorp::signer::Ed25519HashiCorpAppSigner::new(app); + + let signature = app.try_sign(self.test_messsage.as_bytes()).unwrap(); + + println!( + "Elapsed:{} ms. Result: {:?}", + started_at.elapsed().as_millis(), + signature + ); + } +} diff --git a/src/commands/hashicorp/upload.rs b/src/commands/hashicorp/upload.rs new file mode 100644 index 00000000..f073710b --- /dev/null +++ b/src/commands/hashicorp/upload.rs @@ -0,0 +1,299 @@ +//! Test the Hashicorp is working by performing signatures successively + +use crate::{config::provider::hashicorp::HashiCorpConfig, prelude::*}; +use abscissa_core::{Command, Runnable}; +use aes_kw; +use clap::Parser; +use serde::Serialize; +use std::{path::PathBuf, process}; + +use crate::keyring::providers::hashicorp::{client, error}; +use rsa::{pkcs8::DecodePublicKey, PaddingScheme, PublicKey, RsaPublicKey}; + +///AES256 key length +const KEY_SIZE_AES256: usize = 32; //256 bits +///PKCS8 header +const PKCS8_HEADER: &[u8; 16] = b"\x30\x2e\x02\x01\x00\x30\x05\x06\x03\x2b\x65\x70\x04\x22\x04\x20"; + +/// The `hashicorp test` subcommand +#[derive(Command, Debug, Default, Parser)] +pub struct UploadCommand { + /// path to tmkms.toml + #[clap( + short = 'c', + long = "config", + value_name = "CONFIG", + help = "/path/to/tmkms.toml" + )] + pub config: Option, + + /// enable verbose debug logging + #[clap(short = 'v', long = "verbose")] + pub verbose: bool, + + ///key ID in Hashicorp Vault + #[clap(help = "Key ID")] + pk_name: String, + + /// base64 encoded key to upload + #[clap(long = "payload")] + pub payload: String, +} +///Import Secret Key Request +#[derive(Debug, Serialize)] +struct ImportRequest { + #[serde(default = "ed25519")] + r#type: String, + + ciphertext: String, +} + +impl Runnable for UploadCommand { + /// Perform a import using the current TMKMS configuration + fn run(&self) { + if self.pk_name.is_empty() { + status_err!("pk_name cannot be empty!"); + process::exit(1); + } + + let config = APP.config(); + + //finding key in config will point to correct Vault's URL + let config = if let Some(c) = config + .providers + .hashicorp + .iter() + .find(|c| c.pk_name == self.pk_name) + { + c + } else { + let cfg_path = if let Some(path) = self.config.as_ref() { + path.clone() + } else { + PathBuf::from("./tmkms.toml") + }; + status_err!( + "pk_name is not configured in provided \"{}\"!", + cfg_path.as_path().to_str().unwrap() + ); + process::exit(1); + }; + + self.upload(&config); + } +} + +impl UploadCommand { + fn upload(&self, config: &HashiCorpConfig) { + //https://www.vaultproject.io/docs/secrets/transit#bring-your-own-key-byok + //https://learn.hashicorp.com/tutorials/vault/eaas-transit + + //root token or token with enough admin rights + let vault_token = std::env::var("VAULT_TOKEN") + .expect("root token \"VAULT_TOKEN\" is not set (confg token is NOT used)!"); + + let ed25519_input_key = input_key(&self.payload) + .expect("secret: error converting \"key-to-upload\"[ed25519] with PKCS8 wrapping"); + + //create app instance + let app = client::TendermintValidatorApp::connect( + &config.api_endpoint, + &vault_token, + &self.pk_name, + ) + .expect(&format!( + "Unable to connect to Vault at {}", + config.api_endpoint + )); + + use aes_gcm::KeyInit; + let v_aes_key = aes_gcm::Aes256Gcm::generate_key(&mut aes_gcm::aead::OsRng); + debug_assert_eq!( + KEY_SIZE_AES256, + v_aes_key.len(), + "expected aes key length {}, actual:{}", + KEY_SIZE_AES256, + v_aes_key.len() + ); + + let mut aes_key = [0u8; KEY_SIZE_AES256]; + aes_key.copy_from_slice(&v_aes_key[..KEY_SIZE_AES256]); + + let kek = aes_kw::KekAes256::from(aes_key.clone()); + let wrapped_input_key = kek + .wrap_with_padding_vec(&ed25519_input_key) + .expect("input key wrapping error!"); + + let wrapping_key_pem = app + .wrapping_key_pem() + .expect("wrapping key error: fetching error!"); + + let pub_key = RsaPublicKey::from_public_key_pem(&wrapping_key_pem).unwrap(); + + //wrap AES256 into RSA4096 + let wrapped_aes = pub_key + .encrypt( + &mut rand_core::OsRng, + PaddingScheme::new_oaep::(), + &aes_key, + ) + .expect("failed to encrypt"); + + debug_assert_eq!(wrapped_aes.len(), 512); + let wrapped_aes: Vec = [wrapped_aes.as_slice(), wrapped_input_key.as_slice()].concat(); + + app.import_key( + &self.pk_name, + client::CreateKeyType::Ed25519, + &base64::encode(wrapped_aes), + ) + .expect("import key error!"); + } +} + +//https://docs.rs/ed25519/latest/ed25519/pkcs8/index.html +fn input_key(input_key: &str) -> Result, error::Error> { + let bytes = base64::decode(input_key)?; + + let secret_key = if bytes.len() == 64 { + ed25519_dalek::Keypair::from_bytes(&bytes)?.secret + } else { + ed25519_dalek::SecretKey::from_bytes(&bytes)? + }; + + let mut secret_key: Vec = secret_key.to_bytes().into_iter().collect::>(); + + //HashiCorp Vault Transit engine expects PKCS8 + if secret_key.len() == ed25519_dalek::SECRET_KEY_LENGTH { + let mut pkcs8_key = Vec::from(*PKCS8_HEADER); + pkcs8_key.extend_from_slice(&secret_key); + secret_key = pkcs8_key; + } + + debug_assert!(secret_key.len() == ed25519_dalek::SECRET_KEY_LENGTH + PKCS8_HEADER.len()); + + Ok(secret_key) +} + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use super::*; + + #[test] + fn test_input_key_32bit_ok() { + let bytes = ed25519_dalek::SecretKey::generate(&mut rand::thread_rng()).to_bytes(); + assert_eq!(bytes.len(), ed25519_dalek::SECRET_KEY_LENGTH); + + let secret = base64::encode(bytes); + + //under test + let bytes = input_key(&secret).unwrap(); + + assert_eq!( + bytes.len(), + ed25519_dalek::SECRET_KEY_LENGTH + PKCS8_HEADER.len() + ); + } + + #[test] + fn test_input_key_48bit_ok() { + let mut secret = PKCS8_HEADER.into_iter().cloned().collect::>(); + + let bytes = ed25519_dalek::SecretKey::generate(&mut rand::thread_rng()).to_bytes(); + assert_eq!(bytes.len(), ed25519_dalek::SECRET_KEY_LENGTH); + + secret.extend_from_slice(&bytes); + + let secret = base64::encode(bytes); + //under test + let bytes = input_key(&secret).unwrap(); + + assert_eq!( + bytes.len(), + ed25519_dalek::SECRET_KEY_LENGTH + PKCS8_HEADER.len() + ); + } + #[test] + fn test_input_key_64bit_ok() { + let mut secret = PKCS8_HEADER.into_iter().cloned().collect::>(); + + let bytes = ed25519_dalek::SecretKey::generate(&mut rand::thread_rng()).to_bytes(); + assert_eq!(bytes.len(), ed25519_dalek::SECRET_KEY_LENGTH); + + secret.extend_from_slice(&bytes); + + let secret = base64::encode(bytes); + //under test + let bytes = input_key(&secret).unwrap(); + + assert_eq!( + bytes.len(), + ed25519_dalek::SECRET_KEY_LENGTH + PKCS8_HEADER.len() + ); + } + + const PK_NAME: &str = "upload-test"; + const VAULT_TOKEN: &str = "access-token"; + const CHAIN_ID: &str = "mock-chain-id"; + const ED25519: &str = + "4YZKJ/pfJj42tdcl40dXz/ugRgrBR0/Pp5C2kjHL6AZhBFozq5EspBwCb44zef0cLEO/WuLf3dI+BPCNOPwxRw=="; + + use mockito::{mock, server_address}; + + #[test] + fn test_upload() { + let cmd = UploadCommand { + verbose: false, + pk_name: PK_NAME.into(), + config: None, + payload: ED25519.into(), + }; + + let config = HashiCorpConfig { + access_token: "crazy-long-string".into(), + api_endpoint: format!("http://{}", server_address()), + pk_name: PK_NAME.into(), + chain_id: tendermint::chain::Id::try_from(CHAIN_ID).unwrap(), + }; + + std::env::set_var("VAULT_TOKEN", VAULT_TOKEN); + + //init + let lookup_self = mock("GET", "/v1/auth/token/lookup-self") + .match_header("X-Vault-Token", VAULT_TOKEN) + .with_body(TOKEN_DATA) + .create(); + + //upload + let wrapping_key = mock("GET", "/v1/transit/wrapping_key") + .match_header("X-Vault-Token", VAULT_TOKEN) + .with_body(WRAPPING_KEY_RESPONSE) + .create(); + + let end_point = format!("/v1/transit/keys/{}/import", PK_NAME); + + //upload + let export = mock("POST", end_point.as_str()) + .match_header("X-Vault-Token", VAULT_TOKEN) + //.match_body(req.as_str()) //sipher string will be always different + .create(); + + //test + cmd.upload(&config); + + lookup_self.assert(); + export.assert(); + wrapping_key.expect(1).assert(); + // } + } + + //curl --header "X-Vault-Token: hvs.<...valid.token...>>" http://127.0.0.1:8200/v1/auth/token/lookup-self + const TOKEN_DATA: &str = r#" + {"request_id":"119fcc9e-85e2-1fcf-c2a2-96cfb20f7446","lease_id":"","renewable":false,"lease_duration":0,"data":{"accessor":"k1g6PqNWVIlKK9NDCWLiTvrG","creation_time":1661247016,"creation_ttl":2764800,"display_name":"token","entity_id":"","expire_time":"2022-09-24T09:30:16.898359776Z","explicit_max_ttl":0,"id":"hvs.CAESIEzWRWLvyYLGlYsCRI_Vt653K26b-cx_lrxBlFo3_2GBGh4KHGh2cy5GVzZ5b25nMVFpSkwzM1B1eHM2Y0ZqbXA","issue_time":"2022-08-23T09:30:16.898363509Z","meta":null,"num_uses":0,"orphan":false,"path":"auth/token/create","policies":["tmkms-transit-sign-policy"],"renewable":false,"ttl":2758823,"type":"service"},"wrap_info":null,"warnings":null,"auth":null} + "#; + + const WRAPPING_KEY_RESPONSE: &str = r#"{"request_id":"1d739895-ea6d-2e18-3457-edbbf8dcd129","lease_id":"","renewable":false,"lease_duration":0,"data":{"public_key":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1hXp53II1GokeS6UyOvF\nbQnNgstRJ4IINjiQXL0iO+US3p5Zc/wwads6R3sTw6nwf+cXzPEkzyXXBIMgdLTH\nx/7kOuzT+mRJbKQgFXdHyEfm9T6jEKOSJFaQQxYQcMgUiMXiaXSonDnShwQ3BOxT\nzPo9TR8Z6+xMYIFTV9/kHJT2JHAX4xf5+EuRae4XsHW2yaWZzY//qVu/z0hXEeh3\nk0yK0kAULXMlzyJDpCNuWsdtB4ZpFv0eJ5ic84ZmA3B5Y/LQ0VSHLYnJOtt7hMe2\nsEEFHS7sfTbFxtBpSTySikoCLtHOAUXC0u3FQBJRta+uT82Iufdz7Qzw2xmR1WP2\nSTdqVINYci3/cql1xzEdKmieMwEwGbMOjFA7N4hBPgT9Tjod8vqCizk+Z1AH6ijd\nhfhDXlDi2owsngijdKJEoWCIC1IsqOTkZsKspw3a/9gdAkzXC8qkevCtOccC3Nwu\nAiA1Nh+FtFdvTDtwp7/G7lFLJT2E2PdtX8nZsI0TMmQg9Wh4wFP4pJfOGsYtMdNf\nN6cNVgYsTfkKIpXpxJdRf7YNKy1bvVNIPDAREuJTT8J5aSnnE/gjDiTbUDVnLulE\nYu7BaQqzE86k20MakAg1OLMftJJo0UhPxezanG43ZRW/K8OgBKnoD6UFFPzMiJ89\nQAzzkMa+CgjZr6zkIRy5FqkCAwEAAQ==\n-----END PUBLIC KEY-----\n"},"wrap_info":null,"warnings":null,"auth":null} + "#; +} diff --git a/src/commands/init/config_builder.rs b/src/commands/init/config_builder.rs index 5465528c..73c4d8e5 100644 --- a/src/commands/init/config_builder.rs +++ b/src/commands/init/config_builder.rs @@ -83,6 +83,9 @@ impl ConfigBuilder { #[cfg(feature = "fortanixdsm")] self.add_fortanixdsm_provider_config(); + + #[cfg(feature = "hashicorp")] + self.add_hashicorp_provider_config(); } /// Add `[[validator]]` configurations @@ -148,6 +151,13 @@ impl ConfigBuilder { self.add_template_with_chain_id(include_str!("templates/keyring/fortanixdsm.toml")); } + /// Add `[[provider.hashicorp]]` configuration + #[cfg(feature = "hashicorp")] + fn add_hashicorp_provider_config(&mut self) { + self.add_str("### HashiCorp Vault Signer Configuration\n\n"); + self.add_template_with_chain_id(include_str!("templates/keyring/hashicorp.toml")); + } + /// Append a template to the config file, substituting `$KMS_HOME` fn add_template(&mut self, template: &str) { self.add_str(&format_template( diff --git a/src/commands/init/templates/keyring/hashicorp.toml b/src/commands/init/templates/keyring/hashicorp.toml new file mode 100644 index 00000000..8e7cb6e0 --- /dev/null +++ b/src/commands/init/templates/keyring/hashicorp.toml @@ -0,0 +1,9 @@ +[[providers.hashicorp]] +#ChainId this provider is configured for +chain_id = "$CHAIN_ID" +#Vault's api url - VAULT_ADDR +api_endpoint= "http://127.0.0.1:8200" +#Vault's access token - vault token create -policy= +access_token="hvs.CAESINi91lCOFj-_dOGiUfpdZUPKk93LD8YyHz-qZcYLVwH_Gh4KHGh2cy5kdXV1T2tpcXliakFFblU1SUpqanczYjU" +#Vault's transit secret engine key - vault write transit/keys/ type=ed25519 +pk_name="cosmoshub-sign-key" diff --git a/src/config/provider.rs b/src/config/provider.rs index 07e5d5d4..8fb937f4 100644 --- a/src/config/provider.rs +++ b/src/config/provider.rs @@ -2,6 +2,8 @@ #[cfg(feature = "fortanixdsm")] pub mod fortanixdsm; +#[cfg(feature = "hashicorp")] +pub mod hashicorp; #[cfg(feature = "ledger")] pub mod ledgertm; #[cfg(feature = "softsign")] @@ -11,6 +13,8 @@ pub mod yubihsm; #[cfg(feature = "fortanixdsm")] use self::fortanixdsm::FortanixDsmConfig; +#[cfg(feature = "hashicorp")] +use self::hashicorp::HashiCorpConfig; #[cfg(feature = "ledger")] use self::ledgertm::LedgerTendermintConfig; #[cfg(feature = "softsign")] @@ -44,6 +48,11 @@ pub struct ProviderConfig { #[cfg(feature = "fortanixdsm")] #[serde(default)] pub fortanixdsm: Vec, + + /// HashiCorp Vault provider configurations + #[cfg(feature = "hashicorp")] + #[serde(default)] + pub hashicorp: Vec, } /// Types of cryptographic keys diff --git a/src/config/provider/hashicorp.rs b/src/config/provider/hashicorp.rs new file mode 100644 index 00000000..26866868 --- /dev/null +++ b/src/config/provider/hashicorp.rs @@ -0,0 +1,21 @@ +//! Configuration for HashiCorp Vault + +use crate::chain; +use serde::Deserialize; + +#[derive(Clone, Deserialize, Debug)] +#[serde(deny_unknown_fields)] +/// Hashicorp Vault signer configuration +pub struct HashiCorpConfig { + /// Chains this signing key is authorized to be used from + pub chain_id: chain::Id, + + /// HashiCorp Vault API endpoint, e.g. https://127.0.0.1:8200 + pub api_endpoint: String, + + /// Access token for authenticating to HashiCorp Vault + pub access_token: String, + + /// Vault's key name with ed25519 pub+priv key + pub pk_name: String, +} diff --git a/src/keyring.rs b/src/keyring.rs index 364c658b..b572794b 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -229,5 +229,8 @@ pub fn load_config(registry: &mut chain::Registry, config: &ProviderConfig) -> R #[cfg(feature = "fortanixdsm")] providers::fortanixdsm::init(registry, &config.fortanixdsm)?; + #[cfg(feature = "hashicorp")] + providers::hashicorp::init(registry, &config.hashicorp)?; + Ok(()) } diff --git a/src/keyring/providers.rs b/src/keyring/providers.rs index d58bca2d..eba47d52 100644 --- a/src/keyring/providers.rs +++ b/src/keyring/providers.rs @@ -12,6 +12,9 @@ pub mod yubihsm; #[cfg(feature = "fortanixdsm")] pub mod fortanixdsm; +#[cfg(feature = "hashicorp")] +pub mod hashicorp; + use std::fmt::{self, Display}; /// Enumeration of signing key providers @@ -32,6 +35,10 @@ pub enum SigningProvider { /// Fortanix DSM signer #[cfg(feature = "fortanixdsm")] FortanixDsm, + + /// HashiCorp Vault provider + #[cfg(feature = "hashicorp")] + HashiCorp, } impl Display for SigningProvider { @@ -48,6 +55,9 @@ impl Display for SigningProvider { #[cfg(feature = "fortanixdsm")] SigningProvider::FortanixDsm => write!(f, "fortanixdsm"), + + #[cfg(feature = "hashicorp")] + SigningProvider::HashiCorp => write!(f, "hashicorp"), } } } diff --git a/src/keyring/providers/hashicorp.rs b/src/keyring/providers/hashicorp.rs new file mode 100644 index 00000000..4363a447 --- /dev/null +++ b/src/keyring/providers/hashicorp.rs @@ -0,0 +1,80 @@ +//! HashiCorp Vault provider +pub(crate) mod client; +pub(crate) mod error; +pub(crate) mod signer; + +use crate::{ + chain, + config::provider::hashicorp::HashiCorpConfig, + error::{Error, ErrorKind::*}, + keyring::{ + ed25519::{self, Signer}, + SigningProvider, + }, + prelude::*, +}; + +use tendermint::TendermintKey; + +use self::signer::Ed25519HashiCorpAppSigner; + +/// Create HashiCorp Vault Ed25519 signer objects from the given configuration +pub fn init( + chain_registry: &mut chain::Registry, + configs: &[HashiCorpConfig], +) -> Result<(), Error> { + if configs.is_empty() { + fail!( + ConfigError, + "expected at least one [providers.hashicorp] in config, found none!" + ); + } + + let mut chains = Vec::::new(); + + for config in configs { + //misconfiguration check + if chains.contains(&config.chain_id.to_string()) { + fail!( + ConfigError, + format!("already configured! chain id:{}", config.chain_id) + ) + } else { + chains.push(config.chain_id.to_string()) + } + + let mut app = client::TendermintValidatorApp::connect( + &config.api_endpoint, + &config.access_token, + &config.pk_name, + ) + .expect(&format!( + "Failed to authenticate to Vault for chain id:{}", + config.chain_id + )); + + let public_key = app.public_key().expect(&format!( + "Failed to get public key for chain id:{}", + config.chain_id + )); + + let public_key = ed25519::PublicKey::from_bytes(&public_key).expect(&format!( + "invalid Ed25519 public key for chain id:{}", + config.chain_id + )); + + let provider = Ed25519HashiCorpAppSigner::new(app); + + chain_registry.add_consensus_key( + &config.chain_id, + //avoiding need for clone + Signer::new( + SigningProvider::HashiCorp, + TendermintKey::ConsensusKey(public_key.into()), + Box::new(provider), + ), + )?; + } + + Ok(()) +} diff --git a/src/keyring/providers/hashicorp/client.rs b/src/keyring/providers/hashicorp/client.rs new file mode 100644 index 00000000..505a9621 --- /dev/null +++ b/src/keyring/providers/hashicorp/client.rs @@ -0,0 +1,490 @@ +use abscissa_core::prelude::*; +use std::collections::{BTreeMap, HashMap}; + +use super::error::Error; +use hashicorp_vault::{ + client::{EndpointResponse, HttpVerb, TokenData, VaultResponse}, + Client, +}; + +use serde::{Deserialize, Serialize}; + +const VAULT_BACKEND_NAME: &str = "transit"; +pub const CONSENUS_KEY_TYPE: &str = "ed25519"; + +pub(crate) struct TendermintValidatorApp { + client: Client, + key_name: String, + public_key_value: Option<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>, +} + +// TODO(tarcieri): check this is actually sound?! :-) +#[allow(unsafe_code)] +unsafe impl Send for TendermintValidatorApp {} + +///Sign Request Struct +#[derive(Debug, Serialize)] +struct SignRequest { + input: String, //Base64 encoded +} + +///Sign Response Struct +#[derive(Debug, Deserialize)] +struct SignResponse { + signature: String, //Base64 encoded +} + +#[derive(Debug, Serialize)] +pub(crate) struct ImportRequest { + pub r#type: String, + pub ciphertext: String, + pub hash_function: String, +} + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum ExportKeyType { + EncryptionKey, + SigningKey, + HmacKey, +} +impl std::fmt::Display for ExportKeyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExportKeyType::EncryptionKey => write!(f, "encryption-key"), + ExportKeyType::SigningKey => write!(f, "signing-key"), + ExportKeyType::HmacKey => write!(f, "hmac-key"), + } + } +} +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum CreateKeyType { + ///AES-128 wrapped with GCM using a 96-bit nonce size AEAD (symmetric, supports derivation and convergent encryption) + Aes128Gcm96, + ///AES-256 wrapped with GCM using a 96-bit nonce size AEAD (symmetric, supports derivation and convergent encryption, default) + Aes256Gcm96, + ///ChaCha20-Poly1305 AEAD (symmetric, supports derivation and convergent encryption) + Chacha20Poly1305, + ///ED25519 (asymmetric, supports derivation). When using derivation, a sign operation with the same context will derive the same key and signature; this is a signing analogue to convergent_encryption. + Ed25519, + ///ECDSA using the P-256 elliptic curve (asymmetric) + EcdsaP256, + ///ECDSA using the P-384 elliptic curve (asymmetric) + EcdsaP384, + ///ECDSA using the P-521 elliptic curve (asymmetric) + EcdsaP521, + ///RSA with bit size of 2048 (asymmetric) + Rsa2048, + ///RSA with bit size of 3072 (asymmetric) + Rsa3072, + ///RSA with bit size of 4096 (asymmetric) + Rsa4096, +} + +impl std::fmt::Display for CreateKeyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CreateKeyType::Aes128Gcm96 => write!(f, "aes128-gcm96"), + CreateKeyType::Aes256Gcm96 => write!(f, "aes256-gcm96"), + CreateKeyType::Chacha20Poly1305 => write!(f, "chacha20-poly1305"), + CreateKeyType::Ed25519 => write!(f, "ed25519"), + CreateKeyType::EcdsaP256 => write!(f, "ecdsa-p256"), + CreateKeyType::EcdsaP384 => write!(f, "ecdsa-p384"), + CreateKeyType::EcdsaP521 => write!(f, "ecdsa-p521"), + CreateKeyType::Rsa2048 => write!(f, "rsa-2048"), + CreateKeyType::Rsa3072 => write!(f, "rsa-3072"), + CreateKeyType::Rsa4096 => write!(f, "rsa-4096"), + } + } +} + +impl TendermintValidatorApp { + pub fn connect(host: &str, token: &str, key_name: &str) -> Result { + //this call performs token self lookup, to fail fast + let mut client = Client::new(host, token)?; + client.secret_backend(VAULT_BACKEND_NAME); + + let app = TendermintValidatorApp { + client, + key_name: key_name.to_owned(), + public_key_value: None, + }; + + debug!("Initialized with Vault host at {}", host); + Ok(app) + } + + //vault read transit/keys/cosmoshub-sign-key + //GET http://0.0.0.0:8200/v1/transit/keys/cosmoshub-sign-key + /// Get public key + pub fn public_key(&mut self) -> Result<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH], Error> { + if let Some(v) = self.public_key_value { + debug!("using cached public key {}...", self.key_name); + return Ok(v.clone()); + } + + debug!("fetching public key for {}...", self.key_name); + + ///Response struct + #[derive(Debug, Deserialize)] + struct PublicKeyResponse { + keys: BTreeMap>, + } + + let data = self.client.call_endpoint::( + HttpVerb::GET, + &format!("transit/keys/{}", self.key_name), + None, + None, + )?; + + //{ keys: {1: {"name": "ed25519", "public_key": "R5n8OFaknb/3sCTx/aegNzYukwVx0uNtzzK/2RclIOE=", "creation_time": "2022-08-18T12:44:02.136328217Z"}} } + let data = if let EndpointResponse::VaultResponse(VaultResponse { + data: Some(data), .. + }) = data + { + data + } else { + return Err(Error::InvalidPubKey( + "Public key: Vault response unavailable".into(), + )); + }; + + //latest key version + let key_data = data.keys.iter().last(); + + let pubk = if let Some((version, map)) = key_data { + debug!("public key vetion:{}", version); + if let Some(pubk) = map.get("public_key") { + if let Some(key_type) = map.get("name") { + if CONSENUS_KEY_TYPE != key_type { + return Err(Error::InvalidPubKey(format!( + "Public key \"{}\": expected key type:{}, received:{}", + self.key_name, CONSENUS_KEY_TYPE, key_type + ))); + } + } else { + return Err(Error::InvalidPubKey(format!( + "Public key \"{}\": expected key type:{}, unable to determine type", + self.key_name, CONSENUS_KEY_TYPE + ))); + } + pubk + } else { + return Err(Error::InvalidPubKey( + "Public key: unable to retrieve - \"public_key\" key is not found!".into(), + )); + } + } else { + return Err(Error::InvalidPubKey( + "Public key: unable to retrieve last version - not available!".into(), + )); + }; + + debug!("Public key: fetched {}={}...", self.key_name, pubk); + + let pubk = base64::decode(pubk)?; + + debug!( + "Public key: base64 decoded {}, size:{}", + self.key_name, + pubk.len() + ); + + let mut array = [0u8; ed25519_dalek::PUBLIC_KEY_LENGTH]; + array.copy_from_slice(&pubk[..ed25519_dalek::PUBLIC_KEY_LENGTH]); + + //cache it... + self.public_key_value = Some(array.clone()); + debug!("Public key: value cached {}", self.key_name,); + + Ok(array) + } + + //vault write transit/sign/cosmoshub-sign-key plaintext=$(base64 <<< "some-data") + //"https://127.0.0.1:8200/v1/transit/sign/cosmoshub-sign-key" + /// Sign message + pub fn sign(&self, message: &[u8]) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH], Error> { + debug!("signing request: received"); + if message.is_empty() { + return Err(Error::InvalidEmptyMessage); + } + + let body = SignRequest { + input: base64::encode(message), + }; + + debug!("signing request: base64 encoded and about to submit for signing..."); + + let data = self.client.call_endpoint::( + HttpVerb::POST, + &format!("transit/sign/{}", self.key_name), + None, + Some(&serde_json::to_string(&body)?), + )?; + + debug!("signing request: about to submit for signing..."); + + let data = if let EndpointResponse::VaultResponse(VaultResponse { + data: Some(data), .. + }) = data + { + data + } else { + return Err(Error::NoSignature); + }; + + let parts = data.signature.split(":").collect::>(); + if parts.len() != 3 { + return Err(Error::InvalidSignature(format!( + "expected 3 parts, received:{} full:{}", + parts.len(), + data.signature + ))); + } + + //signature: "vault:v1:/bcnnk4p8Uvidrs1/IX9s66UCOmmfdJudcV1/yek9a2deMiNGsVRSjirz6u+ti2wqUZfG6UukaoSHIDSSRV5Cw==" + let base64_signature = if let Some(sign) = parts.last() { + sign.to_owned() + } else { + //this should never happen + return Err(Error::InvalidSignature("last part is not available".into())); + }; + + let signature = base64::decode(base64_signature)?; + if signature.len() != 64 { + return Err(Error::InvalidSignature(format!( + "invalid signature length! 64 == {}", + signature.len() + ))); + } + + let mut array = [0u8; ed25519_dalek::SIGNATURE_LENGTH]; + array.copy_from_slice(&signature[..ed25519_dalek::SIGNATURE_LENGTH]); + Ok(array) + } + + ///fetch RSA wraping key from Vault/Transit. Returned key will be a 4096-bit RSA public key. + pub fn wrapping_key_pem(&self) -> Result { + debug!("getting wraping key..."); + #[derive(Debug, Deserialize)] + struct PublicKeyResponse { + public_key: String, + } + + let data = self.client.call_endpoint::( + HttpVerb::GET, + "transit/wrapping_key", + None, + None, + )?; + + Ok( + if let EndpointResponse::VaultResponse(VaultResponse { data: Some(d), .. }) = data { + debug!("wrapping key:\n{}", d.public_key); + d.public_key.trim().to_owned() + } else { + return Err(Error::InvalidPubKey("Error getting wrapping key!".into())); + }, + ) + } + + pub fn import_key( + &self, + key_name: &str, + key_type: CreateKeyType, + ciphertext: &str, + ) -> Result<(), Error> { + let body = ImportRequest { + r#type: key_type.to_string(), + ciphertext: ciphertext.into(), + hash_function: "SHA256".into(), + }; + + let _ = self.client.call_endpoint::<()>( + HttpVerb::POST, + &format!("transit/keys/{}/import", key_name), + None, + Some(&serde_json::to_string(&body)?), + )?; + + Ok(()) + } +} + +#[cfg(feature = "hashicorp")] +#[cfg(test)] +mod tests { + use super::*; + use base64; + use mockito::{mock, server_address}; + + const TEST_TOKEN: &str = "test-token"; + const TEST_KEY_NAME: &str = "test-key-name"; + const TEST_PUB_KEY_VALUE: &str = "ng+ab41LawVupIXX3ocMn+AfV2W1DEMCfjAdtrwXND8="; //base64 + const TEST_PAYLOAD_TO_SIGN_BASE64: &str = "cXFxcXFxcXFxcXFxcXFxcXFxcXE="; //$(base64 <<< "qqqqqqqqqqqqqqqqqqqq") => "cXFxcXFxcXFxcXFxcXFxcXFxcXEK", 'K' vs "=" ???? + const TEST_PAYLOAD_TO_SIGN: &[u8] = b"qqqqqqqqqqqqqqqqqqqq"; + + const TEST_SIGNATURE:&str = /*vault:v1:*/ "pNcc/FAUu+Ta7itVegaMUMGqXYkzE777y3kOe8AtdRTgLbA8eFnrKbbX/m7zoiC+vArsIUJ1aMCEDRjDK3ZsBg=="; + + #[test] + fn hashicorp_connect_ok() { + //setup + let lookup_self = mock("GET", "/v1/auth/token/lookup-self") + .match_header("X-Vault-Token", TEST_TOKEN) + .with_body(TOKEN_DATA) + .create(); + + //test + let app = TendermintValidatorApp::connect( + &format!("http://{}", server_address()), + TEST_TOKEN, + TEST_KEY_NAME, + ); + + assert!(app.is_ok()); + lookup_self.assert(); + } + + #[test] + fn hashicorp_public_key_ok() { + //setup + let lookup_self = mock("GET", "/v1/auth/token/lookup-self") + .match_header("X-Vault-Token", TEST_TOKEN) + .with_body(TOKEN_DATA) + .create(); + + //app + let mut app = TendermintValidatorApp::connect( + &format!("http://{}", server_address()), + TEST_TOKEN, + TEST_KEY_NAME, + ) + .expect("Failed to connect"); + + //Vault call + let read_key = mock( + "GET", + format!("/v1/transit/keys/{}", TEST_KEY_NAME).as_str(), + ) + .match_header("X-Vault-Token", TEST_TOKEN) + .with_body(READ_KEY_RESP) + .expect_at_most(1) //one call only + .create(); + + //server call + let res = app.public_key(); + assert!(res.is_ok()); + assert_eq!( + res.unwrap(), + base64::decode(TEST_PUB_KEY_VALUE).unwrap().as_slice() + ); + + //cached vaule + let res = app.public_key(); + assert!(res.is_ok()); + assert_eq!( + res.unwrap(), + base64::decode(TEST_PUB_KEY_VALUE).unwrap().as_slice() + ); + + read_key.assert(); + lookup_self.assert(); + } + + #[test] + fn hashicorp_sign_ok() { + //setup + let lookup_self = mock("GET", "/v1/auth/token/lookup-self") + .match_header("X-Vault-Token", TEST_TOKEN) + .with_body(TOKEN_DATA) + .create(); + + //app + let app = TendermintValidatorApp::connect( + &format!("http://{}", server_address()), + TEST_TOKEN, + TEST_KEY_NAME, + ) + .expect("Failed to connect"); + + let body = serde_json::to_string(&SignRequest { + input: TEST_PAYLOAD_TO_SIGN_BASE64.into(), + }) + .unwrap(); + + let sign_mock = mock( + "POST", + format!("/v1/transit/sign/{}", TEST_KEY_NAME).as_str(), + ) + .match_header("X-Vault-Token", TEST_TOKEN) + .match_body(body.as_str()) + .with_body(SIGN_RESPONSE) + .create(); + + //server call + let res = app.sign(TEST_PAYLOAD_TO_SIGN); + assert!(res.is_ok()); + assert_eq!( + res.unwrap(), + base64::decode(TEST_SIGNATURE).unwrap().as_slice() + ); + + lookup_self.assert(); + sign_mock.assert(); + } + + #[test] + fn hashicorp_sign_empty_payload_should_fail() { + //setup + let lookup_self = mock("GET", "/v1/auth/token/lookup-self") + .match_header("X-Vault-Token", TEST_TOKEN) + .with_body(TOKEN_DATA) + .create(); + + //app + let app = TendermintValidatorApp::connect( + &format!("http://{}", server_address()), + TEST_TOKEN, + TEST_KEY_NAME, + ) + .expect("Failed to connect"); + + let body = serde_json::to_string(&SignRequest { + input: TEST_PAYLOAD_TO_SIGN_BASE64.into(), + }) + .unwrap(); + + let sign_mock = mock( + "POST", + format!("/v1/transit/sign/{}", TEST_KEY_NAME).as_str(), + ) + .match_header("X-Vault-Token", TEST_TOKEN) + .match_body(body.as_str()) + .with_body(SIGN_RESPONSE) + .create(); + + //server call + let res = app.sign(&[]); + assert!(res.is_err()); + + lookup_self.assert(); + sign_mock.expect(0); + } + + //curl --header "X-Vault-Token: hvs.<...valid.token...>>" http://127.0.0.1:8200/v1/auth/token/lookup-self + const TOKEN_DATA: &str = r#" + {"request_id":"119fcc9e-85e2-1fcf-c2a2-96cfb20f7446","lease_id":"","renewable":false,"lease_duration":0,"data":{"accessor":"k1g6PqNWVIlKK9NDCWLiTvrG","creation_time":1661247016,"creation_ttl":2764800,"display_name":"token","entity_id":"","expire_time":"2022-09-24T09:30:16.898359776Z","explicit_max_ttl":0,"id":"hvs.CAESIEzWRWLvyYLGlYsCRI_Vt653K26b-cx_lrxBlFo3_2GBGh4KHGh2cy5GVzZ5b25nMVFpSkwzM1B1eHM2Y0ZqbXA","issue_time":"2022-08-23T09:30:16.898363509Z","meta":null,"num_uses":0,"orphan":false,"path":"auth/token/create","policies":["tmkms-transit-sign-policy"],"renewable":false,"ttl":2758823,"type":"service"},"wrap_info":null,"warnings":null,"auth":null} + "#; + + //curl --header "X-Vault-Token: $VAULT_TOKEN" "${VAULT_ADDR}/v1/transit/keys/" + const READ_KEY_RESP: &str = r#" + {"request_id":"9cb10d0a-1877-6da5-284b-8ece4b131ae3","lease_id":"","renewable":false,"lease_duration":0,"data":{"allow_plaintext_backup":false,"auto_rotate_period":0,"deletion_allowed":false,"derived":false,"exportable":false,"imported_key":false,"keys":{"1":{"creation_time":"2022-08-23T09:30:16.676998915Z","name":"ed25519","public_key":"ng+ab41LawVupIXX3ocMn+AfV2W1DEMCfjAdtrwXND8="}},"latest_version":1,"min_available_version":0,"min_decryption_version":1,"min_encryption_version":0,"name":"cosmoshub-sign-key","supports_decryption":false,"supports_derivation":true,"supports_encryption":false,"supports_signing":true,"type":"ed25519"},"wrap_info":null,"warnings":null,"auth":null} + "#; + + //curl --request POST --header "X-Vault-Token: $VAULT_TOKEN" "${VAULT_ADDR}/v1/transit/sign/<..key_name...>" -d '{"input":"base64 encoded"}' + const SIGN_RESPONSE: &str = r#" + {"request_id":"13534911-8e98-9a0f-a701-e9a7736140e2","lease_id":"","renewable":false,"lease_duration":0,"data":{"key_version":1,"signature":"vault:v1:pNcc/FAUu+Ta7itVegaMUMGqXYkzE777y3kOe8AtdRTgLbA8eFnrKbbX/m7zoiC+vArsIUJ1aMCEDRjDK3ZsBg=="},"wrap_info":null,"warnings":null,"auth":null} + "#; +} diff --git a/src/keyring/providers/hashicorp/error.rs b/src/keyring/providers/hashicorp/error.rs new file mode 100644 index 00000000..5cd0c242 --- /dev/null +++ b/src/keyring/providers/hashicorp/error.rs @@ -0,0 +1,53 @@ +//! Ledger errors + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("message cannot be empty")] + InvalidEmptyMessage, + + #[error("Public Key Error:{0}")] + InvalidPubKey(String), + + #[error("received no signature back")] + NoSignature, + + #[error("received an invalid signature: {0}")] + InvalidSignature(String), + + #[error("ApiClient error:{0}")] + ApiClientError(String), + + #[error("Base64 decode error")] + DecodeError(base64::DecodeError), + + #[error("Serde error")] + SerDeError(serde_json::Error), + + #[error("Signature error")] + SignatureError(signature::Error), +} + +impl From for Error { + fn from(err: hashicorp_vault::Error) -> Error { + Error::ApiClientError(err.to_string()) + } +} + +impl From for Error { + fn from(err: base64::DecodeError) -> Error { + Error::DecodeError(err) + } +} + +impl From for Error { + fn from(err: serde_json::Error) -> Error { + Error::SerDeError(err) + } +} +impl From for Error { + fn from(err: signature::Error) -> Error { + Error::SignatureError(err) + } +} diff --git a/src/keyring/providers/hashicorp/signer.rs b/src/keyring/providers/hashicorp/signer.rs new file mode 100644 index 00000000..aacd91bb --- /dev/null +++ b/src/keyring/providers/hashicorp/signer.rs @@ -0,0 +1,28 @@ +use std::sync::Arc; +use std::sync::Mutex; + +use crate::keyring::ed25519::Signature; +use crate::keyring::providers::hashicorp::client::TendermintValidatorApp; +use signature::{Error, Signer}; + +/// ed25519 signature provider for the Ledger Tendermint Validator app +pub(crate) struct Ed25519HashiCorpAppSigner { + app: Arc>, +} + +impl Ed25519HashiCorpAppSigner { + pub fn new(app: TendermintValidatorApp) -> Self { + Ed25519HashiCorpAppSigner { + app: Arc::new(Mutex::new(app)), + } + } +} + +impl Signer for Ed25519HashiCorpAppSigner { + /// c: Compute a compact, fixed-sized signature of the given amino/json vote + fn try_sign(&self, msg: &[u8]) -> Result { + let app = self.app.lock().unwrap(); + let sig = app.sign(msg).map_err(Error::from_source)?; + Ok(Signature::from(sig)) + } +} diff --git a/src/lib.rs b/src/lib.rs index dcb19beb..3e1f8dd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,11 +7,12 @@ feature = "softsign", feature = "yubihsm", feature = "ledger", - feature = "fortanixdsm" + feature = "fortanixdsm", + feature = "hashicorp" )))] compile_error!( "please enable one of the following backends with cargo's --features argument: \ - yubihsm, ledgertm, softsign, fortanixdsm (e.g. --features=yubihsm)" + yubihsm, ledgertm, softsign, fortanixdsm, hashicorp (e.g. --features=yubihsm)" ); pub mod application; diff --git a/tests/cli/init.rs b/tests/cli/init.rs index 6f22887f..c7e60005 100644 --- a/tests/cli/init.rs +++ b/tests/cli/init.rs @@ -1,43 +1,47 @@ //! Integration tests for the `init` subcommand -use crate::cli; -use abscissa_core::Config; -use std::{ffi::OsStr, fs}; -use tmkms::{commands::init::networks::Network, config::KmsConfig}; - -#[test] -fn test_command() { - let parent_dir = tempfile::tempdir().unwrap(); - - let output_dir = parent_dir.path().join("tmkms"); - assert!(!output_dir.exists()); - - // Network names to test with - let networks = Network::all() - .iter() - .map(ToString::to_string) - .collect::>(); - - let result = cli::run([ - OsStr::new("init"), - OsStr::new("-n"), - OsStr::new(&networks.join(",")), - output_dir.as_os_str(), - ]); - - assert!(result.status.success()); - - // Ensure generated configuration file parses - let kms_config_path = output_dir.join("tmkms.toml"); - let kms_config = KmsConfig::load_toml(fs::read_to_string(kms_config_path).unwrap()).unwrap(); - - // Ensure all expected chain IDs are present - assert_eq!( - &kms_config - .chain +#[cfg(feature = "softsign")] +mod softsign_init_test { + use crate::cli; + use abscissa_core::Config; + use std::{ffi::OsStr, fs}; + use tmkms::{commands::init::networks::Network, config::KmsConfig}; + + #[test] + fn test_command() { + let parent_dir = tempfile::tempdir().unwrap(); + + let output_dir = parent_dir.path().join("tmkms"); + assert!(!output_dir.exists()); + + // Network names to test with + let networks = Network::all() .iter() - .map(|c| c.id.as_str().split('-').next().unwrap().to_owned()) - .collect::>(), - &networks - ) + .map(ToString::to_string) + .collect::>(); + + let result = cli::run(&[ + OsStr::new("init"), + OsStr::new("-n"), + OsStr::new(&networks.join(",")), + output_dir.as_os_str(), + ]); + + assert!(result.status.success()); + + // Ensure generated configuration file parses + let kms_config_path = output_dir.join("tmkms.toml"); + let kms_config = + KmsConfig::load_toml(fs::read_to_string(&kms_config_path).unwrap()).unwrap(); + + // Ensure all expected chain IDs are present + assert_eq!( + &kms_config + .chain + .iter() + .map(|c| c.id.as_str().split("-").next().unwrap().to_owned()) + .collect::>(), + &networks + ) + } } diff --git a/tests/integration.rs b/tests/integration.rs index eda8b7f7..e0e463d1 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,59 +1,75 @@ //! KMS integration test -use abscissa_core::prelude::warn; -use chrono::{DateTime, Utc}; -use prost::Message; -use rand::Rng; -use signature::Verifier; -use std::fs::File; -use std::{ - fs, - io::{self, Cursor, Read, Write}, - net::{TcpListener, TcpStream}, - os::unix::net::{UnixListener, UnixStream}, - process::{Child, Command}, -}; -use tempfile::NamedTempFile; -use tendermint_p2p::secret_connection::{self, SecretConnection}; -use tendermint_proto as proto; -use tmkms::{ - config::provider::KeyType, - connection::unix::UnixConnection, - keyring::ed25519, - privval::{SignableMsg, SignedMsgType}, -}; - -/// Integration tests for the KMS command-line interface mod cli; - /// Path to the KMS executable const KMS_EXE_PATH: &str = "target/debug/tmkms"; - -/// Path to the example validator signing key const SIGNING_ED25519_KEY_PATH: &str = "tests/support/signing_ed25519.key"; const SIGNING_SECP256K1_KEY_PATH: &str = "tests/support/signing_secp256k1.key"; -enum KmsSocket { - /// TCP socket type - TCP(TcpStream), +/// Path to the example validator signing key +#[cfg(feature = "softsign")] +mod softsign_integration_tests { + use std::{ + fs, + io::{self, Cursor, Read, Write}, + net::{TcpListener, TcpStream}, + os::unix::net::{UnixListener, UnixStream}, + process::{Child, Command}, + }; - /// UNIX socket type - UNIX(UnixStream), -} + use abscissa_core::prelude::warn; + use chrono::{DateTime, Utc}; + use ed25519_dalek::{self as ed25519, Verifier}; + use rand::Rng; + use tempfile::NamedTempFile; -enum KmsConnection { - /// Secret connection type - Tcp(SecretConnection), + use prost_amino::Message; + use tendermint_p2p::secret_connection::{self, SecretConnection}; - /// UNIX connection type - Unix(UnixConnection), -} + use tmkms::{ + amino_types::{self, *}, + config::validator::ProtocolVersion, + connection::unix::UnixConnection, + }; -impl io::Write for KmsConnection { - fn write(&mut self, data: &[u8]) -> Result { - match *self { - KmsConnection::Tcp(ref mut conn) => conn.write(data), - KmsConnection::Unix(ref mut conn) => conn.write(data), + // /// Integration tests for the KMS command-line interface + // use super::cli; + + // /// Path to the KMS executable + // const KMS_EXE_PATH: &str = "target/debug/tmkms"; + + /// Path to the example validator signing key + const SIGNING_KEY_PATH: &str = "tests/support/signing.key"; + + enum KmsSocket { + /// TCP socket type + TCP(TcpStream), + + /// UNIX socket type + UNIX(UnixStream), + } + + enum KmsConnection { + /// Secret connection type + Tcp(SecretConnection), + + /// UNIX connection type + Unix(UnixConnection), + } + + impl io::Write for KmsConnection { + fn write(&mut self, data: &[u8]) -> Result { + match *self { + KmsConnection::Tcp(ref mut conn) => conn.write(data), + KmsConnection::Unix(ref mut conn) => conn.write(data), + } + } + + fn flush(&mut self) -> Result<(), io::Error> { + match *self { + KmsConnection::Tcp(ref mut conn) => conn.flush(), + KmsConnection::Unix(ref mut conn) => conn.flush(), + } } } @@ -131,8 +147,80 @@ impl KmsProcess { let mut config_file = NamedTempFile::new().unwrap(); let pub_key = test_ed25519_keypair().verifying_key(); let peer_id = secret_connection::PublicKey::from(pub_key).peer_id(); + impl io::Read for KmsConnection { + fn read(&mut self, data: &mut [u8]) -> Result { + match *self { + KmsConnection::Tcp(ref mut conn) => conn.read(data), + KmsConnection::Unix(ref mut conn) => conn.read(data), + } + } + } - writeln!( + /// Receives incoming KMS connection then sends commands + struct KmsProcess { + /// KMS child process + process: Child, + + /// A socket to KMS process + socket: KmsSocket, + } + + impl KmsProcess { + /// Spawn the KMS process and wait for an incoming TCP connection + pub fn create_tcp() -> Self { + // Generate a random port and a config file + let port: u16 = rand::thread_rng().gen_range(60000, 65535); + let config = KmsProcess::create_tcp_config(port); + + // Listen on a random port + let listener = TcpListener::bind(format!("{}:{}", "127.0.0.1", port)).unwrap(); + + let args = &["start", "-c", config.path().to_str().unwrap()]; + let process = Command::new(super::KMS_EXE_PATH) + .args(args) + .spawn() + .unwrap(); + + let (socket, _) = listener.accept().unwrap(); + Self { + process: process, + socket: KmsSocket::TCP(socket), + } + } + + /// Spawn the KMS process and connect to the Unix listener + pub fn create_unix() -> Self { + // Create a random socket path and a config file + let mut rng = rand::thread_rng(); + let letter: char = rng.gen_range(b'a', b'z') as char; + let number: u32 = rng.gen_range(0, 999999); + let socket_path = format!("/tmp/tmkms-{}{:06}.sock", letter, number); + let config = KmsProcess::create_unix_config(&socket_path); + + // Start listening for connections via the Unix socket + let listener = UnixListener::bind(socket_path).unwrap(); + + // Fire up the KMS process and allow it to connect to our Unix socket + let args = &["start", "-c", config.path().to_str().unwrap()]; + let process = Command::new(super::KMS_EXE_PATH) + .args(args) + .spawn() + .unwrap(); + + let (socket, _) = listener.accept().unwrap(); + Self { + process: process, + socket: KmsSocket::UNIX(socket), + } + } + + /// Create a config file for a TCP KMS and return its path + fn create_tcp_config(port: u16) -> NamedTempFile { + let mut config_file = NamedTempFile::new().unwrap(); + let pub_key = test_ed25519_keypair().public; + let peer_id = secret_connection::PublicKey::from(pub_key).peer_id(); + + writeln!( config_file, r#" [[chain]] @@ -157,8 +245,8 @@ impl KmsProcess { ) .unwrap(); - config_file - } + config_file + } /// Create a config file for a UNIX KMS and return its path fn create_unix_config(socket_path: &str, key_type: &KeyType) -> NamedTempFile { @@ -186,8 +274,8 @@ impl KmsProcess { ) .unwrap(); - config_file - } + config_file + } /// Get a connection from the socket pub fn create_connection(&self) -> KmsConnection { @@ -196,8 +284,8 @@ impl KmsProcess { // we use the same key for both sides: let identity_key = test_ed25519_keypair(); - // Here we reply to the kms with a "remote" ephermal key, auth signature etc: - let socket_cp = sock.try_clone().unwrap(); + // Here we reply to the kms with a "remote" ephermal key, auth signature etc: + let socket_cp = sock.try_clone().unwrap(); KmsConnection::Tcp( SecretConnection::new( @@ -205,26 +293,24 @@ impl KmsProcess { identity_key.into(), secret_connection::Version::V0_34, ) - .unwrap(), - ) - } + } - KmsSocket::UNIX(ref sock) => { - let socket_cp = sock.try_clone().unwrap(); + KmsSocket::UNIX(ref sock) => { + let socket_cp = sock.try_clone().unwrap(); - KmsConnection::Unix(UnixConnection::new(socket_cp)) + KmsConnection::Unix(UnixConnection::new(socket_cp)) + } } } } -} -/// A struct to hold protocol integration tests contexts -struct ProtocolTester { - tcp_device: KmsProcess, - tcp_connection: KmsConnection, - unix_device: KmsProcess, - unix_connection: KmsConnection, -} + /// A struct to hold protocol integration tests contexts + struct ProtocolTester { + tcp_device: KmsProcess, + tcp_connection: KmsConnection, + unix_device: KmsProcess, + unix_connection: KmsConnection, + } impl ProtocolTester { pub fn apply(key_type: &KeyType, functor: F) @@ -236,11 +322,317 @@ impl ProtocolTester { let unix_device = KmsProcess::create_unix(key_type); let unix_connection = unix_device.create_connection(); - functor(Self { - tcp_device, - tcp_connection, - unix_device, - unix_connection, + functor(Self { + tcp_device, + tcp_connection, + unix_device, + unix_connection, + }); + } + } + + impl Drop for ProtocolTester { + fn drop(&mut self) { + self.tcp_device.process.kill().unwrap(); + self.unix_device.process.kill().unwrap(); + + match fs::remove_file("test_chain_id_priv_validator_state.json") { + Err(ref e) if e.kind() != io::ErrorKind::NotFound => { + panic!("{}", e); + } + _ => (), + } + } + } + + impl io::Write for ProtocolTester { + fn write(&mut self, data: &[u8]) -> Result { + let unix_sz = self.unix_connection.write(data)?; + let tcp_sz = self.tcp_connection.write(data)?; + + // Assert caller sanity + assert!(unix_sz == tcp_sz); + Ok(unix_sz) + } + + fn flush(&mut self) -> Result<(), io::Error> { + self.unix_connection.flush()?; + self.tcp_connection.flush()?; + Ok(()) + } + } + + impl io::Read for ProtocolTester { + fn read(&mut self, data: &mut [u8]) -> Result { + let mut unix_buf = vec![0u8; data.len()]; + + self.tcp_connection.read(data)?; + let unix_sz = self.unix_connection.read(&mut unix_buf)?; + + // Assert handler sanity + if unix_buf != data { + warn!("binary protocol differs between TCP and UNIX sockets"); + } + + Ok(unix_sz) + } + } + + /// Get the Ed25519 signing keypair used by the tests + fn test_ed25519_keypair() -> ed25519::Keypair { + tmkms::key_utils::load_base64_ed25519_key(SIGNING_KEY_PATH).unwrap() + } + + /// Extract the actual length of an amino message + pub fn extract_actual_len(buf: &[u8]) -> Result { + let mut buff = Cursor::new(buf); + let actual_len = prost_amino::encoding::decode_varint(&mut buff)?; + if actual_len == 0 { + return Ok(1); + } + Ok(actual_len + (prost_amino::encoding::encoded_len_varint(actual_len) as u64)) + } + + #[test] + fn test_handle_and_sign_proposal() { + let chain_id = "test_chain_id"; + let pub_key = test_ed25519_keypair().public; + + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + + ProtocolTester::apply(|mut pt| { + let proposal = amino_types::proposal::Proposal { + msg_type: amino_types::SignedMsgType::Proposal.to_u32(), + height: 12345, + round: 1, + timestamp: Some(t), + pol_round: -1, + block_id: None, + signature: vec![], + }; + + let spr = amino_types::proposal::SignProposalRequest { + proposal: Some(proposal), + }; + + let mut buf = vec![]; + spr.encode(&mut buf).unwrap(); + pt.write_all(&buf).unwrap(); + + // receive response: + let mut resp_buf = vec![0u8; 1024]; + pt.read(&mut resp_buf).unwrap(); + + let actual_len = extract_actual_len(&resp_buf).unwrap(); + let mut resp = vec![0u8; actual_len as usize]; + resp.copy_from_slice(&mut resp_buf[..(actual_len as usize)]); + + let p_req = proposal::SignedProposalResponse::decode(resp.as_ref()) + .expect("decoding proposal failed"); + let mut sign_bytes: Vec = vec![]; + spr.sign_bytes( + chain_id.parse().unwrap(), + ProtocolVersion::Legacy, + &mut sign_bytes, + ) + .unwrap(); + + let prop: amino_types::proposal::Proposal = p_req + .proposal + .expect("proposal should be embedded but none was found"); + + let signature = ed25519::Signature::try_from(prop.signature.as_slice()).unwrap(); + let msg: &[u8] = sign_bytes.as_slice(); + + assert!(pub_key.verify(msg, &signature).is_ok()); + }); + } + + #[test] + fn test_handle_and_sign_vote() { + let chain_id = "test_chain_id"; + let pub_key = test_ed25519_keypair().public; + + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + + ProtocolTester::apply(|mut pt| { + let vote_msg = amino_types::vote::Vote { + vote_type: 0x01, + height: 12345, + round: 2, + timestamp: Some(t), + block_id: Some(BlockId { + hash: b"some hash00000000000000000000000".to_vec(), + parts_header: Some(PartsSetHeader { + total: 1000000, + hash: b"parts_hash0000000000000000000000".to_vec(), + }), + }), + validator_address: vec![ + 0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, + 0xf4, 0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35, + ], + validator_index: 56789, + signature: vec![], + }; + + let svr = amino_types::vote::SignVoteRequest { + vote: Some(vote_msg), + }; + let mut buf = vec![]; + svr.encode(&mut buf).unwrap(); + pt.write_all(&buf).unwrap(); + + // receive response: + let mut resp_buf = vec![0u8; 1024]; + pt.read(&mut resp_buf).unwrap(); + + let actual_len = extract_actual_len(&resp_buf).unwrap(); + let mut resp = vec![0u8; actual_len as usize]; + resp.copy_from_slice(&resp_buf[..actual_len as usize]); + + let v_resp = + vote::SignedVoteResponse::decode(resp.as_ref()).expect("decoding vote failed"); + let mut sign_bytes: Vec = vec![]; + svr.sign_bytes( + chain_id.parse().unwrap(), + ProtocolVersion::Legacy, + &mut sign_bytes, + ) + .unwrap(); + + let vote_msg: amino_types::vote::Vote = v_resp + .vote + .expect("vote should be embedded int the response but none was found"); + + let sig: Vec = vote_msg.signature; + assert_ne!(sig.len(), 0); + + let signature = ed25519::Signature::try_from(sig.as_slice()).unwrap(); + let msg: &[u8] = sign_bytes.as_slice(); + + assert!(pub_key.verify(msg, &signature).is_ok()); + }); + } + + #[test] + #[should_panic] + fn test_exceed_max_height() { + let chain_id = "test_chain_id"; + let pub_key = test_ed25519_keypair().public; + + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + + ProtocolTester::apply(|mut pt| { + let vote_msg = amino_types::vote::Vote { + vote_type: 0x01, + height: 500001, + round: 2, + timestamp: Some(t), + block_id: Some(BlockId { + hash: b"some hash00000000000000000000000".to_vec(), + parts_header: Some(PartsSetHeader { + total: 1000000, + hash: b"parts_hash0000000000000000000000".to_vec(), + }), + }), + validator_address: vec![ + 0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, + 0xf4, 0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35, + ], + validator_index: 56789, + signature: vec![], + }; + + let svr = amino_types::vote::SignVoteRequest { + vote: Some(vote_msg), + }; + let mut buf = vec![]; + svr.encode(&mut buf).unwrap(); + pt.write_all(&buf).unwrap(); + + // receive response: + let mut resp_buf = vec![0u8; 1024]; + pt.read(&mut resp_buf).unwrap(); + + let actual_len = extract_actual_len(&resp_buf).unwrap(); + let mut resp = vec![0u8; actual_len as usize]; + resp.copy_from_slice(&resp_buf[..actual_len as usize]); + + let v_resp = + vote::SignedVoteResponse::decode(resp.as_ref()).expect("decoding vote failed"); + let mut sign_bytes: Vec = vec![]; + svr.sign_bytes( + chain_id.parse().unwrap(), + ProtocolVersion::Legacy, + &mut sign_bytes, + ) + .unwrap(); + + let vote_msg: amino_types::vote::Vote = v_resp + .vote + .expect("vote should be embedded int the response but none was found"); + + let sig: Vec = vote_msg.signature; + assert_ne!(sig.len(), 0); + + let signature = ed25519::Signature::try_from(sig.as_slice()).unwrap(); + let msg: &[u8] = sign_bytes.as_slice(); + + assert!(pub_key.verify(msg, &signature).is_ok()); + }); + } + + #[test] + fn test_handle_and_sign_get_publickey() { + ProtocolTester::apply(|mut pt| { + let mut buf = vec![]; + + PubKeyRequest {}.encode(&mut buf).unwrap(); + + pt.write_all(&buf).unwrap(); + + // receive response: + let mut resp_buf = vec![0u8; 1024]; + pt.read(&mut resp_buf).unwrap(); + + let actual_len = extract_actual_len(&resp_buf).unwrap(); + let mut resp = vec![0u8; actual_len as usize]; + resp.copy_from_slice(&resp_buf[..actual_len as usize]); + + let pk_resp = + PubKeyResponse::decode(resp.as_ref()).expect("decoding public key failed"); + assert_ne!(pk_resp.pub_key_ed25519.len(), 0); + }); + } + + #[test] + fn test_handle_and_sign_ping_pong() { + ProtocolTester::apply(|mut pt| { + let mut buf = vec![]; + PingRequest {}.encode(&mut buf).unwrap(); + pt.write_all(&buf).unwrap(); + + // receive response: + let mut resp_buf = vec![0u8; 1024]; + pt.read(&mut resp_buf).unwrap(); + + let actual_len = extract_actual_len(&resp_buf).unwrap(); + let mut resp = vec![0u8; actual_len as usize]; + resp.copy_from_slice(&resp_buf[..actual_len as usize]); + PingResponse::decode(resp.as_ref()).expect("decoding ping response failed"); }); } } diff --git a/tmkms-hashicorp.toml b/tmkms-hashicorp.toml new file mode 100644 index 00000000..40ac8e2d --- /dev/null +++ b/tmkms-hashicorp.toml @@ -0,0 +1,41 @@ +# Tendermint KMS configuration file + +## Chain Configuration +### Cosmos Hub Network +[[chain]] +id = "cosmoshub-4" +key_format = { type = "bech32", account_key_prefix = "cosmospub", consensus_key_prefix = "cosmosvalconspub" } +state_file = "/home/soleinik/work/rust/tmkms/state/cosmoshub-4-consensus.json" + +[[chain]] +id = "cosmoshub-3" +key_format = { type = "bech32", account_key_prefix = "cosmospub", consensus_key_prefix = "cosmosvalconspub" } +state_file = "/home/soleinik/work/rust/tmkms/state/cosmoshub-3-consensus.json" + + + +## Signing Provider Configuration +[[providers.hashicorp]] +chain_id = "cosmoshub-4" +api_endpoint= "http://127.0.0.1:8200" +access_token="hvs.CAESIBwDAKQh2JKuxibeHOV-jmc2T68loAKLNWO2_QX1l7L3Gh4KHGh2cy5iYnRJRVVLdHBqTVMwUGdLZFNGWjNCZkc" +pk_name="cosmoshub-sign-key" + +[[providers.hashicorp]] +chain_id = "cosmoshub-4" +api_endpoint= "http://127.0.0.1:8200" +access_token="hvs.CAESIBwDAKQh2JKuxibeHOV-jmc2T68loAKLNWO2_QX1l7L3Gh4KHGh2cy5iYnRJRVVLdHBqTVMwUGdLZFNGWjNCZkc" +pk_name="cosmoshub-sign-key" + + +## Validator Configuration + +# [[validator]] +# chain_id = "cosmoshub-3" +# addr = "tcp://deadbeefdeadbeefdeadbeefdeadbeefdeadbeef@example1.example.com:26658" +# secret_key = "/home/soleinik/work/rust/tmkms/secrets/kms-identity.key" +# protocol_version = "legacy" +# reconnect = true + + + From fc45dbc8c7da801340c1f4b2d79b769204b768ef Mon Sep 17 00:00:00 2001 From: Serguei Oleinik Date: Tue, 18 Oct 2022 15:01:22 -0500 Subject: [PATCH 02/38] tests+clippy fixes --- Cargo.toml | 1 + src/commands/hashicorp/test.rs | 5 +--- src/commands/hashicorp/upload.rs | 9 +++---- src/keyring/providers/hashicorp.rs | 32 +++++++++++------------ src/keyring/providers/hashicorp/client.rs | 18 ++++++------- src/keyring/providers/hashicorp/error.rs | 17 +++++------- 6 files changed, 37 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9ebcb761..2086d71a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ yubihsm-server = ["yubihsm/http-server", "rpassword"] fortanixdsm = ["elliptic-curve", "sdkms", "url", "uuid"] hashicorp = ["hashicorp_vault", "base64", "aes-kw", "rsa", "rand", "aes-gcm"] + # Enable integer overflow checks in release builds for security reasons [profile.release] overflow-checks = true diff --git a/src/commands/hashicorp/test.rs b/src/commands/hashicorp/test.rs index 7895a1f3..cba93f3f 100644 --- a/src/commands/hashicorp/test.rs +++ b/src/commands/hashicorp/test.rs @@ -59,10 +59,7 @@ impl Runnable for TestCommand { &config.access_token, &self.pk_name, ) - .expect(&format!( - "Unable to connect to Vault at {}", - config.api_endpoint - )); + .unwrap_or_else(|_| panic!("Unable to connect to Vault at {}", config.api_endpoint)); let mut app = crate::keyring::providers::hashicorp::signer::Ed25519HashiCorpAppSigner::new(app); diff --git a/src/commands/hashicorp/upload.rs b/src/commands/hashicorp/upload.rs index f073710b..3ae68817 100644 --- a/src/commands/hashicorp/upload.rs +++ b/src/commands/hashicorp/upload.rs @@ -79,7 +79,7 @@ impl Runnable for UploadCommand { process::exit(1); }; - self.upload(&config); + self.upload(config); } } @@ -101,10 +101,7 @@ impl UploadCommand { &vault_token, &self.pk_name, ) - .expect(&format!( - "Unable to connect to Vault at {}", - config.api_endpoint - )); + .unwrap_or_else(|_| panic!("Unable to connect to Vault at {}", config.api_endpoint)); use aes_gcm::KeyInit; let v_aes_key = aes_gcm::Aes256Gcm::generate_key(&mut aes_gcm::aead::OsRng); @@ -119,7 +116,7 @@ impl UploadCommand { let mut aes_key = [0u8; KEY_SIZE_AES256]; aes_key.copy_from_slice(&v_aes_key[..KEY_SIZE_AES256]); - let kek = aes_kw::KekAes256::from(aes_key.clone()); + let kek = aes_kw::KekAes256::from(aes_key); let wrapped_input_key = kek .wrap_with_padding_vec(&ed25519_input_key) .expect("input key wrapping error!"); diff --git a/src/keyring/providers/hashicorp.rs b/src/keyring/providers/hashicorp.rs index 4363a447..e750a270 100644 --- a/src/keyring/providers/hashicorp.rs +++ b/src/keyring/providers/hashicorp.rs @@ -24,10 +24,7 @@ pub fn init( configs: &[HashiCorpConfig], ) -> Result<(), Error> { if configs.is_empty() { - fail!( - ConfigError, - "expected at least one [providers.hashicorp] in config, found none!" - ); + return Ok(()); } let mut chains = Vec::::new(); @@ -48,20 +45,23 @@ pub fn init( &config.access_token, &config.pk_name, ) - .expect(&format!( - "Failed to authenticate to Vault for chain id:{}", - config.chain_id - )); + .unwrap_or_else(|_| { + panic!( + "Failed to authenticate to Vault for chain id:{}", + config.chain_id + ) + }); - let public_key = app.public_key().expect(&format!( - "Failed to get public key for chain id:{}", - config.chain_id - )); + let public_key = app.public_key().unwrap_or_else(|_| { + panic!("Failed to get public key for chain id:{}", config.chain_id,) + }); - let public_key = ed25519::PublicKey::from_bytes(&public_key).expect(&format!( - "invalid Ed25519 public key for chain id:{}", - config.chain_id - )); + let public_key = ed25519::PublicKey::from_bytes(&public_key).unwrap_or_else(|_| { + panic!( + "invalid Ed25519 public key for chain id:{}", + config.chain_id + ) + }); let provider = Ed25519HashiCorpAppSigner::new(app); diff --git a/src/keyring/providers/hashicorp/client.rs b/src/keyring/providers/hashicorp/client.rs index 505a9621..3854dae0 100644 --- a/src/keyring/providers/hashicorp/client.rs +++ b/src/keyring/providers/hashicorp/client.rs @@ -44,16 +44,16 @@ pub(crate) struct ImportRequest { #[allow(dead_code)] #[derive(Debug)] pub(crate) enum ExportKeyType { - EncryptionKey, - SigningKey, - HmacKey, + Encryption, + Signing, + Hmac, } impl std::fmt::Display for ExportKeyType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ExportKeyType::EncryptionKey => write!(f, "encryption-key"), - ExportKeyType::SigningKey => write!(f, "signing-key"), - ExportKeyType::HmacKey => write!(f, "hmac-key"), + ExportKeyType::Encryption => write!(f, "encryption-key"), + ExportKeyType::Signing => write!(f, "signing-key"), + ExportKeyType::Hmac => write!(f, "hmac-key"), } } } @@ -121,7 +121,7 @@ impl TendermintValidatorApp { pub fn public_key(&mut self) -> Result<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH], Error> { if let Some(v) = self.public_key_value { debug!("using cached public key {}...", self.key_name); - return Ok(v.clone()); + return Ok(v); } debug!("fetching public key for {}...", self.key_name); @@ -196,7 +196,7 @@ impl TendermintValidatorApp { array.copy_from_slice(&pubk[..ed25519_dalek::PUBLIC_KEY_LENGTH]); //cache it... - self.public_key_value = Some(array.clone()); + self.public_key_value = Some(array); debug!("Public key: value cached {}", self.key_name,); Ok(array) @@ -235,7 +235,7 @@ impl TendermintValidatorApp { return Err(Error::NoSignature); }; - let parts = data.signature.split(":").collect::>(); + let parts = data.signature.split(':').collect::>(); if parts.len() != 3 { return Err(Error::InvalidSignature(format!( "expected 3 parts, received:{} full:{}", diff --git a/src/keyring/providers/hashicorp/error.rs b/src/keyring/providers/hashicorp/error.rs index 5cd0c242..a11d3a66 100644 --- a/src/keyring/providers/hashicorp/error.rs +++ b/src/keyring/providers/hashicorp/error.rs @@ -17,37 +17,34 @@ pub enum Error { InvalidSignature(String), #[error("ApiClient error:{0}")] - ApiClientError(String), + ApiClient(String), #[error("Base64 decode error")] - DecodeError(base64::DecodeError), + Decode(base64::DecodeError), #[error("Serde error")] - SerDeError(serde_json::Error), - - #[error("Signature error")] - SignatureError(signature::Error), + SerDe(serde_json::Error), } impl From for Error { fn from(err: hashicorp_vault::Error) -> Error { - Error::ApiClientError(err.to_string()) + Error::ApiClient(err.to_string()) } } impl From for Error { fn from(err: base64::DecodeError) -> Error { - Error::DecodeError(err) + Error::Decode(err) } } impl From for Error { fn from(err: serde_json::Error) -> Error { - Error::SerDeError(err) + Error::SerDe(err) } } impl From for Error { fn from(err: signature::Error) -> Error { - Error::SignatureError(err) + Error::InvalidSignature(err.to_string()) } } From 6eb467321974e984157d528b323a5906c85bda4f Mon Sep 17 00:00:00 2001 From: Serguei Oleinik Date: Wed, 19 Oct 2022 15:57:25 -0500 Subject: [PATCH 03/38] vault_client dependency replaced with ureq --- Cargo.lock | 475 ++++++++++++++- Cargo.toml | 7 +- src/amino_types/block_id.rs | 188 ++++++ src/amino_types/ed25519.rs | 178 ++++++ src/amino_types/ping.rs | 14 + src/amino_types/proposal.rs | 358 +++++++++++ src/amino_types/remote_error.rs | 32 + src/amino_types/time.rs | 66 ++ src/amino_types/vote.rs | 563 ++++++++++++++++++ src/commands/hashicorp/test.rs | 2 +- src/keyring/providers/hashicorp.rs | 1 + src/keyring/providers/hashicorp/client.rs | 145 +++-- src/keyring/providers/hashicorp/error.rs | 17 +- src/keyring/providers/hashicorp/vault_data.rs | 52 ++ 14 files changed, 2027 insertions(+), 71 deletions(-) create mode 100644 src/amino_types/block_id.rs create mode 100644 src/amino_types/ed25519.rs create mode 100644 src/amino_types/ping.rs create mode 100644 src/amino_types/proposal.rs create mode 100644 src/amino_types/remote_error.rs create mode 100644 src/amino_types/time.rs create mode 100644 src/amino_types/vote.rs create mode 100644 src/keyring/providers/hashicorp/vault_data.rs diff --git a/Cargo.lock b/Cargo.lock index f3ebea45..25fb8bce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,19 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD +======= +name = "abscissa_tokio" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ce48eff491a0ab32c0e1cadd7ba5221a38dc53438992c5e857ab9b8a3f1a3e" +dependencies = [ + "abscissa_core", + "tokio", +] + +[[package]] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -130,6 +143,7 @@ version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ +<<<<<<< HEAD "anstyle", "anstyle-parse", "anstyle-query", @@ -202,6 +216,31 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD +======= +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -230,6 +269,7 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" +<<<<<<< HEAD version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" @@ -237,6 +277,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" version = "0.21.5" +======= +version = "0.13.0" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" @@ -414,7 +457,11 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", +<<<<<<< HEAD + "windows-targets 0.48.5", +======= + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -511,7 +558,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ "lazy_static", +<<<<<<< HEAD "windows-sys 0.48.0", +======= + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -591,6 +642,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crypto-bigint" version = "0.3.2" @@ -814,6 +874,7 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "encoding_rs" version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -825,6 +886,10 @@ dependencies = [ [[package]] name = "equivalent" version = "1.0.1" +======= +name = "eyre" +version = "0.6.8" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" @@ -887,10 +952,21 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +======= +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "flex-error" @@ -945,10 +1021,14 @@ dependencies = [ name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +<<<<<<< HEAD checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] +======= +checksum = "64db3e262960f0662f43a6366788d5f10f7f244b8f7d7d987f560baf5ded5c50" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "futures" @@ -1087,11 +1167,19 @@ dependencies = [ [[package]] name = "h2" +<<<<<<< HEAD version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", +======= +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes 1.2.1", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "fnv", "futures-core", "futures-sink", @@ -1111,6 +1199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] +<<<<<<< HEAD name = "hashicorp_vault" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1128,13 +1217,21 @@ dependencies = [ ] [[package]] +======= +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "headers" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ +<<<<<<< HEAD "base64 0.21.5", "bytes", +======= + "base64", + "bitflags", + "bytes 1.2.1", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "headers-core", "http", "httpdate", @@ -1255,6 +1352,7 @@ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" +<<<<<<< HEAD version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" @@ -1266,6 +1364,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", +======= +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes 1.2.1", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "futures-channel", "futures-core", "futures-util", @@ -1276,7 +1387,11 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", +<<<<<<< HEAD "socket2 0.4.10", +======= + "socket2", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "tokio", "tower-service", "tracing", @@ -1294,6 +1409,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", +<<<<<<< HEAD "windows-core", ] @@ -1304,6 +1420,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", +======= + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -1360,10 +1479,20 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +======= +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "itertools" @@ -1382,9 +1511,15 @@ dependencies = [ [[package]] name = "itoa" +<<<<<<< HEAD version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +======= +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "js-sys" @@ -1413,10 +1548,14 @@ dependencies = [ name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +<<<<<<< HEAD +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures 0.2.11", ] +======= +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "lazy_static" @@ -1520,7 +1659,11 @@ dependencies = [ name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" +<<<<<<< HEAD checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +======= +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "miniz_oxide" @@ -1539,8 +1682,12 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", - "wasi", - "windows-sys 0.52.0", + "wasi 0.11.0+wasi-snapshot-preview1", +<<<<<<< HEAD + "windows-sys 0.48.0", +======= + "windows-sys", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2086,6 +2233,11 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "winreg", +======= +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2094,8 +2246,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ +<<<<<<< HEAD "hmac", "subtle", +======= + "crypto-bigint 0.4.8", + "hmac 0.12.1", + "zeroize", +] + +[[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 0.5.2", + "untrusted", + "web-sys", + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2114,8 +2287,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", +<<<<<<< HEAD "rtoolbox", "windows-sys 0.48.0", +======= + "serde", + "serde_json", + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2170,7 +2349,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ +<<<<<<< HEAD "semver", +======= + "base64", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2179,11 +2366,51 @@ version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags 2.6.0", +<<<<<<< HEAD + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", +======= + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +dependencies = [ + "base64", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2213,8 +2440,13 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "474a033e7cb7c343a496026464a55bee3d98bc308d2f2ed53a9d8a21c68e1419" dependencies = [ +<<<<<<< HEAD "base64 0.13.1", "bitflags 1.3.2", +======= + "base64", + "bitflags", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "headers", "log", "serde", @@ -2456,6 +2688,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" +<<<<<<< HEAD version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" @@ -2467,11 +2700,18 @@ dependencies = [ [[package]] name = "socket2" version = "0.5.5" +======= +version = "0.4.7" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.52.0", +<<<<<<< HEAD + "windows-sys 0.48.0", +======= + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2669,7 +2909,44 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", +<<<<<<< HEAD "time", +======= + "time 0.3.11", +] + +[[package]] +name = "tendermint-rpc" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f14aafe3528a0f75e9f3f410b525617b2de16c4b7830a21f717eee62882ec60" +dependencies = [ + "async-trait", + "bytes 1.2.1", + "flex-error", + "futures", + "getrandom 0.2.7", + "http", + "hyper", + "hyper-proxy", + "hyper-rustls 0.22.1", + "peg", + "pin-project", + "serde", + "serde_bytes", + "serde_json", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto", + "thiserror", + "time 0.3.11", + "tokio", + "tracing", + "url 2.3.1", + "uuid 0.8.2", + "walkdir", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2723,10 +3000,27 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ +<<<<<<< HEAD "deranged", "itoa", "num-conv", "powerfmt", +======= + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "itoa", + "libc", + "num_threads", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "serde", "time-core", "time-macros", @@ -2782,7 +3076,11 @@ dependencies = [ "abscissa_core", "aes-gcm", "aes-kw", +<<<<<<< HEAD "base64 0.13.1", +======= + "base64", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "byteorder", "bytes", "chrono", @@ -2792,10 +3090,18 @@ dependencies = [ "ed25519-consensus", "elliptic-curve", "eyre", +<<<<<<< HEAD "getrandom 0.2.11", "hashicorp_vault", "hkd32", "hkdf", +======= + "getrandom 0.2.7", + "hkd32", + "hkdf 0.12.3", + "hyper", + "hyper-rustls 0.23.0", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "k256", "ledger", "mockito", @@ -2819,8 +3125,14 @@ dependencies = [ "tendermint-p2p", "tendermint-proto", "thiserror", - "url 2.5.2", +<<<<<<< HEAD + "url 2.5.0", "uuid", +======= + "ureq", + "url 2.3.1", + "uuid 1.1.2", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "wait-timeout", "yubihsm", "zeroize", @@ -2840,7 +3152,23 @@ dependencies = [ "pin-project-lite", "socket2 0.5.5", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.48.0", +======= +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes 1.2.1", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2862,6 +3190,31 @@ checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", +<<<<<<< HEAD +======= +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.6", + "tokio", + "webpki 0.22.0", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) ] [[package]] @@ -2882,6 +3235,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", +======= +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes 1.2.1", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "futures-core", "futures-sink", "pin-project-lite", @@ -2943,6 +3303,10 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ +<<<<<<< HEAD +======= + "cfg-if 1.0.0", +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2970,6 +3334,7 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "tracing-log" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2981,6 +3346,8 @@ dependencies = [ ] [[package]] +======= +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3019,7 +3386,11 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" +<<<<<<< HEAD checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +======= +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) [[package]] name = "unicode-bidi" @@ -3069,6 +3440,34 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD +======= +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +dependencies = [ + "base64", + "chunked_transfer", + "flate2", + "log", + "once_cell", + "rustls 0.20.6", + "serde", + "serde_json", + "url 2.3.1", + "webpki 0.22.0", + "webpki-roots 0.22.5", +] + +[[package]] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "url" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3141,6 +3540,20 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD +======= +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3182,6 +3595,7 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD name = "wasm-bindgen-futures" version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3194,6 +3608,8 @@ dependencies = [ ] [[package]] +======= +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "wasm-bindgen-macro" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3233,6 +3649,47 @@ dependencies = [ ] [[package]] +<<<<<<< HEAD +======= +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[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.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +dependencies = [ + "webpki 0.22.0", +] + +[[package]] +>>>>>>> 4c17928 (vault_client dependency replaced with ureq) name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 2086d71a..232ba48f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,9 @@ wait-timeout = "0.2" yubihsm = { version = "0.42", features = ["secp256k1", "setup", "usb"], optional = true } zeroize = "1" -hashicorp_vault = { version = "2.1.0", optional = true } + + +ureq = { version = "~2.5", default-features=false, features = ["tls", "json", "gzip"], optional = true} base64 = { version = "0.13.0", optional = true} aes-kw = { version = "0.2.1", features = ["std"], optional = true} rsa = { version = "0.6.1", default = true, optional = true} @@ -72,8 +74,7 @@ softsign = [] yubihsm-mock = ["yubihsm/mockhsm"] yubihsm-server = ["yubihsm/http-server", "rpassword"] fortanixdsm = ["elliptic-curve", "sdkms", "url", "uuid"] -hashicorp = ["hashicorp_vault", "base64", "aes-kw", "rsa", "rand", "aes-gcm"] - +hashicorp = ["ureq", "base64", "aes-kw", "rsa", "rand", "aes-gcm"] # Enable integer overflow checks in release builds for security reasons [profile.release] diff --git a/src/amino_types/block_id.rs b/src/amino_types/block_id.rs new file mode 100644 index 00000000..392201a2 --- /dev/null +++ b/src/amino_types/block_id.rs @@ -0,0 +1,188 @@ +use super::validate::{self, ConsensusMessage, Error::*}; +use eyre::eyre; +use prost_amino_derive::Message; +use tendermint::{ + block::{self, parts}, + hash::{Hash, SHA256_HASH_SIZE}, +}; +use tendermint_proto as proto; + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct BlockId { + #[prost_amino(bytes, tag = "1")] + pub hash: Vec, + #[prost_amino(message, tag = "2")] + pub parts_header: Option, +} + +impl BlockId { + pub fn new(hash: Vec, parts_header: Option) -> Self { + BlockId { hash, parts_header } + } +} + +/// Parse an Amino-encoded SHA-256 hash +fn parse_sha256_hash(bytes: &[u8]) -> eyre::Result { + Ok(bytes.try_into().map(Hash::Sha256)?) +} + +/// Parse `block::Id` from a type +pub trait ParseId { + /// Parse `block::Id`, or return an `Error` if parsing failed + fn parse_block_id(&self) -> eyre::Result; +} + +impl ParseId for BlockId { + fn parse_block_id(&self) -> eyre::Result { + let hash = parse_sha256_hash(&self.hash)?; + + let part_set_header = self + .parts_header + .as_ref() + .ok_or_else(|| eyre!("missing block ID parts header"))? + .parse_parts_header()?; + + Ok(block::Id { + hash, + part_set_header, + }) + } +} + +impl From<&block::Id> for BlockId { + fn from(bid: &block::Id) -> Self { + let bid_hash = bid.hash.as_bytes(); + + BlockId::new(bid_hash.to_vec(), Some(bid.part_set_header.into())) + } +} + +impl From for BlockId { + fn from(block_id: proto::types::BlockId) -> BlockId { + BlockId::new( + block_id.hash, + block_id.part_set_header.map(|psh| PartsSetHeader { + total: psh.total as i64, + hash: psh.hash, + }), + ) + } +} + +impl From for proto::types::BlockId { + fn from(block_id: BlockId) -> proto::types::BlockId { + proto::types::BlockId { + hash: block_id.hash, + part_set_header: block_id + .parts_header + .map(|psh| proto::types::PartSetHeader { + total: psh.total as u32, + hash: psh.hash, + }), + } + } +} + +impl ConsensusMessage for BlockId { + fn validate_basic(&self) -> Result<(), validate::Error> { + // Hash can be empty in case of POLBlockID in Proposal. + if !self.hash.is_empty() && self.hash.len() != SHA256_HASH_SIZE { + return Err(InvalidHashSize); + } + self.parts_header + .as_ref() + .map_or(Ok(()), ConsensusMessage::validate_basic) + } +} + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct CanonicalBlockId { + #[prost_amino(bytes, tag = "1")] + pub hash: Vec, + #[prost_amino(message, tag = "2")] + pub parts_header: Option, +} + +impl ParseId for CanonicalBlockId { + fn parse_block_id(&self) -> eyre::Result { + let hash = parse_sha256_hash(&self.hash)?; + let part_set_header = self + .parts_header + .as_ref() + .ok_or_else(|| eyre!("missing block ID parts header"))? + .parse_parts_header()?; + + Ok(block::Id { + hash, + part_set_header, + }) + } +} + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct PartsSetHeader { + #[prost_amino(int64, tag = "1")] + pub total: i64, + #[prost_amino(bytes, tag = "2")] + pub hash: Vec, +} + +impl PartsSetHeader { + pub fn new(total: i64, hash: Vec) -> Self { + PartsSetHeader { total, hash } + } +} + +impl From<&parts::Header> for PartsSetHeader { + fn from(parts: &parts::Header) -> Self { + PartsSetHeader::new(parts.total as i64, parts.hash.as_bytes().to_vec()) + } +} + +impl PartsSetHeader { + fn parse_parts_header(&self) -> eyre::Result { + Ok(block::parts::Header::new( + self.total as u32, + parse_sha256_hash(&self.hash)?, + )?) + } +} + +impl ConsensusMessage for PartsSetHeader { + fn validate_basic(&self) -> Result<(), validate::Error> { + if self.total < 0 { + return Err(NegativeTotal); + } + // Hash can be empty in case of POLBlockID.PartsHeader in Proposal. + if !self.hash.is_empty() && self.hash.len() != SHA256_HASH_SIZE { + return Err(InvalidHashSize); + } + Ok(()) + } +} + +impl From for PartsSetHeader { + fn from(header: block::parts::Header) -> PartsSetHeader { + PartsSetHeader { + total: header.total as i64, + hash: header.hash.into(), + } + } +} + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct CanonicalPartSetHeader { + #[prost_amino(bytes, tag = "1")] + pub hash: Vec, + #[prost_amino(int64, tag = "2")] + pub total: i64, +} + +impl CanonicalPartSetHeader { + fn parse_parts_header(&self) -> eyre::Result { + Ok(block::parts::Header::new( + self.total as u32, + parse_sha256_hash(&self.hash)?, + )?) + } +} diff --git a/src/amino_types/ed25519.rs b/src/amino_types/ed25519.rs new file mode 100644 index 00000000..7be1df0c --- /dev/null +++ b/src/amino_types/ed25519.rs @@ -0,0 +1,178 @@ +use super::compute_prefix; +use once_cell::sync::Lazy; +use prost_amino_derive::Message; +use tendermint::public_key::{Ed25519, PublicKey}; + +// Note:On the golang side this is generic in the sense that it could everything that implements +// github.com/tendermint/tendermint/crypto.PubKey +// While this is meant to be used with different key-types, it currently only uses a PubKeyEd25519 +// version. +// TODO(ismail): make this more generic (by modifying prost and adding a trait for PubKey) + +pub const AMINO_NAME: &str = "tendermint/remotesigner/PubKeyRequest"; +pub static AMINO_PREFIX: Lazy> = Lazy::new(|| compute_prefix(AMINO_NAME)); + +#[derive(Clone, Eq, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PubKeyResponse"] +pub struct PubKeyResponse { + #[prost_amino(bytes, tag = "1", amino_name = "tendermint/PubKeyEd25519")] + pub pub_key_ed25519: Vec, +} + +#[derive(Clone, Eq, PartialEq, Message)] +#[amino_name = "tendermint/remotesigner/PubKeyRequest"] +pub struct PubKeyRequest {} + +impl TryFrom for PublicKey { + type Error = eyre::Report; + + // This does not check if the underlying pub_key_ed25519 has the right size. + // The caller needs to make sure that this is actually the case. + fn try_from(response: PubKeyResponse) -> eyre::Result { + Ok(Ed25519::from_bytes(&response.pub_key_ed25519)?.into()) + } +} + +impl From for PubKeyResponse { + fn from(public_key: PublicKey) -> PubKeyResponse { + if let PublicKey::Ed25519(ref pk) = public_key { + PubKeyResponse { + pub_key_ed25519: pk.as_bytes().to_vec(), + } + } else { + unimplemented!( + "PubKeyResponse unimplemented for this type: {:?}", + public_key + ); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ed25519_dalek::PUBLIC_KEY_LENGTH; + use prost_amino::Message; + + #[test] + fn test_empty_pubkey_msg() { + // test-vector generated via the following go code: + // + // -------------------------------------------------------------------- + //package main + // + //import ( + // "fmt" + // + // "github.com/tendermint/go-amino" + // "github.com/tendermint/tendermint/crypto" + // "github.com/tendermint/tendermint/privval" + //) + // + //func main() { + // cdc := amino.NewCodec() + // + // cdc.RegisterInterface((*crypto.PubKey)(nil), nil) + // cdc.RegisterConcrete(crypto.PubKeyEd25519{}, + // "tendermint/PubKeyEd25519", nil) + // cdc.RegisterConcrete(&privval.PubKeyRequest{}, + // "tendermint/remotesigner/PubKeyRequest", nil) + // b, _ := cdc.MarshalBinary(&privval.PubKeyRequest{}) + // fmt.Printf("%#v\n\n", b) + //} + // -------------------------------------------------------------------- + // Output: + // []byte{0x4, 0xcb, 0x94, 0xd6, 0x20} + // + // + + let want = vec![0x4, 0xcb, 0x94, 0xd6, 0x20]; + let msg = PubKeyRequest {}; + let mut got = vec![]; + let _have = msg.encode(&mut got); + + assert_eq!(got, want); + + match PubKeyRequest::decode(want.as_ref()) { + Ok(have) => assert_eq!(have, msg), + Err(err) => panic!("{}", err.to_string()), + } + } + + #[test] + fn test_ed25519_pubkey_msg() { + // test-vector generated exactly as for test_empty_pubkey_msg + // but with the following modifications: + // cdc.RegisterConcrete(&privval.PubKeyResponse{}, + // "tendermint/remotesigner/PubKeyResponse", nil) + // + // var pubKey [32]byte + // copy(pubKey[:],[]byte{0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, + // 0xb5, 0x61, 0xbc, 0xe7, 0xc1, + // 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, 0x37, 0x32, 0xef, + // 0xed}) + // + // b, _ = cdc.MarshalBinary(&privval.PubKeyResponse{PubKey: crypto.PubKeyEd25519(pubKey)}) + // fmt.Printf("%#v\n\n", b) + // + let encoded = vec![ + 0x2b, // length + 0x17, 0xe, 0xd5, 0x7c, // prefix + 0xa, 0x25, 0x16, 0x24, 0xde, 0x64, 0x20, 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, + 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, + 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, 0x37, 0x32, 0xef, 0xed, + ]; + + let msg = PubKeyResponse { + pub_key_ed25519: vec![ + 0x79, 0xce, 0xd, 0xe0, 0x43, 0x33, 0x4a, 0xec, 0xe0, 0x8b, 0x7b, 0xb5, 0x61, 0xbc, + 0xe7, 0xc1, 0xd4, 0x69, 0xc3, 0x44, 0x26, 0xec, 0xef, 0xc0, 0x72, 0xa, 0x52, 0x4d, + 0x37, 0x32, 0xef, 0xed, + ], + }; + let mut got = vec![]; + let _have = msg.encode(&mut got); + + assert_eq!(got, encoded); + + match PubKeyResponse::decode(encoded.as_ref()) { + Ok(have) => assert_eq!(have, msg), + Err(err) => panic!("{}", err), + } + } + + #[test] + fn test_into() { + let raw_pk: [u8; PUBLIC_KEY_LENGTH] = [ + 0xaf, 0xf3, 0x94, 0xc5, 0xb7, 0x5c, 0xfb, 0xd, 0xd9, 0x28, 0xe5, 0x8a, 0x92, 0xdd, + 0x76, 0x55, 0x2b, 0x2e, 0x8d, 0x19, 0x6f, 0xe9, 0x12, 0x14, 0x50, 0x80, 0x6b, 0xd0, + 0xd9, 0x3f, 0xd0, 0xcb, + ]; + let want = PublicKey::Ed25519(Ed25519::from_bytes(&raw_pk).unwrap()); + let pk = PubKeyResponse { + pub_key_ed25519: vec![ + 0xaf, 0xf3, 0x94, 0xc5, 0xb7, 0x5c, 0xfb, 0xd, 0xd9, 0x28, 0xe5, 0x8a, 0x92, 0xdd, + 0x76, 0x55, 0x2b, 0x2e, 0x8d, 0x19, 0x6f, 0xe9, 0x12, 0x14, 0x50, 0x80, 0x6b, 0xd0, + 0xd9, 0x3f, 0xd0, 0xcb, + ], + }; + let orig = pk.clone(); + let got: PublicKey = pk.try_into().unwrap(); + + assert_eq!(got, want); + + // and back: + let round_trip_pk: PubKeyResponse = got.into(); + assert_eq!(round_trip_pk, orig); + } + + #[test] + #[should_panic] + fn test_empty_into() { + let empty_msg = PubKeyResponse { + pub_key_ed25519: vec![], + }; + // we expect this to panic: + let _got: PublicKey = empty_msg.try_into().unwrap(); + } +} diff --git a/src/amino_types/ping.rs b/src/amino_types/ping.rs new file mode 100644 index 00000000..41f4afa7 --- /dev/null +++ b/src/amino_types/ping.rs @@ -0,0 +1,14 @@ +use super::compute_prefix; +use once_cell::sync::Lazy; +use prost_amino_derive::Message; + +pub const AMINO_NAME: &str = "tendermint/remotesigner/PingRequest"; +pub static AMINO_PREFIX: Lazy> = Lazy::new(|| compute_prefix(AMINO_NAME)); + +#[derive(Clone, PartialEq, Eq, Message)] +#[amino_name = "tendermint/remotesigner/PingRequest"] +pub struct PingRequest {} + +#[derive(Clone, PartialEq, Eq, Message)] +#[amino_name = "tendermint/remotesigner/PingResponse"] +pub struct PingResponse {} diff --git a/src/amino_types/proposal.rs b/src/amino_types/proposal.rs new file mode 100644 index 00000000..0b23c639 --- /dev/null +++ b/src/amino_types/proposal.rs @@ -0,0 +1,358 @@ +use super::{ + block_id::{BlockId, CanonicalBlockId, CanonicalPartSetHeader, ParseId}, + compute_prefix, + remote_error::RemoteError, + signature::{SignableMsg, SignedMsgType}, + time::TimeMsg, + validate::{self, ConsensusMessage, Error::*}, + ParseChainId, TendermintRequest, +}; +use crate::{config::validator::ProtocolVersion, rpc}; +use bytes::BufMut; +use bytes_v0_5::BytesMut as BytesMutV05; +use ed25519_dalek as ed25519; +use once_cell::sync::Lazy; +use prost::Message as _; +use prost_amino::{EncodeError, Message}; +use prost_amino_derive::Message; +use tendermint::{block, chain, consensus, error}; +use tendermint_proto::types as proto_types; + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct Proposal { + #[prost_amino(uint32, tag = "1")] + pub msg_type: u32, + #[prost_amino(int64)] + pub height: i64, + #[prost_amino(int64)] + pub round: i64, + #[prost_amino(int64)] + pub pol_round: i64, + #[prost_amino(message)] + pub block_id: Option, + #[prost_amino(message)] + pub timestamp: Option, + #[prost_amino(bytes)] + pub signature: Vec, +} + +// TODO(tony): custom derive proc macro for this e.g. `derive(ParseBlockHeight)` +impl block::ParseHeight for Proposal { + fn parse_block_height(&self) -> Result { + block::Height::try_from(self.height) + } +} + +pub const AMINO_NAME: &str = "tendermint/remotesigner/SignProposalRequest"; +pub static AMINO_PREFIX: Lazy> = Lazy::new(|| compute_prefix(AMINO_NAME)); + +#[derive(Clone, PartialEq, Eq, Message)] +#[amino_name = "tendermint/remotesigner/SignProposalRequest"] +pub struct SignProposalRequest { + #[prost_amino(message, tag = "1")] + pub proposal: Option, +} + +#[derive(Clone, PartialEq, Message)] +struct CanonicalProposal { + #[prost_amino(uint32, tag = "1")] + msg_type: u32, /* this is a byte in golang, which is a varint encoded UInt8 (using amino's + * EncodeUvarint) */ + #[prost_amino(sfixed64)] + height: i64, + #[prost_amino(sfixed64)] + round: i64, + #[prost_amino(sfixed64)] + pol_round: i64, + #[prost_amino(message)] + block_id: Option, + #[prost_amino(message)] + timestamp: Option, + #[prost_amino(string)] + pub chain_id: String, +} + +impl ParseChainId for CanonicalProposal { + fn parse_chain_id(&self) -> Result { + self.chain_id.parse() + } +} + +impl block::ParseHeight for CanonicalProposal { + fn parse_block_height(&self) -> Result { + block::Height::try_from(self.height) + } +} + +#[derive(Clone, PartialEq, Eq, Message)] +#[amino_name = "tendermint/remotesigner/SignedProposalResponse"] +pub struct SignedProposalResponse { + #[prost_amino(message, tag = "1")] + pub proposal: Option, + #[prost_amino(message, tag = "2")] + pub err: Option, +} + +impl SignableMsg for SignProposalRequest { + fn sign_bytes( + &self, + chain_id: chain::Id, + protocol_version: ProtocolVersion, + sign_bytes: &mut B, + ) -> Result + where + B: BufMut, + { + let mut spr = self.clone(); + if let Some(ref mut pr) = spr.proposal { + pr.signature = vec![]; + } + let proposal = spr.proposal.unwrap(); + + if protocol_version.is_protobuf() { + let block_id = match proposal.block_id.as_ref() { + Some(x) if x.hash.is_empty() => None, + Some(x) => Some(proto_types::CanonicalBlockId { + hash: x.hash.clone(), + part_set_header: x.parts_header.as_ref().map(|y| { + proto_types::CanonicalPartSetHeader { + total: y.total as u32, + hash: y.hash.clone(), + } + }), + }), + None => None, + }; + + let cp = proto_types::CanonicalProposal { + chain_id: chain_id.to_string(), + r#type: SignedMsgType::Proposal.to_u32() as i32, + height: proposal.height, + block_id, + pol_round: proposal.pol_round, + round: proposal.round, + timestamp: proposal.timestamp.map(Into::into), + }; + + cp.encode_length_delimited(sign_bytes).unwrap(); + } else { + let cp = CanonicalProposal { + chain_id: chain_id.to_string(), + msg_type: SignedMsgType::Proposal.to_u32(), + height: proposal.height, + block_id: match proposal.block_id { + Some(bid) => Some(CanonicalBlockId { + hash: bid.hash, + parts_header: match bid.parts_header { + Some(psh) => Some(CanonicalPartSetHeader { + hash: psh.hash, + total: psh.total, + }), + None => None, + }, + }), + None => None, + }, + pol_round: proposal.pol_round, + round: proposal.round, + timestamp: proposal.timestamp, + }; + + let mut sign_bytes_v0_5 = BytesMutV05::new(); + cp.encode_length_delimited(&mut sign_bytes_v0_5)?; + sign_bytes.put_slice(sign_bytes_v0_5.as_ref()); + } + + Ok(true) + } + fn set_signature(&mut self, sig: &ed25519::Signature) { + if let Some(ref mut prop) = self.proposal { + prop.signature = sig.as_ref().to_vec(); + } + } + fn validate(&self) -> Result<(), validate::Error> { + match self.proposal { + Some(ref p) => p.validate_basic(), + None => Err(MissingConsensusMessage), + } + } + fn consensus_state(&self) -> Option { + match self.proposal { + Some(ref p) => Some(consensus::State { + height: match block::Height::try_from(p.height) { + Ok(h) => h, + Err(_err) => return None, // TODO(tarcieri): return an error? + }, + round: block::Round::from(p.round as u16), + step: 3, + block_id: { + match p.block_id { + Some(ref b) => match b.parse_block_id() { + Ok(id) => Some(id), + Err(_) => None, + }, + None => None, + } + }, + }), + None => None, + } + } + + fn height(&self) -> Option { + self.proposal.as_ref().map(|proposal| proposal.height) + } + + fn msg_type(&self) -> Option { + Some(SignedMsgType::Proposal) + } +} + +impl TendermintRequest for SignProposalRequest { + fn build_response(self, error: Option) -> rpc::Response { + let response = if let Some(e) = error { + SignedProposalResponse { + proposal: None, + err: Some(e), + } + } else { + SignedProposalResponse { + proposal: self.proposal, + err: None, + } + }; + + rpc::Response::SignedProposal(response) + } +} + +impl ConsensusMessage for Proposal { + fn validate_basic(&self) -> Result<(), validate::Error> { + if self.msg_type != SignedMsgType::Proposal.to_u32() { + return Err(InvalidMessageType); + } + if self.height < 0 { + return Err(NegativeHeight); + } + if self.round < 0 { + return Err(NegativeRound); + } + if self.pol_round < -1 { + return Err(NegativePolRound); + } + // TODO validate proposal's block_id + + // signature will be missing as the KMS provides it + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::amino_types::block_id::PartsSetHeader; + use chrono::{DateTime, Utc}; + use prost_amino::Message; + + #[test] + fn test_serialization() { + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + let proposal = Proposal { + msg_type: SignedMsgType::Proposal.to_u32(), + height: 12345, + round: 23456, + pol_round: -1, + block_id: Some(BlockId { + hash: b"hash".to_vec(), + parts_header: Some(PartsSetHeader { + total: 1_000_000, + hash: b"parts_hash".to_vec(), + }), + }), + timestamp: Some(t), + signature: vec![], + }; + let mut got = vec![]; + + let _have = SignProposalRequest { + proposal: Some(proposal), + } + .encode(&mut got); + // test-vector generated via: + // cdc := amino.NewCodec() + // privval.RegisterRemoteSignerMsg(cdc) + // stamp, _ := time.Parse(time.RFC3339Nano, "2018-02-11T07:09:22.765Z") + // data, _ := cdc.MarshalBinaryLengthPrefixed(privval.SignProposalRequest{Proposal: + // &types.Proposal{ Type: types.ProposalType, // 0x20 + // Height: 12345, + // Round: 23456, + // POLRound: -1, + // BlockID: types.BlockID{ + // Hash: []byte("hash"), + // PartsHeader: types.PartSetHeader{ + // Hash: []byte("parts_hash"), + // Total: 1000000, + // }, + // }, + // Timestamp: stamp, + // }}) + // fmt.Println(strings.Join(strings.Split(fmt.Sprintf("%v", data), " "), ", ")) + let want = vec![ + 66, // len + 189, 228, 152, 226, // prefix + 10, 60, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 1, 42, 24, 10, 4, 104, 97, 115, 104, 18, 16, 8, 192, 132, 61, 18, 10, 112, + 97, 114, 116, 115, 95, 104, 97, 115, 104, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, + 242, 227, 236, 2, + ]; + + assert_eq!(got, want) + } + + #[test] + fn test_deserialization() { + let dt = "2018-02-11T07:09:22.765Z".parse::>().unwrap(); + let t = TimeMsg { + seconds: dt.timestamp(), + nanos: dt.timestamp_subsec_nanos() as i32, + }; + let proposal = Proposal { + msg_type: SignedMsgType::Proposal.to_u32(), + height: 12345, + round: 23456, + timestamp: Some(t), + + pol_round: -1, + block_id: Some(BlockId { + hash: b"hash".to_vec(), + parts_header: Some(PartsSetHeader { + total: 1_000_000, + hash: b"parts_hash".to_vec(), + }), + }), + signature: vec![], + }; + let want = SignProposalRequest { + proposal: Some(proposal), + }; + + let data = vec![ + 66, // len + 189, 228, 152, 226, // prefix + 10, 60, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 1, 42, 24, 10, 4, 104, 97, 115, 104, 18, 16, 8, 192, 132, 61, 18, 10, 112, + 97, 114, 116, 115, 95, 104, 97, 115, 104, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, + 242, 227, 236, 2, + ]; + + match SignProposalRequest::decode(data.as_ref()) { + Ok(have) => assert_eq!(have, want), + Err(err) => panic!("{}", err.to_string()), + } + } +} diff --git a/src/amino_types/remote_error.rs b/src/amino_types/remote_error.rs new file mode 100644 index 00000000..277ceceb --- /dev/null +++ b/src/amino_types/remote_error.rs @@ -0,0 +1,32 @@ +use prost_amino_derive::Message; + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct RemoteError { + #[prost_amino(sint32, tag = "1")] + pub code: i32, + #[prost_amino(string, tag = "2")] + pub description: String, +} + +/// Error codes for remote signer failures +// TODO(tarcieri): add these to Tendermint. See corresponding TODO here: +// +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum RemoteErrorCode { + /// Generic error code useful when the others don't apply + RemoteSignerError = 1, + + /// Double signing detected + DoubleSignError = 2, +} + +impl RemoteError { + /// Create a new double signing error with the given message + pub fn double_sign(height: i64) -> Self { + RemoteError { + code: RemoteErrorCode::DoubleSignError as i32, + description: format!("double signing requested at height: {}", height), + } + } +} diff --git a/src/amino_types/time.rs b/src/amino_types/time.rs new file mode 100644 index 00000000..0a94f14f --- /dev/null +++ b/src/amino_types/time.rs @@ -0,0 +1,66 @@ +//! Timestamps + +use prost_amino_derive::Message; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tendermint::{ + error::Error, + time::{ParseTimestamp, Time}, +}; +use tendermint_proto as proto; + +#[derive(Clone, PartialEq, Eq, Message)] +pub struct TimeMsg { + // TODO(ismail): switch to protobuf's well known type as soon as + // https://github.com/tendermint/go-amino/pull/224 was merged + // and tendermint caught up on the latest amino release. + #[prost_amino(int64, tag = "1")] + pub seconds: i64, + #[prost_amino(int32, tag = "2")] + pub nanos: i32, +} + +impl ParseTimestamp for TimeMsg { + fn parse_timestamp(&self) -> Result { + Time::from_unix_timestamp(self.seconds, self.nanos as u32) + } +} + +impl From