diff --git a/.gitignore b/.gitignore index 3bf25c0..ea846e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ +local-mini-kms.db +.idea/ # ---> Rust # Generated by Cargo # will have compiled files and executables debug/ target/ -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7c24477 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1068 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "josekit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee6af62ad98bdf699ad2ecc8323479a1fdc7aa5faa6043d93119d83f6c5fca8" +dependencies = [ + "anyhow", + "base64", + "flate2", + "once_cell", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libsqlite3-sys" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "local-mini-kms" +version = "0.1.0" +dependencies = [ + "base64", + "clap", + "hex", + "hyper", + "josekit", + "lazy_static", + "rusqlite", + "rust_util", + "serde", + "serde_derive", + "serde_json", + "sha2", + "tokio", + "zeroize", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "openssl" +version = "0.10.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rust_util" +version = "0.6.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df24005feacce81f4ae340464b39c380f7e01e7225bfdef62d40cb44cb1c11d7" +dependencies = [ + "clap", + "lazy_static", + "libc", + "term", + "term_size", +] + +[[package]] +name = "rustversion" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ccb12ad --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "local-mini-kms" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +zeroize = "1.5.7" +clap = "2.33" +hex = "0.4" +base64 = "0.13.0" +sha2 = "0.10.2" +lazy_static = "1.4.0" +serde_derive = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +josekit = "0.8.1" +rust_util = { version = "0.6", features = ["use_clap"] } +tokio = { version = "1.19", features = ["full"] } +hyper = { version = "0.14.20", features = ["client", "server", "tcp", "http1", "http2"] } +rusqlite = "0.28.0" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..ae567c1 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,191 @@ +use std::io::Write; + +use clap::{App, Arg, ArgMatches, SubCommand}; +use hyper::{Body, Client, Method, Request, Response, StatusCode}; +use hyper::body::Buf; +use josekit::jwk::Jwk; +use rust_util::{debugging, opt_value_result, simple_error, success, XResult}; +use rust_util::util_clap::{Command, CommandError}; +use serde_json::{json, Value}; + +use crate::jose; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "cli" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("Local mini KMS cli") + .arg(Arg::with_name("connect").long("connect").takes_value(true).default_value("127.0.0.1:5567").help("Connect server")) + .arg(Arg::with_name("init").long("init").help("Init server")) + .arg(Arg::with_name("offline-init").long("offline-init").help("Offline init server")) + .arg(Arg::with_name("encrypt").long("encrypt").help("Encrypt text")) + .arg(Arg::with_name("decrypt").long("decrypt").help("Decrypt text")) + .arg(Arg::with_name("value").long("value").takes_value(true).help("Value, for encrypt or decrypt")) + .arg(Arg::with_name("value-hex").long("value-hex").takes_value(true).help("Value(hex), for encrypt")) + .arg(Arg::with_name("value-base64").long("value-base64").takes_value(true).help("Value(base64), for encrypt")) + } + + fn run(&self, arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let init = sub_arg_matches.is_present("init"); + let offline_init = sub_arg_matches.is_present("offline-init"); + let encrypt = sub_arg_matches.is_present("encrypt"); + let decrypt = sub_arg_matches.is_present("decrypt"); + let rt = tokio::runtime::Runtime::new().expect("Create tokio runtime error"); + if init { + rt.block_on(async { + do_init(arg_matches, sub_arg_matches).await + }) + } else if offline_init { + do_offline_init(arg_matches, sub_arg_matches) + } else if encrypt { + rt.block_on(async { + do_encrypt(arg_matches, sub_arg_matches).await + }) + } else if decrypt { + rt.block_on(async { + do_decrypt(arg_matches, sub_arg_matches).await + }) + } else { + simple_error!("Need a flag") + } + } +} + +async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError { + let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error"); + + let client = Client::new(); + let uri = format!("http://{}/status", connect); + debugging!("Request uri: {}", &uri); + let req = Request::builder().method(Method::GET).uri(uri).body(Body::empty())?; + let req_response = client.request(req).await?; + if req_response.status() != StatusCode::OK { + return simple_error!("Server status is not success: {}", req_response.status().as_u16()); + } + let data = response_to_value(req_response).await?; + debugging!("Get status: {}", &data); + let status = &data["status"]; + if let Some(status) = status.as_str() { + if status == "ready" { + success!("Server is already init"); + return Ok(Some(0)); + } + if status != "not-ready" { + return simple_error!("Server status is NOT not-ready"); + } + } + let instance_public_key_jwk = &data["instance_public_key_jwk"]; + println!("Instance server public key JWK: {}", instance_public_key_jwk); + + let line = read_line("Input encrypted master key: ")?; + let uri = format!("http://{}/init", connect); + debugging!("Request uri: {}", &uri); + let body = json!({ + "encrypted_master_key": line, + }); + let body = serde_json::to_string(&body)?; + let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?; + + let req_response = client.request(req).await?; + if req_response.status() != StatusCode::OK { + let status = req_response.status().as_u16(); + let data = response_to_value(req_response).await?; + return simple_error!("Server status is not success: {}, response: {}", status, data); + } + success!("Init finished"); + Ok(Some(0)) +} + +async fn do_encrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError { + let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error"); + let value = sub_arg_matches.value_of("value"); + let value_hex = sub_arg_matches.value_of("value-hex"); + let value_base64 = sub_arg_matches.value_of("value-base64"); + + let client = Client::new(); + let uri = format!("http://{}/encrypt", connect); + debugging!("Request uri: {}", &uri); + let body = if let Some(value) = value { + json!({ "value": value }) + } else if let Some(value_hex) = value_hex { + json!({ "value_hex": value_hex }) + } else if let Some(value_base64) = value_base64 { + json!({ "value_base64": value_base64 }) + } else { + return simple_error!("Require one of value, value-hex, value-base64"); + }; + let body = serde_json::to_string(&body)?; + let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?; + + let req_response = client.request(req).await?; + if req_response.status() != StatusCode::OK { + let status = req_response.status().as_u16(); + let data = response_to_value(req_response).await?; + return simple_error!("Server status is not success: {}, response: {}", status, data); + } + let data = response_to_value(req_response).await?; + success!("Encrypted value: {}", data["encrypted_value"].as_str().expect("Get encrypted_value error")); + Ok(Some(0)) +} + +async fn do_decrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError { + let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error"); + let value = opt_value_result!(sub_arg_matches.value_of("value"), "Argument value required"); + + let client = Client::new(); + let uri = format!("http://{}/decrypt", connect); + debugging!("Request uri: {}", &uri); + let body = json!({ "encrypted_value": value }); + let body = serde_json::to_string(&body)?; + let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?; + + let req_response = client.request(req).await?; + if req_response.status() != StatusCode::OK { + let status = req_response.status().as_u16(); + let data = response_to_value(req_response).await?; + return simple_error!("Server status is not success: {}, response: {}", status, data); + } + let data = response_to_value(req_response).await?; + success!("Encrypted value(hex): {}", data["value_hex"].as_str().expect("Get value_hex error")); + success!("Encrypted value(base64): {}", data["value_base64"].as_str().expect("Get value_base64 error")); + success!("Encrypted value: {}", String::from_utf8_lossy(&hex::decode(data["value_hex"].as_str().expect("Get value_hex error"))?).to_string()); + Ok(Some(0)) +} + +fn do_offline_init(_arg_matches: &ArgMatches<'_>, _sub_arg_matches: &ArgMatches<'_>) -> CommandError { + let line = read_line("Input master key: ")?; + let master_key = if line.starts_with("hex:") { + let hex: String = line.chars().skip(4).collect(); + hex::decode(&hex)? + } else if line.starts_with("base64:") { + let base64: String = line.chars().skip(7).collect(); + base64::decode(&base64)? + } else { + line.as_bytes().to_vec() + }; + let jwk = read_line("Input JWK: ")?; + let jwk = Jwk::from_bytes(jwk.as_bytes())?; + let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &jwk)?; + + success!("Encrypted master key: {}", encrypted_master_key); + Ok(Some(0)) +} + +fn read_line(prompt: &str) -> XResult { + std::io::stdout().write(prompt.as_bytes()).ok(); + std::io::stdout().flush().ok(); + let mut line = String::new(); + if let Err(e) = std::io::stdin().read_line(&mut line) { + return simple_error!("Read from terminal failed: {}", e); + } + Ok(line.trim().to_string()) +} + +async fn response_to_value(response: Response) -> XResult { + let req_body = response.into_body(); + let whole_body = hyper::body::aggregate(req_body).await?; + let data: Value = serde_json::from_reader(whole_body.reader())?; + Ok(data) +} \ No newline at end of file diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..adda78a --- /dev/null +++ b/src/db.rs @@ -0,0 +1,57 @@ +use rusqlite::{Connection, params}; +use rust_util::{debugging, information, opt_result, simple_error, success, XResult}; + +pub const DEFAULT_MASTER_KEY_VERIFICATION_KEY: &'static str = "__master_verification_key"; + +pub struct Key { + pub name: String, + pub encrypted_key: String, +} + +pub fn open_db(db: &str) -> XResult { + let con = opt_result!(Connection::open(db), "Open sqlite db: {}, failed: {}", db); + debugging!("Db auto commit: {}", con.is_autocommit()); + Ok(con) +} + +pub fn init_db(conn: &Connection) -> XResult { + let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='keys'")?; + let mut rows = stmt.query(())?; + if rows.next()?.is_some() { + information!("Table keys exists, skip init"); + return Ok(false); + } + + let _ = conn.execute(r##" + CREATE TABLE keys ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + value TEXT + ) + "##, ())?; + success!("Table keys created"); + Ok(true) +} + +pub fn insert_key(conn: &Connection, key: &Key) -> XResult<()> { + let _ = conn.execute( + "INSERT INTO keys (name, value) VALUES (?1, ?2)", + (&key.name, &key.encrypted_key), + )?; + Ok(()) +} + +pub fn find_key(conn: &Connection, name: &str) -> XResult> { + let mut stmt = conn.prepare("SELECT id, name, value FROM keys WHERE name = ?1")?; + let mut key_iter = stmt.query_map(params![name], |row| { + Ok(Key { + name: row.get(1)?, + encrypted_key: row.get(2)?, + }) + })?; + match key_iter.next() { + None => Ok(None), + Some(Ok(r)) => Ok(Some(r)), + Some(Err(e)) => simple_error!("Find key failed: {}", e), + } +} \ No newline at end of file diff --git a/src/jose.rs b/src/jose.rs new file mode 100644 index 0000000..ae42240 --- /dev/null +++ b/src/jose.rs @@ -0,0 +1,60 @@ +use josekit::jwe; +use josekit::jwe::alg::aeskw::AeskwJweAlgorithm; +use josekit::jwe::alg::rsaes::RsaesJweAlgorithm; +use josekit::jwe::JweHeader; +use josekit::jwk::alg::rsa::RsaKeyPair; +use josekit::jwk::Jwk; +use rust_util::XResult; +use serde_json::Value; +use sha2::Digest; + +const LOCAL_KMS_PREFIX: &'static str = "LKMS:"; + +pub fn generate_rsa_key(bits: u32) -> XResult { + Ok(RsaKeyPair::generate(bits)?) +} + +pub fn serialize_jwe_rsa(payload: &[u8], jwk: &Jwk) -> XResult { + let mut header = JweHeader::new(); + header.set_content_encryption("A256GCM"); + header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?; + let encrypter = RsaesJweAlgorithm::RsaOaep.encrypter_from_jwk(&jwk)?; + Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?)) +} + +pub fn deserialize_jwe_rsa(jwe: &str, jwk: &Jwk) -> XResult<(Vec, JweHeader)> { + let decrypter = RsaesJweAlgorithm::RsaOaep.decrypter_from_jwk(jwk)?; + Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?) +} + +pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult { + let mut header = JweHeader::new(); + header.set_content_encryption("A256GCM"); + header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?; + header.set_claim("version", Some(Value::String(get_master_key_checksum(key))))?; + let encrypter = AeskwJweAlgorithm::A256kw.encrypter_from_bytes(key)?; + Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?)) +} + +pub fn deserialize_jwe_aes(jwe: &str, key: &[u8]) -> XResult<(Vec, JweHeader)> { + let decrypter = AeskwJweAlgorithm::A256kw.decrypter_from_bytes(key)?; + Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?) +} + +fn get_master_key_checksum(key: &[u8]) -> String { + let digest = sha2::Sha256::digest(&key); + let digest = sha2::Sha256::digest(&digest.as_slice()); + let digest = sha2::Sha256::digest(&digest.as_slice()); + let digest = sha2::Sha256::digest(&digest.as_slice()); + let digest = sha2::Sha256::digest(&digest.as_slice()); + let digest = sha2::Sha256::digest(&digest.as_slice()); + hex::encode(&digest[0..8]) +} + +fn get_jwe(jwe: &str) -> String { + if jwe.starts_with(LOCAL_KMS_PREFIX) { + jwe.chars().skip(LOCAL_KMS_PREFIX.len()).collect() + } else { + jwe.to_string() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4015375 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,49 @@ +use clap::{App, AppSettings, ArgMatches}; +use rust_util::{failure_and_exit, information}; +use rust_util::util_clap::{Command, CommandError}; + +mod db; +mod jose; +mod cli; +mod serve; + +pub struct DefaultCommandImpl; + +impl DefaultCommandImpl { + pub fn process_command<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + } + pub fn run(_arg_matches: &ArgMatches) -> CommandError { + information!("Local mini KMS cli, use --help for help"); + Ok(None) + } +} + +fn main() { + if let Err(e) = inner_main() { + failure_and_exit!("Run local-mini-kms error: {}", e); + } +} + +fn inner_main() -> CommandError { + let commands: Vec> = vec![ + Box::new(cli::CommandImpl), + Box::new(serve::CommandImpl), + ]; + let mut app = App::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .long_about("Local mini KMS") + .setting(AppSettings::ColoredHelp); + app = DefaultCommandImpl::process_command(app); + for command in &commands { + app = app.subcommand(command.subcommand()); + } + let matches = app.get_matches(); + for command in &commands { + if let Some(sub_cmd_matches) = matches.subcommand_matches(command.name()) { + return command.run(&matches, sub_cmd_matches); + } + } + DefaultCommandImpl::run(&matches) +} \ No newline at end of file diff --git a/src/serve.rs b/src/serve.rs new file mode 100644 index 0000000..f90661d --- /dev/null +++ b/src/serve.rs @@ -0,0 +1,340 @@ +use std::sync::RwLock; + +use clap::{App, Arg, ArgMatches, SubCommand}; +use hyper::{Body, Client, Method, Request, Response, Server, StatusCode}; +use hyper::body::Buf; +use hyper::client::HttpConnector; +use hyper::service::{make_service_fn, service_fn}; +use josekit::jwk::alg::rsa::RsaKeyPair; +use josekit::jwk::KeyPair; +use rust_util::{debugging, failure_and_exit, information, opt_result, simple_error, success, XResult}; +use rust_util::util_clap::{Command, CommandError}; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Map, Value}; +use zeroize::Zeroize; + +use crate::{db, jose}; +use crate::db::Key; + +type GenericError = Box; +type Result = std::result::Result; + + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "serve" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("Local mini KMS serve") + .arg(Arg::with_name("listen").long("listen").takes_value(true).default_value("127.0.0.1:5567").help("Listen")) + .arg(Arg::with_name("local-db").long("local-db").takes_value(true).default_value("local-mini-kms.db").help("Local db file")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let local_mini_kms_db = sub_arg_matches.value_of("local-db").expect("Get local mini kms db error"); + match init_instance(local_mini_kms_db) { + Ok(true) => success!("Init server success"), + Ok(false) => failure_and_exit!("SHOULD NOT HAPPEN, server already init"), + Err(e) => failure_and_exit!("Init server failed: {}", e), + } + + let listen = sub_arg_matches.value_of("listen").expect("Get argument listen error"); + let rt = tokio::runtime::Runtime::new().expect("Create tokio runtime error"); + rt.block_on(async { + let addr = listen.parse().expect(&format!("Parse listen error: {}", listen)); + let client = Client::new(); + let new_service = make_service_fn(move |_| { + let client = client.clone(); + async { + Ok::<_, GenericError>(service_fn(move |req| { + response_requests(req, client.to_owned()) + })) + } + }); + let server = Server::bind(&addr).serve(new_service); + information!("Listening on http://{}", addr); + match server.await { + Err(e) => failure_and_exit!("Server error: {}", e), + Ok(_) => success!("Server ended"), + } + }); + Ok(Some(0)) + } +} + +// ref: https://github.com/hyperium/hyper/blob/master/examples/web_api.rs +// ref: https://crates.io/crates/rusqlite +async fn response_requests( + req: Request, + _client: Client, +) -> Result> { + match (req.method(), req.uri().path()) { + (&Method::POST, "/init") => init(req).await, + (&Method::POST, "/update") => update().await, + (&Method::POST, "/decrypt") => decrypt(req).await, + (&Method::POST, "/encrypt") => encrypt(req).await, + (&Method::GET, "/status") => status().await, + (&Method::GET, "/version") => get_version().await, + _ => Ok(Response::builder().status(StatusCode::NOT_FOUND).body(serde_json::to_string_pretty(&json!({ "error": "not_found" }))?.into())?), + } +} + +macro_rules! do_response { + ($ex: expr) => ( + match $ex { + Ok((status_code, body)) => Ok(Response::builder().status(status_code).body(serde_json::to_string_pretty(&body)?.into())?), + Err(e) => Ok(Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(serde_json::to_string_pretty(&json!({ + "error": "internal_error", + "error_message": format!("{}", e), + }))?.into())?), + } + ) +} + +// ------------------------------------------------------------------------------------------------- + +struct MemoryKey { + database_file: String, + instance_rsa_key_pair: RsaKeyPair, + master_key: Option>, +} + +lazy_static::lazy_static! { + static ref STATUP_RW_LOCK: RwLock> = RwLock::new(None); +} + +fn init_instance(db: &str) -> XResult { + let conn = db::open_db(db)?; + db::init_db(&conn)?; + + let mut startup_rw_lock = STATUP_RW_LOCK.write().expect("Lock write startup rw lock error"); + match &*startup_rw_lock { + Some(_) => Ok(false), + None => { + let memory_key = MemoryKey { + database_file: db.to_string(), + instance_rsa_key_pair: jose::generate_rsa_key(4096)?, + master_key: None, + }; + *startup_rw_lock = Some(memory_key); + Ok(true) + } + } +} + +fn update_instance_rsa_key_pair() -> XResult { + let mut startup_rw_lock = STATUP_RW_LOCK.write().expect("Lock write startup rw lock error"); + match &mut *startup_rw_lock { + Some(k) => { + k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?; + Ok(true) + } + None => Ok(false), + } +} + +#[derive(Serialize, Deserialize)] +struct MultipleViewValue { + value: Option, + value_hex: Option, + value_base64: Option, +} + +impl MultipleViewValue { + fn from(v: &[u8]) -> Self { + Self { + value: Some(String::from_utf8_lossy(v).to_string()), + value_hex: Some(hex::encode(v)), + value_base64: Some(base64::encode(v)), + } + } + + fn to_bytes(&self) -> XResult> { + if let Some(v) = &self.value { + Ok(v.as_bytes().to_vec()) + } else if let Some(v) = &self.value_hex { + let v = opt_result!(hex::decode(v), "Decode hex failed: {}"); + Ok(v) + } else if let Some(v) = &self.value_base64 { + let v = opt_result!(base64::decode(v), "Decode base64 failed: {}"); + Ok(v) + } else { + simple_error!("Multiple view value is all empty") + } + } +} + + +#[derive(Serialize, Deserialize)] +struct DecryptRequest { + encrypted_value: String, +} + +async fn decrypt(req: Request) -> Result> { + do_response!(inner_decrypt(req).await) +} + +async fn inner_decrypt(req: Request) -> XResult<(StatusCode, Value)> { + let whole_body = hyper::body::aggregate(req).await?; + let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?; + + debugging!("To be decrypted value: {}", &data.encrypted_value); + let mut key = match get_master_key() { + None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))), + Some(key) => key, + }; + let decrypted_value = jose::deserialize_jwe_aes(&data.encrypted_value, &key); + key.zeroize(); + + decrypted_value.map(|v| { + let v = MultipleViewValue::from(&v.0); + let mut map = Map::new(); + if let Some(v) = &v.value { + map.insert("value".to_string(), Value::String(v.to_string())); + } + if let Some(v) = &v.value_hex { + map.insert("value_hex".to_string(), Value::String(v.to_string())); + } + if let Some(v) = &v.value_base64 { + map.insert("value_base64".to_string(), Value::String(v.to_string())); + } + (StatusCode::OK, Value::Object(map)) + }) +} + +async fn encrypt(req: Request) -> Result> { + do_response!(inner_encrypt(req).await) +} + +async fn inner_encrypt(req: Request) -> XResult<(StatusCode, Value)> { + let whole_body = hyper::body::aggregate(req).await?; + let data: MultipleViewValue = serde_json::from_reader(whole_body.reader())?; + let value = data.to_bytes()?; + let mut key = match get_master_key() { + None => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "status_not_ready" }))), + Some(key) => key, + }; + let encrypt_result = jose::serialize_jwe_aes(&value, &key); + key.zeroize(); + + encrypt_result.map(|e| { + (StatusCode::OK, json!({ + "encrypted_value": e, + })) + }) +} + +async fn update() -> Result> { + do_response!(inner_update().await) +} + +async fn inner_update() -> XResult<(StatusCode, Value)> { + let update = update_instance_rsa_key_pair()?; + Ok((StatusCode::OK, json!({ + "update": update, + }))) +} + +async fn init(req: Request) -> Result> { + do_response!(inner_init(req).await) +} + +#[derive(Serialize, Deserialize)] +struct InitRequest { + clear_master_key_hex: Option, + clear_master_key_base64: Option, + encrypted_master_key: Option, +} + +async fn inner_init(req: Request) -> XResult<(StatusCode, Value)> { + let whole_body = hyper::body::aggregate(req).await?; + let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?; + + let mut startup_rw_lock = STATUP_RW_LOCK.write().expect("Lock read startup rw lock error"); + match &*startup_rw_lock { + None => return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " }))), + Some(memory_key) => match memory_key.master_key { + Some(_) => return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "already init " }))), + None => {} + }, + } + + let clear_master_key = if let Some(clear_master_key_base64) = &init_request.clear_master_key_base64 { + base64::decode(clear_master_key_base64)? + } else if let Some(clear_master_key_hex) = init_request.clear_master_key_hex { + hex::decode(clear_master_key_hex)? + } else if let Some(encrypted_master_key) = init_request.encrypted_master_key { + debugging!("Received encrypted master key: {}", encrypted_master_key); + if let Some(k) = &*startup_rw_lock { + let (clear_master_key, _) = jose::deserialize_jwe_rsa(&encrypted_master_key, &k.instance_rsa_key_pair.to_jwk_private_key())?; + clear_master_key + } else { + return Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": "internal_error", "error_message": "not init " }))); + } + } else { + return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "master key is not assigned" }))); + }; + + if clear_master_key.len() != 32 { + return Ok((StatusCode::BAD_REQUEST, json!({ "error": "bad_request", "error_message": "bad clear_master_key length" }))); + } + + if let Some(k) = &mut *startup_rw_lock { + let conn = opt_result!(db::open_db(&k.database_file), "Open db failed: {}"); + let default_master_key_verification_key = db::find_key(&conn, db::DEFAULT_MASTER_KEY_VERIFICATION_KEY)?; + match default_master_key_verification_key { + None => { + let key = Key { + name: db::DEFAULT_MASTER_KEY_VERIFICATION_KEY.to_string(), + encrypted_key: jose::serialize_jwe_aes("LOCAL-MINI-KMS:MAGIC-VERIFICATION-KEY".as_bytes(), &clear_master_key)?, + }; + db::insert_key(&conn, &key)?; + } + Some(key) => { + debugging!("Found jwe: {}", &key.encrypted_key); + let _ = opt_result!(jose::deserialize_jwe_aes(&key.encrypted_key, &clear_master_key), "Deserialize master key verification key failed: {}"); + } + } + information!("Set master key success"); + k.master_key = Some(clear_master_key); + k.instance_rsa_key_pair = jose::generate_rsa_key(4096)?; + } + Ok((StatusCode::OK, json!({}))) +} + +async fn status() -> Result> { + do_response!(inner_status().await) +} + +async fn inner_status() -> XResult<(StatusCode, Value)> { + let startup_rw_lock = STATUP_RW_LOCK.read().expect("Lock read startup rw lock error"); + let body = match &*startup_rw_lock { + None => json!({ "status": "n/a" }), + Some(memory_key) => match memory_key.master_key { + None => json!({ + "status": "not-ready", + "instance_public_key_jwk": memory_key.instance_rsa_key_pair.to_jwk_key_pair().to_public_key()?, + "instance_public_key_pem": String::from_utf8_lossy(&memory_key.instance_rsa_key_pair.to_pem_public_key()).to_string(), + }), + Some(_) => json!({ + "status": "ready", + }), + } + }; + Ok((StatusCode::OK, body)) +} + +async fn get_version() -> Result> { + Ok(Response::builder().body(format!( + "{} - {}\n", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") + ).into())?) +} + +fn get_master_key() -> Option> { + let startup_rw_lock = STATUP_RW_LOCK.read().expect("Lock read startup rw lock error"); + match &*startup_rw_lock { + None => None, + Some(k) => k.master_key.clone(), + } +}