diff --git a/.github/workflows/artifacts.yml b/.github/workflows/stagex.yml similarity index 97% rename from .github/workflows/artifacts.yml rename to .github/workflows/stagex.yml index 3d848e6c..80546a27 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/stagex.yml @@ -1,4 +1,4 @@ -name: artifacts-build +name: stagex-build on: push: diff --git a/.gitignore b/.gitignore index e589aef0..ec33ad6a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ target/ !src/integration/mock/boot-e2e/all-personal-dir/user2-dir/* !src/integration/mock/boot-e2e/all-personal-dir/user3-dir/* !src/integration/mock/boot-e2e/genesis-dir/* +!src/integration/mock/new-share-set-secrets/* +!src/integration/mock/reshard/user1/qkey1/* +!src/integration/mock/reshard/user2/qkey1/* +!src/integration/mock/reshard/user3/qkey1/* src/integration/mock/pivot-build-fingerprints.txt src/integration/pivot_ok2_works src/integration/pivot_ok_works diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 diff --git a/.lfsconfig b/.lfsconfig deleted file mode 100644 index 0c9ce0f4..00000000 --- a/.lfsconfig +++ /dev/null @@ -1,2 +0,0 @@ -[lfs] - url = https://turnkey.engineering/if-you-see-this-lfs-is-not-configured-correctly diff --git a/CHANGELOG.MD b/CHANGELOG.MD index e3da98f8..19ac69d1 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -17,6 +17,14 @@ Removed: for now removed features. Fixed: for any bug fixes. Security: in case of vulnerabilities. +## Unreleased + +### Added + +- BREAKING CHANGE: qos_core: quorum key resharding service, new state machine transitions, and new `ProtocolMsg` variants (#428) +- qos_client: commands to run quorum key resharding and high level documentation (#428) +- qos_crypto: function to generate n choose k variants (#428) +- qos_hex: support more array sizes for serde deserialize ## Added: `qos_net` crate In PR #449 we introduce `qos_net`, a crate which contains a socket<>TCP proxy to let enclave application communicate with the outside world. diff --git a/README.md b/README.md index c75cb596..dac6d1da 100644 --- a/README.md +++ b/README.md @@ -139,22 +139,4 @@ make toolchain-shell make toolchain-update ``` - -### Release Process - - 0. Determine the release semver version by consulting the [changelog](./CHANGELOG.MD). - 1. Create a branch for your release e.g. - `git checkout -b release/v1.0.0` - 2. Run `make dist` as described in ["Release" section](#release) - 3. Commit the new dist folder `git commit -m "Release v1.0.0" -- dist/` - 4. Push up your branch to github, and make a pull request. - 5. You may also create and push a signed `-rcX` git tag where the number after `rc` doesn't already exist. - `git tag -S v1.0.0-rc0 -m v1.0.0-rc0` - `git push origin v1.0.0-rc0` - 6. Wait for others to replicate your build, see ["Verify" section](#verify) - 7. Once the release has enough `git sig` signatures, make the final tag and merge the pull request. - `git tag -S v1.0.0 -m v1.0.0` - `git push origin v1.0.0` - - [gs]: https://codeberg.org/distrust/git-sig diff --git a/src/Cargo.lock b/src/Cargo.lock index ab5ef970..965a31a4 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -123,7 +123,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -255,6 +255,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -292,7 +298,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.68", + "syn 2.0.77", "which", ] @@ -314,7 +320,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -337,7 +343,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", "syn_derive", ] @@ -478,7 +484,19 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", "rand_core", "subtle", "zeroize", @@ -490,7 +508,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] @@ -524,7 +542,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -535,7 +553,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -622,7 +640,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -638,7 +656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", - "elliptic-curve", + "elliptic-curve 0.12.3", "rfc6979", "signature 1.6.4", ] @@ -650,7 +668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" dependencies = [ "der", - "elliptic-curve", + "elliptic-curve 0.12.3", "rfc6979", "signature 2.0.0", ] @@ -667,13 +685,13 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", "der", "digest", - "ff", - "generic-array", - "group", + "ff 0.12.1", + "generic-array 0.14.7", + "group 0.12.1", "hkdf", "pem-rfc7468", "pkcs8", @@ -683,6 +701,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "elliptic-curve" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", + "hkdf", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "enum-as-inner" version = "0.6.0" @@ -692,7 +728,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -721,6 +757,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "flagset" version = "0.4.5" @@ -804,7 +850,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -845,6 +891,16 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe739944a5406424e080edccb6add95685130b9f160d5407c639c7df0c5836b0" +dependencies = [ + "typenum", ] [[package]] @@ -886,7 +942,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core", "subtle", ] @@ -1137,7 +1204,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1159,6 +1226,7 @@ dependencies = [ "rand", "rustls", "serde", + "serde_json", "tokio", "ureq", "webpki-roots", @@ -1203,6 +1271,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1470,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ "ecdsa 0.14.8", - "elliptic-curve", + "elliptic-curve 0.12.3", "sha2", ] @@ -1481,7 +1558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" dependencies = [ "ecdsa 0.15.1", - "elliptic-curve", + "elliptic-curve 0.12.3", "primeorder", "sha2", ] @@ -1493,7 +1570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" dependencies = [ "ecdsa 0.14.8", - "elliptic-curve", + "elliptic-curve 0.12.3", "sha2", ] @@ -1504,7 +1581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" dependencies = [ "ecdsa 0.15.1", - "elliptic-curve", + "elliptic-curve 0.12.3", "primeorder", "sha2", ] @@ -1598,7 +1675,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -1672,7 +1749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -1681,7 +1758,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.12.3", ] [[package]] @@ -1742,6 +1819,7 @@ dependencies = [ "qos_test_primitives", "rand_core", "rpassword", + "serde", "serde_json", "ureq", "x509", @@ -1765,6 +1843,7 @@ dependencies = [ "rustls", "serde", "serde_bytes", + "vsss-rs", "webpki-roots", ] @@ -1774,7 +1853,10 @@ version = "0.1.0" dependencies = [ "qos_hex", "rand", + "rand_core", "sha2", + "thiserror", + "vsss-rs", ] [[package]] @@ -1856,9 +1938,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1937,7 +2019,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] @@ -2090,9 +2172,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", + "base16ct 0.1.1", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "subtle", "zeroize", @@ -2143,7 +2225,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2176,7 +2258,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2206,7 +2288,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2231,6 +2313,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2323,9 +2415,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2341,7 +2433,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2364,22 +2456,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] [[package]] @@ -2453,7 +2565,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2519,7 +2631,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] [[package]] @@ -2626,6 +2738,22 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsss-rs" +version = "4.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fabeca519a296f0b39428cfe496b600c0179c9498687986449d61fa40e60806" +dependencies = [ + "crypto-bigint 0.5.5", + "elliptic-curve 0.13.7", + "generic-array 1.0.0", + "rand_core", + "serde", + "sha3", + "subtle", + "thiserror-no-std", +] + [[package]] name = "want" version = "0.3.1" @@ -2662,7 +2790,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -2684,7 +2812,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2929,12 +3057,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10e6fa9476951a9b93d9a31aa5554b5bbac7aafdc5b23e663eb3f9b635c86053" dependencies = [ - "base16ct", + "base16ct 0.1.1", "chrono", "cookie-factory", "der-parser", "des", - "elliptic-curve", + "elliptic-curve 0.12.3", "hmac", "log", "nom", @@ -2974,5 +3102,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.77", ] diff --git a/src/Makefile b/src/Makefile index f4d53eab..875a54fb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -48,7 +48,7 @@ local-host: cargo run --bin qos_host \ -- \ --host-ip 127.0.0.1 \ - --host-port 3000 \ + --host-port 3001 \ --usock ./dev.sock .PHONY: vm-host diff --git a/src/README.md b/src/README.md index 6fdc3c62..d0a5c852 100644 --- a/src/README.md +++ b/src/README.md @@ -12,19 +12,15 @@ The Quorum Key itself can be used by QuorumOS and enclave apps to encrypt and au ### Submitting a PR -Before a PR can be merged it must: - -Be formatted +Before a PR can be merged, you must run ```bash -make lint +make lint # apply standardized code formatting +make test # run unit tests locally ``` -And pass all tests +All tests must pass. -```bash -make test-all -``` The PR will also need to pass the `build-linux-only-crates` github workflow. There are 3 crates excluded from the rust workspace: `qos_system`, `qos_aws`, and `init`. These crates are excluded because they only build on linux. If you are not working directly with these crates they generally only need to be updated if the dependencies for `qos_core` change. The linux only crates each have their own lockfile and that will need to be up to date for deterministic builds to work. To update the locks files you will need a linux build environment. Once in a linux build enviroment you can run `make build-linux-only`, which will update lock files if neccesary; any updated lock files should then be committed. diff --git a/src/images/common/Containerfile b/src/images/common/Containerfile index b723b21a..b380c077 100644 --- a/src/images/common/Containerfile +++ b/src/images/common/Containerfile @@ -50,4 +50,4 @@ COPY --from=file . / COPY --from=gcc . / COPY --from=linux-nitro /bzImage . COPY --from=linux-nitro /nsm.ko . -COPY --from=linux-nitro /linux.config . +COPY --from=linux-nitro /linux.config . \ No newline at end of file diff --git a/src/init/Cargo.lock b/src/init/Cargo.lock index d75afd8f..1302ec8b 100644 --- a/src/init/Cargo.lock +++ b/src/init/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -91,6 +91,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -115,7 +121,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -138,7 +144,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "syn_derive", ] @@ -148,11 +154,20 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" -version = "1.0.99" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -197,15 +212,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -216,7 +231,19 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", "rand_core", "subtle", "zeroize", @@ -228,7 +255,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] @@ -243,9 +270,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -253,27 +280,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -329,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" dependencies = [ "der", - "elliptic-curve", + "elliptic-curve 0.12.3", "rfc6979", "signature", ] @@ -340,13 +367,13 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", "der", "digest", - "ff", - "generic-array", - "group", + "ff 0.12.1", + "generic-array 0.14.7", + "group 0.12.1", "hkdf", "rand_core", "sec1", @@ -354,6 +381,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", + "hkdf", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -370,11 +415,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "flagset" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" [[package]] name = "fnv" @@ -390,13 +445,23 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a" +dependencies = [ + "typenum", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -419,7 +484,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core", "subtle", ] @@ -508,9 +584,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -534,7 +610,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -545,13 +621,22 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.149" @@ -560,9 +645,9 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" @@ -626,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" dependencies = [ "ecdsa", - "elliptic-curve", + "elliptic-curve 0.12.3", "primeorder", "sha2", ] @@ -638,7 +723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" dependencies = [ "ecdsa", - "elliptic-curve", + "elliptic-curve 0.12.3", "primeorder", "sha2", ] @@ -688,9 +773,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "primeorder" @@ -698,14 +786,14 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.12.3", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] @@ -736,9 +824,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -765,6 +853,7 @@ dependencies = [ "qos_p256", "serde", "serde_bytes", + "vsss-rs", ] [[package]] @@ -772,7 +861,10 @@ name = "qos_crypto" version = "0.1.0" dependencies = [ "rand", + "rand_core", "sha2", + "thiserror", + "vsss-rs", ] [[package]] @@ -821,9 +913,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -864,7 +956,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] @@ -896,9 +988,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", + "base16ct 0.1.1", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "subtle", "zeroize", @@ -906,18 +998,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] @@ -934,22 +1026,23 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -962,20 +1055,20 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] name = "serde_with" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -985,14 +1078,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] [[package]] @@ -1006,6 +1099,22 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.0.0" @@ -1040,9 +1149,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1057,9 +1166,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1075,7 +1184,47 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] [[package]] @@ -1111,17 +1260,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "toml_datetime", "winnow", ] @@ -1134,9 +1283,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "universal-hash" @@ -1156,9 +1305,25 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsss-rs" +version = "4.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "8fabeca519a296f0b39428cfe496b600c0179c9498687986449d61fa40e60806" +dependencies = [ + "crypto-bigint 0.5.5", + "elliptic-curve 0.13.8", + "generic-array 1.1.0", + "rand_core", + "serde", + "sha3", + "subtle", + "thiserror-no-std", +] [[package]] name = "wasi" @@ -1168,34 +1333,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1203,22 +1369,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "webpki" @@ -1250,9 +1416,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1266,57 +1432,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -1333,6 +1499,27 @@ dependencies = [ "spki", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -1350,5 +1537,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.77", ] diff --git a/src/integration/Cargo.toml b/src/integration/Cargo.toml index fe0d2dd8..70ceda58 100644 --- a/src/integration/Cargo.toml +++ b/src/integration/Cargo.toml @@ -27,3 +27,4 @@ aws-nitro-enclaves-nsm-api = { version = "0.3", default-features = false } rand = "0.8" ureq = { version = "2.9", features = ["json"], default-features = false } serde = { version = "1", features = ["derive"] } +serde_json = "1.0" diff --git a/src/integration/mock/keys/new-share-set/quorum_threshold b/src/integration/mock/keys/new-share-set/quorum_threshold new file mode 100644 index 00000000..e440e5c8 --- /dev/null +++ b/src/integration/mock/keys/new-share-set/quorum_threshold @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/src/integration/mock/keys/new-share-set/reshard-1.pub b/src/integration/mock/keys/new-share-set/reshard-1.pub new file mode 100644 index 00000000..9e6fad97 --- /dev/null +++ b/src/integration/mock/keys/new-share-set/reshard-1.pub @@ -0,0 +1 @@ +040ee9045f3718bd1345dccf88693c993626d08448fdeba8ecaf1b867f4d0572d439852ef460963a9e8fab08864a55994c0779216b44a165b4eaced98722ed3778041646e59014eaec046b2636d3943f446282363c26cf995320d5944b8b4d7af0aa588c208c13ded5c86c3e9a31af687c4027d4636173f405503e7b1baeeee7eaa5 \ No newline at end of file diff --git a/src/integration/mock/keys/new-share-set/reshard-2.pub b/src/integration/mock/keys/new-share-set/reshard-2.pub new file mode 100644 index 00000000..42103a6b --- /dev/null +++ b/src/integration/mock/keys/new-share-set/reshard-2.pub @@ -0,0 +1 @@ +04c82672b2f8c4d520c5c7cda207b4a05f433e4db7f0daed9bbde6f54d42814af5aeabec191d2dda32ba4cdc6616aa3fda0a6711affa0d42efbe11144043028622044810d6d24626abfe6c31e884e674c870a2197c9e9cd80786b2fd3a087e2c38cad8376d9b7086901915d261ecb92bde5a757d27bbf1a20904120ff079b8a8ef71 \ No newline at end of file diff --git a/src/integration/mock/keys/new-share-set/reshard-3.pub b/src/integration/mock/keys/new-share-set/reshard-3.pub new file mode 100644 index 00000000..8b39c7af --- /dev/null +++ b/src/integration/mock/keys/new-share-set/reshard-3.pub @@ -0,0 +1 @@ +049872acc56bca90eea07e1e1185e3015be3b7295b4ba484299702489bf4858b1374928b335d3405a16221ec240e80817fbfd783c7052446a31bd1821a9a10ff9c0469361a228e22e7cad34774a50f7cd8f97e7d6542f3903bf9d14647302691ef9195ae2c08ec62dcd0e845bc75e94ef8b9fa45925199a2f7d94d00981d6d2e0d85 \ No newline at end of file diff --git a/src/integration/mock/keys/new-share-set/reshard-4.pub b/src/integration/mock/keys/new-share-set/reshard-4.pub new file mode 100644 index 00000000..f4b9c0a3 --- /dev/null +++ b/src/integration/mock/keys/new-share-set/reshard-4.pub @@ -0,0 +1 @@ +0442993076a3b8345cb58b860477bce9db21bb6caceae8df298860410594ea08d4fc2ffec944fd7623a893b57037e0f20c44ff8eee6eff03110717efb9269181ed04bb495296212027597e2eb93ffbba07f0c41ae3018409b9ad2177e87b53a2729806f52ad6d0f6399ca3d37edddc81a687cd2a0a9f8aab914d76be2930ff8f5bba \ No newline at end of file diff --git a/src/integration/mock/new-share-set-secrets/reshard-1.secret b/src/integration/mock/new-share-set-secrets/reshard-1.secret new file mode 100644 index 00000000..38887d3a --- /dev/null +++ b/src/integration/mock/new-share-set-secrets/reshard-1.secret @@ -0,0 +1 @@ +60dd1d44decfa12be68c49abdb47b02c7d03e63de8f6d61ac7d9c4a59e2bf381 \ No newline at end of file diff --git a/src/integration/mock/new-share-set-secrets/reshard-2.secret b/src/integration/mock/new-share-set-secrets/reshard-2.secret new file mode 100644 index 00000000..7f06d1f4 --- /dev/null +++ b/src/integration/mock/new-share-set-secrets/reshard-2.secret @@ -0,0 +1 @@ +1b28ba3a047709e4bac8f5911bd213dbeca7b7023a702ea5333837a80c2ed170 \ No newline at end of file diff --git a/src/integration/mock/new-share-set-secrets/reshard-3.secret b/src/integration/mock/new-share-set-secrets/reshard-3.secret new file mode 100644 index 00000000..ded6e818 --- /dev/null +++ b/src/integration/mock/new-share-set-secrets/reshard-3.secret @@ -0,0 +1 @@ +f37186894abb1f45ce0eb5b24b5184334d7d85278037d28af11423f50043d83b \ No newline at end of file diff --git a/src/integration/mock/new-share-set-secrets/reshard-4.secret b/src/integration/mock/new-share-set-secrets/reshard-4.secret new file mode 100644 index 00000000..e008df7d --- /dev/null +++ b/src/integration/mock/new-share-set-secrets/reshard-4.secret @@ -0,0 +1 @@ +ccb796f57e4a5f52f2ebd81af50a7c98d7576b5503b5dddc337e67b6217d1fa3 \ No newline at end of file diff --git a/src/integration/mock/reshard/user1/qkey1/quorum_key.pub b/src/integration/mock/reshard/user1/qkey1/quorum_key.pub new file mode 100644 index 00000000..3ecb87a8 --- /dev/null +++ b/src/integration/mock/reshard/user1/qkey1/quorum_key.pub @@ -0,0 +1 @@ +04c9434ba0a681ee7c21e17c7ce4f668360803686b198774c9362dac090f9995eeb68961319370969bd0d657167d9cfce13a7466ec47aba9845fbfc4fe9277866d04043daa777f57c1ebef21ff3eb71e00a681921da56186ac96b5d3b06b645c88c512fe8072d12971ce1f9592ef6bafd98b4982f8cf73cb6e80c8f6424294e54c71 \ No newline at end of file diff --git a/src/integration/mock/reshard/user1/qkey1/user1.share b/src/integration/mock/reshard/user1/qkey1/user1.share new file mode 100644 index 00000000..f42b37d9 Binary files /dev/null and b/src/integration/mock/reshard/user1/qkey1/user1.share differ diff --git a/src/integration/mock/reshard/user2/qkey1/quorum_key.pub b/src/integration/mock/reshard/user2/qkey1/quorum_key.pub new file mode 100644 index 00000000..3ecb87a8 --- /dev/null +++ b/src/integration/mock/reshard/user2/qkey1/quorum_key.pub @@ -0,0 +1 @@ +04c9434ba0a681ee7c21e17c7ce4f668360803686b198774c9362dac090f9995eeb68961319370969bd0d657167d9cfce13a7466ec47aba9845fbfc4fe9277866d04043daa777f57c1ebef21ff3eb71e00a681921da56186ac96b5d3b06b645c88c512fe8072d12971ce1f9592ef6bafd98b4982f8cf73cb6e80c8f6424294e54c71 \ No newline at end of file diff --git a/src/integration/mock/reshard/user2/qkey1/user2.share b/src/integration/mock/reshard/user2/qkey1/user2.share new file mode 100644 index 00000000..87faf26a Binary files /dev/null and b/src/integration/mock/reshard/user2/qkey1/user2.share differ diff --git a/src/integration/mock/reshard/user3/qkey1/quorum_key.pub b/src/integration/mock/reshard/user3/qkey1/quorum_key.pub new file mode 100644 index 00000000..3ecb87a8 --- /dev/null +++ b/src/integration/mock/reshard/user3/qkey1/quorum_key.pub @@ -0,0 +1 @@ +04c9434ba0a681ee7c21e17c7ce4f668360803686b198774c9362dac090f9995eeb68961319370969bd0d657167d9cfce13a7466ec47aba9845fbfc4fe9277866d04043daa777f57c1ebef21ff3eb71e00a681921da56186ac96b5d3b06b645c88c512fe8072d12971ce1f9592ef6bafd98b4982f8cf73cb6e80c8f6424294e54c71 \ No newline at end of file diff --git a/src/integration/mock/reshard/user3/qkey1/user3.share b/src/integration/mock/reshard/user3/qkey1/user3.share new file mode 100644 index 00000000..9ad63c59 Binary files /dev/null and b/src/integration/mock/reshard/user3/qkey1/user3.share differ diff --git a/src/integration/src/lib.rs b/src/integration/src/lib.rs index 3d074171..2a1a5fea 100644 --- a/src/integration/src/lib.rs +++ b/src/integration/src/lib.rs @@ -38,6 +38,8 @@ pub const LOCAL_HOST: &str = "127.0.0.1"; pub const PCR3: &str = "78fce75db17cd4e0a3fb8dad3ad128ca5e77edbb2b2c7f75329dccd99aa5f6ef4fc1f1a452e315b9e98f9e312e6921e6"; /// QOS dist directory. pub const QOS_DIST_DIR: &str = "./mock/dist"; +/// Mock pcr3 pre-image. +pub const PCR3_PRE_IMAGE_PATH: &str = "./mock/namespaces/pcr3-preimage.txt"; const MSG: &str = "msg"; diff --git a/src/integration/tests/boot.rs b/src/integration/tests/boot.rs index e5f0bd05..8df54b02 100644 --- a/src/integration/tests/boot.rs +++ b/src/integration/tests/boot.rs @@ -7,7 +7,8 @@ use std::{ use borsh::de::BorshDeserialize; use integration::{ - LOCAL_HOST, PIVOT_OK2_PATH, PIVOT_OK2_SUCCESS_FILE, QOS_DIST_DIR, + LOCAL_HOST, PCR3_PRE_IMAGE_PATH, PIVOT_OK2_PATH, PIVOT_OK2_SUCCESS_FILE, + QOS_DIST_DIR, }; use qos_core::protocol::{ services::{ @@ -51,7 +52,6 @@ async fn standard_boot_e2e() { let namespace = "quit-coding-to-vape"; let personal_dir = |user: &str| format!("{all_personal_dir}/{user}-dir"); - let user1 = "user1"; let user2 = "user2"; let user3 = "user3"; @@ -81,7 +81,7 @@ async fn standard_boot_e2e() { "--qos-release-dir", QOS_DIST_DIR, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--manifest-path", &cli_manifest_path, "--pivot-args", @@ -157,7 +157,7 @@ async fn standard_boot_e2e() { "--manifest-approvals-dir", &*boot_dir, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--pivot-hash-path", PIVOT_HASH_PATH, "--qos-release-dir", @@ -306,7 +306,7 @@ async fn standard_boot_e2e() { "--host-ip", LOCAL_HOST, "--pcr3-preimage-path", - "./mock/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--unsafe-skip-attestation", ]) .spawn() @@ -361,7 +361,7 @@ async fn standard_boot_e2e() { "--manifest-envelope-path", &manifest_envelope_path, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--manifest-set-dir", "./mock/keys/manifest-set", "--alias", @@ -400,9 +400,9 @@ async fn standard_boot_e2e() { stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); assert_eq!( - &stdout.next().unwrap().unwrap(), - "Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (yes/no)" - ); + &stdout.next().unwrap().unwrap(), + "Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (yes/no)" + ); stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); assert_eq!( diff --git a/src/integration/tests/genesis.rs b/src/integration/tests/genesis.rs index 10518b16..3d6d4675 100644 --- a/src/integration/tests/genesis.rs +++ b/src/integration/tests/genesis.rs @@ -6,7 +6,7 @@ use std::{ }; use borsh::de::BorshDeserialize; -use integration::{LOCAL_HOST, QOS_DIST_DIR}; +use integration::{LOCAL_HOST, PCR3_PRE_IMAGE_PATH, QOS_DIST_DIR}; use qos_core::protocol::services::genesis::GenesisOutput; use qos_crypto::{sha_512, shamir::shares_reconstruct}; use qos_nsm::nitro::unsafe_attestation_doc_from_der; @@ -153,7 +153,7 @@ async fn genesis_e2e() { "--qos-release-dir", QOS_DIST_DIR, "--pcr3-preimage-path", - "./mock/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--dr-key-path", DR_KEY_PUBLIC_PATH, "--unsafe-skip-attestation" @@ -199,7 +199,10 @@ async fn genesis_e2e() { // Try recovering from a random permutation decrypted_shares.shuffle(&mut thread_rng()); let master_secret: [u8; qos_p256::MASTER_SEED_LEN] = - shares_reconstruct(&decrypted_shares[0..threshold]).try_into().unwrap(); + shares_reconstruct(&decrypted_shares[0..threshold]) + .unwrap() + .try_into() + .unwrap(); let reconstructed = P256Pair::from_master_seed(&master_secret).unwrap(); assert!( reconstructed.public_key() @@ -225,7 +228,7 @@ async fn genesis_e2e() { "--qos-release-dir", QOS_DIST_DIR, "--pcr3-preimage-path", - "./mock/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--unsafe-skip-attestation" ]) .spawn() diff --git a/src/integration/tests/key.rs b/src/integration/tests/key.rs index 3769c83b..0568948f 100644 --- a/src/integration/tests/key.rs +++ b/src/integration/tests/key.rs @@ -1,6 +1,8 @@ use std::{fs, process::Command}; -use integration::{LOCAL_HOST, PIVOT_LOOP_PATH, QOS_DIST_DIR}; +use integration::{ + LOCAL_HOST, PCR3_PRE_IMAGE_PATH, PIVOT_LOOP_PATH, QOS_DIST_DIR, +}; use qos_crypto::sha_256; use qos_p256::{P256Pair, P256Public}; use qos_test_primitives::{ChildWrapper, PathWrapper}; @@ -158,7 +160,7 @@ fn generate_manifest_envelope() { "--restart-policy", "always", "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--pivot-hash-path", PIVOT_HASH_PATH, "--qos-release-dir", @@ -196,7 +198,7 @@ fn generate_manifest_envelope() { "--manifest-approvals-dir", BOOT_DIR, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--pivot-hash-path", PIVOT_HASH_PATH, "--qos-release-dir", @@ -293,7 +295,7 @@ fn boot_old_enclave(old_host_port: u16) -> (ChildWrapper, ChildWrapper) { "--host-ip", LOCAL_HOST, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--unsafe-skip-attestation", ]) .spawn() @@ -343,7 +345,7 @@ fn boot_old_enclave(old_host_port: u16) -> (ChildWrapper, ChildWrapper) { "--manifest-envelope-path", MANIFEST_ENVELOPE_PATH, "--pcr3-preimage-path", - "./mock/namespaces/pcr3-preimage.txt", + PCR3_PRE_IMAGE_PATH, "--manifest-set-dir", "./mock/keys/manifest-set", "--alias", diff --git a/src/integration/tests/reshard.rs b/src/integration/tests/reshard.rs new file mode 100644 index 00000000..cb8fa379 --- /dev/null +++ b/src/integration/tests/reshard.rs @@ -0,0 +1,338 @@ +use std::{ + collections::HashMap, + fs, + io::{BufRead, BufReader, Write}, + path::Path, + process::{Command, Stdio}, +}; + +use integration::{LOCAL_HOST, PCR3_PRE_IMAGE_PATH, QOS_DIST_DIR}; +use qos_crypto::n_choose_k; +use qos_p256::P256Pair; +use qos_test_primitives::{ChildWrapper, PathWrapper}; + +#[tokio::test] +async fn reshard_e2e() { + let tmp: PathWrapper = "/tmp/reshard_e2e".into(); + drop(fs::create_dir_all(&*tmp)); + let _eph_path: PathWrapper = "/tmp/reshard_e2e/eph.secret".into(); + let usock: PathWrapper = "/tmp/reshard_e2e/usock.sock".into(); + let secret_path: PathWrapper = "/tmp/reshard_e2e/quorum.secret".into(); + let attestation_doc_path: PathWrapper = "/tmp/reshard_e2e/att_doc".into(); + let reshard_input_path: PathWrapper = + "/tmp/reshard_e2e/reshard_input.json".into(); + let reshard_output_path: PathWrapper = + "/tmp/reshard_e2e/reshard_output.json".into(); + let eph_path: PathWrapper = "/tmp/reshard_e2e/ephemeral_key.secret".into(); + + let all_personal_dir = "./mock/boot-e2e/all-personal-dir"; + let personal_dir = |user: &str| format!("{all_personal_dir}/{user}-dir"); + let user1 = "user1"; + let user2 = "user2"; + + let host_port = qos_test_primitives::find_free_port().unwrap(); + + // Start Enclave + let mut _enclave_child_process: ChildWrapper = + Command::new("../target/debug/qos_core") + .args([ + "--usock", + &*usock, + "--quorum-file", + &*secret_path, + "--pivot-file", + "/tmp/reshard_e2e/never_write_pivot_file", + "--ephemeral-file", + &*eph_path, + "--mock", + "--manifest-file", + "/tmp/reshard_e2e/never_write_manifest", + ]) + .spawn() + .unwrap() + .into(); + + // Start Host + let mut _host_child_process: ChildWrapper = + Command::new("../target/debug/qos_host") + .args([ + "--host-port", + &host_port.to_string(), + "--host-ip", + LOCAL_HOST, + "--usock", + &*usock, + ]) + .spawn() + .unwrap() + .into(); + + assert!(Command::new("../target/debug/qos_client") + .args([ + "generate-reshard-input", + "--qos-release-dir", + QOS_DIST_DIR, + "--pcr3-preimage-path", + PCR3_PRE_IMAGE_PATH, + "--quorum-key-path-multiple", + "./mock/namespaces/quit-coding-to-vape/quorum_key.pub", + "--old-share-set-dir", + "./mock/keys/share-set", + "--new-share-set-dir", + "./mock/keys/new-share-set", + "--reshard-input-path", + &*reshard_input_path, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + + qos_test_primitives::wait_until_port_is_bound(host_port); + + assert!(Command::new("../target/debug/qos_client") + .args([ + "boot-reshard", + "--reshard-input-path", + &*reshard_input_path, + "--host-port", + &host_port.to_string(), + "--host-ip", + LOCAL_HOST, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + + assert!(Command::new("../target/debug/qos_client") + .args([ + "get-reshard-attestation-doc", + "--attestation-doc-path", + &*attestation_doc_path, + "--host-port", + &host_port.to_string(), + "--host-ip", + LOCAL_HOST, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + + for user in [&user1, &user2] { + let secret_path = format!("{}/{}.secret", &personal_dir(user), user); + let provision_input_path = + format!("{}/{}.provision_input.json", &*tmp, user); + let quorum_share_dir1 = format!("./mock/reshard/{}/qkey1", user); + + let mut child = Command::new("../target/debug/qos_client") + .args([ + "reshard-re-encrypt-share", + "--secret-path", + &secret_path, + "--quorum-share-dir-multiple", + &*quorum_share_dir1, + "--attestation-doc-path", + &*attestation_doc_path, + "--provision-input-path", + &*provision_input_path, + "--reshard-input-path", + &*reshard_input_path, + "--qos-release-dir", + QOS_DIST_DIR, + "--pcr3-preimage-path", + PCR3_PRE_IMAGE_PATH, + "--new-share-set-dir", + "./mock/keys/new-share-set", + "--old-share-set-dir", + "./mock/keys/share-set", + "--alias", + user, + "--unsafe-skip-attestation", + "--unsafe-eph-path-override", + &*eph_path, + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + + let mut stdin = child.stdin.take().expect("Failed to open stdin"); + let mut stdout = { + let stdout = child.stdout.as_mut().unwrap(); + let stdout_reader = BufReader::new(stdout); + stdout_reader.lines() + }; + assert_eq!( + &stdout.next().unwrap().unwrap(), + "**WARNING:** Skipping attestation document verification.", + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "Does this AWS IAM role belong to the intended organization: arn:aws:iam::123456789012:role/Webserver? (yes/no)", + ); + stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + + assert_eq!( + &stdout.next().unwrap().unwrap(), + "Does this new share set look correct? (yes/no)" + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "040ee9045f3718bd1345dccf88693c993626d08448fdeba8ecaf1b867f4d0572d439852ef460963a9e8fab08864a55994c0779216b44a165b4eaced98722ed3778041646e59014eaec046b2636d3943f446282363c26cf995320d5944b8b4d7af0aa588c208c13ded5c86c3e9a31af687c4027d4636173f405503e7b1baeeee7eaa5" + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "04c82672b2f8c4d520c5c7cda207b4a05f433e4db7f0daed9bbde6f54d42814af5aeabec191d2dda32ba4cdc6616aa3fda0a6711affa0d42efbe11144043028622044810d6d24626abfe6c31e884e674c870a2197c9e9cd80786b2fd3a087e2c38cad8376d9b7086901915d261ecb92bde5a757d27bbf1a20904120ff079b8a8ef71" + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "049872acc56bca90eea07e1e1185e3015be3b7295b4ba484299702489bf4858b1374928b335d3405a16221ec240e80817fbfd783c7052446a31bd1821a9a10ff9c0469361a228e22e7cad34774a50f7cd8f97e7d6542f3903bf9d14647302691ef9195ae2c08ec62dcd0e845bc75e94ef8b9fa45925199a2f7d94d00981d6d2e0d85" + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "0442993076a3b8345cb58b860477bce9db21bb6caceae8df298860410594ea08d4fc2ffec944fd7623a893b57037e0f20c44ff8eee6eff03110717efb9269181ed04bb495296212027597e2eb93ffbba07f0c41ae3018409b9ad2177e87b53a2729806f52ad6d0f6399ca3d37edddc81a687cd2a0a9f8aab914d76be2930ff8f5bba" + ); + stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "Is this the correct reconstruction threshold for the new share set: 3? (yes/no)" + ); + stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "Are these the correct quorum keys to reshard? (yes/no)" + ); + assert_eq!( + &stdout.next().unwrap().unwrap(), + "04c9434ba0a681ee7c21e17c7ce4f668360803686b198774c9362dac090f9995eeb68961319370969bd0d657167d9cfce13a7466ec47aba9845fbfc4fe9277866d04043daa777f57c1ebef21ff3eb71e00a681921da56186ac96b5d3b06b645c88c512fe8072d12971ce1f9592ef6bafd98b4982f8cf73cb6e80c8f6424294e54c71" + ); + stdin.write_all("yes\n".as_bytes()).expect("Failed to write to stdin"); + + assert_eq!( + stdout.next().unwrap().unwrap(), + format!("Reshard provision input written to: /tmp/reshard_e2e/{}.provision_input.json", user), + ); + assert!(child.wait().unwrap().success()); + + // Post the encrypted shares and approval over reshard input + assert!(Command::new("../target/debug/qos_client") + .args([ + "reshard-post-share", + "--host-port", + &host_port.to_string(), + "--host-ip", + LOCAL_HOST, + "--provision-input-path", + &*provision_input_path, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + } + + assert!(Command::new("../target/debug/qos_client") + .args([ + "get-reshard-output", + "--host-port", + &host_port.to_string(), + "--host-ip", + LOCAL_HOST, + "--reshard-output-path", + &reshard_output_path, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + + let secret_path_fn = |user: &str| { + format!("./mock/new-share-set-secrets/reshard-{}.secret", user) + }; + + type Share = Vec; + type QuorumPubKey = Vec; + let mut seen_shares = HashMap::>::new(); + for user in ["1", "2", "3", "4"] { + let secret_path = secret_path_fn(user); + let share_dir: PathWrapper = + format!("{}/{}/quorum_shares", &*tmp, user).into(); + fs::create_dir_all(&*share_dir).unwrap(); + let pair = P256Pair::from_hex_file(&secret_path).unwrap(); + + assert!(Command::new("../target/debug/qos_client") + .args([ + "verify-reshard-output", + "--reshard-output-path", + &reshard_output_path, + "--secret-path", + &secret_path, + "--share-dir", + &share_dir, + ]) + .spawn() + .unwrap() + .wait() + .unwrap() + .success()); + + for entry in fs::read_dir(&*share_dir).unwrap() { + let path = entry.unwrap().path(); + + if path.is_dir() { + let mut quorum_key = None; + let mut share = None; + for inner_entry in fs::read_dir(&*path).unwrap() { + let inner_path = inner_entry.unwrap().path(); + if inner_path.is_file() { + let split_name = split_file_name(&inner_path); + let buf = fs::read(inner_path).unwrap(); + + if split_name.first().unwrap() == "quorum_key" { + quorum_key = Some(buf) + } else if split_name.last().unwrap() == "share" { + share = Some(buf) + } + } + } + + let share = share.unwrap(); + let decrypted_share = pair.decrypt(&share).unwrap(); + let quorum_key_pub_bytes = + qos_hex::decode_from_vec(quorum_key.unwrap()).unwrap(); + seen_shares + .entry(quorum_key_pub_bytes) + .or_default() + .push(decrypted_share) + } + } + } + + for (pub_key, shares) in seen_shares.iter() { + for combo in n_choose_k::combinations( + shares, 3, /* new share set threshold */ + ) { + let secret: [u8; 32] = + qos_crypto::shamir::shares_reconstruct(&combo) + .unwrap() + .try_into() + .unwrap(); + + let quorum_key = P256Pair::from_master_seed(&secret).unwrap(); + assert_eq!(*pub_key, quorum_key.public_key().to_bytes()); + } + } +} + +fn split_file_name(p: &Path) -> Vec { + let file_name = + p.file_name().map(std::ffi::OsStr::to_string_lossy).unwrap(); + file_name.split('.').map(String::from).collect() +} diff --git a/src/qos_client/Cargo.toml b/src/qos_client/Cargo.toml index 8b9674aa..f338997a 100644 --- a/src/qos_client/Cargo.toml +++ b/src/qos_client/Cargo.toml @@ -20,6 +20,7 @@ rand_core = { version = "0.6", default-features = false } zeroize = { version = "1.6", default-features = false } rpassword = { version = "7", default-features = false } serde_json = { version = "1" } +serde = { version = "1", default-features = false } x509 = { version = "0.2", default-features = false, optional = true } yubikey = { version = "*", features = ["untested"], default-features = false, optional = true } diff --git a/src/qos_client/RESHARD_GUIDE.md b/src/qos_client/RESHARD_GUIDE.md new file mode 100644 index 00000000..3cc9b1fb --- /dev/null +++ b/src/qos_client/RESHARD_GUIDE.md @@ -0,0 +1,122 @@ +# Quorum Key Resharding Guide + +This guide covers how to reshard a quorum key using the qos_client CLI. + +## Overview + +Flow: + +1) Generate the configuration for how to reshard the given quorum keys. This configuration is called the `ReshardInput`. +2) Boot the enclave in reshard mode using the `ReshardInput`. +3) A threshold of the _old_ share holders query the enclave for an attestation document. +4) A threshold of the _old_ share holders re-encrypt their shares to the enclaves ephemeral key and post those shares in a single message. The data structure used to group a users shares and their corresponding quorum keys is called the `ReshardProvisionInput`. +5) All of the _new_ share holders fetch the `ReshardOutput` to verify they can decrypt their shares. + +## Steps + +### 1 - Generate ReshardInput (Lead) + +Generate the configuration for resharding the quorum keys. + +```sh +qos_client generate-reshard-input \ + --qos-release-dir \ + --pcr3-preimage-path \ + --quorum-key-path-multiple \ + --old-share-set-dir \ + --new-share-set-dir \ + --reshard-input-path +``` + +### 2 - Reshard Boot (Lead) + +Post the reshard boot instruction with the reshard input. + +```sh +qos_client boot-reshard \ + --reshard-input-path \ + --host-port 3001 \ + --host-ip localhost +``` + +### 3 - Get attestation doc (Old Share Holder) + +Get the attestation doc from the enclave. The attestation doc contains a reference to the reshard input and the ephemeral key which shares will be encrypted. + +```sh +qos_client get-reshard-attestation-doc \ + --attestation-doc-path \ + --host-port 3001 \ + --host-ip localhost +``` + +### 4 - Re-encrypt share to ephemeral key (Old Share Holder) + +Use the attestation doc to verify that the enclave is properly setup and running the expected code and re-encrypt relevant shares to the ephemeral key of the enclave. This step should be done on an airgapped machine as unencrypted shares will be exposed to memory. + +For each quorum key being resharded, the user will need a separate directory with just the quorum key and their targeted share. Each directory must be organized like: + +``` +- quorum-share-dir + - quorum_key.pub + - my_alias.share +``` + +Note that the logic looks at the extension of the file to determine if it's a share or the quorum key. + +```sh +qos_client reshard-re-encrypt-share \ + --yubikey \ + --quorum-share-dir-multiple \ + --attestation-doc-path \ + --provision-input-path \ + --reshard-input-path \ + --qos-release-dir \ + --pcr3-preimage-path \ + --new-share-set-dir \ + --old-share-set-dir \ + --alias +``` + +### 5 - Post reshard input (Old Share Holder) + +Post the re-encrypted shares from last step in order to reconstruct the quorum keys. + +```sh +qos_client reshard-post-share + --provision-input-path \ + --host-port 3001 \ + --host-ip localhost +``` + +### 6 - Get the new shares (New Share Holder) + +```sh +qos_client get-reshard-output \ + --reshard-output-path \ + --host-port 3001 \ + --host-ip localhost +``` + +### 7 - Verify shares (New Share Holder) + +Verify that we can decrypt our shares. This step should be done on an airgapped machine as the unencrypted share will be exposed to memory. + +The new shares will be written to subdirectories generated within the given share dir. The subdirectories will be named with the first four bytes of the quorum key. Each subdirectory will contain a new share and the quorum key it targets. After running the command against an empty `share-dir` and two sharded quorum, keys, the layout would look like: + +```sh +- share-dir + - 04009fd6 + - quorum_key.pub + - my_alias.share + - 041acdf2 + - quorum_key.pub + - my_alias.share +``` + +```sh +qos_client verify-reshard-output \ + --yubikey \ + --reshard-output-path \ + --share-dir +``` diff --git a/src/qos_client/src/cli/mod.rs b/src/qos_client/src/cli/mod.rs index 437fd714..279a5911 100644 --- a/src/qos_client/src/cli/mod.rs +++ b/src/qos_client/src/cli/mod.rs @@ -34,6 +34,8 @@ const QOS_REALEASE_DIR: &str = "qos-release-dir"; const PCR3_PREIMAGE_PATH: &str = "pcr3-preimage-path"; const PIVOT_HASH_PATH: &str = "pivot-hash-path"; const SHARE_SET_DIR: &str = "share-set-dir"; +const NEW_SHARE_SET_DIR: &str = "new-share-set-dir"; +const OLD_SHARE_SET_DIR: &str = "old-share-set-dir"; const MANIFEST_SET_DIR: &str = "manifest-set-dir"; const PATCH_SET_DIR: &str = "patch-set-dir"; const NAMESPACE_DIR: &str = "namespace-dir"; @@ -51,6 +53,7 @@ const APPROVAL_PATH: &str = "approval-path"; const EPH_WRAPPED_SHARE_PATH: &str = "eph-wrapped-share-path"; const ATTESTATION_DOC_PATH: &str = "attestation-doc-path"; const MASTER_SEED_PATH: &str = "master-seed-path"; +const RESHARD_OUTPUT_PATH: &str = "reshard-output-path"; const SHARE: &str = "share"; const OUTPUT_DIR: &str = "output-dir"; const THRESHOLD: &str = "threshold"; @@ -69,6 +72,11 @@ const PLAINTEXT_PATH: &str = "plaintext-path"; const OUTPUT_HEX: &str = "output-hex"; const VALIDATION_TIME_OVERRIDE: &str = "validation-time-override"; const JSON: &str = "json"; +const RESHARD_INPUT_PATH: &str = "reshard-input-path"; +const QUORUM_KEY_PATH_MULTIPLE: &str = "quorum-key-path-multiple"; +const QUORUM_SHARE_DIR_MULTIPLE: &str = "quorum-share-dir-multiple"; +const SHARE_DIR: &str = "share-dir"; +const PROVISION_INPUT_PATH: &str = "provision-input-path"; pub(crate) enum DisplayType { Manifest, @@ -169,7 +177,7 @@ pub enum Command { /// Pivot the enclave to the specified binary. /// /// This command goes through the steps of generating a Quorum Key, - /// sharding it (N=1), creating/signing/posting a Manifest, and + /// sharding it (N=2), creating/signing/posting a Manifest, and /// provisioning the quorum key. DangerousDevBoot, /// Provision a yubikey with a singing and encryption key. @@ -211,6 +219,24 @@ pub enum Command { P256AsymmetricEncrypt, /// Decrypt a payload encrypted to a qos_p256 public key. P256AsymmetricDecrypt, + /// Generate the input for the reshard ceremony. + GenerateReshardInput, + /// Broadcast the boot reshard instruction. + BootReshard, + /// Get the attestation doc with the reshard input hash as the user + /// data and the ephemeral key as the public key. + GetReshardAttestationDoc, + /// Reencrypt a quorum key share to the ephemeral key of an enclave booted + /// for resharding. + ReshardReEncryptShare, + /// Submit an encrypted share along with the associated approval to an + /// enclave booted for resharding. + ReshardPostShare, + /// Fetch the reshard output after the quorum key has been provisioned for + /// resharding. + GetReshardOutput, + /// Verify the reshard output + VerifyReshardOutput, } impl From<&str> for Command { @@ -247,6 +273,13 @@ impl From<&str> for Command { "p256-sign" => Self::P256Sign, "p256-asymmetric-encrypt" => Self::P256AsymmetricEncrypt, "p256-asymmetric-decrypt" => Self::P256AsymmetricDecrypt, + "generate-reshard-input" => Self::GenerateReshardInput, + "boot-reshard" => Self::BootReshard, + "get-reshard-attestation-doc" => Self::GetReshardAttestationDoc, + "reshard-re-encrypt-share" => Self::ReshardReEncryptShare, + "reshard-post-share" => Self::ReshardPostShare, + "get-reshard-output" => Self::GetReshardOutput, + "verify-reshard-output" => Self::VerifyReshardOutput, _ => panic!( "Unrecognized command, try something like `host-health --help`" ), @@ -333,7 +366,39 @@ impl Command { fn share_set_dir_token() -> Token { Token::new( SHARE_SET_DIR, - "Director with public keys for members of the share set.", + "Directory with public keys for members of the share set.", + ) + .takes_value(true) + .required(true) + } + fn old_share_set_dir_token() -> Token { + Token::new( + OLD_SHARE_SET_DIR, + "Directory with public keys for members of the OLD share set.", + ) + .takes_value(true) + .required(true) + } + fn new_share_set_dir_token() -> Token { + Token::new( + NEW_SHARE_SET_DIR, + "Directory with public keys for members of the NEW share set.", + ) + .takes_value(true) + .required(true) + } + fn reshard_input_path_token() -> Token { + Token::new( + RESHARD_INPUT_PATH, + "Path to the file to read/write the reshard input.", + ) + .takes_value(true) + .required(true) + } + fn reshard_output_path_token() -> Token { + Token::new( + RESHARD_OUTPUT_PATH, + "Path to the file to read/write the reshard output.", ) .takes_value(true) .required(true) @@ -362,6 +427,14 @@ impl Command { .takes_value(true) .required(true) } + fn share_dir_token() -> Token { + Token::new( + SHARE_DIR, + "Directory to read/write subdirectories that contain quorum key and your associated share.", + ) + .takes_value(true) + .required(true) + } fn alias_token() -> Token { Token::new(ALIAS, "Alias for identifying the key pair") .takes_value(true) @@ -380,7 +453,6 @@ impl Command { .takes_value(true) .required(true) } - fn yubikey_token() -> Token { Token::new(YUBIKEY, "Flag to indicate using a yubikey for signing") .takes_value(false) @@ -556,6 +628,32 @@ impl Command { .required(false) .takes_value(false) } + fn quorum_key_path_multiple_token() -> Token { + Token::new( + QUORUM_KEY_PATH_MULTIPLE, + "Use multiple times to specify multiple quorum public key files.", + ) + .required(true) + .takes_value(true) + .allow_multiple(true) + } + fn quorum_share_dir_multiple_token() -> Token { + Token::new( + QUORUM_SHARE_DIR_MULTIPLE, + "Path to directory with just your share and the associated quorum key." + ) + .required(true) + .takes_value(true) + .allow_multiple(true) + } + fn provision_input_path_token() -> Token { + Token::new( + PROVISION_INPUT_PATH, + "Path to file to read/write ReshardProvisionInput.", + ) + .required(true) + .takes_value(true) + } fn base() -> Parser { Parser::new() @@ -643,6 +741,16 @@ impl Command { .token(Self::pivot_args_token()) } + fn generate_reshard_input() -> Parser { + Parser::new() + .token(Self::qos_release_dir_token()) + .token(Self::quorum_key_path_multiple_token()) + .token(Self::pcr3_preimage_path_token()) + .token(Self::old_share_set_dir_token()) + .token(Self::new_share_set_dir_token()) + .token(Self::reshard_input_path_token()) + } + fn approve_manifest() -> Parser { Parser::new() .token(Self::yubikey_token()) @@ -668,12 +776,20 @@ impl Command { .token(Self::unsafe_skip_attestation_token()) } + fn boot_reshard() -> Parser { + Self::base().token(Self::reshard_input_path_token()) + } + fn get_attestation_doc() -> Parser { Self::base() .token(Self::attestation_doc_path_token()) .token(Self::manifest_envelope_path_token()) } + fn get_reshard_attestation_doc() -> Parser { + Self::base().token(Self::attestation_doc_path_token()) + } + fn proxy_re_encrypt_share() -> Parser { Parser::new() .token(Self::yubikey_token()) @@ -691,6 +807,24 @@ impl Command { .token(Self::unsafe_auto_confirm_token()) } + fn reshard_re_encrypt_share() -> Parser { + Parser::new() + .token(Self::yubikey_token()) + .token(Self::secret_path_token()) + .token(Self::attestation_doc_path_token()) + .token(Self::provision_input_path_token()) + .token(Self::quorum_share_dir_multiple_token()) + .token(Self::reshard_input_path_token()) + .token(Self::qos_release_dir_token()) + .token(Self::pcr3_preimage_path_token()) + .token(Self::new_share_set_dir_token()) + .token(Self::old_share_set_dir_token()) + .token(Self::alias_token()) + .token(Self::unsafe_skip_attestation_token()) + .token(Self::unsafe_eph_path_override_token()) + .token(Self::unsafe_auto_confirm_token()) + } + fn post_share() -> Parser { Self::base() .token(Self::approval_path_token()) @@ -803,6 +937,22 @@ impl Command { .token(Self::master_seed_path_token()) .token(Self::output_hex_token()) } + + fn get_reshard_output() -> Parser { + Self::base().token(Self::reshard_output_path_token()) + } + + fn verify_reshard_output() -> Parser { + Parser::new() + .token(Self::yubikey_token()) + .token(Self::secret_path_token()) + .token(Self::reshard_output_path_token()) + .token(Self::share_dir_token()) + } + + fn reshard_post_share() -> Parser { + Self::base().token(Self::provision_input_path_token()) + } } impl GetParserForCommand for Command { @@ -842,6 +992,15 @@ impl GetParserForCommand for Command { Self::P256Sign => Self::p256_sign(), Self::P256AsymmetricEncrypt => Self::p256_asymmetric_encrypt(), Self::P256AsymmetricDecrypt => Self::p256_asymmetric_decrypt(), + Self::GenerateReshardInput => Self::generate_reshard_input(), + Self::BootReshard => Self::boot_reshard(), + Self::GetReshardAttestationDoc => { + Self::get_reshard_attestation_doc() + } + Self::ReshardReEncryptShare => Self::reshard_re_encrypt_share(), + Self::ReshardPostShare => Self::reshard_post_share(), + Self::GetReshardOutput => Self::get_reshard_output(), + Self::VerifyReshardOutput => Self::verify_reshard_output(), } } } @@ -1049,6 +1208,27 @@ impl ClientOpts { .to_string() } + fn reshard_input_path(&self) -> String { + self.parsed + .single(RESHARD_INPUT_PATH) + .expect("Missing `--reshard-input-path`") + .to_string() + } + + fn new_share_set_dir(&self) -> String { + self.parsed + .single(NEW_SHARE_SET_DIR) + .expect("Missing `--new-share-set-dir`") + .to_string() + } + + fn old_share_set_dir(&self) -> String { + self.parsed + .single(OLD_SHARE_SET_DIR) + .expect("Missing `--old-share-set-dir`") + .to_string() + } + fn output_dir(&self) -> String { self.parsed .single(OUTPUT_DIR) @@ -1144,6 +1324,13 @@ impl ClientOpts { .to_string() } + fn reshard_output_path(&self) -> String { + self.parsed + .single(RESHARD_OUTPUT_PATH) + .expect("Missing `--reshard-output-path`") + .to_string() + } + fn ciphertext_path(&self) -> String { self.parsed .single(CIPHERTEXT_PATH) @@ -1151,6 +1338,20 @@ impl ClientOpts { .to_string() } + fn provision_input_path(&self) -> String { + self.parsed + .single(PROVISION_INPUT_PATH) + .expect("Missing `--provision-input-path`") + .to_string() + } + + fn share_dir(&self) -> String { + self.parsed + .single(SHARE_DIR) + .expect("Missing `--share-dir`") + .to_string() + } + fn yubikey(&self) -> bool { self.parsed.flag(YUBIKEY).unwrap_or(false) } @@ -1174,6 +1375,20 @@ impl ClientOpts { fn json(&self) -> bool { self.parsed.flag(JSON).unwrap_or(false) } + + fn quorum_key_path_multiple(&self) -> Vec { + self.parsed + .multiple(QUORUM_KEY_PATH_MULTIPLE) + .expect("Missing `--quorum-key-path-multiple`") + .to_vec() + } + + fn quorum_share_dir_multiple(&self) -> Vec { + self.parsed + .multiple(QUORUM_SHARE_DIR_MULTIPLE) + .expect("Missing `--quorum-share-dir-multiple`") + .to_vec() + } } #[derive(Clone, PartialEq, Debug)] @@ -1264,6 +1479,25 @@ impl ClientRunner { Command::P256AsymmetricDecrypt => { handlers::p256_asymmetric_decrypt(&self.opts); } + Command::GenerateReshardInput => { + handlers::generate_reshard_input(&self.opts); + } + Command::BootReshard => handlers::boot_reshard(&self.opts), + Command::GetReshardAttestationDoc => { + handlers::get_reshard_attestation_doc(&self.opts); + } + Command::ReshardReEncryptShare => { + handlers::reshard_re_encrypt_share(&self.opts); + } + Command::ReshardPostShare => { + handlers::reshard_post_share(&self.opts); + } + Command::GetReshardOutput => { + handlers::get_reshard_output(&self.opts); + } + Command::VerifyReshardOutput => { + handlers::verify_reshard_output(&self.opts); + } } } } @@ -1283,10 +1517,15 @@ impl CLI { } mod handlers { - use super::services::{ApproveManifestArgs, ProxyReEncryptShareArgs}; + use super::services::{ + ApproveManifestArgs, ProxyReEncryptShareArgs, ReshardReEncryptShareArgs, + }; use crate::{ cli::{ - services::{self, GenerateManifestArgs, PairOrYubi}, + services::{ + self, GenerateManifestArgs, GenerateReshardInputArgs, + PairOrYubi, + }, ClientOpts, ProtocolMsg, }, request, @@ -1495,6 +1734,21 @@ mod handlers { } } + pub(super) fn generate_reshard_input(opts: &ClientOpts) { + if let Err(e) = + services::generate_reshard_input(GenerateReshardInputArgs { + qos_release_dir: opts.qos_release_dir(), + quorum_key_paths: opts.quorum_key_path_multiple(), + pcr3_preimage_path: opts.pcr3_preimage_path(), + new_share_set_dir: opts.new_share_set_dir(), + old_share_set_dir: opts.old_share_set_dir(), + reshard_input_path: opts.reshard_input_path(), + }) { + println!("Error: {e:?}"); + std::process::exit(1); + } + } + pub(super) fn approve_manifest(opts: &ClientOpts) { let pair = get_pair_or_yubi(opts); @@ -1530,6 +1784,16 @@ mod handlers { } } + pub(super) fn boot_reshard(opts: &ClientOpts) { + if let Err(e) = services::boot_reshard( + &opts.path_message(), + opts.reshard_input_path(), + ) { + println!("Error: {e:?}"); + std::process::exit(1); + } + } + pub(super) fn get_attestation_doc(opts: &ClientOpts) { services::get_attestation_doc( &opts.path_message(), @@ -1538,6 +1802,13 @@ mod handlers { ); } + pub(super) fn get_reshard_attestation_doc(opts: &ClientOpts) { + services::get_reshard_attestation_doc( + &opts.path_message(), + opts.attestation_doc_path(), + ); + } + pub(super) fn proxy_re_encrypt_share(opts: &ClientOpts) { let pair = get_pair_or_yubi(opts); @@ -1561,6 +1832,32 @@ mod handlers { } } + pub(super) fn reshard_re_encrypt_share(opts: &ClientOpts) { + let pair = get_pair_or_yubi(opts); + + if let Err(e) = + services::reshard_re_encrypt_share(ReshardReEncryptShareArgs { + pair, + quorum_share_dirs: opts.quorum_share_dir_multiple(), + attestation_doc_path: opts.attestation_doc_path(), + provision_input_path: opts.provision_input_path(), + + reshard_input_path: opts.reshard_input_path(), + qos_release_dir: opts.qos_release_dir(), + pcr3_preimage_path: opts.pcr3_preimage_path(), + new_share_set_dir: opts.new_share_set_dir(), + old_share_set_dir: opts.old_share_set_dir(), + + alias: opts.alias(), + unsafe_skip_attestation: opts.unsafe_skip_attestation(), + unsafe_eph_path_override: opts.unsafe_eph_path_override(), + unsafe_auto_confirm: opts.unsafe_auto_confirm(), + }) { + eprintln!("Error: {e:?}"); + std::process::exit(1); + } + } + pub(super) fn post_share(opts: &ClientOpts) { if let Err(e) = services::post_share( &opts.path_message(), @@ -1572,6 +1869,36 @@ mod handlers { } } + pub(super) fn reshard_post_share(opts: &ClientOpts) { + if let Err(e) = services::reshard_post_share( + &opts.path_message(), + opts.provision_input_path(), + ) { + eprintln!("Error: {e:?}"); + std::process::exit(1); + } + } + + pub(super) fn get_reshard_output(opts: &ClientOpts) { + services::get_reshard_output( + &opts.path_message(), + &opts.reshard_output_path(), + ); + } + + pub(super) fn verify_reshard_output(opts: &ClientOpts) { + let pair = get_pair_or_yubi(opts); + + if let Err(e) = services::verify_reshard_output( + opts.reshard_output_path(), + pair, + &opts.share_dir(), + ) { + eprintln!("Error: {e:?}"); + std::process::exit(1); + } + } + pub(super) fn display(opts: &ClientOpts) { if let Err(e) = services::display( &opts.display_type(), diff --git a/src/qos_client/src/cli/services.rs b/src/qos_client/src/cli/services.rs index a1d01f78..d5b9681a 100644 --- a/src/qos_client/src/cli/services.rs +++ b/src/qos_client/src/cli/services.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashSet, fs, fs::File, io, @@ -19,6 +20,10 @@ use qos_core::protocol::{ }, genesis::{GenesisOutput, GenesisSet}, key::EncryptedQuorumKey, + reshard::{ + ReshardInput, ReshardOutput, ReshardProvisionInput, + ReshardProvisionShare, + }, }, QosHash, }; @@ -46,6 +51,7 @@ const QUORUM_THRESHOLD_FILE: &str = "quorum_threshold"; const DR_WRAPPED_QUORUM_KEY: &str = "dr_wrapped_quorum_key"; const PCRS_PATH: &str = "aws-x86_64.pcrs"; const GENESIS_DR_ARTIFACTS: &str = "genesis_dr_artifacts"; +const SHARE_EXT: &str = "share"; const DANGEROUS_DEV_BOOT_MEMBER: &str = "DANGEROUS_DEV_BOOT_MEMBER"; const DANGEROUS_DEV_BOOT_NAMESPACE: &str = @@ -86,7 +92,7 @@ pub enum Error { #[cfg(feature = "smartcard")] PinEntryError(std::io::Error), /// Failed to read share - ReadShare, + ReadShare(String), /// Error while try to read the quorum public key. FailedToReadQuorumPublicKey(qos_p256::P256Error), /// Error trying to the read a file that is supposed to have a manifest. @@ -109,10 +115,16 @@ pub enum Error { /// Failed to read file that was supposed to contain Ephemeral Key wrapped /// share. FailedToReadEphWrappedShare(std::io::Error), + /// Failed to read [Self::path]. FailedToRead { path: String, error: String, }, + /// Failed to write to [Self::path]. + FailedToWrite { + path: String, + error: String, + }, /// Failed to decode some hex CouldNotDecodeHex(qos_hex::HexError), /// Failed to deserialize something from borsh. @@ -141,6 +153,20 @@ pub enum Error { /// Given quorum key seed does not match the hash of the expected quorum /// key seed. SecretDoesNotMatch, + /// The contents of the file could not be deserialized as `ReshardInput`. + FailedToDeserializeReshardInput(String), + /// Could not read the file with the reshard output + FailedToReadReshardOutput(std::io::Error), + /// Could not serialize the reshard output in the file + FailedToDeserializeReshardOutput(String), + /// Could not find key in new genesis member outputs + KeyNotInNewShareSet, + /// Failed to write to the file specified for the encrypted share. + FailedToWriteEncryptedShare(std::io::Error), + /// Expected only 1 share in the directory. + ExpectedExactlyOneShare, + /// Expected only 1 quorum key in the directory. + ExpectedExactlyOneQuorumKey, } impl From for Error { @@ -756,6 +782,61 @@ pub(crate) fn generate_manifest>( Ok(()) } +pub struct GenerateReshardInputArgs { + pub qos_release_dir: String, + pub quorum_key_paths: Vec, + pub new_share_set_dir: String, + pub old_share_set_dir: String, + pub reshard_input_path: String, + pub pcr3_preimage_path: String, +} + +pub fn generate_reshard_input( + GenerateReshardInputArgs { + qos_release_dir, + quorum_key_paths, + new_share_set_dir, + old_share_set_dir, + reshard_input_path, + pcr3_preimage_path, + }: GenerateReshardInputArgs, +) -> Result<(), Error> { + let nitro_config = + extract_nitro_config(qos_release_dir, pcr3_preimage_path); + + let quorum_keys = quorum_key_paths + .iter() + .map(|path| { + P256Public::from_hex_file(path) + .map_err(|_| Error::FailedToRead { + path: path.clone(), + error: "p256 error".to_string(), + }) + .map(|pair| pair.to_bytes()) + }) + .collect::, _>>()?; + + let old_share_set = get_share_set(old_share_set_dir); + let new_share_set = get_share_set(new_share_set_dir); + + let mut reshard_input = ReshardInput { + quorum_keys, + new_share_set, + old_share_set, + enclave: nitro_config, + }; + + reshard_input.deterministic(); + + write_json_with_msg( + reshard_input_path.as_ref(), + &reshard_input, + "ReshardInput", + ); + + Ok(()) +} + fn extract_nitro_config>( qos_release_dir_path: P, pcr3_preimage_path: P, @@ -1154,6 +1235,27 @@ pub(crate) fn boot_standard>( Ok(()) } +pub(crate) fn boot_reshard( + uri: &str, + reshard_input_path: String, +) -> Result<(), Error> { + // Create manifest envelope + let reshard_input = read_reshard_input(reshard_input_path)?; + + let req = ProtocolMsg::BootReshardRequest { reshard_input }; + + // Broadcast boot reshard instruction and make sure it has the attestation + // doc as the response + let _cose_sign1 = match request::post(uri, &req).unwrap() { + ProtocolMsg::BootReshardResponse { + nsm_response: NsmResponse::Attestation { document }, + } => document, + r => panic!("Unexpected response: {r:?}"), + }; + + Ok(()) +} + pub(crate) fn get_attestation_doc>( uri: &str, attestation_doc_path: P, @@ -1185,6 +1287,26 @@ pub(crate) fn get_attestation_doc>( ); } +pub(crate) fn get_reshard_attestation_doc>( + uri: &str, + attestation_doc_path: P, +) { + let (cose_sign1, _) = + match request::post(uri, &ProtocolMsg::ReshardAttestationDocRequest) { + Ok(ProtocolMsg::ReshardAttestationDocResponse { + nsm_response: NsmResponse::Attestation { document }, + reshard_input, + }) => (document, reshard_input), + r => panic!("Unexpected response: {r:?}"), + }; + + write_with_msg( + attestation_doc_path.as_ref(), + &cose_sign1, + "COSE Sign1 Attestation Doc", + ); +} + pub(crate) struct ProxyReEncryptShareArgs> { pub pair: PairOrYubi, pub share_path: P, @@ -1224,10 +1346,8 @@ pub(crate) fn proxy_re_encrypt_share>( let manifest_envelope = read_manifest_envelope(&manifest_envelope_path)?; let attestation_doc = read_attestation_doc(&attestation_doc_path, unsafe_skip_attestation)?; - let encrypted_share = std::fs::read(share_path).map_err(|e| { - eprintln!("{e:?}"); - Error::ReadShare - })?; + let encrypted_share = std::fs::read(share_path) + .map_err(|e| Error::ReadShare(e.to_string()))?; let pcr3_preimage = find_pcr3(&pcr3_preimage_path); @@ -1401,6 +1521,228 @@ where true } +pub struct ReshardReEncryptShareArgs { + pub pair: PairOrYubi, + pub quorum_share_dirs: Vec, + pub attestation_doc_path: String, + pub provision_input_path: String, + pub reshard_input_path: String, + pub qos_release_dir: String, + pub pcr3_preimage_path: String, + pub new_share_set_dir: String, + pub old_share_set_dir: String, + pub alias: String, + pub unsafe_skip_attestation: bool, + pub unsafe_eph_path_override: Option, + pub unsafe_auto_confirm: bool, +} + +pub(crate) fn reshard_re_encrypt_share( + ReshardReEncryptShareArgs { + mut pair, + quorum_share_dirs, + attestation_doc_path, + provision_input_path, + reshard_input_path, + qos_release_dir, + pcr3_preimage_path, + new_share_set_dir, + old_share_set_dir, + alias, + unsafe_skip_attestation, + unsafe_eph_path_override, + unsafe_auto_confirm, + }: ReshardReEncryptShareArgs, +) -> Result<(), Error> { + let mut reshard_input = read_reshard_input(reshard_input_path)?; + reshard_input.deterministic(); + let attestation_doc = + read_attestation_doc(attestation_doc_path, unsafe_skip_attestation)?; + let mut new_share_set = get_share_set(new_share_set_dir); + let member = QuorumMember { pub_key: pair.public_key_bytes()?, alias }; + let pcr3_preimage = find_pcr3(&pcr3_preimage_path); + + // Verify the attestation doc matches up with the pcrs in the manifest + if unsafe_skip_attestation { + println!("**WARNING:** Skipping attestation document verification."); + } else { + verify_attestation_doc_against_user_input( + &attestation_doc, + &reshard_input.qos_hash(), + &reshard_input.enclave.pcr0, + &reshard_input.enclave.pcr1, + &reshard_input.enclave.pcr2, + &extract_pcr3(pcr3_preimage_path.clone()), + )?; + } + + // Pull out the ephemeral key or use the override + let eph_pub: P256Public = if let Some(eph_path) = unsafe_eph_path_override { + P256Pair::from_hex_file(eph_path) + .expect("Could not read ephemeral key override") + .public_key() + } else { + P256Public::from_bytes( + &attestation_doc + .public_key + .expect("No ephemeral key in the attestation doc"), + ) + .expect("Ephemeral key not valid public key") + }; + + // Programmatic verification + reshard_re_encrypt_share_programmatic_verification( + &member, + old_share_set_dir, + &new_share_set, + &reshard_input, + qos_release_dir, + pcr3_preimage_path, + ); + + // Human verification + if !unsafe_auto_confirm { + reshard_re_encrypt_share_human_verification( + &pcr3_preimage, + &mut new_share_set, + &reshard_input, + ); + } + + let provision_shares = quorum_share_dirs + .iter() + .map(find_quorum_key_and_share) + .collect::, _>>()?; + let found_quorum_keys: HashSet<_> = + provision_shares.iter().map(|p| p.pub_key.clone()).collect(); + if found_quorum_keys != reshard_input.quorum_keys() { + let keys = found_quorum_keys + .symmetric_difference(&reshard_input.quorum_keys()) + .map(|b| qos_hex::encode(b)) + .collect::>() + .join(", "); + panic!("quorum keys given did not match keys in reshard input: difference: {keys}"); + } + + let shares = provision_shares + .into_iter() + .map(|p| { + let plaintext_share = &pair + .decrypt(&p.share) + .expect("Failed to decrypt share with personal key."); + + ReshardProvisionShare { + share: eph_pub + .encrypt(plaintext_share) + .expect("Envelope encryption error"), + pub_key: p.pub_key, + } + }) + .collect(); + + let approval = Approval { + signature: pair + .sign(&reshard_input.qos_hash()) + .expect("Failed to sign"), + member, + }; + + let provision_input = ReshardProvisionInput { approval, shares }; + + write_json_with_msg( + provision_input_path.as_ref(), + &provision_input, + "Reshard provision input", + ); + + Ok(()) +} + +fn reshard_re_encrypt_share_programmatic_verification( + member: &QuorumMember, + old_share_set_dir: String, + new_share_set: &ShareSet, + reshard_input: &ReshardInput, + qos_release_dir: String, + pcr3_preimage_path: String, +) { + let old_share_set = get_share_set(old_share_set_dir); + let nitro_config: NitroConfig = + extract_nitro_config(qos_release_dir, pcr3_preimage_path); + + // Verify that member is part of new share set + assert!( + reshard_input.old_share_set.members.contains(member), + "Member does not belong to old share set." + ); + // Verify old share set matches reshard input + assert_eq!(old_share_set, reshard_input.old_share_set, "Specified old share set does not match old share set in reshard input."); + // Verify new share set matches reshard input + assert_eq!(*new_share_set, reshard_input.new_share_set, "Specified new share set does not match new share set in reshard input."); + // Verify that pcrs 0, 1, 2 & 3 match reshard input + assert_eq!( + nitro_config, reshard_input.enclave, + "Enclave configuration in reshard input does not match given qos dist (PCRs, qos version, etc)" + ); +} + +fn reshard_re_encrypt_share_human_verification( + pcr3_preimage: &str, + new_share_set: &mut ShareSet, + reshard_input: &ReshardInput, +) { + let stdin = io::stdin(); + let stdin_locked = stdin.lock(); + let mut prompter = Prompter { reader: stdin_locked, writer: io::stdout() }; + { + // Last chance to verify that this enclave belongs to turnkey and + // not an attacker + let prompt = format!( + "Does this AWS IAM role belong to the intended organization: {pcr3_preimage}? (yes/no)" + ); + assert!(prompter.prompt_is_yes(&prompt), "You indicated that this IAM role does not belong to your organization."); + } + { + new_share_set.members.sort(); + let public_keys = new_share_set + .members + .iter() + .map(|m| qos_hex::encode(&m.pub_key)) + .collect::>() + .join("\n"); + let prompt = format!( + "Does this new share set look correct? (yes/no)\n{public_keys}" + ); + assert!( + prompter.prompt_is_yes(&prompt), + "You indicated that this is not the correct share set." + ); + } + { + let prompt = format!( + "Is this the correct reconstruction threshold for the new share set: {}? (yes/no)", + new_share_set.threshold + ); + assert!(prompter.prompt_is_yes(&prompt), "You indicated that this is not the correct reconstruction threshold."); + } + { + let quorum_public_keys = reshard_input + .quorum_keys + .iter() + .map(|p| qos_hex::encode(p)) + .collect::>() + .join("\n"); + + let prompt = format!( + "Are these the correct quorum keys to reshard? (yes/no)\n{quorum_public_keys}" + ); + assert!( + prompter.prompt_is_yes(&prompt), + "You indicated that this was not the correct set of quorum keys to reshard" + ); + } +} + pub(crate) fn post_share>( uri: &str, eph_wrapped_share_path: P, @@ -1426,6 +1768,113 @@ pub(crate) fn post_share>( Ok(()) } +pub(crate) fn reshard_post_share( + uri: &str, + provision_input_path: String, +) -> Result<(), Error> { + // Get the ephemeral key wrapped share + let input: ReshardProvisionInput = { + let buf = fs::read(&provision_input_path).map_err(|e| { + Error::FailedToRead { + path: provision_input_path, + error: e.to_string(), + } + })?; + + serde_json::from_slice(&buf) + .expect("failed to deserialize provision input") + }; + + let req = ProtocolMsg::ReshardProvisionRequest { input }; + let is_reconstructed = match request::post(uri, &req).unwrap() { + ProtocolMsg::ReshardProvisionResponse { reconstructed } => { + reconstructed + } + r => panic!("Unexpected response: {r:?}"), + }; + + if is_reconstructed { + println!("The quorum key has been reconstructed."); + } else { + println!("The quorum key has *not* been reconstructed."); + }; + + Ok(()) +} + +pub(crate) fn get_reshard_output(uri: &str, reshard_output_path: &str) { + let req = ProtocolMsg::ReshardOutputRequest; + let reshard_output = match request::post(uri, &req).unwrap() { + ProtocolMsg::ReshardOutputResponse { reshard_output } => reshard_output, + r => panic!("Unexpected response: {r:?}"), + }; + + write_json_with_msg( + reshard_output_path.as_ref(), + &reshard_output, + "ReshardOutput", + ); +} + +pub(crate) fn verify_reshard_output( + reshard_output_path: String, + mut pair: PairOrYubi, + share_dir: &str, +) -> Result<(), Error> { + let reshard_output = read_reshard_output(reshard_output_path)?; + let pub_key = pair.public_key_bytes()?; + + let mut alias: Option = None; + let member_shares = reshard_output + .outputs + .into_iter() + .map(|(quorum_pub, member_outputs)| { + let member_output = member_outputs + .iter() + .find(|m| m.share_set_member.pub_key == pub_key) + .ok_or(Error::KeyNotInNewShareSet)?; + let decrypted_share_hash = sha_512( + &pair.decrypt(&member_output.encrypted_quorum_key_share)?, + ); + assert_eq!( + member_output.share_hash, decrypted_share_hash, + "decrypted share did not match expected hash" + ); + alias = Some(member_output.share_set_member.alias.clone()); + + Ok((quorum_pub, member_output.encrypted_quorum_key_share.clone())) + }) + .collect::, Vec)>, Error>>()?; + + let alias = alias.expect( + "we exit early above if the person is not a member of the share set", + ); + for (quorum_key, share) in member_shares { + let dir_name = qos_hex::encode(&quorum_key[0..4]); + let dir_path = Path::new(&share_dir).join(dir_name); + fs::create_dir(&dir_path) + .map_err(|e: io::Error| { + panic!("failed to create dir {dir_path:?}: {e}") + }) + .unwrap(); + let quorum_key_path = + dir_path.clone().join(format!("quorum_key.{PUB_EXT}")); + let share_path = dir_path.join(format!("{alias}.{SHARE_EXT}")); + + fs::write(&quorum_key_path, qos_hex::encode(&quorum_key)).map_err( + |e| Error::FailedToWrite { + path: quorum_key_path.into_os_string().into_string().unwrap(), + error: e.to_string(), + }, + )?; + fs::write(&share_path, share).map_err(|e| Error::FailedToWrite { + path: share_path.into_os_string().into_string().unwrap(), + error: e.to_string(), + })?; + } + Ok(()) +} + #[cfg(feature = "smartcard")] pub(crate) fn yubikey_sign(hex_payload: &str) -> Result<(), Error> { let bytes = qos_hex::decode(hex_payload)?; @@ -1532,7 +1981,8 @@ pub(crate) fn display>( file_path: P, json: bool, ) -> Result<(), Error> { - let bytes = fs::read(file_path).map_err(|_| Error::ReadShare)?; + let bytes = + fs::read(file_path).map_err(|e| Error::ReadShare(e.to_string()))?; match *display_type { DisplayType::Manifest => { let decoded = Manifest::try_from_slice(&bytes)?; @@ -1574,26 +2024,21 @@ pub(crate) fn dangerous_dev_boot>( pub_key: quorum_public_der.clone(), }; - // Shard it with N=1, K=1 - let share = { - let mut shares = qos_crypto::shamir::shares_generate( - quorum_pair.to_master_seed(), - 1, - 1, - ); - assert_eq!( - shares.len(), - 1, - "Error generating shares - did not get exactly one share." - ); - shares.remove(0) - }; + // Shard it with N=2, K=2 + let shares = + qos_crypto::shamir::shares_generate(quorum_pair.to_master_seed(), 2, 2) + .unwrap(); + assert_eq!( + shares.len(), + 2, + "Error generating shares - did not get exactly two share." + ); // Read in the pivot let pivot = fs::read(&pivot_path).expect("Failed to read pivot binary."); let mock_pcr = vec![0; 48]; - // Create a manifest with manifest set of 1 - everything hardcoded expect + // Create a manifest with manifest set of 1 - everything hardcoded except // pivot config let manifest = Manifest { namespace: Namespace { @@ -1616,7 +2061,7 @@ pub(crate) fn dangerous_dev_boot>( members: vec![member.clone()], }, share_set: ShareSet { - threshold: 1, + threshold: 2, // The only member is the quorum member members: vec![member.clone()], }, @@ -1670,16 +2115,34 @@ pub(crate) fn dangerous_dev_boot>( }, }; - // Post the share - let req = ProtocolMsg::ProvisionRequest { + // Post the share a first time. It won't work because it'll be the first + // share. + let req1 = ProtocolMsg::ProvisionRequest { share: eph_pub - .encrypt(&share) + .encrypt(&shares[0]) .expect("Failed to encrypt share to eph key."), - approval, + approval: approval.clone(), }; - match request::post(uri, &req).unwrap() { + match request::post(uri, &req1).unwrap() { + ProtocolMsg::ProvisionResponse { reconstructed } => { + assert!( + !reconstructed, + "Quorum Key should NOT be reconstructed (1/2)" + ); + } + r => panic!("Unexpected response: {r:?}"), + }; + + // Post the second share; expected to reconstruct. + let req2 = ProtocolMsg::ProvisionRequest { + share: eph_pub + .encrypt(&shares[1]) + .expect("Failed to encrypt share to eph key."), + approval: approval.clone(), + }; + match request::post(uri, &req2).unwrap() { ProtocolMsg::ProvisionResponse { reconstructed } => { - assert!(reconstructed, "Quorum Key was not reconstructed"); + assert!(reconstructed, "Quorum Key should be reconstructed (2/2)"); } r => panic!("Unexpected response: {r:?}"), }; @@ -1698,7 +2161,8 @@ pub(crate) fn shamir_split( error: e.to_string(), })?; let shares = - qos_crypto::shamir::shares_generate(&secret, total_shares, threshold); + qos_crypto::shamir::shares_generate(&secret, total_shares, threshold) + .expect("failed to generate shares"); for (i, share) in shares.iter().enumerate() { let file_name = format!("{}.share", i + 1); @@ -1723,8 +2187,9 @@ pub(crate) fn shamir_reconstruct( }) .collect::>, Error>>()?; - let secret = - Zeroizing::new(qos_crypto::shamir::shares_reconstruct(&shares)); + let reconstructed = qos_crypto::shamir::shares_reconstruct(shares) + .expect("Reconstruction failed"); + let secret = Zeroizing::new(reconstructed); write_with_msg(output_path.as_ref(), &secret, "Reconstructed secret"); @@ -1884,6 +2349,58 @@ fn get_genesis_set>(dir: P) -> GenesisSet { GenesisSet { members, threshold: find_threshold(dir) } } +fn find_quorum_key_and_share>( + dir: P, +) -> Result { + let dir_contents = find_file_paths(&dir); + let mut share = dir_contents + .iter() + .filter(|path| { + let file_name = split_file_name(path); + file_name.last().map_or(false, |s| s.as_str() == SHARE_EXT) + }) + .map(|p| { + fs::read(p).map_err(|e| Error::FailedToRead { + path: p.to_string_lossy().to_string(), + error: e.to_string(), + }) + }) + .collect::, _>>()?; + + if share.len() != 1 { + return Err(Error::ExpectedExactlyOneShare); + } + + let mut quorum_key: Vec<_> = dir_contents + .iter() + .filter_map(|path| { + let file_name = split_file_name(path); + if file_name.last().map_or(true, |s| s.as_str() != PUB_EXT) { + return None; + }; + if file_name.first().map_or(true, |s| s.as_str() != "quorum_key") { + return None; + }; + + let quorum_key = P256Public::from_hex_file(path) + .map_err(|e| { + panic!("Could not read hex from quorum_key.pub: {path:?}: {e:?}") + }) + .unwrap() + .to_bytes(); + Some(quorum_key) + }) + .collect(); + if quorum_key.len() != 1 { + return Err(Error::ExpectedExactlyOneQuorumKey); + } + + Ok(ReshardProvisionShare { + pub_key: mem::take(&mut quorum_key[0]), + share: mem::take(&mut share[0]), + }) +} + fn find_approvals>( boot_dir: P, manifest: &Manifest, @@ -1956,6 +2473,29 @@ fn read_manifest_envelope>( .map_err(|_| Error::FileDidNotHaveValidManifestEnvelope) } +fn read_reshard_input(file: String) -> Result { + let buf = fs::read(&file).map_err(|e| Error::FailedToRead { + path: file, + error: e.to_string(), + })?; + + let mut reshard_input: ReshardInput = serde_json::from_slice(&buf) + .map_err(|e| Error::FailedToDeserializeReshardInput(e.to_string()))?; + reshard_input.deterministic(); + + Ok(reshard_input) +} + +fn read_reshard_output(file: String) -> Result { + let buf = fs::read(&file).map_err(|e| Error::FailedToRead { + path: file, + error: e.to_string(), + })?; + + serde_json::from_slice(&buf) + .map_err(|e| Error::FailedToDeserializeReshardOutput(e.to_string())) +} + fn read_attestation_approval>( path: P, ) -> Result { @@ -2097,6 +2637,25 @@ fn write_with_msg(path: &Path, buf: &[u8], item_name: &str) { println!("{item_name} written to: {path_str}"); } +fn write_json_with_msg( + path: &Path, + item: &T, + item_name: &str, +) { + let path_str = path.as_os_str().to_string_lossy(); + + let buf = serde_json::to_vec_pretty(item).unwrap_or_else(|_| { + panic!( + "Failed serializing to json when writing {} to file", + path_str.clone() + ) + }); + fs::write(path, buf).unwrap_or_else(|_| { + panic!("Failed writing {} to file", path_str.clone()) + }); + println!("{item_name} written to: {path_str}"); +} + struct Prompter { reader: R, writer: W, diff --git a/src/qos_core/Cargo.toml b/src/qos_core/Cargo.toml index 74700295..2518c433 100644 --- a/src/qos_core/Cargo.toml +++ b/src/qos_core/Cargo.toml @@ -11,8 +11,9 @@ qos_p256 = { path = "../qos_p256" } qos_nsm = { path = "../qos_nsm", default-features = false } nix = { version = "0.26", features = ["socket"], default-features = false } -libc = "=0.2.149" -borsh = { version = "1.0", features = ["std", "derive"] , default-features = false} +libc = "0.2.149" +borsh = { version = "1.0", features = ["std", "derive"] } +vsss-rs = { version = "4.3", default-features = false, features = ["std"] } # For AWS Nitro aws-nitro-enclaves-nsm-api = { version = "0.3", default-features = false } diff --git a/src/qos_core/src/protocol/error.rs b/src/qos_core/src/protocol/error.rs index 74e5f4bb..31e5e65f 100644 --- a/src/qos_core/src/protocol/error.rs +++ b/src/qos_core/src/protocol/error.rs @@ -141,6 +141,32 @@ pub enum ProtocolError { /// The new manifest was different from the old manifest when we expected /// them to be the same because they have the same nonce DifferentManifest, + /// Expected to have [crate::protocol::services::reshard::ReshardInput] in + /// enclave state, but it was not found. + MissingReshardInput, + /// Expected to have [crate::protocol::services::reshard::ReshardOutput] in + /// enclave state, but it was not found. + MissingReshardOutput, + /// The same member was in the share set multiple times. + DuplicateNewShareSetMember, + /// The share set member posted more or less shares than the number of + /// quorum keys targeted for reconstruction. + ShareCountDoesNotMatchExpectedQuorumKeyCount, + /// Could not decrypt the share with the ephemeral key. + ShareDecryptionFailed, + /// Internal error indicating that the count of shares in each secret + /// builder do not match. We expect each secret builder to have the same + /// count of shares because we enforce that the count of shares post by + /// each member equals the count of quorum keys specified for + /// reconstruction in the reshard input. + InternalDiffCountsForQuorumKeyShares, + /// The same quorum key was specified multiple times in the reshard input. + DuplicateQuorumKeys, + /// Internal error indicating that reshard provisioner was not initialized + /// on boot. + MissingReshardProvisioner, + /// Error from the qos crypto library. + QosCrypto(String), } impl From for ProtocolError { diff --git a/src/qos_core/src/protocol/msg.rs b/src/qos_core/src/protocol/msg.rs index 9b1a3e98..5f337f88 100644 --- a/src/qos_core/src/protocol/msg.rs +++ b/src/qos_core/src/protocol/msg.rs @@ -2,10 +2,12 @@ use qos_nsm::types::NsmResponse; +use super::services::reshard::ReshardProvisionInput; use crate::protocol::{ services::{ boot::{Approval, ManifestEnvelope}, genesis::{GenesisOutput, GenesisSet}, + reshard::{ReshardInput, ReshardOutput}, }, ProtocolError, }; @@ -138,6 +140,49 @@ pub enum ProtocolMsg { /// if the manifest envelope does not exist. manifest_envelope: Box>, }, + + /// Reshard a quorum key to the `new_share_set` in the [`ReshardInput`] + BootReshardRequest { + /// The parameters for resharding + reshard_input: ReshardInput, + }, + /// Response to [`Self::BootReshardRequest`]. + BootReshardResponse { + /// Should be `[NsmResponse::Attestation`]. The `user_data` field of + /// of the attestation document is the qos hash of [`ReshardInput`]. + nsm_response: NsmResponse, + }, + + /// Request an attestation doc with the `ReshardInput` as the user data/ + ReshardAttestationDocRequest, + /// Response to [`Self::ReshardAttestationDocRequest`] + ReshardAttestationDocResponse { + /// Should be `[NsmResponse::Attestation`]. The `user_data` field of + /// of the attestation document is the qos hash of [`ReshardInput`]. + nsm_response: NsmResponse, + /// The reshard parameters this enclave is setup for. + reshard_input: ReshardInput, + }, + + /// Post a quorum key shard so it can be provisioned and resharded. + ReshardProvisionRequest { + /// Approval and shares for each quorum key + input: ReshardProvisionInput, + }, + /// Response to a `Self::ReshardProvisionRequest` + ReshardProvisionResponse { + /// If the Quorum key was reconstructed. False indicates still waiting + /// for the Kth share. + reconstructed: bool, + }, + + /// Request the reshard service's output. + ReshardOutputRequest, + /// Response to [Self::ReshardOutputRequest]. + ReshardOutputResponse { + /// The output of the reshard services. + reshard_output: ReshardOutput, + }, } #[cfg(test)] diff --git a/src/qos_core/src/protocol/services/attestation.rs b/src/qos_core/src/protocol/services/attestation.rs index 3f206de6..df261a6a 100644 --- a/src/qos_core/src/protocol/services/attestation.rs +++ b/src/qos_core/src/protocol/services/attestation.rs @@ -5,6 +5,7 @@ use qos_nsm::{ use crate::protocol::{ProtocolError, ProtocolState, QosHash}; +/// manifest hash in user data pub(in crate::protocol) fn live_attestation_doc( state: &mut ProtocolState, ) -> Result { @@ -20,13 +21,32 @@ pub(in crate::protocol) fn live_attestation_doc( )) } +/// reshard input hash in user data +pub(in crate::protocol) fn reshard_attestation_doc( + state: &mut ProtocolState, +) -> Result { + let ephemeral_public_key = + state.handles.get_ephemeral_key()?.public_key().to_bytes(); + let mut reshard_input = state + .reshard_input + .clone() + .ok_or(ProtocolError::MissingReshardInput)?; + reshard_input.deterministic(); + + Ok(get_post_boot_attestation_doc( + &*state.attestor, + ephemeral_public_key, + reshard_input.qos_hash().to_vec(), + )) +} + pub(super) fn get_post_boot_attestation_doc( attestor: &dyn NsmProvider, ephemeral_public_key: Vec, - manifest_hash: Vec, + user_data: Vec, ) -> NsmResponse { let request = NsmRequest::Attestation { - user_data: Some(manifest_hash), + user_data: Some(user_data), nonce: None, public_key: Some(ephemeral_public_key), }; diff --git a/src/qos_core/src/protocol/services/genesis.rs b/src/qos_core/src/protocol/services/genesis.rs index 0a20e960..3239aae3 100644 --- a/src/qos_core/src/protocol/services/genesis.rs +++ b/src/qos_core/src/protocol/services/genesis.rs @@ -51,15 +51,24 @@ pub struct RecoveredPermutation(Vec); /// Genesis output per Setup Member. #[derive( - PartialEq, Eq, Clone, borsh::BorshSerialize, borsh::BorshDeserialize, + PartialEq, + Eq, + Clone, + borsh::BorshSerialize, + borsh::BorshDeserialize, + serde::Serialize, + serde::Deserialize, )] +#[serde(rename_all = "camelCase")] pub struct GenesisMemberOutput { /// The Quorum Member whom's Setup Key was used. pub share_set_member: QuorumMember, /// Quorum Key Share encrypted to the `setup_member`'s Personal Key. + #[serde(with = "qos_hex::serde")] pub encrypted_quorum_key_share: Vec, /// Sha512 hash of the plaintext quorum key share. Used by the share set /// member to verify they correctly decrypted the share. + #[serde(with = "qos_hex::serde")] pub share_hash: [u8; 64], } @@ -127,7 +136,8 @@ pub(in crate::protocol) fn boot_genesis( master_seed, genesis_set.members.len(), genesis_set.threshold as usize, - ); + ) + .map_err(|e| ProtocolError::QosCrypto(format!("{e:?}")))?; let member_outputs: Result, _> = zip(shares, genesis_set.members.iter().cloned()) @@ -245,6 +255,7 @@ mod test { qos_crypto::shamir::shares_reconstruct( &shares[0..threshold as usize], ) + .unwrap() .try_into() .unwrap(); let reconstructed_quorum_key = diff --git a/src/qos_core/src/protocol/services/mod.rs b/src/qos_core/src/protocol/services/mod.rs index 5a18d19b..f6cbf34d 100644 --- a/src/qos_core/src/protocol/services/mod.rs +++ b/src/qos_core/src/protocol/services/mod.rs @@ -5,3 +5,4 @@ pub mod boot; pub mod genesis; pub mod key; pub mod provision; +pub mod reshard; diff --git a/src/qos_core/src/protocol/services/provision.rs b/src/qos_core/src/protocol/services/provision.rs index 799af628..46d50721 100644 --- a/src/qos_core/src/protocol/services/provision.rs +++ b/src/qos_core/src/protocol/services/provision.rs @@ -33,7 +33,8 @@ impl SecretBuilder { /// Attempt to reconstruct the secret from the pub(crate) fn build(&self) -> Result { - let secret = qos_crypto::shamir::shares_reconstruct(&self.shares); + let secret = qos_crypto::shamir::shares_reconstruct(&self.shares) + .map_err(|e| ProtocolError::QosCrypto(format!("{e:?}")))?; if secret.is_empty() { return Err(ProtocolError::ReconstructionErrorEmptySecret); @@ -47,7 +48,7 @@ impl SecretBuilder { self.shares.len() } - fn clear(&mut self) { + pub(crate) fn clear(&mut self) { self.shares = vec![]; } } @@ -250,6 +251,7 @@ mod test { let quorum_key = quorum_pair.to_master_seed(); let encrypted_shares: Vec<_> = shares_generate(quorum_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); @@ -307,6 +309,7 @@ mod test { P256Pair::generate().unwrap().to_master_seed().to_vec(); let encrypted_shares: Vec<_> = shares_generate(&random_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); @@ -351,6 +354,7 @@ mod test { let encrypted_shares: Vec<_> = shares_generate(quorum_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); @@ -368,7 +372,7 @@ mod test { } // 6) Add a bogus shard as the Kth shard - let bogus_share = &[69u8; 2349]; + let bogus_share = &[69u8; 33]; let encrypted_bogus_share = eph_pair.public_key().encrypt(bogus_share).unwrap(); let approval = approvals[threshold].clone(); @@ -401,6 +405,7 @@ mod test { let quorum_key = quorum_pair.to_master_seed(); let mut encrypted_shares: Vec<_> = shares_generate(quorum_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); @@ -437,6 +442,7 @@ mod test { let quorum_key = quorum_pair.to_master_seed(); let mut encrypted_shares: Vec<_> = shares_generate(quorum_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); @@ -479,6 +485,7 @@ mod test { let quorum_key = quorum_pair.to_master_seed(); let mut encrypted_shares: Vec<_> = shares_generate(quorum_key, 4, threshold) + .unwrap() .iter() .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) .collect(); diff --git a/src/qos_core/src/protocol/services/reshard.rs b/src/qos_core/src/protocol/services/reshard.rs new file mode 100644 index 00000000..f553225c --- /dev/null +++ b/src/qos_core/src/protocol/services/reshard.rs @@ -0,0 +1,668 @@ +//! Quorum Key Resharding logic and types. + +use core::iter::zip; +use std::collections::{HashMap, HashSet}; + +use qos_crypto::sha_512; +use qos_nsm::types::NsmResponse; +use qos_p256::{P256Pair, P256Public}; + +use super::provision::SecretBuilder; +use crate::protocol::{ + services::{ + attestation, + boot::{Approval, NitroConfig, ShareSet}, + genesis::GenesisMemberOutput, + }, + ProtocolError, ProtocolState, QosHash, +}; + +/// A share and the quorum key it is for. +#[derive( + Debug, + PartialEq, + Eq, + Clone, + PartialOrd, + Hash, + Ord, + borsh::BorshSerialize, + borsh::BorshDeserialize, + serde::Serialize, + serde::Deserialize, +)] +pub struct ReshardProvisionShare { + /// Share, encrypted to the ephemeral key + #[serde(with = "qos_hex::serde")] + pub share: Vec, + /// Public key the share targets + #[serde(with = "qos_hex::serde")] + pub pub_key: Vec, +} + +/// A single members input +#[derive( + Debug, + PartialEq, + Eq, + Clone, + borsh::BorshSerialize, + borsh::BorshDeserialize, + serde::Serialize, + serde::Deserialize, +)] +pub struct ReshardProvisionInput { + /// Approval over reshard input + pub approval: Approval, + /// Shares and the associated quorum keys + pub shares: Vec, +} + +/// The parameters for setting up the reshard service. +#[derive( + Debug, + PartialEq, + Eq, + Clone, + borsh::BorshSerialize, + borsh::BorshDeserialize, + serde::Serialize, + serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(any(feature = "mock", test), derive(Default))] +pub struct ReshardInput { + /// List of quorum public keys + pub quorum_keys: Vec>, + /// The share set and threshold to shard the quorum keys to. + pub new_share_set: ShareSet, + /// The share set the quorum keys are currently sharded too. + pub old_share_set: ShareSet, + /// The expected configuration of the enclave. Useful to verify the + /// attestation document against. We also want those posting shares to + /// explicitly approve the version of QOS used. + pub enclave: NitroConfig, +} + +impl ReshardInput { + /// Make sure reshard input is deterministic + pub fn deterministic(&mut self) { + self.quorum_keys.sort(); + self.new_share_set.members.sort(); + self.old_share_set.members.sort(); + } + + fn validate(&mut self) -> Result<(), ProtocolError> { + self.deterministic(); + + let new_share_set_members: HashSet<_> = self + .new_share_set + .members + .iter() + .map(|m| m.pub_key.clone()) + .collect(); + + if new_share_set_members.len() != self.new_share_set.members.len() { + return Err(ProtocolError::DuplicateNewShareSetMember); + } + + let quorum_pub_keys: HashSet<_> = self.quorum_keys.iter().collect(); + if quorum_pub_keys.len() != self.quorum_keys.len() { + return Err(ProtocolError::DuplicateQuorumKeys); + } + + Ok(()) + } + + /// Get a set of the quorum keys. + #[must_use] + pub fn quorum_keys(&self) -> HashSet> { + self.quorum_keys.iter().cloned().collect() + } +} + +pub(crate) struct ReshardProvisioner { + secret_builders: HashMap, SecretBuilder>, + quorum_key_count: usize, +} + +impl ReshardProvisioner { + pub(in crate::protocol) fn new(quorum_key_count: usize) -> Self { + Self { secret_builders: HashMap::new(), quorum_key_count } + } + + pub(in crate::protocol) fn add_shares( + &mut self, + shares: Vec, + eph_key: &P256Pair, + ) -> Result<(), ProtocolError> { + if shares.len() != self.quorum_key_count { + return Err( + ProtocolError::ShareCountDoesNotMatchExpectedQuorumKeyCount, + ); + } + + for ReshardProvisionShare { share, pub_key } in shares { + let decrypted_share = eph_key + .decrypt(&share) + .map_err(|_| ProtocolError::ShareDecryptionFailed)?; + + let builder = self + .secret_builders + .entry(pub_key) + .or_insert_with(SecretBuilder::new); + builder.add_share(decrypted_share)?; + } + + Ok(()) + } + + pub(in crate::protocol) fn share_count( + &self, + ) -> Result { + Ok(self + .secret_builders + .values() + .try_fold(None, |count, builder| { + if let Some(current_count) = count { + if current_count != builder.count() { + return Err( + ProtocolError::InternalDiffCountsForQuorumKeyShares, + ); + } + Ok(count) + } else { + Ok(Some(builder.count())) + } + })? + .unwrap_or(0)) + } + + pub(in crate::protocol) fn build( + &mut self, + ) -> Result, ProtocolError> { + self.secret_builders + .drain() + .map(|(public, builder)| { + let master_seed: [u8; 32] = builder + .build()? + .try_into() + .map_err(|_| ProtocolError::IncorrectSecretLen)?; + + let pair = P256Pair::from_master_seed(&master_seed)?; + let public_key_bytes = pair.public_key().to_bytes(); + + if public_key_bytes != public { + return Err( + ProtocolError::ReconstructionErrorIncorrectPubKey, + ); + } + + Ok(pair) + }) + .collect::, ProtocolError>>() + } +} + +/// The output of performing a quorum key reshard. +#[derive( + Debug, + PartialEq, + Eq, + Clone, + borsh::BorshSerialize, + borsh::BorshDeserialize, + serde::Serialize, + serde::Deserialize, +)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(any(feature = "mock", test), derive(Default))] +pub struct ReshardOutput { + /// The new encrypted shards along with metadata about the share set member + /// they where encrypted to. + pub outputs: Vec<(Vec, Vec)>, + /// The set the keys where sharded too. + pub new_share_set: ShareSet, +} + +pub(in crate::protocol) fn boot_reshard( + state: &mut ProtocolState, + mut reshard_input: ReshardInput, +) -> Result { + reshard_input.validate()?; + + state.reshard_provisioner = + Some(ReshardProvisioner::new(reshard_input.quorum_keys.len())); + + state.reshard_input = Some(reshard_input); + + let ephemeral_key = P256Pair::generate()?; + state.handles.put_ephemeral_key(&ephemeral_key)?; + + attestation::reshard_attestation_doc(state) +} + +pub(in crate::protocol) fn reshard_output( + state: &mut ProtocolState, +) -> Result { + state.reshard_output.clone().ok_or(ProtocolError::MissingReshardOutput) +} + +pub(in crate::protocol) fn reshard_provision( + input: ReshardProvisionInput, + state: &mut ProtocolState, +) -> Result { + let mut reshard_input = state + .reshard_input + .as_ref() + .ok_or(ProtocolError::MissingReshardInput)? + .clone(); + + reshard_input.deterministic(); + input.approval.verify(&reshard_input.qos_hash())?; + + if !reshard_input.old_share_set.members.contains(&input.approval.member) { + return Err(ProtocolError::NotShareSetMember); + } + + let ephemeral_key = state.handles.get_ephemeral_key()?; + state + .get_mut_reshard_provisioner()? + .add_shares(input.shares, &ephemeral_key)?; + + let quorum_threshold = reshard_input.old_share_set.threshold as usize; + if state.get_mut_reshard_provisioner()?.share_count()? < quorum_threshold { + // Nothing else to do if we don't have the threshold to reconstruct + return Ok(false); + } + + let quorum_key_pairs = state.get_mut_reshard_provisioner()?.build()?; + let outputs = quorum_key_pairs + .iter() + .map(|pair| { + let master_seed = pair.to_master_seed(); + let pub_key = pair.public_key().to_bytes(); + + let shares = qos_crypto::shamir::shares_generate( + &master_seed[..], + reshard_input.new_share_set.members.len(), + reshard_input.new_share_set.threshold as usize, + ) + .map_err(|e| ProtocolError::QosCrypto(format!("{e:?}")))?; + + // Now, let's create the new shards + let member_outputs = + zip(shares, reshard_input.new_share_set.members.iter().cloned()) + .map(|(share, share_set_member)| -> Result { + let personal_pub = P256Public::from_bytes(&share_set_member.pub_key)?; + let encrypted_quorum_key_share = personal_pub.encrypt(&share)?; + + Ok(GenesisMemberOutput { + share_set_member, + encrypted_quorum_key_share, + share_hash: sha_512(&share), + }) + }) + .collect::, _>>()?; + + Ok((pub_key, member_outputs)) + }) + .collect::, ProtocolError>>()?; + + state.reshard_output = Some(ReshardOutput { + outputs, + new_share_set: reshard_input.new_share_set, + }); + + Ok(true) +} + +#[cfg(test)] +mod tests { + use qos_crypto::{n_choose_k, shamir::shares_generate}; + use qos_nsm::mock::MockNsm; + use qos_test_primitives::PathWrapper; + + use super::*; + use crate::{ + handles::Handles, + io::SocketAddress, + protocol::{services::boot::QuorumMember, ProtocolPhase, QosHash}, + }; + + struct ReshardSetup { + state: ProtocolState, + new_members: Vec<(QuorumMember, P256Pair)>, + eph_pair: P256Pair, + /// Quorum pairs and associated shares, encrypted to the ephemeral key. + provision_inputs: Vec, + } + + fn reshard_setup(eph_file: &str) -> ReshardSetup { + let handles = Handles::new( + eph_file.to_string(), + "/tmp/qos-quorum".to_string(), + "/tmp/qos-manifest".to_string(), + "/tmp/qos-pivot".to_string(), + ); + let eph_pair = P256Pair::generate().unwrap(); + handles.put_ephemeral_key(&eph_pair).unwrap(); + + let quorum_pairs: Vec<_> = (0..4) + .map(|_| { + let pair = P256Pair::generate().unwrap(); + + let encrypted_shares: Vec<_> = shares_generate( + &pair.to_master_seed()[..], + 4, + 3, // old share set threshold + ) + .unwrap() + .iter() + .map(|s| eph_pair.public_key().encrypt(s).unwrap()) + .collect(); + + (pair, encrypted_shares) + }) + .collect(); + let quorum_keys: Vec<_> = + quorum_pairs.iter().map(|p| p.0.public_key().to_bytes()).collect(); + + let old_members: Vec<_> = (0..4) + .map(|_| P256Pair::generate().unwrap()) + .enumerate() + .map(|(i, pair)| { + let member = QuorumMember { + alias: i.to_string(), + pub_key: pair.public_key().to_bytes(), + }; + + (member, pair) + }) + .collect(); + + let new_members: Vec<_> = (0..4) + .map(|_| P256Pair::generate().unwrap()) + .enumerate() + .map(|(i, pair)| { + let member = QuorumMember { + alias: i.to_string(), + pub_key: pair.public_key().to_bytes(), + }; + + (member, pair) + }) + .collect(); + + let mut reshard_input = ReshardInput { + quorum_keys, + new_share_set: ShareSet { + threshold: 2, + members: new_members.iter().map(|(qm, _)| qm.clone()).collect(), + }, + old_share_set: ShareSet { + threshold: 3, + members: old_members.iter().map(|(qm, _)| qm.clone()).collect(), + }, + enclave: NitroConfig { + pcr0: vec![4; 32], + pcr1: vec![3; 32], + pcr2: vec![2; 32], + pcr3: vec![1; 32], + aws_root_certificate: b"bezo's son, a dad of certs".to_vec(), + qos_commit: "super chill commit ref you can bro down with" + .to_string(), + }, + }; + + // SUPER IMPORTANT: we need to ensure we always sign the correct hash. + reshard_input.deterministic(); + + let provision_inputs: Vec<_> = old_members + .into_iter() + .enumerate() + .map(|(idx, (member, pair))| { + let shares = quorum_pairs + .iter() + .map(|(pair, shares)| ReshardProvisionShare { + share: shares[idx].clone(), + pub_key: pair.public_key().to_bytes(), + }) + .collect(); + let approval = Approval { + member, + signature: pair.sign(&reshard_input.qos_hash()).unwrap(), + }; + + ReshardProvisionInput { approval, shares } + }) + .collect(); + + let mut state = ProtocolState::new( + Box::new(MockNsm), + handles, + SocketAddress::new_unix("./never.sock"), + None, + ); + state.reshard_input = Some(reshard_input); + state.reshard_provisioner = + Some(ReshardProvisioner::new(quorum_pairs.len())); + state.transition(ProtocolPhase::ReshardWaitingForQuorumShards).unwrap(); + + ReshardSetup { state, new_members, eph_pair, provision_inputs } + } + + #[test] + fn reshard_provision_works() { + let eph_file: PathWrapper = + "/tmp/reshard_provision_works.eph.key".into(); + + let ReshardSetup { mut state, new_members, provision_inputs, .. } = + reshard_setup(&eph_file); + + // We expect reshard_provision to return Ok(false) for the first + // 2 + for i in provision_inputs.iter().take(2) { + assert_eq!(reshard_provision(i.clone(), &mut state), Ok(false)); + } + + // And then return Ok(true) for the 3rd share to signal it has been + // reconstructed + assert_eq!( + reshard_provision(provision_inputs[2].clone(), &mut state), + Ok(true) + ); + + let reshard_output = state.reshard_output.clone().unwrap(); + let reshard_input = state.reshard_input.clone().unwrap(); + assert_eq!(reshard_output.new_share_set, reshard_input.new_share_set); + + for (quorum_pub, member_outputs) in reshard_output.outputs { + // Check that decrypted shares match hash + let mut decrypted_shares = vec![]; + for (member_out, (member, pair)) in + zip(member_outputs, new_members.clone()) + { + let share = pair + .decrypt(&member_out.encrypted_quorum_key_share) + .unwrap(); + assert_eq!( + &member_out.share_hash, + &qos_crypto::sha_512(&share), + ); + assert_eq!(member_out.share_set_member, member); + + decrypted_shares.push(share); + } + + // Now make sure all combos of shares work + for combo in n_choose_k::combinations( + &decrypted_shares, + reshard_output.new_share_set.threshold as usize, + ) { + let secret: [u8; 32] = + qos_crypto::shamir::shares_reconstruct(&combo) + .unwrap() + .try_into() + .unwrap(); + let quorum_key = P256Pair::from_master_seed(&secret).unwrap(); + assert_eq!(quorum_pub, quorum_key.public_key().to_bytes()); + } + } + } + + #[test] + fn reshard_provision_rejects_wrong_reconstructed_key() { + let eph_file: PathWrapper = + "/tmp/reshard_provision_rejects_wrong_reconstructed_key.eph.key" + .into(); + + let ReshardSetup { eph_pair, mut state, mut provision_inputs, .. } = + reshard_setup(&eph_file); + + let reshard_input = state.reshard_input.clone().unwrap(); + let random_pair = P256Pair::generate().unwrap(); + let encrypted_shares: Vec<_> = shares_generate( + random_pair.to_master_seed(), + 4, + reshard_input.new_share_set.threshold as usize, + ) + .unwrap() + .iter() + .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) + .collect(); + + for (i, user_input) in provision_inputs.iter_mut().enumerate() { + // loop through each of the users input and modify their first share + // to be for a different public key + user_input.shares[0].share = encrypted_shares[i].clone(); + } + + // We expect reshard_provision to return Ok(false) for the first + // 2 + for i in provision_inputs.iter().take(2) { + assert_eq!(reshard_provision(i.clone(), &mut state), Ok(false)); + } + + // And then return an error for the 3rd share to signal it has been + // reconstructed + assert_eq!( + reshard_provision(provision_inputs[2].clone(), &mut state), + Err(ProtocolError::ReconstructionErrorIncorrectPubKey) + ); + } + + #[test] + fn reshard_provision_rejects_bad_approval_signature() { + let eph_file: PathWrapper = + "/tmp/reshard_provision_rejects_bad_approval_signature.eph.key" + .into(); + + let ReshardSetup { + mut state, mut provision_inputs, new_members, .. + } = reshard_setup(&eph_file); + + // give the third approval a random signature + provision_inputs[2].approval.signature = + new_members[2].1.sign(&[42; 32]).unwrap(); + + // We expect reshard_provision to return Ok(false) for the first + // 2 + for i in provision_inputs.iter().take(2) { + assert_eq!(reshard_provision(i.clone(), &mut state), Ok(false)); + } + + // And then return an error for the 3rd share to signal it has been + // reconstructed + assert_eq!( + reshard_provision(provision_inputs[2].clone(), &mut state), + Err(ProtocolError::CouldNotVerifyApproval) + ); + } + + #[test] + fn reshard_provision_rejects_approval_not_from_member() { + let eph_file: PathWrapper = + "/tmp/reshard_provision_rejects_approval_not_from_member.eph.key" + .into(); + + let ReshardSetup { + eph_pair, + mut state, + mut provision_inputs, + new_members, + .. + } = reshard_setup(&eph_file); + + let reshard_input = state.reshard_input.clone().unwrap(); + let random_pair = P256Pair::generate().unwrap(); + let _encrypted_shares: Vec<_> = shares_generate( + random_pair.to_master_seed(), + 4, + reshard_input.new_share_set.threshold as usize, + ) + .unwrap() + .iter() + .map(|shard| eph_pair.public_key().encrypt(shard).unwrap()) + .collect(); + + // the old and new members are unique. We only expect approvals from the + // old members. So if a new members approval comes in, we don't accept + // it. + provision_inputs[2].approval.signature = + new_members[0].1.sign(&reshard_input.qos_hash()).unwrap(); + provision_inputs[2].approval.member = new_members[0].0.clone(); + + // We expect reshard_provision to return Ok(false) for the first + // 2 + for i in provision_inputs.iter().take(2) { + assert_eq!(reshard_provision(i.clone(), &mut state), Ok(false)); + } + + // And then return an error for the 3rd share to signal it has been + // reconstructed + assert_eq!( + reshard_provision(provision_inputs[2].clone(), &mut state), + Err(ProtocolError::NotShareSetMember) + ); + } + + #[test] + fn boot_reshard_works() { + let eph_file: PathWrapper = "/tmp/boot_reshard_works.eph.key".into(); + + let handles = Handles::new( + eph_file.to_string(), + "/tmp/qos-quorum".to_string(), + "/tmp/qos-manifest".to_string(), + "/tmp/qos-pivot".to_string(), + ); + let mut state = ProtocolState::new( + Box::new(MockNsm), + handles, + SocketAddress::new_unix("./never.sock"), + None, + ); + + let reshard_input = ReshardInput { + quorum_keys: vec![vec![1; 65], vec![2; 65]], + new_share_set: ShareSet { threshold: 2, members: vec![] }, + old_share_set: ShareSet { threshold: 3, members: vec![] }, + enclave: NitroConfig { + pcr0: vec![4; 32], + pcr1: vec![3; 32], + pcr2: vec![2; 32], + pcr3: vec![1; 32], + aws_root_certificate: + b"super swag root cert your friends told you about".to_vec(), + qos_commit: "a commit ref".to_string(), + }, + }; + + assert!(boot_reshard(&mut state, reshard_input.clone(),).is_ok()); + + assert_eq!(state.reshard_input, Some(reshard_input)); + assert_eq!(state.reshard_output, None); + assert!(state.handles.get_ephemeral_key().is_ok()); + } +} diff --git a/src/qos_core/src/protocol/state.rs b/src/qos_core/src/protocol/state.rs index 6df38852..1ebc70ca 100644 --- a/src/qos_core/src/protocol/state.rs +++ b/src/qos_core/src/protocol/state.rs @@ -3,9 +3,17 @@ use nix::sys::time::{TimeVal, TimeValLike}; use qos_nsm::NsmProvider; use super::{ - error::ProtocolError, msg::ProtocolMsg, services::provision::SecretBuilder, + error::ProtocolError, + msg::ProtocolMsg, + services::{ + provision::SecretBuilder, + reshard::{ReshardInput, ReshardProvisioner}, + }, +}; +use crate::{ + client::Client, handles::Handles, io::SocketAddress, + protocol::services::reshard::ReshardOutput, }; -use crate::{client::Client, handles::Handles, io::SocketAddress}; /// The timeout for the qos core when making requests to an enclave app. pub const ENCLAVE_APP_SOCKET_CLIENT_TIMEOUT_SECS: i64 = 5; @@ -35,6 +43,13 @@ pub enum ProtocolPhase { QuorumKeyProvisioned, /// Waiting for a forwarded key to be injected WaitingForForwardedKey, + /// Waiting for quorum key shards to be posted so the reshard service can + /// be executed. + ReshardWaitingForQuorumShards, + /// Reshard service has completed + ReshardBooted, + /// Reshard failed to reconstruct the quorum key + UnrecoverableReshardFailedBadShares, } /// Enclave routes @@ -57,11 +72,12 @@ impl ProtocolRoute { let resp = (self.handler)(msg, state); // ignore transitions in special cases - if let Some(Ok(ProtocolMsg::ProvisionResponse { reconstructed })) = resp - { - if !reconstructed { - return resp; - } + match resp { + Some(Ok( + ProtocolMsg::ProvisionResponse { reconstructed } + | ProtocolMsg::ReshardProvisionResponse { reconstructed }, + )) if !reconstructed => return resp, + _ => { /* This isn't a special case, keep going */ } } // handle state transitions @@ -106,6 +122,14 @@ impl ProtocolRoute { ) } + pub fn reshard_attestation_doc(current_phase: ProtocolPhase) -> Self { + ProtocolRoute::new( + Box::new(handlers::reshard_attestation_doc), + current_phase, + current_phase, + ) + } + pub fn boot_genesis(_current_phase: ProtocolPhase) -> Self { ProtocolRoute::new( Box::new(handlers::boot_genesis), @@ -122,6 +146,14 @@ impl ProtocolRoute { ) } + pub fn boot_reshard(_current_phase: ProtocolPhase) -> Self { + ProtocolRoute::new( + Box::new(handlers::boot_reshard), + ProtocolPhase::ReshardWaitingForQuorumShards, + ProtocolPhase::UnrecoverableError, + ) + } + pub fn boot_key_forward(_current_phase: ProtocolPhase) -> Self { ProtocolRoute::new( Box::new(handlers::boot_key_forward), @@ -138,6 +170,22 @@ impl ProtocolRoute { ) } + pub fn reshard_provision(_current_phase: ProtocolPhase) -> Self { + ProtocolRoute::new( + Box::new(handlers::reshard_provision), + ProtocolPhase::ReshardBooted, + ProtocolPhase::UnrecoverableReshardFailedBadShares, + ) + } + + pub fn reshard_output(current_phase: ProtocolPhase) -> Self { + ProtocolRoute::new( + Box::new(handlers::reshard_output), + current_phase, + current_phase, + ) + } + pub fn proxy(current_phase: ProtocolPhase) -> Self { ProtocolRoute::new( Box::new(handlers::proxy), @@ -178,9 +226,13 @@ pub(crate) struct ProtocolState { pub app_client: Client, pub handles: Handles, phase: ProtocolPhase, + pub reshard_input: Option, + pub reshard_output: Option, + pub(crate) reshard_provisioner: Option, } impl ProtocolState { + #[allow(unused_variables)] pub fn new( attestor: Box, handles: Handles, @@ -207,6 +259,9 @@ impl ProtocolState { app_addr, TimeVal::seconds(ENCLAVE_APP_SOCKET_CLIENT_TIMEOUT_SECS), ), + reshard_input: None, + reshard_output: None, + reshard_provisioner: None, } } @@ -214,6 +269,14 @@ impl ProtocolState { self.phase } + pub(crate) fn get_mut_reshard_provisioner( + &mut self, + ) -> Result<&mut ReshardProvisioner, ProtocolError> { + self.reshard_provisioner + .as_mut() + .ok_or(ProtocolError::MissingReshardProvisioner) + } + pub fn handle_msg(&mut self, msg_req: &ProtocolMsg) -> Vec { for route in &self.routes() { match route.try_msg(msg_req, self) { @@ -255,6 +318,7 @@ impl ProtocolState { ProtocolRoute::boot_genesis(self.phase), ProtocolRoute::boot_standard(self.phase), ProtocolRoute::boot_key_forward(self.phase), + ProtocolRoute::boot_reshard(self.phase), ], ProtocolPhase::WaitingForQuorumShards => { vec![ @@ -287,6 +351,29 @@ impl ProtocolState { ProtocolRoute::inject_key(self.phase), ] } + ProtocolPhase::ReshardWaitingForQuorumShards => { + vec![ + // baseline routes + ProtocolRoute::status(self.phase), + ProtocolRoute::reshard_attestation_doc(self.phase), + // phase specific routes + ProtocolRoute::reshard_provision(self.phase), + ] + } + ProtocolPhase::UnrecoverableReshardFailedBadShares => { + vec![ + // baseline routes + ProtocolRoute::status(self.phase), + ] + } + ProtocolPhase::ReshardBooted => { + vec![ + // baseline routes + ProtocolRoute::status(self.phase), + // phase specific routes + ProtocolRoute::reshard_output(self.phase), + ] + } } } @@ -305,6 +392,7 @@ impl ProtocolState { ProtocolPhase::UnrecoverableError, ProtocolPhase::GenesisBooted, ProtocolPhase::WaitingForQuorumShards, + ProtocolPhase::ReshardWaitingForQuorumShards, ProtocolPhase::WaitingForForwardedKey, ], ProtocolPhase::GenesisBooted => { @@ -325,6 +413,18 @@ impl ProtocolState { ProtocolPhase::QuorumKeyProvisioned, ] } + ProtocolPhase::ReshardWaitingForQuorumShards => { + vec![ + ProtocolPhase::UnrecoverableReshardFailedBadShares, + ProtocolPhase::ReshardBooted, + ] + } + ProtocolPhase::ReshardBooted => { + vec![ProtocolPhase::UnrecoverableError] + } + ProtocolPhase::UnrecoverableReshardFailedBadShares => { + vec![ProtocolPhase::UnrecoverableError] + } }; if !transitions.contains(&next) { @@ -341,9 +441,11 @@ impl ProtocolState { mod handlers { use super::ProtocolRouteResponse; use crate::protocol::{ + error::ProtocolError, msg::ProtocolMsg, services::{ - attestation, boot, genesis, key, key::EncryptedQuorumKey, provision, + attestation, boot, genesis, key, key::EncryptedQuorumKey, + provision, reshard, }, ProtocolState, }; @@ -410,6 +512,40 @@ mod handlers { } } + pub(super) fn reshard_provision( + req: &ProtocolMsg, + state: &mut ProtocolState, + ) -> ProtocolRouteResponse { + if let ProtocolMsg::ReshardProvisionRequest { input } = req { + let result = reshard::reshard_provision(input.clone(), state) + .map(|reconstructed| ProtocolMsg::ReshardProvisionResponse { + reconstructed, + }) + .map_err(ProtocolMsg::ProtocolErrorResponse); + + Some(result) + } else { + None + } + } + + pub(super) fn reshard_output( + req: &ProtocolMsg, + state: &mut ProtocolState, + ) -> ProtocolRouteResponse { + if let ProtocolMsg::ReshardOutputRequest = req { + let result = reshard::reshard_output(state) + .map(|reshard_output| ProtocolMsg::ReshardOutputResponse { + reshard_output, + }) + .map_err(ProtocolMsg::ProtocolErrorResponse); + + Some(result) + } else { + None + } + } + /// Handle `ProtocolMsg::BootStandardRequest`. pub(super) fn boot_standard( req: &ProtocolMsg, @@ -450,6 +586,23 @@ mod handlers { } } + pub(super) fn boot_reshard( + req: &ProtocolMsg, + state: &mut ProtocolState, + ) -> ProtocolRouteResponse { + if let ProtocolMsg::BootReshardRequest { reshard_input } = req { + let result = reshard::boot_reshard(state, reshard_input.clone()) + .map(|nsm_response| ProtocolMsg::BootReshardResponse { + nsm_response, + }) + .map_err(ProtocolMsg::ProtocolErrorResponse); + + Some(result) + } else { + None + } + } + pub(super) fn live_attestation_doc( req: &ProtocolMsg, state: &mut ProtocolState, @@ -472,6 +625,37 @@ mod handlers { } } + pub(super) fn reshard_attestation_doc( + req: &ProtocolMsg, + state: &mut ProtocolState, + ) -> ProtocolRouteResponse { + if let ProtocolMsg::ReshardAttestationDocRequest = req { + let reshard_input = match state + .reshard_input + .as_ref() + .ok_or(ProtocolError::MissingReshardInput) + { + Ok(r) => r.clone(), + Err(e) => { + return Some(Ok(ProtocolMsg::ProtocolErrorResponse(e))) + } + }; + + let result = attestation::reshard_attestation_doc(state) + .map(|nsm_response| { + ProtocolMsg::ReshardAttestationDocResponse { + nsm_response, + reshard_input, + } + }) + .map_err(ProtocolMsg::ProtocolErrorResponse); + + Some(result) + } else { + None + } + } + pub(super) fn boot_key_forward( req: &ProtocolMsg, state: &mut ProtocolState, diff --git a/src/qos_crypto/Cargo.toml b/src/qos_crypto/Cargo.toml index b6a77599..c841d2e2 100644 --- a/src/qos_crypto/Cargo.toml +++ b/src/qos_crypto/Cargo.toml @@ -9,6 +9,9 @@ publish = false [dependencies] sha2 = { version = "0.10", default-features = false } rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } +vsss-rs = { version = "4.3", default-features = false, features = ["std"] } +rand_core = { version = "0.6.4", default-features = false } +thiserror = "1.0.63" [dev-dependencies] qos_hex = { path = "../qos_hex" } diff --git a/src/qos_crypto/mock/rsa_private.mock.pem b/src/qos_crypto/mock/rsa_private.mock.pem deleted file mode 100644 index f67905f3..00000000 --- a/src/qos_crypto/mock/rsa_private.mock.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAzjT6oPhEHDs87REJ5DQEbzr81/0NY0hpI7CkkDy2H57oQX0k -hD+LW7qFu16vbIKmxSaJX1SaMHsWqbHIBCTTVZlsxXOrSwuO3YP78inrvNZrxafJ -+LkIaV8UZeXY1h3ACqj3kDJLV1/5EZfTyfMaNUwIUZdiqoa4bwFWDFhNXrlCgIfM -SGyErfMRR7qQCGhEtRUnZPW/+hbuZU1OfbkUAXvsb2IwgJHwqAdYcb6F0bkXZIta -aXEoUG2qIgspYJgOF81rvzdvdEh8ezBEJZIaExovIa0oc5D3yI7ZHbgGMRRT3Mah -oS+UoSBHTjR2yBCMmPMwDuhUCZtqf6/Mz5EouRWst5ezAG2vAqrPRFD0jI8tUDY7 -wSQ0nC8NLUO66CdvHkVZQhHedePEfnk1tJcGvvKWCIDf5AtHARZ89DS5uooH0DyD -ACLJevVE+UAUHxjgOz0Y6SQ4R2sQnROm7b5tWZD24puZ2bsyLz2FMd+Hb/UtDF5C -ZPc5kXqVpQ6W9TaHtQPxS9QrUsyLP2JjQvYZokrBQPwLrdXKdutGO76LjbyTF6ep -lGz87T86zuHW70rEjLXUCo2Xz32gbrNgNRLugNSwrMuIihhGaoFWOZLPXEV30c2r -z0uEDdqt5jFt0fDbNJPSMZnStaIrZjlv/g5CKA1kgPFbYytGcJ1/6cUGjSUCAwEA -AQKCAgAOZRZl7E7c5anAJuNY4eS5WxXRgiHQH3rveeJTC1nvZSlzgOfur3zr/15c -kSSP36MCukj2tbI51i3j1LxQxb1XCWnVctivWXQ0tIT/B7wkJ5fIaYko6snSiUek -QWJcuCDy3Y7CqzAlPlblyoKHY4gd1lvyTi4eF1+CqEY4gGWOSkKBNHmnSKQOfJxl -NHnfjF/XgE2Kt7kaHUWxHq9vCV+DJwJ/WAxovUdeg3zCG/m9hT1D0JKUL8kPrEgn -Lj5KNvMs6DMwWw9Vv8Wo19q6ALP/R2Go6SycvK7/ejFf6LvN60xbaiZYWTj38ofn -xrPQDY+zEa72K3PKY+YQWdZjWw/Lk7zwhQfmFFY9Hfn2ff9IpaPOTyeman7msGSO -fya0WRG4ZE/SoekoGrQ/A+PkuYxeOBfRu1WL349+hKs3zI0y/cOMsL/BMAX1mdrz -N9KLFz4l4oYhABgMTwnR+EjSn4sVzhPVsM9YbfMmHlb6L3hgxMvm/Fg8hz8FR95w -OkkOK0lXT5jlfwrV3rwKojLv2T8lXnQtlpUa5kl+H5Md8OZYacvsyTRZE2BTwoY7 -g+w3n3uBMXL0Q0c9K6lxoyIn6LKRMADk6/L0IpC40POWRHoiaZYbOBVAQDHPiSuL -c/pQdq5ClXVEkbPSFRP4xKwQvCxnEiZqCk5CI9+zHmOCU6AWoQKCAQEA83NGnmQy -/jZqLceYsHETdzQZ7F6nN/OWs6sGd94F91QbVOLFV5XayaVMR8GOfOl7T8jVzQly -s+Hc+4tHItbMT9Umje682MS5K0RPtnJFTIgyL1SurIV97ni8YCVG8iGKUOkTMQ+U -bVoRhbb8bXYB9q0QNhYboUKsc9OoyUnR1jBy/zqJGkP1daEQWZozUSn6pWJBIXb0 -UpheCVGa80a6ccYdi92OGoTOYVF5t4ZA0LRxOKMjfN/0A1Fysz/PsmRfmrgnSFOl -HuDf0YO6vSwlgXX9LQ2//gkTsibc1/GHazmvTk57sjYyjCWGRKJCJ/rpjECNNjDK -X3F7v9jM+14waQKCAQEA2NY3ZYD9NXuJM0ZtWZLTMAJkSWsWHBQwqezflJEXP0QO -vomu73Qx6Xnqurg2lZrJg3bBYebwMDSCt9w391z/LyTtmJ6P9jAWkr4unUlG3nXa -WUAyu668VQZkFKh9iasjvC9ln2qyrp2/9sA/C4ZPGLS1PxpGkSKtzcEHXZINB9ht -caYxPkccV6WsMe/ytGFhKdhPb+O1POSE0YwEVt0e7+iki09zSNJ7e5jA31lEriat -CK6KY4FjAmpxMlS2d0mBuBd4NCW5vzXKKNVH8BUXVXbm6h4gAjc6rU6nV0KhpW34 -VVvBmewYDB2b4JzcgjiDlFy8r5EVdZy4ij4nhhhfXQKCAQEAkdyFeS0LqGgt4dPu -1fhJ82fSCF8FzW4y4t8bdwIdjPxli8x69GkityJEu9Fqb8jsSvdHshtxD/nJjyT5 -sBQGQeaxvORHXZEwaI37PJLmll4bw2P3bAJnW1QXeXucMEKMPsIG76QoCASo7vad -82966bLzPZStZUcvUA6G2GNUSAKrQ+RsdMI29Q1VYHoVORHvzNs7rrM426vS2757 -GjtMRhKvbTeHhrf/dyt7w8u6VdFm7MpB3vXHm51XHbKj3HxrE6Y2Uw1ap0+QilVk -sycaKaDp2e6dE7WYiWrjcraRrlrXgBFh53q1emaZNdIJ1S5uc8vRT6CX/+tce6uH -1Suv0QKCAQAFq8STIQZ+SZbTAnqFpzNixA0/Zk+TuGt1Zj6Ksii7fNot3Yf3t0A+ -7PNYosy6qOuwRoDUQKfzeswYZugHziTWZM7Z+Pum4qcUe2jYsDvsQYTOZMFu6yj9 -yEcBy05NNW6f01WDD9VQf8uvdmOvt3mGGePLnLJPxWpqQSwiJFm25NAn8sLC8DUr -jaetPqtIUGusHn4lXP02dHuMx26tnubaO2liQ1euheK43svci4ciTtyjp3zzEUU3 -oPUI7fI/uGpuGB8KrhnniE6bNsjE3KhZkdyELvmDVVJxiecSfymfG/sssFOl5OjU -GEolW7Tgqv21+Z7tsIuxIcIpy2pZNXX5AoIBAQCgTSzO6goiQrJTD/OU338IDTYx -nnJ0+vecyfPOBymvn5Q0QBIzjs+Mxy0X1IAwBAUlC8kcDJyoaFzyt67K+OKB04vc -+bezx2efDzRCaHqYlP6NeFP+C/LdcZvjsQb5UYMebk7XYqDHhKoy39n838ahYsjZ -NoeRk9bEBZZhhEa715Vmugkk+SL7lbFvSXvtJfJltW0oqjnjY1lVVWrzrHJPq+J1 -D0RqOc/pJ/b6bAo3PAODqGcMV5XQE0zC9ALTH/K20g0PpfCe9xuEYW6qeycyVIAa -0JY2WbC6v4PTjpO1X3cP1UF0GCMrkmc0fSbUxd5+sl4ThVPdtPxCpmBzORYB ------END RSA PRIVATE KEY----- diff --git a/src/qos_crypto/mock/rsa_public.mock.pem b/src/qos_crypto/mock/rsa_public.mock.pem deleted file mode 100644 index 87226850..00000000 --- a/src/qos_crypto/mock/rsa_public.mock.pem +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzjT6oPhEHDs87REJ5DQE -bzr81/0NY0hpI7CkkDy2H57oQX0khD+LW7qFu16vbIKmxSaJX1SaMHsWqbHIBCTT -VZlsxXOrSwuO3YP78inrvNZrxafJ+LkIaV8UZeXY1h3ACqj3kDJLV1/5EZfTyfMa -NUwIUZdiqoa4bwFWDFhNXrlCgIfMSGyErfMRR7qQCGhEtRUnZPW/+hbuZU1OfbkU -AXvsb2IwgJHwqAdYcb6F0bkXZItaaXEoUG2qIgspYJgOF81rvzdvdEh8ezBEJZIa -ExovIa0oc5D3yI7ZHbgGMRRT3MahoS+UoSBHTjR2yBCMmPMwDuhUCZtqf6/Mz5Eo -uRWst5ezAG2vAqrPRFD0jI8tUDY7wSQ0nC8NLUO66CdvHkVZQhHedePEfnk1tJcG -vvKWCIDf5AtHARZ89DS5uooH0DyDACLJevVE+UAUHxjgOz0Y6SQ4R2sQnROm7b5t -WZD24puZ2bsyLz2FMd+Hb/UtDF5CZPc5kXqVpQ6W9TaHtQPxS9QrUsyLP2JjQvYZ -okrBQPwLrdXKdutGO76LjbyTF6eplGz87T86zuHW70rEjLXUCo2Xz32gbrNgNRLu -gNSwrMuIihhGaoFWOZLPXEV30c2rz0uEDdqt5jFt0fDbNJPSMZnStaIrZjlv/g5C -KA1kgPFbYytGcJ1/6cUGjSUCAwEAAQ== ------END PUBLIC KEY----- diff --git a/src/qos_crypto/src/lib.rs b/src/qos_crypto/src/lib.rs index cf670b34..093aa025 100644 --- a/src/qos_crypto/src/lib.rs +++ b/src/qos_crypto/src/lib.rs @@ -5,10 +5,27 @@ #![warn(missing_docs, clippy::pedantic)] #![allow(clippy::missing_errors_doc)] +use std::fmt; + use sha2::Digest; +use thiserror::Error; +pub mod n_choose_k; pub mod shamir; +/// Errors for this crate +#[derive(Error, Debug, PartialEq, Eq, Clone)] +pub enum QosCryptoError { + /// Errors from vsss-rs lib + Vsss(vsss_rs::Error), +} + +impl fmt::Display for QosCryptoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "QosCryptoError: {self:?}") + } +} + /// Create a SHA256 hash digest of `buf`. #[must_use] pub fn sha_256(buf: &[u8]) -> [u8; 32] { diff --git a/src/qos_crypto/src/n_choose_k.rs b/src/qos_crypto/src/n_choose_k.rs new file mode 100644 index 00000000..91fe7c42 --- /dev/null +++ b/src/qos_crypto/src/n_choose_k.rs @@ -0,0 +1,140 @@ +//! n choose k helper + +/// Computes n choose k combinations over a vector of elements of type T. +/// +/// # Arguments +/// +/// * `input` - A reference to a vector of elements of type T. +/// * `k` - The number of elements to choose in each combination. +/// +/// # Examples +/// +/// ``` +/// use qos_crypto::n_choose_k::combinations; +/// +/// let input = vec![1, 2, 3, 4]; +/// let k = 2; +/// let combinations = combinations(&input, k); +/// +/// // Verify that the computed combinations match the expected result +/// assert_eq!(combinations, vec![ +/// vec![1, 2], +/// vec![1, 3], +/// vec![1, 4], +/// vec![2, 3], +/// vec![2, 4], +/// vec![3, 4], +/// ]); +/// ``` +#[must_use] +pub fn combinations(input: &[T], k: usize) -> Vec> { + let n = input.len(); + + if k > n || k == 0 { + return Vec::new(); + } + + let mut combos = + Vec::with_capacity(expected_combinations_count(input.len(), k)); + let mut indices: Vec<_> = (0..k).collect(); + + // Generate combinations + while indices[0] <= n - k { + // Create a combination by mapping indices to corresponding elements in + // the input + let combination: Vec<_> = + indices.iter().map(|&i| input[i].clone()).collect(); + combos.push(combination); + + let mut i = k; + while i > 1 && indices[i - 1] == n - k + i - 1 { + i -= 1; + } + + indices[i - 1] += 1; + + for j in i..k { + indices[j] = indices[j - 1] + 1; + } + } + + combos +} + +fn expected_combinations_count(n: usize, k: usize) -> usize { + factorial(n) / (factorial(k) * factorial(n - k)) +} + +fn factorial(n: usize) -> usize { + if n == 0 || n == 1 { + 1 + } else { + (2..=n).product() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normal_cases() { + // n = 4, k = 2 + let byte_input = vec![b'a', b'b', b'c', b'd']; + let k1 = 2; + let byte_result = combinations(&byte_input, k1); + assert_eq!( + byte_result.len(), + expected_combinations_count(byte_input.len(), k1) + ); + assert!(byte_result.contains(&vec![b'a', b'b'])); + assert!(byte_result.contains(&vec![b'a', b'c'])); + assert!(byte_result.contains(&vec![b'a', b'd'])); + assert!(byte_result.contains(&vec![b'b', b'c'])); + assert!(byte_result.contains(&vec![b'b', b'd'])); + assert!(byte_result.contains(&vec![b'c', b'd'])); + + // n = 3, k = 3 + let char3_input = vec!['x', 'y', 'z']; + let k2 = 3; + let char3_result = combinations(&char3_input, k2); + assert_eq!( + char3_result.len(), + expected_combinations_count(char3_input.len(), k2) + ); + assert_eq!(char3_result, vec![vec!['x', 'y', 'z']]); + + // n = 2, k = 1 + let char2_input = vec!['p', 'q']; + let k3 = 1; + let char2_result = combinations(&char2_input, k3); + assert_eq!( + char2_result.len(), + expected_combinations_count(char2_input.len(), k3) + ); + assert_eq!(char2_result, vec![vec!['p'], vec!['q']]); + } + + #[test] + fn edge_cases() { + // empty input + let empty_input: Vec = Vec::new(); + let empty_result = combinations(&empty_input, 0); + assert_eq!(empty_result.len(), 0); + + // k = 0 + let input = vec![1, 2, 3, 4, 5]; + let k = 0; + let result = combinations(&input, k); + assert_eq!(result.len(), 0); + } + + #[test] + fn factorial_works() { + assert_eq!(factorial(0), 1); + assert_eq!(factorial(1), 1); + assert_eq!(factorial(5), 120); + assert_eq!(factorial(10), 3_628_800); + assert_eq!(factorial(20), 2_432_902_008_176_640_000); + } +} diff --git a/src/qos_crypto/src/shamir.rs b/src/qos_crypto/src/shamir.rs index f668745a..244b1aa3 100644 --- a/src/qos_crypto/src/shamir.rs +++ b/src/qos_crypto/src/shamir.rs @@ -1,214 +1,34 @@ -//! Shamir Secret Sharing implementation. -// Grabbed from here: -// https://github.com/veracruz-project/veracruz/blob/main/sdk/data-generators/shamir-secret-sharing/src/main.rs +//! Shamir's Secret Sharing implementation +// The original self-contained SSS Gf256 implementation is adopted from +// https://github.com/veracruz-project/veracruz/blob/398e4d3ab3023492a64ea91740528e58776e1827/sdk/data-generators/shamir-secret-sharing/src/main.rs +// The original code is under MIT license, see +// https://github.com/veracruz-project/veracruz/blob/398e4d3ab3023492a64ea91740528e58776e1827/LICENSE_MIT.markdown -use std::{convert::TryFrom, iter}; +use vsss_rs::Gf256; -use rand::Rng; +use crate::QosCryptoError; -// lookup tables for log and exp of polynomials in GF(256), -#[rustfmt::skip] -const GF256_LOG: [u8; 256] = [ - 0xff, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1a, 0xc6, - 0x4b, 0xc7, 0x1b, 0x68, 0x33, 0xee, 0xdf, 0x03, - 0x64, 0x04, 0xe0, 0x0e, 0x34, 0x8d, 0x81, 0xef, - 0x4c, 0x71, 0x08, 0xc8, 0xf8, 0x69, 0x1c, 0xc1, - 0x7d, 0xc2, 0x1d, 0xb5, 0xf9, 0xb9, 0x27, 0x6a, - 0x4d, 0xe4, 0xa6, 0x72, 0x9a, 0xc9, 0x09, 0x78, - 0x65, 0x2f, 0x8a, 0x05, 0x21, 0x0f, 0xe1, 0x24, - 0x12, 0xf0, 0x82, 0x45, 0x35, 0x93, 0xda, 0x8e, - 0x96, 0x8f, 0xdb, 0xbd, 0x36, 0xd0, 0xce, 0x94, - 0x13, 0x5c, 0xd2, 0xf1, 0x40, 0x46, 0x83, 0x38, - 0x66, 0xdd, 0xfd, 0x30, 0xbf, 0x06, 0x8b, 0x62, - 0xb3, 0x25, 0xe2, 0x98, 0x22, 0x88, 0x91, 0x10, - 0x7e, 0x6e, 0x48, 0xc3, 0xa3, 0xb6, 0x1e, 0x42, - 0x3a, 0x6b, 0x28, 0x54, 0xfa, 0x85, 0x3d, 0xba, - 0x2b, 0x79, 0x0a, 0x15, 0x9b, 0x9f, 0x5e, 0xca, - 0x4e, 0xd4, 0xac, 0xe5, 0xf3, 0x73, 0xa7, 0x57, - 0xaf, 0x58, 0xa8, 0x50, 0xf4, 0xea, 0xd6, 0x74, - 0x4f, 0xae, 0xe9, 0xd5, 0xe7, 0xe6, 0xad, 0xe8, - 0x2c, 0xd7, 0x75, 0x7a, 0xeb, 0x16, 0x0b, 0xf5, - 0x59, 0xcb, 0x5f, 0xb0, 0x9c, 0xa9, 0x51, 0xa0, - 0x7f, 0x0c, 0xf6, 0x6f, 0x17, 0xc4, 0x49, 0xec, - 0xd8, 0x43, 0x1f, 0x2d, 0xa4, 0x76, 0x7b, 0xb7, - 0xcc, 0xbb, 0x3e, 0x5a, 0xfb, 0x60, 0xb1, 0x86, - 0x3b, 0x52, 0xa1, 0x6c, 0xaa, 0x55, 0x29, 0x9d, - 0x97, 0xb2, 0x87, 0x90, 0x61, 0xbe, 0xdc, 0xfc, - 0xbc, 0x95, 0xcf, 0xcd, 0x37, 0x3f, 0x5b, 0xd1, - 0x53, 0x39, 0x84, 0x3c, 0x41, 0xa2, 0x6d, 0x47, - 0x14, 0x2a, 0x9e, 0x5d, 0x56, 0xf2, 0xd3, 0xab, - 0x44, 0x11, 0x92, 0xd9, 0x23, 0x20, 0x2e, 0x89, - 0xb4, 0x7c, 0xb8, 0x26, 0x77, 0x99, 0xe3, 0xa5, - 0x67, 0x4a, 0xed, 0xde, 0xc5, 0x31, 0xfe, 0x18, - 0x0d, 0x63, 0x8c, 0x80, 0xc0, 0xf7, 0x70, 0x07, -]; - -#[rustfmt::skip] -const GF256_EXP: [u8; 2*255] = [ - 0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, - 0x1a, 0x2e, 0x72, 0x96, 0xa1, 0xf8, 0x13, 0x35, - 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, - 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa, - 0xe5, 0x34, 0x5c, 0xe4, 0x37, 0x59, 0xeb, 0x26, - 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31, - 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, - 0x4f, 0xd1, 0x68, 0xb8, 0xd3, 0x6e, 0xb2, 0xcd, - 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, - 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88, - 0x83, 0x9e, 0xb9, 0xd0, 0x6b, 0xbd, 0xdc, 0x7f, - 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a, - 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, - 0x0b, 0x1d, 0x27, 0x69, 0xbb, 0xd6, 0x61, 0xa3, - 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, - 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0, - 0xfb, 0x16, 0x3a, 0x4e, 0xd2, 0x6d, 0xb7, 0xc2, - 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41, - 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, - 0x5b, 0xed, 0x2c, 0x74, 0x9c, 0xbf, 0xda, 0x75, - 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, - 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80, - 0x9b, 0xb6, 0xc1, 0x58, 0xe8, 0x23, 0x65, 0xaf, - 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54, - 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, - 0x1b, 0x2d, 0x77, 0x99, 0xb0, 0xcb, 0x46, 0xca, - 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, - 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e, - 0x12, 0x36, 0x5a, 0xee, 0x29, 0x7b, 0x8d, 0x8c, - 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17, - 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, - 0x1c, 0x24, 0x6c, 0xb4, 0xc7, 0x52, 0xf6, - - 0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, - 0x1a, 0x2e, 0x72, 0x96, 0xa1, 0xf8, 0x13, 0x35, - 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, - 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa, - 0xe5, 0x34, 0x5c, 0xe4, 0x37, 0x59, 0xeb, 0x26, - 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31, - 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, - 0x4f, 0xd1, 0x68, 0xb8, 0xd3, 0x6e, 0xb2, 0xcd, - 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, - 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88, - 0x83, 0x9e, 0xb9, 0xd0, 0x6b, 0xbd, 0xdc, 0x7f, - 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a, - 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, - 0x0b, 0x1d, 0x27, 0x69, 0xbb, 0xd6, 0x61, 0xa3, - 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, - 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0, - 0xfb, 0x16, 0x3a, 0x4e, 0xd2, 0x6d, 0xb7, 0xc2, - 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41, - 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, - 0x5b, 0xed, 0x2c, 0x74, 0x9c, 0xbf, 0xda, 0x75, - 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, - 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80, - 0x9b, 0xb6, 0xc1, 0x58, 0xe8, 0x23, 0x65, 0xaf, - 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54, - 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, - 0x1b, 0x2d, 0x77, 0x99, 0xb0, 0xcb, 0x46, 0xca, - 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, - 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e, - 0x12, 0x36, 0x5a, 0xee, 0x29, 0x7b, 0x8d, 0x8c, - 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17, - 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, - 0x1c, 0x24, 0x6c, 0xb4, 0xc7, 0x52, 0xf6, -]; - -/// Multiply in GF(256). -fn gf256_mul(a: u8, b: u8) -> u8 { - if a == 0 || b == 0 { - 0 - } else { - GF256_EXP[usize::from(GF256_LOG[usize::from(a)]) - + usize::from(GF256_LOG[usize::from(b)])] - } -} - -/// Divide in GF(256)/ -fn gf256_div(a: u8, b: u8) -> u8 { - // multiply a against inverse b - gf256_mul(a, GF256_EXP[usize::from(255 - GF256_LOG[usize::from(b)])]) -} - -/// Evaluate a polynomial at x over GF(256) using Horner's method. -fn gf256_eval(f: &[u8], x: u8) -> u8 { - f.iter().rev().fold(0, |acc, c| gf256_mul(acc, x) ^ c) -} - -/// Generate a random polynomial of given degree, fixing f(0) = secret. -fn gf256_generate(secret: u8, degree: usize) -> Vec { - let mut rng = rand::thread_rng(); - iter::once(secret) - .chain(iter::repeat_with(|| rng.gen_range(1..=255)).take(degree)) - .collect() -} - -/// Find f(0) using Lagrange interpolation. -fn gf256_interpolate(xs: &[u8], ys: &[u8]) -> u8 { - assert!(xs.len() == ys.len()); - let mut y = 0u8; - for (i, (x0, y0)) in xs.iter().zip(ys).enumerate() { - let mut li = 1u8; - for (j, (x1, _y1)) in xs.iter().zip(ys).enumerate() { - if i != j { - li = gf256_mul(li, gf256_div(*x1, *x0 ^ *x1)); - } - } - - y ^= gf256_mul(li, *y0); - } - - y -} - -/// Generate n shares requiring k shares to reconstruct. +/// Generate `share_count` shares requiring `threshold` shares to reconstruct. /// -/// # Panics -/// Panics if the number of shares exceeds 255 -#[must_use] -pub fn shares_generate(secret: &[u8], n: usize, k: usize) -> Vec> { - let mut shares = vec![vec![]; n]; - - // we need to store x for each point somewhere, so just prepend - // each array with it - for (i, share) in shares.iter_mut().enumerate().take(n) { - share.push(u8::try_from(i + 1).expect("exceeded 255 shares")); - } - - for x in secret { - // generate random polynomial for each byte - let f = gf256_generate(*x, k - 1); - - // assign each share a point at f(i) - for (i, share) in shares.iter_mut().enumerate().take(n) { - share.push(gf256_eval( - &f, - u8::try_from(i + 1).expect("exceeded 255 shares"), - )); - } - } - - shares +/// Known limitations: +/// threshold >= 2 +/// `share_count` <= 255 +pub fn shares_generate( + secret: &[u8], + share_count: usize, + threshold: usize, +) -> Result>, QosCryptoError> { + use rand_core::OsRng; + + Gf256::split_array(threshold, share_count, secret, OsRng) + .map_err(QosCryptoError::Vsss) } /// Reconstruct our secret from the given `shares`. -pub fn shares_reconstruct>(shares: &[S]) -> Vec { - let len = shares.iter().map(|s| s.as_ref().len()).min().unwrap_or(0); - // rather than erroring, return empty secrets if input is malformed. - // This matches the behavior of bad shares (random output) and simplifies - // consumers. - if len == 0 { - return vec![]; - } - - let mut secret = vec![]; - - // x is prepended to each share - let xs: Vec = shares.iter().map(|v| v.as_ref()[0]).collect(); - for i in 1..len { - let ys: Vec = shares.iter().map(|v| v.as_ref()[i]).collect(); - secret.push(gf256_interpolate(&xs, &ys)); - } - - secret +pub fn shares_reconstruct]>>( + shares: B, +) -> Result, QosCryptoError> { + Gf256::combine_array(shares).map_err(QosCryptoError::Vsss) } #[cfg(test)] @@ -221,27 +41,34 @@ mod test { let secret = b"this is a crazy secret"; let n = 6; let k = 3; - let all_shares = shares_generate(secret, n, k); + let all_shares = shares_generate(secret, n, k).unwrap(); // Reconstruct with all the shares let shares = all_shares.clone(); - let reconstructed = shares_reconstruct(&shares); + let reconstructed = shares_reconstruct(shares).unwrap(); assert_eq!(secret.to_vec(), reconstructed); // Reconstruct with enough shares let shares = &all_shares[..k]; - let reconstructed = shares_reconstruct(shares); + let reconstructed = shares_reconstruct(shares).unwrap(); assert_eq!(secret.to_vec(), reconstructed); // Reconstruct with not enough shares let shares = &all_shares[..(k - 1)]; - let reconstructed = shares_reconstruct(shares); + let reconstructed = shares_reconstruct(shares).unwrap(); + let old_reconstructed = shares_reconstruct(shares).unwrap(); assert!(secret.to_vec() != reconstructed); + assert!(secret.to_vec() != old_reconstructed); // Reconstruct with enough shuffled shares let mut shares = all_shares.clone()[..k].to_vec(); shares.shuffle(&mut rand::thread_rng()); - let reconstructed = shares_reconstruct(&shares); + let reconstructed = shares_reconstruct(&shares).unwrap(); assert_eq!(secret.to_vec(), reconstructed); + + for combo in crate::n_choose_k::combinations(&all_shares, k) { + let reconstructed = shares_reconstruct(&combo).unwrap(); + assert_eq!(secret.to_vec(), reconstructed); + } } } diff --git a/src/qos_enclave/src/main.rs b/src/qos_enclave/src/main.rs index 400b145f..dbd4086c 100644 --- a/src/qos_enclave/src/main.rs +++ b/src/qos_enclave/src/main.rs @@ -81,7 +81,7 @@ fn boot() -> String { memory_mib: memory_mib.parse::().unwrap(), cpu_ids: None, debug_mode: debug_mode.parse::().unwrap(), - attach_console: false, + attach_console: debug_mode.parse::().unwrap(), cpu_count: Some(cpu_count.parse::().unwrap()), enclave_name: Some(enclave_name.clone()), }; diff --git a/src/qos_host/src/lib.rs b/src/qos_host/src/lib.rs index d4ba6816..bbb38ad8 100644 --- a/src/qos_host/src/lib.rs +++ b/src/qos_host/src/lib.rs @@ -203,9 +203,14 @@ impl HostServer { ProtocolPhase::UnrecoverableError | ProtocolPhase::WaitingForBootInstruction | ProtocolPhase::WaitingForQuorumShards - | ProtocolPhase::WaitingForForwardedKey => StatusCode::SERVICE_UNAVAILABLE, + | ProtocolPhase::WaitingForForwardedKey + | ProtocolPhase::ReshardWaitingForQuorumShards + | ProtocolPhase::UnrecoverableReshardFailedBadShares => { + StatusCode::SERVICE_UNAVAILABLE + } ProtocolPhase::QuorumKeyProvisioned - | ProtocolPhase::GenesisBooted => StatusCode::OK, + | ProtocolPhase::GenesisBooted + | ProtocolPhase::ReshardBooted => StatusCode::OK, }; (status, Html(inner)) diff --git a/src/qos_net/Cargo.toml b/src/qos_net/Cargo.toml index e4899c4a..9363c4ea 100644 --- a/src/qos_net/Cargo.toml +++ b/src/qos_net/Cargo.toml @@ -23,3 +23,4 @@ webpki-roots = { version = "0.26.1" } [features] default = ["proxy"] # keep this as a default feature ensures we lint by default proxy = ["rand", "hickory-resolver"] +vm = []