From 939b31de642d951b1ce5e07635a585be8b207803 Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Sun, 17 Apr 2022 10:13:42 -0700 Subject: [PATCH] Rustica 0.9: Add Support For U2F keys (#18) --- .github/workflows/integration.yml | 2 +- Cargo.lock | 1031 +++++++++++++---- docker/Dockerfile.amd64 | 2 +- examples/example.db | Bin 69632 -> 94208 bytes examples/rustica_external.toml | 60 +- examples/rustica_local_amazonkms.toml | 8 + examples/rustica_local_file.toml | 6 + examples/rustica_local_file_alt.toml | 6 + examples/rustica_local_file_with_influx.toml | 38 +- examples/rustica_local_file_with_splunk.toml | 38 +- examples/rustica_local_file_with_webhook.toml | 99 ++ examples/rustica_local_yubikey.toml | 39 +- proto/rustica.proto | 13 + rustica-agent/Cargo.toml | 16 +- rustica-agent/src/config.rs | 193 ++- rustica-agent/src/lib.rs | 257 +++- rustica-agent/src/main.rs | 49 +- rustica-agent/src/rustica/key.rs | 53 +- rustica-agent/src/rustica/mod.rs | 67 +- rustica-agent/src/sshagent/agent.rs | 1 - rustica-agent/src/sshagent/protocol.rs | 2 +- rustica/Cargo.toml | 18 +- .../2022-02-23-205236_add_u2f/down.sql | 12 + .../2022-02-23-205236_add_u2f/up.sql | 6 + rustica/src/auth/database.rs | 43 +- rustica/src/auth/database/models.rs | 6 + rustica/src/auth/database/schema.rs | 6 + rustica/src/auth/external.rs | 47 +- rustica/src/auth/mod.rs | 19 +- rustica/src/config.rs | 16 +- rustica/src/error.rs | 4 +- rustica/src/key.rs | 20 +- rustica/src/logging/influx.rs | 9 +- rustica/src/logging/mod.rs | 49 +- rustica/src/logging/splunk.rs | 8 +- rustica/src/logging/stdout.rs | 3 +- rustica/src/logging/webhook.rs | 79 ++ rustica/src/main.rs | 2 +- rustica/src/server.rs | 262 ++++- rustica/src/signing/amazon_kms.rs | 18 +- rustica/src/signing/mod.rs | 10 +- rustica/src/verification.rs | 52 + rustica/src/yubikey.rs | 108 -- tests/integration.sh | 9 +- 44 files changed, 2079 insertions(+), 707 deletions(-) create mode 100644 examples/rustica_local_file_with_webhook.toml create mode 100644 rustica/migrations/2022-02-23-205236_add_u2f/down.sql create mode 100644 rustica/migrations/2022-02-23-205236_add_u2f/up.sql create mode 100644 rustica/src/logging/webhook.rs create mode 100644 rustica/src/verification.rs delete mode 100644 rustica/src/yubikey.rs diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index da705bf..a823d12 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -16,6 +16,6 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install libpcsc - run: sudo apt install -y libpcsclite-dev + run: sudo apt install -y libpcsclite-dev libusb-1.0-0-dev libudev-dev - name: Run Integration Tests run: ./tests/integration.sh diff --git a/Cargo.lock b/Cargo.lock index 039009b..90c4e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -13,9 +25,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" [[package]] name = "arrayvec" @@ -23,11 +35,50 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "asn1-rs" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.1", + "num-traits", + "rusticata-macros 4.1.0", + "thiserror", + "time 0.3.9", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -35,9 +86,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -46,9 +97,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -68,15 +119,18 @@ dependencies = [ [[package]] name = "autocfg" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aws-config" @@ -227,7 +281,7 @@ dependencies = [ "percent-encoding", "regex", "ring", - "time 0.3.5", + "time 0.3.9", "tracing", ] @@ -283,7 +337,7 @@ dependencies = [ "percent-encoding", "pin-project", "tokio", - "tokio-util", + "tokio-util 0.6.9", "tracing", ] @@ -327,10 +381,10 @@ version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193abb2559d65d6eaeacc45dd3764cb8f821a90425f6b051a8fd17ea12cbd0d1" dependencies = [ - "itoa 1.0.1", + "itoa", "num-integer", "ryu", - "time 0.3.5", + "time 0.3.9", ] [[package]] @@ -401,6 +455,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + [[package]] name = "bumpalo" version = "3.9.1" @@ -421,9 +491,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "bytes-utils" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e314712951c43123e5920a446464929adc667a5eade7f8fb3997776c9df6e54" +checksum = "1934a3ef9cac8efde4966a92781e77713e1ba329f1d42e446c7d7eba340d8ef1" dependencies = [ "bytes", "either", @@ -431,9 +501,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -466,19 +536,28 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.7" +version = "3.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e8611f9ae4e068fa3e56931fded356ff745e70987ff76924a6e0ab1c8ef2e3" +checksum = "6aad2534fad53df1cc12519c5cda696dd3e20e6118a027e24054aea14a0bdcbe" dependencies = [ "atty", "bitflags", + "clap_lex", "indexmap", - "os_str_bytes", "strsim", "termcolor", "textwrap", ] +[[package]] +name = "clap_lex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "const-oid" version = "0.6.2" @@ -493,9 +572,9 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -509,18 +588,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if", "crossbeam-utils", @@ -528,9 +607,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if", "lazy_static", @@ -567,6 +646,29 @@ dependencies = [ "sct 0.6.1", ] +[[package]] +name = "ctap-hid-fido2" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bec213530d681a9d9fec5d14ab072ca9e4e530428e0b5ef8b607cdb5a1a8db" +dependencies = [ + "aes", + "anyhow", + "base64 0.13.0", + "block-modes", + "byteorder", + "hex", + "hidapi", + "num", + "pad", + "ring", + "serde", + "serde_cbor", + "strum", + "strum_macros", + "x509-parser 0.13.2", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -621,15 +723,29 @@ dependencies = [ [[package]] name = "der-parser" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9807efb310ce4ea172924f3a69d82f9fd6c9c3a19336344591153e665b31c43e" +checksum = "4cddf120f700b411b2b02ebeb7f04dc0b7c8835909a6c2f52bf72ed0dd3433b2" dependencies = [ "der-oid-macro 0.5.0", - "nom 7.1.0", + "nom 7.1.1", "num-bigint", "num-traits", - "rusticata-macros 4.0.0", + "rusticata-macros 4.1.0", +] + +[[package]] +name = "der-parser" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.1", + "num-bigint", + "num-traits", + "rusticata-macros 4.1.0", ] [[package]] @@ -674,6 +790,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ecdsa" version = "0.12.4" @@ -710,9 +837,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if", ] @@ -732,9 +859,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -755,6 +882,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + [[package]] name = "fnv" version = "1.0.7" @@ -779,24 +912,24 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -805,21 +938,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-core", "futures-macro", @@ -841,13 +974,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -863,9 +996,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -876,10 +1009,16 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.1", "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.11.2" @@ -895,6 +1034,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -910,6 +1055,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hidapi" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b1717343691998deb81766bfcd1dce6df0d5d6c37070b5a3de2bb6d39f7822" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "hmac" version = "0.11.0" @@ -928,7 +1084,7 @@ checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] @@ -944,9 +1100,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba" [[package]] name = "httpdate" @@ -962,9 +1118,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ "bytes", "futures-channel", @@ -975,7 +1131,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -1009,9 +1165,21 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "rustls 0.20.2", + "rustls 0.20.4", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", ] [[package]] @@ -1027,19 +1195,19 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "hashbrown", ] [[package]] name = "influxdb" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf0afd592134cb2a6cfa2eeea86f66c171fc346cdb8cdc75fb8b936e117d6284" +checksum = "39023407f0546c3b30607950f8b600c7db4ef7621fbaa0159de733d73e68b23f" dependencies = [ "chrono", "futures-util", @@ -1063,9 +1231,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" @@ -1077,10 +1245,13 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "0.4.8" +name = "itertools" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] [[package]] name = "itoa" @@ -1090,9 +1261,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -1119,17 +1290,81 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lexical-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92912c4af2e7d9075be3e5e3122c4d7263855fa6cce34fbece4dd08e5884624d" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f518eed87c3be6debe6d26b855c97358d8a11bf05acec137e5f53080f5ad2dd8" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc852ec67c6538bbb2b9911116a385b24510e879a69ab516e6a151b15a79168" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c72a9d52c5c4e62fa2cdc2cb6c694a39ae1382d9c2a17a466f18e272a0930eb1" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a89ec1d062e481210c309b672f73a0567b7855f21e7d2fae636df44d12e97f9" +dependencies = [ + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094060bd2a7c2ff3a16d5304a6ae82727cb3cc9d1c70f813cc73f744c319337e" +dependencies = [ + "lexical-util", + "static_assertions", +] + [[package]] name = "libc" -version = "0.2.112" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" [[package]] name = "libm" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" [[package]] name = "libsqlite3-sys" @@ -1143,18 +1378,19 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg 1.1.0", "scopeguard", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", ] @@ -1177,6 +1413,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minicbor" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124d887cb82f0b1469bdac3d1b65764a381eed1a54fdab0070e5772b13114521" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1185,14 +1427,15 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.7.14" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" dependencies = [ "libc", "log", "miow", "ntapi", + "wasi 0.11.0+wasi-snapshot-preview1", "winapi", ] @@ -1219,38 +1462,51 @@ checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", "funty", - "lexical-core", + "lexical-core 0.7.6", "memchr", "version_check", ] [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-integer", "num-traits", ] @@ -1261,7 +1517,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "byteorder", "lazy_static", "libm", @@ -1274,13 +1530,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-traits", ] @@ -1290,7 +1555,19 @@ version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg 1.1.0", + "num-bigint", "num-integer", "num-traits", ] @@ -1301,7 +1578,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "libm", ] @@ -1315,6 +1592,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + [[package]] name = "oid-registry" version = "0.1.5" @@ -1330,14 +1616,23 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe554cb2393bc784fd678c82c84cc0599c31ceadc7f03a594911f822cb8d1815" dependencies = [ - "der-parser 6.0.0", + "der-parser 6.0.1", +] + +[[package]] +name = "oid-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" +dependencies = [ + "asn1-rs", ] [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "opaque-debug" @@ -1356,9 +1651,6 @@ name = "os_str_bytes" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] [[package]] name = "p256" @@ -1380,29 +1672,36 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys", ] [[package]] @@ -1416,9 +1715,9 @@ dependencies = [ [[package]] name = "pcsc" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7b356209b9904510024c97332563870af40acdc3ccacbae1b3155b87a6f65b" +checksum = "7e29e4de78a433aeecd06fb5bd55a0f9fde11dc85a14c22d482972c7edc4fdc4" dependencies = [ "bitflags", "pcsc-sys", @@ -1463,7 +1762,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset 0.4.1", "indexmap", ] @@ -1525,9 +1834,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "ppv-lite86" @@ -1537,9 +1846,9 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] @@ -1551,7 +1860,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.7.0", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive 0.9.0", ] [[package]] @@ -1561,13 +1880,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ "bytes", - "heck", - "itertools", + "heck 0.3.3", + "itertools 0.9.0", + "log", + "multimap", + "petgraph 0.5.1", + "prost 0.7.0", + "prost-types 0.7.0", + "tempfile", + "which", +] + +[[package]] +name = "prost-build" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +dependencies = [ + "bytes", + "heck 0.3.3", + "itertools 0.10.3", + "lazy_static", "log", "multimap", - "petgraph", - "prost", - "prost-types", + "petgraph 0.6.0", + "prost 0.9.0", + "prost-types 0.9.0", + "regex", "tempfile", "which", ] @@ -1579,7 +1918,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" dependencies = [ "anyhow", - "itertools", + "itertools 0.9.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools 0.10.3", "proc-macro2", "quote", "syn", @@ -1592,14 +1944,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" dependencies = [ "bytes", - "prost", + "prost 0.7.0", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes", + "prost 0.9.0", ] [[package]] name = "quote" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -1612,14 +1974,13 @@ checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] @@ -1641,15 +2002,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - [[package]] name = "rcgen" version = "0.8.14" @@ -1664,18 +2016,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -1699,9 +2051,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64 0.13.0", "bytes", @@ -1720,13 +2072,13 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustls 0.20.2", + "rustls 0.20.4", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls 0.23.3", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -1781,7 +2133,7 @@ dependencies = [ [[package]] name = "rustica" -version = "0.8.3" +version = "0.9.0" dependencies = [ "aws-config", "aws-sdk-kms", @@ -1796,23 +2148,25 @@ dependencies = [ "hex", "influxdb", "log", - "prost", + "minicbor", + "prost 0.7.0", "reqwest", "ring", "serde", + "serde_cbor", "serde_json", "sha2", "sshcerts", "tokio", "toml", - "tonic", - "tonic-build", + "tonic 0.4.3", + "tonic-build 0.4.2", "x509-parser 0.9.2", ] [[package]] name = "rustica-agent" -version = "0.8.3" +version = "0.9.0" dependencies = [ "base64 0.12.3", "byteorder", @@ -1821,9 +2175,9 @@ dependencies = [ "futures-core", "futures-util", "hex", - "lexical-core", + "lexical-core 0.8.3", "log", - "prost", + "prost 0.9.0", "ring", "serde", "serde_derive", @@ -1831,8 +2185,8 @@ dependencies = [ "sshcerts", "tokio", "toml", - "tonic", - "tonic-build", + "tonic 0.6.2", + "tonic-build 0.6.2", "yubikey", ] @@ -1847,11 +2201,11 @@ dependencies = [ [[package]] name = "rusticata-macros" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c52377bb2288aa522a0c8208947fada1e0c76397f108cc08f57efe6077b50d" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom 7.1.0", + "nom 7.1.1", ] [[package]] @@ -1869,9 +2223,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ "log", "ring", @@ -1893,9 +2247,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" dependencies = [ "base64 0.13.0", ] @@ -1959,9 +2313,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09d3c15d814eda1d6a836f2f2b56a6abc1446c8a34351cb3180d3db92ffe4ce" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -1972,9 +2326,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e90dd10c41c6bfc633da6e0c659bd25d31e0791e5974ac42970267d59eba87f7" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -1982,24 +2336,34 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" [[package]] name = "serde" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -2008,23 +2372,23 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] [[package]] name = "serde_urlencoded" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] @@ -2088,9 +2452,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" @@ -2100,9 +2464,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -2125,19 +2489,22 @@ dependencies = [ [[package]] name = "sshcerts" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d170d1d0f2749220d73126609cd3b6e6179f0d5ed677d24cf4061dbfd2fb27" +checksum = "673c11b3455e43e149ed8bc0a7421f7b63f2ed6012ef9ebe5e86638665e647a8" dependencies = [ "base64 0.13.0", + "ctap-hid-fido2", "der-parser 5.1.2", + "minicbor", "num-bigint", "rcgen", "ring", "simple_asn1", "x509", - "x509-parser 0.11.0", + "x509-parser 0.13.2", "yubikey", + "zeroize", ] [[package]] @@ -2152,6 +2519,25 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -2169,9 +2555,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.85" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" dependencies = [ "proc-macro2", "quote", @@ -2212,18 +2598,18 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" @@ -2257,13 +2643,22 @@ dependencies = [ [[package]] name = "time" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ + "itoa", "libc", + "num_threads", + "time-macros", ] +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tinyvec" version = "1.5.1" @@ -2281,9 +2676,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes", "libc", @@ -2294,10 +2689,21 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "winapi", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "1.7.0" @@ -2322,11 +2728,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" dependencies = [ - "rustls 0.20.2", + "rustls 0.20.4", "tokio", "webpki 0.22.0", ] @@ -2356,11 +2762,25 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -2383,18 +2803,50 @@ dependencies = [ "hyper", "percent-encoding", "pin-project", - "prost", - "prost-derive", + "prost 0.7.0", + "prost-derive 0.7.0", "tokio", "tokio-rustls 0.22.0", "tokio-stream", - "tokio-util", + "tokio-util 0.6.9", "tower", "tower-service", "tracing", "tracing-futures", ] +[[package]] +name = "tonic" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.13.0", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.9.0", + "prost-derive 0.9.0", + "tokio", + "tokio-rustls 0.22.0", + "tokio-stream", + "tokio-util 0.6.9", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + [[package]] name = "tonic-build" version = "0.4.2" @@ -2402,16 +2854,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c695de27302f4697191dda1c7178131a8cb805463dda02864acb80fe1322fdcf" dependencies = [ "proc-macro2", - "prost-build", + "prost-build 0.7.0", + "quote", + "syn", +] + +[[package]] +name = "tonic-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +dependencies = [ + "proc-macro2", + "prost-build 0.9.0", "quote", "syn", ] [[package]] name = "tower" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5651b5f6860a99bd1adb59dbfe1db8beb433e73709d9032b413a77e2fb7c066a" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", @@ -2421,8 +2885,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-stream", - "tokio-util", + "tokio-util 0.7.1", "tower-layer", "tower-service", "tracing", @@ -2442,9 +2905,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if", "log", @@ -2455,9 +2918,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" dependencies = [ "proc-macro2", "quote", @@ -2466,9 +2929,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", ] @@ -2512,9 +2975,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -2583,11 +3052,17 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2595,9 +3070,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static", @@ -2610,9 +3085,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if", "js-sys", @@ -2622,9 +3097,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2632,9 +3107,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -2645,15 +3120,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -2681,18 +3156,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" dependencies = [ "webpki 0.22.0", ] [[package]] name = "which" -version = "4.2.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -2730,11 +3205,54 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +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.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "winreg" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] @@ -2776,37 +3294,38 @@ dependencies = [ [[package]] name = "x509-parser" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ce30cd4a10592affdced3f5c95e03e8f23599d282e727fc44035c21250d552" +checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" dependencies = [ "base64 0.13.0", "chrono", "data-encoding", - "der-parser 5.1.2", + "der-parser 6.0.1", "lazy_static", - "nom 6.1.2", - "oid-registry 0.1.5", - "ring", - "rusticata-macros 3.2.0", + "nom 7.1.1", + "oid-registry 0.2.0", + "rusticata-macros 4.1.0", "thiserror", ] [[package]] name = "x509-parser" -version = "0.12.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc90836a84cb72e6934137b1504d0cae304ef5d83904beb0c8d773bbfe256ed" +checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" dependencies = [ + "asn1-rs", "base64 0.13.0", - "chrono", "data-encoding", - "der-parser 6.0.0", + "der-parser 7.0.0", "lazy_static", - "nom 7.1.0", - "oid-registry 0.2.0", - "rusticata-macros 4.0.0", + "nom 7.1.1", + "oid-registry 0.4.0", + "ring", + "rusticata-macros 4.1.0", "thiserror", + "time 0.3.9", ] [[package]] @@ -2832,12 +3351,12 @@ checksum = "e82a7de0613c75d93e4330613e24d859cab51770b081fe92f1dae4a1e38d9177" dependencies = [ "chrono", "cookie-factory", - "der-parser 6.0.0", + "der-parser 6.0.1", "des", "elliptic-curve", "hmac", "log", - "nom 7.1.0", + "nom 7.1.1", "num-bigint-dig", "num-integer", "num-traits", @@ -2869,9 +3388,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/docker/Dockerfile.amd64 b/docker/Dockerfile.amd64 index 7341945..46d011b 100644 --- a/docker/Dockerfile.amd64 +++ b/docker/Dockerfile.amd64 @@ -7,7 +7,7 @@ COPY proto /tmp/proto COPY rustica /tmp/rustica WORKDIR /tmp/rustica -RUN cargo build --target=x86_64-unknown-linux-musl --features="splunk,amazon-kms" --release +RUN cargo build --target=x86_64-unknown-linux-musl --features="splunk,amazon-kms,webhook" --release FROM alpine:3.6 as alpine RUN apk add -U --no-cache ca-certificates diff --git a/examples/example.db b/examples/example.db index 905a6e79844b67f0e0757ecf0e7aba1c36a228d5..8f51c5c8b0205c18937b34b744e9580d7fb7a7a4 100644 GIT binary patch literal 94208 zcmeI5No<@)cE{PGB#N?S%FaxBaxg$34r0Pv?TZZqp)6{#Buds|SptGityEiFB(f=4 z9^@bo2Dt^}O9ly$gOPhqxz90^+~%0%keqVNEl7e4F!@(=5h;Q~Fx?_ckgFMV>hKPZ!RX?Y`@!XH+>+7$089q82 zZyxjdqmg&7o;ot|-y=s4{CwnlI^xp%YNTY2c?(T^_9 zOkbXvygYsE?9Ajt2PfY=JYM^Yb7lF~a({6!d3olu%aa!_o<28y@!I5>nQN1?=Pysr zUO9XA?ZcCJtge*hwKNaDAHUUK?A=>=L`639GstbfE$z}v%l!ee zn(r;$YAjYBckGpI4gR>4t@TmWW2aA^p53k4AMGAnn*O0;^%ldzM%!%moAHMe-3jxn z8>!G$4o_Wu5oSAiaX+|h~qS6>?Idf)l#^dmsg3CiNi!zdqo`|_?3AEbF^ ze4Cfr8Qg=zk03kq^1*J;KMAtUi>z#i_2By>KY49@toy+Sou7X22&P*YP5qTeIBog* z@UEb?BzlTzX8gv|%AmKpxQubX+)smo{(1iV*_r9t$>TF8rmvj6Jb7aJ?4_A4Gt+pR zyHfuahR4&LmjY&$<$}4QH_WxoFe~4l-ImkZggBg-p5U~F@WongZ8975F;dnC-bTGR z+pN^#sdI-9cEblx(!yIfZ*Sr)2VS~=VQj2>=1k|S?jvnHta_UtmzMiq4rjA%)5{Kr z5AE9KJ0yPQQPQsY=)`h(>W>c{?A|`}B!qS-)pk-lBpAQ{CkMv5SFUuv`u#_UJ?L+{ zW7{pw!Cl$ycE&RU+`RDmZF7BUK0Gyd@L>1HSDu96<|W$wY)ePkj&ZkCV|jF}+wFGl z|KSj2qrEcxvws_m;a9D;?ahN0-|}rf_g2!4vJiR;{ki3}k+A!becQ^ud%DRt@08^>gHC=reet7H z(-+^=Y-;D zSUtY`u~GbxKGc3pAHj2U;?GAW{(0i>@IntFKm>>Y5g-CYfCvx)B0vO)01+SpMBx9O zz#F5ZQ&KR|{==&l(zLhlF!_$~lgz#^-1|blv2QIEXOcT^wbJXU_&X378GB#5_qlwd zvyqB(#WeRjraFu|jZXY>Wa2+2{sk}eAOb{y2oM1xKm>>Y5g-CYfCvx)B0vPb#{^y* zo$72C4o26%+M*Kddv$bb9H}_llmy!PKb-#QK?H~Z5g-CYfCvx)B0vO)01+SpM1TnF zVFGRapX~oV+`v>dB0vO)01+SpM1Tko0U|&IhyW2F0wB=V|M#`{|G%f`f9eoKfCvx) zB0vO)01+SpM1Tko0U|&Iz8eB|-~RFR`pNk_C#Gi>=S#kJf_-+9&#iuJ^JS6Qxtq?N zT(~gZKXuc+_2oy$=BL}<$!&*H+u468y?*@CG@DV&w^uLUy>#6lKQVhv&rP4RbAIsU zopR;m-5YhO%*s;jXLmPFZ0rB~CjM(=;@9{?4;3I^NkgI(lfF;{W$jvr=7%01+SpM1Tko0U|&IhyW2F0z`la>`Z{-|99p>-x2{L zKm>>Y5g-CYfCvx)B0vO)01?O7l%ZZ=urFv0ahyW2F0z`la5CI}U1c(3; zAOb{y2z;Ljn0;eu{LrEC{ZFw8z=8k#x8In~`gu>W2fzn^^$)*Ui~s*-Wa3v7-+Z6B zP}zt85g-CYfCvx)B0vO)01+SpM1TnFX#($!rp{An0sGz^P2*3Y0Z{z^p6)$VIU+y= zhyW2F0z`la5CI}U1c(3;AOb@I6#q{hfd~)*B0vO)01+SpM1Tko0U|&Ih``<_KR?+U-%igau)sP4f0U|&IhyW2F0z`la5CI}U1c<-8PQ>ksk# zT|9rV<$V|LkK#!WB0vO)01+SpM1Tko0U|&IhyW2F0z}|BCb0Sc|NCa*SI==ZsOUt1 z2oM1xKm>>Y5g-CYfCvx)B0vP5ZvyTA|99cMSh{)r!uiz`r!Osqd2{F7-RoSqRXcnB zqcgX~CAYGi-n+i4uAg(m|Nq|{v;5e#6E|=BE9uPA^sQyX=ki-+`od~q@yk!HpFQ*W zJ$~=FK7aFc+yAdKc5np$(SryO0U|&Ih`F&s1{OO;)`qex4kK8}f zIrkLz(Lq@mtge)0{jPr3Nah3+*$rQLVu3Nq1mk!L<{7KiS(8K#)k^CV(y`18^CCE- zoHNzpGsnNk^zhUoxk$J~Kq{#eRpCAG9T8AXytN?fQF4LRbS!gnZ5`|iO%9k^q4c!nN+>+!dx-2A75WCmqq5jF9f zWi;*5S}H9$M}C}b9tkWlzrFN|D}h=fop_Kgvc$dOR$FD9(WcfUJCmi1B`)wDl&X>YUBqE9YzWQYWFfcS%K^UDi<~5o@+N>&$hiMd@g9 zL2FTai{m=7C}d$u8-@=hSR0j-kyoQU>L`P(B9>x;^dU&c9SUvhdf9`BC@)9<;Z7ul zj@kN|LN7y)!e0;rH-R+%k(3QKT2d4#@ich0_iE#H*n6B5>z_Ah#jV#V8iS!RYk9UD zTW4)AjpCyz*VCa3E6%dhLS$dH)u^yB&IKQX_cbt4rEWbh;VKv%Jfq4uw^rnVR%|2a zapMckyjCU@26(*kIfjsVFe-6mQ6f5_i;~-tB(FZ8Qy3Y8=7z^1fS`N|84#;f=%T{K z=)5UD)ht|Q5*K6WFG6GdI_Zj!CaIDlw;?MO&9i{9>5ENLDenLnGlZ7W2=wmaYRVNg z2nC-p2DFVhD{_;KwVv7F(2FqEbr2{jF3uvxAWDSM6synPsDJ^_45k}Zt4xe>rAoxx zj4Z9y4r5>0>#b>11_pi%CPe&kg`w?J5g6*;#FQ~%0=|>Us)EjoJ}O)ZQK1b? zz__z$15uKxnHT9CrjjCxugS$wDz1q})I1uE`Z*`GH5g53UR%)>(X(x^-MuIiF!L3X3j}mJOAe#0m!jz;GFZfmc~o(HTQRl!z2_Z2wFUn#ud=dsb<_>}P zg_T^;<%CZfy~wUz1w=C@lf;o1x*Qi6R0fPeUcR)P6_oDo`t2j&|27euHlOA_*{B0@PO%tixof+v+60|d>J9NDmF#I(` zYk;oOxU=BL1KL>$ZbI>xQ6u_pj<~_Jdog}|#PmP7F=E7_(Q)qZCON!)Dvk>dZ>MYa z|H8=F!VC73kE%-qhyW2F0z}}uBJlOm(W6^shN=5U?hS6weSYlo>FIo9{^ZRIVKu#X z?fBVG-35E`E_}RNem=0G|_=1hBfJdBnFZgo;!&lWhid7LWqUY z3=Iz2L(>3Y(I)5;FlT*mxq7HF5NBkGA}Ht>tZ40A6)^WVBnFtD-!3u0a10D$mV@11 zSy7@E32h)jM9GRHzg%2s>R)gU@}rLt7BU1^Xgh_;1OWkRmgY`tU7*Lnv^Rl64ss|o z$`xTzLVvS1vsfVynbZWH3OOr68H0Mz)H+d`{9vJQqUMkRQdFo+EZK*eVr3AB6tWgD zaFGp8Ov)lRoD69bGH8HdZmdW}u_g&YF-oFI10tu0dcY$0DJg^F#W4wO#E5J>hanu~ zP7(toHw7_|M^`f}Xba&0=PL+jP(F@C?>I6enn}O(RuM z+!*u@uT1th*+2zCp`oo=nC!9^fm5O1B@1E8LKyInMW8J~nH~yVYXUfX4|xJ2DX3=$i2{ORN!36kc~*g8&Hi5;8C(3WegsiIM1Tko z0U|&IUNizxnhwlbFL`-`R zlUfKkQospAad>DbAznHRSZ`7u}Ti-0be03U`d@>II6&r3)yCl9N_Jz;pUyjoPeAVrM7T`Xf6i0 z2E4~m$511jx{`$74~%~JG9?HaXs&7_gC~ac2~I;G4z&di4@`rbEAT4&?b!dc{{N!= z5~cbQ0U|&Ih`@J9;OnCY9$x>SJ%9GoJ2U0eC4Z40yZ77`vNLcVkX%HXAVUWZ3fFU?0A&>yR z13QEmAVvr4?W(!$14wgAD8;&fnq^y1y9`8#xoL3(7XB9;K>}FSPtB16t_Q8)>+Ao} zC!jJQq68TVgh?Q@$ze_2L2*HBADk3|LErvVn3CEiPm|LR1jRu(tm1ThNL@ z$c;e25%d)iG{h1}Go>J$!PlnS*rGyc!7NzsFNoMca1cTPWQ$pN<^Y4W_5Z^oV{Icq zdJq93Km>>Y5qRMUe7&-7+a3T|n{RsnJjLESWT2CQP6j#|=wzTj#|HXuZ4Usf?0&n0 zK78VB#9ji}AO%}8z-2u1sxr1PC@>dcF2gtuyt&DjW@BQL2MZHkVUqx(EB0k@5upRv z%m9w>2`|`tfqU#2fZakI*2Iy2#J&&MX9oMfAj}*~`w_c4RD|{m3;*=Cg9L13xR$FL zVLoGr0W4&@4C}if3q%Ycq`vBkHSD&LiH9-EvB^Qe-YZ(Ruy~0r5=wPUqwEM@FA3s7 z_E=Gm&<5ba&agd#!}@uO*g^x_LtvYX=I7rc)gAB@Y^Ab;fe!zBgz7Uy)5G%~k??R2 zXc<*3&?_k&{Nxh?_189EfHdISRwdXVf+21KCB_~d>&x@jLhr!76xbaFtMiC6KQpXZILJbgr;InJX^IS%dOAO)%BnLP*R7Bbrs2_+^ zz_uv{y9i*Hj~0sHqJ!oDUkHxy2?R(WzyjMFJnQm2a>Z^L88y!$dhAi*8P72z6a<|G in+T+A;4@Jyw+^C(j@U&8+iE~SfR}{9t{2}L|NnpDqF6ov delta 205 zcmZp8z}m2YWrDOI3j+g#AP~a<$3z`t9u@{Y?}@zpKNtj9|4z)7WB$vozgbYAkd>=R zpPyY^Tbr>(bn`-1b;ikh?DmX;oAcPGad0s4Z(-oS#lK~6M*iar{KtU;d->VS8Cf`zWhY;puRZxkJP%On4+H-npwxSQcA#UJ snK?K&8L)t~oM7NT0TkcIKl#Lb8zBE81OG)J|2Y5Vi|h3r7F|#P09"] edition = "2018" @@ -16,23 +16,23 @@ futures-util = "0.3" hex = "0.4.2" lexical-core = {version = ">0.7.4"} log = "0.4.13" -prost = "0.7" -ring = "0.16.9" +prost = "0.9" +ring = "0.16.20" serde = "1.0.97" serde_derive = "1.0" sha2 = "0.9.2" # For Production -sshcerts = {version = "0.10.1", features = ["yubikey-support"]} +sshcerts = {version = "0.11.0", features = ["yubikey-support", "fido-support"]} # For Development -# sshcerts = {git = "https://github.com/obelisk/sshcerts", features = ["yubikey-support"]} -# sshcerts = {path = "../../sshcerts", features = ["yubikey-support"]} +#sshcerts = {git = "https://github.com/obelisk/sshcerts", branch = "new_fido2", features = ["yubikey-support", "fido-support"]} +#sshcerts = {path = "../../sshcerts", features = ["yubikey-support", "fido-support"]} tokio = { version = "1.0.0", features = ["full"] } toml = "0.5.8" -tonic = {version = "0.4", features = ["tls"] } +tonic = {version = "0.6", features = ["tls"] } yubikey = {version = "0.5", features = ["untested"]} [build-dependencies] -tonic-build = "0.4" +tonic-build = "0.6" [lib] name = "rustica_agent" diff --git a/rustica-agent/src/config.rs b/rustica-agent/src/config.rs index 4d6f852..0b15c9a 100644 --- a/rustica-agent/src/config.rs +++ b/rustica-agent/src/config.rs @@ -1,4 +1,8 @@ -use clap::{App, Arg}; +use clap::{ + Arg, + ArgMatches, + Command, +}; use sshcerts::{CertType, PublicKey, PrivateKey}; use sshcerts::yubikey::piv::{SlotId, Yubikey}; @@ -27,6 +31,7 @@ pub enum ConfigurationError { ParsingError, YubikeyManagementKeyInvalid, YubikeyNoKeypairFound, + InvalidFidoKeyName, } pub struct RunConfig { @@ -35,7 +40,7 @@ pub struct RunConfig { pub handler: Handler, } -pub struct ProvisionConfig { +pub struct ProvisionPIVConfig { pub yubikey: YubikeySigner, pub pin: String, pub management_key: Vec, @@ -43,10 +48,23 @@ pub struct ProvisionConfig { pub subject: String, } +pub enum SKType { + Ed25519, + Ecdsa, +} + +pub struct ProvisionAndRegisterFidoConfig { + pub server: RusticaServer, + pub app_name: String, + pub comment: String, + pub key_type: SKType, + pub out: Option, +} + pub struct RegisterConfig { pub server: RusticaServer, pub signatory: Signatory, - pub key_config: KeyConfig, + pub attestation: PIVAttestation, } pub struct ImmediateConfig { @@ -59,8 +77,9 @@ pub struct ImmediateConfig { pub enum RusticaAgentAction { Run(RunConfig), Immediate(ImmediateConfig), - Provision(ProvisionConfig), + ProvisionPIV(ProvisionPIVConfig), Register(RegisterConfig), + ProvisionAndRegisterFido(ProvisionAndRegisterFidoConfig), } @@ -76,9 +95,48 @@ impl From for ConfigurationError { } } +fn get_signatory(cmd_slot: &Option, config_slot: &Option, matches: &ArgMatches, config_key: &Option) -> Result { + // Determine the signatory to be used. These match statements, execute in order, + // create a hierarchy of which keys override others. + // If a file is specified at the command line, that overrides everything else. + // If there is no file, check for a key in the config file. + // If there is no key in the config, check if a slot has been passed. + // If there is a slot both on the command line and config file, prefer the command line + // Otherwise use the slot in the config + // If none of these, error. + match (cmd_slot, config_slot, matches.value_of("file"), &config_key) { + (Some(slot), _, _, _) => { + match slot_parser(slot) { + Some(s) => Ok(Signatory::Yubikey(YubikeySigner { + yk: Yubikey::new().unwrap(), + slot: s, + })), + None => Err(ConfigurationError::BadSlot) + } + }, + (_, _, Some(file), _) => { + match PrivateKey::from_path(file) { + Ok(p) => Ok(Signatory::Direct(p)), + Err(e) => Err(ConfigurationError::CannotReadFile(format!("{}: {}", e, file))), + } + }, + (_, Some(slot), _, _) => { + match slot_parser(slot) { + Some(s) => Ok(Signatory::Yubikey(YubikeySigner { + yk: Yubikey::new().unwrap(), + slot: s, + })), + None => Err(ConfigurationError::BadSlot), + } + }, + (_, _, _, Some(key_string)) => Ok(Signatory::Direct(PrivateKey::from_string(key_string)?)), + (None, None, None, None) => Err(ConfigurationError::MissingSSHKey) + } +} + pub fn configure() -> Result { - let matches = App::new("rustica-agent") + let matches = Command::new("rustica-agent") .version(env!("CARGO_PKG_VERSION")) .author("Mitchell Grenier ") .about("The SSH Agent component of Rustica") @@ -177,7 +235,7 @@ pub fn configure() -> Result { .takes_value(true) ) .subcommand( - App::new("register") + Command::new("register") .about("Take your key and register with the backend. If a hardware key, proof of providence will be sent to the backend") .arg( Arg::new("no-attest") @@ -186,7 +244,7 @@ pub fn configure() -> Result { ) ) .subcommand( - App::new("provision") + Command::new("provision-piv") .about("Provision this slot with a new private key") .arg( Arg::new("management-key") @@ -220,6 +278,43 @@ pub fn configure() -> Result { .short('j') ) ) + .subcommand( + Command::new("fido-setup") + .about("Provision and register a new FIDO/U2F key") + .arg( + Arg::new("application") + .help("Specify application you are creating the key for") + .default_value("ssh:RusticaAgent") + .long("application") + .short('a') + .required(false) + .takes_value(true), + ) + .arg( + Arg::new("comment") + .help("A comment about what this SSH key will be for") + .long("comment") + .short('c') + .required(false) + .default_value("RusticaAgentProvisionedKey") + ) + .arg( + Arg::new("kind") + .help("Whether you'd like an Ed25519 or ECDSA P256 key") + .possible_values(vec!["ed25519", "ecdsa"]) + .default_value("ed25519") + .long("kind") + .short('k') + ) + .arg( + Arg::new("out") + .help("Relative path to write your new private key handle to") + .required(false) + .long("out") + .takes_value(true) + .short('o') + ) + ) .get_matches(); // Read the configuration file and use it as a base. Command line parameters @@ -284,54 +379,23 @@ pub fn configure() -> Result { let cmd_slot = matches.value_of("slot").map(|x| x.to_owned()); - // Determine the signatory to be used. These match statements, execute in order, - // create a hierarchy of which keys override others. - // If a file is specified at the command line, that overrides everything else. - // If there is no file, check for a key in the config file. - // If there is no key in the config, check if a slot has been passed. - // If there is a slot both on the command line and config file, prefer the command line - // Otherwise use the slot in the config - // If none of these, error. - let mut signatory = match (&cmd_slot, &config.slot, matches.value_of("file"), &config.key) { - (Some(slot), _, _, _) => { - match slot_parser(slot) { - Some(s) => Signatory::Yubikey(YubikeySigner { - yk: Yubikey::new().unwrap(), - slot: s, - }), - None => return Err(ConfigurationError::BadSlot) - } - }, - (_, _, Some(file), _) => Signatory::Direct(PrivateKey::from_path(file)?), - (_, Some(slot), _, _) => { - match slot_parser(slot) { - Some(s) => Signatory::Yubikey(YubikeySigner { - yk: Yubikey::new().unwrap(), - slot: s, - }), - None => return Err(ConfigurationError::BadSlot), - } - }, - (_, _, _, Some(key_string)) => Signatory::Direct(PrivateKey::from_string(key_string)?), - (None, None, None, None) => return Err(ConfigurationError::MissingSSHKey) - }; - - if let Some(matches) = matches.subcommand_matches("provision") { + if let Some(cmd_matches) = matches.subcommand_matches("provision-piv") { + let signatory = get_signatory(&cmd_slot, &config.slot, &matches, &config.key)?; let yubikey = match signatory { Signatory::Yubikey(yk_sig) => yk_sig, Signatory::Direct(_) => return Err(ConfigurationError::CannotProvisionFile) }; - let require_touch = matches.is_present("require-touch"); - let subject = matches.value_of("subject").unwrap().to_string(); - let management_key = match hex::decode(matches.value_of("management-key").unwrap()) { + let require_touch = cmd_matches.is_present("require-touch"); + let subject = cmd_matches.value_of("subject").unwrap().to_string(); + let management_key = match hex::decode(cmd_matches.value_of("management-key").unwrap()) { Ok(mgm) => mgm, Err(_) => return Err(ConfigurationError::YubikeyManagementKeyInvalid), }; - let pin = matches.value_of("pin").unwrap().to_string(); + let pin = cmd_matches.value_of("pin").unwrap().to_string(); - let provision_config = ProvisionConfig { + let provision_config = ProvisionPIVConfig { yubikey, pin, management_key, @@ -339,9 +403,36 @@ pub fn configure() -> Result { require_touch, }; - return Ok(RusticaAgentAction::Provision(provision_config)); + return Ok(RusticaAgentAction::ProvisionPIV(provision_config)); + } + + if let Some(matches) = matches.subcommand_matches("fido-setup") { + let app_name = matches.value_of("application").unwrap().to_string(); + + if !app_name.starts_with("ssh:") { + return Err(ConfigurationError::InvalidFidoKeyName); + } + + let comment = matches.value_of("comment").unwrap().to_string(); + let out = matches.value_of("out").map(String::from); + + let key_type = match matches.value_of("kind") { + Some("ecdsa") => SKType::Ecdsa, + _ => SKType::Ed25519, + }; + + let provision_config = ProvisionAndRegisterFidoConfig { + server, + app_name, + comment, + key_type, + out, + }; + + return Ok(RusticaAgentAction::ProvisionAndRegisterFido(provision_config)); } + let mut signatory = get_signatory(&cmd_slot, &config.slot, &matches, &config.key)?; let pubkey = match &mut signatory { Signatory::Yubikey(signer) => match signer.yk.ssh_cert_fetch_pubkey(&signer.slot) { Ok(cert) => cert, @@ -351,7 +442,7 @@ pub fn configure() -> Result { }; if let Some(matches) = matches.subcommand_matches("register") { - let mut key_config = KeyConfig { + let mut attestation = PIVAttestation { certificate: vec![], intermediate: vec![], }; @@ -362,10 +453,10 @@ pub fn configure() -> Result { Signatory::Direct(_) => return Err(ConfigurationError::CannotAttestFileBasedKey), }; - key_config.certificate = signer.yk.fetch_attestation(&signer.slot).unwrap_or_default(); - key_config.intermediate = signer.yk.fetch_certificate(&SlotId::Attestation).unwrap_or_default(); + attestation.certificate = signer.yk.fetch_attestation(&signer.slot).unwrap_or_default(); + attestation.intermediate = signer.yk.fetch_certificate(&SlotId::Attestation).unwrap_or_default(); - if key_config.certificate.is_empty() || key_config.intermediate.is_empty() { + if attestation.certificate.is_empty() || attestation.intermediate.is_empty() { error!("Part of the attestation could not be generated. Registration may fail"); } } @@ -373,7 +464,7 @@ pub fn configure() -> Result { return Ok(RusticaAgentAction::Register(RegisterConfig { server, signatory, - key_config, + attestation, })) } diff --git a/rustica-agent/src/lib.rs b/rustica-agent/src/lib.rs index 6013fd2..bbcc94f 100644 --- a/rustica-agent/src/lib.rs +++ b/rustica-agent/src/lib.rs @@ -8,17 +8,22 @@ use serde_derive::Deserialize; pub use sshagent::{Agent, error::Error as AgentError, Identity, SshAgentHandler, Response}; pub use rustica::{ - key::KeyConfig, + key::{ + PIVAttestation, + }, RefreshError::{ConfigurationError, SigningError} }; +use rustica::key::U2FAttestation; + use sshcerts::ssh::{Certificate, CertType, PrivateKey, SSHCertificateSigner}; -use sshcerts::utils::format_signature_for_ssh; +use sshcerts::fido::generate::generate_new_ssh_key; use sshcerts::yubikey::piv::{AlgorithmId, SlotId, RetiredSlotId, TouchPolicy, PinPolicy, Yubikey}; use std::collections::HashMap; use std::convert::TryFrom; +use std::fs::File; use std::time::SystemTime; @@ -130,7 +135,7 @@ impl SshAgentHandler for Handler { // Build identities from the private keys we have loaded let mut identities: Vec = self.identities.iter().map(|x| Identity { key_blob: x.1.pubkey.encode().to_vec(), - key_comment: x.1.comment.as_ref().unwrap_or(&String::new()).to_string(), + key_comment: x.1.comment.clone(), }).collect(); // If the time hasn't expired on our certificate, we don't need to fetch a new one @@ -142,6 +147,7 @@ impl SshAgentHandler for Handler { return Ok(Response::Identities(vec![cert.clone()])); } } + if let Some(f) = &self.notification_function { f() } @@ -149,14 +155,19 @@ impl SshAgentHandler for Handler { // Grab a new certificate from the server because we don't have a valid one match self.server.get_custom_certificate(&mut self.signatory, &self.certificate_options) { Ok(response) => { - info!("{:#}", Certificate::from_string(&response.cert).unwrap()); + let parsed_cert = Certificate::from_string(&response.cert).map_err(|e| + AgentError { + details: e.to_string(), + })?; + info!("{:#}", parsed_cert); let cert: Vec<&str> = response.cert.split(' ').collect(); let raw_cert = base64::decode(cert[1]).unwrap_or_default(); let ident = Identity { key_blob: raw_cert, - key_comment: response.comment, + key_comment: response.comment.clone(), }; self.cert = Some(ident.clone()); + identities.push(ident); Ok(Response::Identities(identities)) }, @@ -178,19 +189,15 @@ impl SshAgentHandler for Handler { } else if let Signatory::Direct(privkey) = &self.signatory { Some(privkey) } else if let Signatory::Yubikey(signer) = &mut self.signatory { - // If using long lived certificates you might need to tap again here because you didn't have to - // to get the certificate the first time + // Since we are using the Yubikey for a signing operation the only time they + // won't have to tap here is if they are using cached keys and this is right after + // a secure Rustica tap. In most cases, we'll need to send this, rarely, it'll be + // spurious. if let Some(f) = &self.notification_function { f() } - let pubkey = signer.yk.ssh_cert_fetch_pubkey(&signer.slot).unwrap(); let signature = signer.yk.ssh_cert_signer(&data, &signer.slot).map_err(|_| AgentError::from("Yubikey signing error"))?; - - let signature = match format_signature_for_ssh(&pubkey, &signature) { - Some(s) => s, - None => return Err(AgentError::from("Signature could not be converted to SSH format")), - }; return Ok(Response::SignResponse { signature, @@ -203,12 +210,7 @@ impl SshAgentHandler for Handler { Some(key) => { let signature = match key.sign(&data) { None => return Err(AgentError::from("Signing Error")), - Some(signature) => format_signature_for_ssh(&key.pubkey, &signature), - }; - - let signature = match signature { - Some(s) => s, - None => return Err(AgentError::from("Signature could not be converted to SSH format")) + Some(signature) => signature, }; Ok(Response::SignResponse { @@ -248,7 +250,7 @@ pub fn slot_validator(slot: &str) -> Result<(), String> { } /// Provisions a new keypair on the Yubikey with the given settings. -pub fn provision_new_key(mut yubikey: YubikeySigner, pin: &str, subj: &str, mgm_key: &[u8], require_touch: bool) -> Option { +pub fn provision_new_key(mut yubikey: YubikeySigner, pin: &str, subj: &str, mgm_key: &[u8], require_touch: bool) -> Option { println!("Provisioning new NISTP384 key in slot: {:?}", &yubikey.slot); let policy = if require_touch { @@ -269,7 +271,7 @@ pub fn provision_new_key(mut yubikey: YubikeySigner, pin: &str, subj: &str, mgm_ let intermediate = yubikey.yk.fetch_certificate(&SlotId::Attestation); match (certificate, intermediate) { - (Ok(certificate), Ok(intermediate)) => Some(KeyConfig {certificate, intermediate}), + (Ok(certificate), Ok(intermediate)) => Some(PIVAttestation{certificate, intermediate}), _ => None, } }, @@ -358,6 +360,21 @@ pub unsafe extern fn list_keys(yubikey_serial: u32, out_length: *mut c_int) -> * } } +/// The return from this function must be freed by the caller because we can no longer track it +/// once we return +#[no_mangle] +pub extern fn check_yubikey_slot_provisioned(yubikey_serial: u32, slot_id: u8) -> bool { + match &mut Yubikey::open(yubikey_serial) { + Ok(yk) => { + match SlotId::try_from(slot_id) { + Ok(slot) => yk.fetch_subject(&slot).is_ok(), + Err(_) => false, + } + }, + Err(_) => false + } +} + /// Free the list of Yubikey keys /// /// # Safety @@ -378,6 +395,118 @@ pub unsafe extern fn free_list_keys(length: c_int, keys: *mut *mut c_char) { } } +#[no_mangle] +/// Generate and enroll a new FIDO key with a Rustica backend +/// +/// # Safety +/// All c_char pointers passed to this function must be null terminated C +/// strings or undefined behaviour occurs possibly resulting in corruption +/// or crashes. +pub unsafe extern fn generate_and_enroll_fido(config_data: *const c_char, out: *const c_char, comment: *const c_char, pin: *const c_char, device: *const c_char) -> bool { + let cf = CStr::from_ptr(config_data); + let config: Config = match cf.to_str() { + Err(_) => return false, + Ok(s) => { + match toml::from_str(s) { + Ok(c) => c, + Err(e) => { + println!("Error: Could not parse the configuration data: {}", e); + return false + } + } + }, + }; + + let out = CStr::from_ptr(out); + let out = match out.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + + let comment = if !comment.is_null() { + let comment = CStr::from_ptr(comment); + let comment = match comment.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + comment.to_string() + } else { + "FFI-RusticaAgent-Generated-Key".to_string() + }; + + let pin = if !pin.is_null() { + let pin = CStr::from_ptr(pin); + let pin = match pin.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + Some(pin.to_string()) + } else { + None + }; + + let device = if !device.is_null() { + let device = CStr::from_ptr(device); + let device = match device.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + Some(device.to_string()) + } else { + None + }; + + let new_fido_key = match generate_new_ssh_key("ssh:RusticaAgentFIDOKey", &comment, pin, device) { + Ok(nfk) => nfk, + Err(e) => { + println!("Error: {}", e); + return false; + } + }; + + let server = RusticaServer { + address: config.server.unwrap(), + ca: config.ca_pem.unwrap(), + mtls_cert: config.mtls_cert.unwrap(), + mtls_key: config.mtls_key.unwrap(), + }; + + let mut signatory = Signatory::Direct(new_fido_key.private_key.clone()); + let u2f_attestation = U2FAttestation { + auth_data: new_fido_key.attestation.auth_data, + auth_data_sig: new_fido_key.attestation.auth_data_sig, + intermediate: new_fido_key.attestation.intermediate, + challenge: new_fido_key.attestation.challenge, + alg: new_fido_key.attestation.alg, + }; + + let mut out_file = match File::create(out) { + Ok(f) => f, + Err(e) => { + println!("Error: Could not create keyfile at {}: {}", out, e); + return false + }, + }; + + if new_fido_key.private_key.write(&mut out_file).is_err() { + std::fs::remove_file(out).unwrap_or_default(); + println!("Error: Could not write to file. Basically should never happen"); + return false; + }; + + match server.register_u2f_key(&mut signatory, "ssh:RusticaAgentFIDOKey", &u2f_attestation) { + Ok(_) => { + println!("Key was successfully registered"); + true + }, + Err(e) => { + error!("Key could not be registered. Server said: {}", e); + std::fs::remove_file(out).unwrap(); + false + }, + } +} + /// Generate and enroll a new key on the given yubikey in the given slot /// /// # Safety @@ -416,7 +545,7 @@ pub unsafe extern fn generate_and_enroll(yubikey_serial: u32, slot: u8, high_sec let intermediate = yk.fetch_certificate(&SlotId::Attestation); match (certificate, intermediate) { - (Ok(certificate), Ok(intermediate)) => KeyConfig {certificate, intermediate}, + (Ok(certificate), Ok(intermediate)) => PIVAttestation{certificate, intermediate}, _ => return false, } }, @@ -447,6 +576,88 @@ pub unsafe extern fn generate_and_enroll(yubikey_serial: u32, slot: u8, high_sec } } +/// Start a new Rustica instance. Does not return unless Rustica exits. +/// # Safety +/// `config_data` and `socket_path` must be a null terminated C strings +/// or behaviour is undefined and will result in a crash. +#[no_mangle] +pub unsafe extern fn start_direct_rustica_agent(private_key: *const c_char, config_data: *const c_char, socket_path: *const c_char, pin: *const c_char, device: *const c_char, notification_fn: unsafe extern "C" fn() -> ()) -> bool { + println!("Starting a new Rustica instance!"); + + let notification_f = move || { + notification_fn(); + }; + + let cf = CStr::from_ptr(config_data); + let config_data = match cf.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + + let sp = CStr::from_ptr(socket_path); + let socket_path = match sp.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + + let private_key = CStr::from_ptr(private_key); + let mut private_key = match private_key.to_str() { + Err(_) => return false, + Ok(s) => { + if let Ok(p) = PrivateKey::from_string(s) { + p + } else { + return false + } + }, + }; + + if !pin.is_null() { + let pin = CStr::from_ptr(pin); + let pin = match pin.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + private_key.set_pin(pin); + } + + if !device.is_null() { + let device = CStr::from_ptr(device); + let device = match device.to_str() { + Err(_) => return false, + Ok(s) => s, + }; + + private_key.set_device_path(device); + } + + println!("Fingerprint: {:?}", private_key.pubkey.fingerprint().hash); + + let config: Config = toml::from_str(config_data).unwrap(); + let certificate_options = CertificateConfig::from(config.options); + let handler = Handler { + cert: None, + stale_at: 0, + certificate_options, + server: RusticaServer { + address: config.server.unwrap(), + ca: config.ca_pem.unwrap(), + mtls_cert: config.mtls_cert.unwrap(), + mtls_key: config.mtls_key.unwrap(), + }, + signatory: Signatory::Direct(private_key), + identities: HashMap::new(), + notification_function: Some(Box::new(notification_f)), + }; + + + let socket = UnixListener::bind(socket_path).unwrap(); + Agent::run(handler, socket); + + true +} + + /// Start a new Rustica instance. Does not return unless Rustica exits. /// # Safety /// `config_data` and `socket_path` must be a null terminated C strings @@ -456,7 +667,7 @@ pub unsafe extern fn start_yubikey_rustica_agent(yubikey_serial: u32, slot: u8, println!("Starting a new Rustica instance!"); let notification_f = move || { - unsafe { notification_fn(); } + notification_fn(); }; let cf = CStr::from_ptr(config_data); diff --git a/rustica-agent/src/main.rs b/rustica-agent/src/main.rs index 0d2651d..1e5efa9 100644 --- a/rustica-agent/src/main.rs +++ b/rustica-agent/src/main.rs @@ -4,11 +4,17 @@ mod config; use crate::config::RusticaAgentAction; use rustica_agent::*; +use rustica_agent::rustica::key::{ + U2FAttestation +}; -use sshcerts::Certificate; +use sshcerts::{ + Certificate, + fido::generate::generate_new_ssh_key, +}; -use std::io::Write; use std::fs::File; +use std::io::Write; use std::os::unix::net::{UnixListener}; @@ -17,7 +23,7 @@ fn main() -> Result<(), Box> { match config::configure() { // Generates a new hardware backed key in a Yubikey then exits. // This always generates a NISTP384 key. - Ok(RusticaAgentAction::Provision(config)) => { + Ok(RusticaAgentAction::ProvisionPIV(config)) => { match provision_new_key(config.yubikey, &config.pin, &config.subject, &config.management_key, config.require_touch) { Some(_) => (), None => { @@ -26,8 +32,43 @@ fn main() -> Result<(), Box> { }, }; }, + // This is done in one step instead of two because there is no way (that I know of) to get an attestation + // for a previously generated FIDO key. So we have to send the attestation data at generation time. + Ok(RusticaAgentAction::ProvisionAndRegisterFido(prf)) => { + let new_fido_key = generate_new_ssh_key(&prf.app_name, &prf.comment, None, None)?; + + let mut signatory = Signatory::Direct(new_fido_key.private_key.clone()); + let u2f_attestation = U2FAttestation { + auth_data: new_fido_key.attestation.auth_data, + auth_data_sig: new_fido_key.attestation.auth_data_sig, + intermediate: new_fido_key.attestation.intermediate, + challenge: new_fido_key.attestation.challenge, + alg: new_fido_key.attestation.alg, + }; + + match prf.server.register_u2f_key(&mut signatory, &prf.app_name, &u2f_attestation) { + Ok(_) => { + println!("Key was successfully registered"); + + if let Some(out) = prf.out { + let mut out = File::create(out)?; + new_fido_key.private_key.write(&mut out)?; + } else { + let mut buf = std::io::BufWriter::new(Vec::new()); + new_fido_key.private_key.write(&mut buf).unwrap(); + let serialized = String::from_utf8(buf.into_inner().unwrap()).unwrap(); + println!("Your new private key handle:\n{}", serialized); + println!("You key fingerprint: {}", new_fido_key.private_key.pubkey.fingerprint().hash); + } + }, + Err(e) => { + error!("Key could not be registered. Server said: {}", e); + return Err(Box::new(e)) + }, + }; + }, Ok(RusticaAgentAction::Register(mut config)) => { - match config.server.register_key(&mut config.signatory, &config.key_config) { + match config.server.register_key(&mut config.signatory, &config.attestation) { Ok(_) => { println!("Key was successfully registered"); }, diff --git a/rustica-agent/src/rustica/key.rs b/rustica-agent/src/rustica/key.rs index 471ce32..4433414 100644 --- a/rustica-agent/src/rustica/key.rs +++ b/rustica-agent/src/rustica/key.rs @@ -1,5 +1,5 @@ use super::error::{RefreshError}; -use super::{RegisterKeyRequest, RusticaServer, Signatory}; +use super::{RegisterKeyRequest, RegisterU2fKeyRequest, RusticaServer, Signatory}; use tokio::runtime::Runtime; @@ -8,29 +8,66 @@ pub mod rustica { } #[derive(Debug)] -pub struct KeyConfig { +pub struct PIVAttestation { pub certificate: Vec, pub intermediate: Vec, } +#[derive(Debug)] +pub struct U2FAttestation { + pub auth_data: Vec, + pub auth_data_sig: Vec, + pub intermediate: Vec, + pub challenge: Vec, + pub alg: i32, +} + + impl RusticaServer { - pub async fn register_key_async(&self, signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { + pub async fn register_key_async(&self, signatory: &mut Signatory, attestation: &PIVAttestation) -> Result<(), RefreshError> { let (mut client, challenge) = super::complete_rustica_challenge(self, signatory).await.unwrap(); - let request = tonic::Request::new(RegisterKeyRequest { - certificate: key.certificate.clone(), - intermediate: key.intermediate.clone(), + let request = RegisterKeyRequest { + certificate: attestation.certificate.clone(), + intermediate: attestation.intermediate.clone(), challenge: Some(challenge), - }); + }; + + let request = tonic::Request::new(request); client.register_key(request).await?; Ok(()) } - pub fn register_key(&self, signatory: &mut Signatory, key: &KeyConfig) -> Result<(), RefreshError> { + pub fn register_key(&self, signatory: &mut Signatory, key: &PIVAttestation) -> Result<(), RefreshError> { Runtime::new().unwrap().block_on(async { self.register_key_async(signatory, key).await }) } + + pub async fn register_u2f_key_async(&self, signatory: &mut Signatory, application: &str, attestation: &U2FAttestation) -> Result<(), RefreshError> { + let (mut client, challenge) = super::complete_rustica_challenge(self, signatory).await.unwrap(); + + let request = RegisterU2fKeyRequest { + auth_data: attestation.auth_data.clone(), + auth_data_signature: attestation.auth_data_sig.clone(), + sk_application: application.as_bytes().to_vec(), + u2f_challenge: attestation.challenge.clone(), + intermediate: attestation.intermediate.clone(), + alg: attestation.alg, + challenge: Some(challenge), + }; + + let request = tonic::Request::new(request); + + client.register_u2f_key(request).await?; + Ok(()) + } + + pub fn register_u2f_key(&self, signatory: &mut Signatory, application: &str, key: &U2FAttestation) -> Result<(), RefreshError> { + Runtime::new().unwrap().block_on(async { + self.register_u2f_key_async(signatory, application, key).await + }) + } } \ No newline at end of file diff --git a/rustica-agent/src/rustica/mod.rs b/rustica-agent/src/rustica/mod.rs index 98e583f..d1a27e8 100644 --- a/rustica-agent/src/rustica/mod.rs +++ b/rustica-agent/src/rustica/mod.rs @@ -11,11 +11,13 @@ pub use rustica_proto::{ Challenge, ChallengeRequest, RegisterKeyRequest, + RegisterU2fKeyRequest, }; -use sshcerts::ssh::{CurveKind, PublicKeyKind, PrivateKeyKind}; +use sshcerts::ssh::{ + Certificate as SSHCertificate, +}; -use ring::{rand, signature}; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; use crate::{RusticaServer, Signatory}; @@ -62,10 +64,9 @@ pub async fn complete_rustica_challenge(server: &RusticaServer, signatory: &mut let response = client.challenge(request).await?; let response = response.into_inner(); - let decoded_challenge = hex::decode(&response.challenge)?; if response.no_signature_required { - debug!("This server does not require signatures be sent to Rustica, not signing the challenge"); + debug!("This server does not require signatures be sent, not resigning the certificate"); return Ok(( client, Challenge { @@ -76,53 +77,17 @@ pub async fn complete_rustica_challenge(server: &RusticaServer, signatory: &mut })) } - let challenge_signature = match signatory { - Signatory::Yubikey(signer) => { - let alg = match signer.yk.get_ssh_key_type(&signer.slot){ - Ok(alg) => alg, - Err(_) => return Err(RefreshError::SigningError), - }; + debug!("{}", &response.challenge); - hex::encode(signer.yk.sign_data(&decoded_challenge, alg, &signer.slot)?) - }, - // TODO: @obelisk Find a way to replace this with sshcerts::ssh::signer code - Signatory::Direct(privkey) => { - let rng = rand::SystemRandom::new(); - - match &privkey.kind { - PrivateKeyKind::Rsa(_) => return Err(RefreshError::UnsupportedMode), - PrivateKeyKind::Ecdsa(key) => { - let alg = match key.curve.kind { - CurveKind::Nistp256 => &signature::ECDSA_P256_SHA256_ASN1_SIGNING, - CurveKind::Nistp384 => &signature::ECDSA_P384_SHA384_ASN1_SIGNING, - CurveKind::Nistp521 => return Err(RefreshError::UnsupportedMode), - }; - - let pubkey = match &privkey.pubkey.kind { - PublicKeyKind::Ecdsa(key) => &key.key, - _ => return Err(RefreshError::UnsupportedMode), - }; - - let key = if key.key[0] == 0x0_u8 {&key.key[1..]} else {&key.key}; - let key_pair = signature::EcdsaKeyPair::from_private_key_and_public_key(alg, key, pubkey)?; - - hex::encode(key_pair.sign(&rng, &decoded_challenge)?) - }, - PrivateKeyKind::Ed25519(key) => { - let public_key = match &privkey.pubkey.kind { - PublicKeyKind::Ed25519(key) => &key.key, - _ => return Err(RefreshError::UnsupportedMode), - }; - - let key_pair = match signature::Ed25519KeyPair::from_seed_and_public_key(&key.key[..32], public_key) { - Ok(kp) => kp, - Err(_) => return Err(RefreshError::SigningError), - }; - - hex::encode(key_pair.sign(&decoded_challenge)) - }, - } + let mut challenge_certificate = SSHCertificate::from_string(&response.challenge).map_err(|_| RefreshError::SigningError)?; + challenge_certificate.signature_key = challenge_certificate.key.clone(); + + let resigned_certificate = match signatory { + Signatory::Yubikey(signer) => { + let signature = signer.yk.ssh_cert_signer(&challenge_certificate.tbs_certificate(), &signer.slot).map_err(|_| RefreshError::SigningError)?; + challenge_certificate.add_signature(&signature).map_err(|_| RefreshError::SigningError)? }, + Signatory::Direct(privkey) => challenge_certificate.sign(privkey).map_err(|_| RefreshError::SigningError)?, }; Ok(( @@ -130,7 +95,7 @@ pub async fn complete_rustica_challenge(server: &RusticaServer, signatory: &mut Challenge { pubkey: encoded_key.to_string(), challenge_time: response.time, - challenge: response.challenge, - challenge_signature, + challenge: format!("{}", resigned_certificate), + challenge_signature: String::new(), })) } \ No newline at end of file diff --git a/rustica-agent/src/sshagent/agent.rs b/rustica-agent/src/sshagent/agent.rs index f801f04..dd31763 100644 --- a/rustica-agent/src/sshagent/agent.rs +++ b/rustica-agent/src/sshagent/agent.rs @@ -13,7 +13,6 @@ pub struct Agent; impl Agent { fn handle_client(handler: Arc>, mut stream: UnixStream) -> HandleResult<()> { - debug!("handling new connection"); loop { let req = Request::read(&mut stream)?; trace!("request: {:?}", req); diff --git a/rustica-agent/src/sshagent/protocol.rs b/rustica-agent/src/sshagent/protocol.rs index 767e1b1..c4c0865 100644 --- a/rustica-agent/src/sshagent/protocol.rs +++ b/rustica-agent/src/sshagent/protocol.rs @@ -47,7 +47,7 @@ impl MessageRequest { fn read_message(stream: &mut R) -> ParsingError> { let len = stream.read_u32::()?; - + let mut buf = vec![0; len as usize]; stream.read_exact(&mut buf)?; diff --git a/rustica/Cargo.toml b/rustica/Cargo.toml index fb17f63..a56814e 100644 --- a/rustica/Cargo.toml +++ b/rustica/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "rustica" -version = "0.8.3" +version = "0.9.0" authors = ["Mitchell Grenier "] edition = "2018" [features] default = [] -all = ["amazon-kms", "influx", "splunk", "yubikey-support", "local-db"] +all = ["amazon-kms", "influx", "splunk", "yubikey-support", "local-db", "webhook"] amazon-kms = ["aws-config", "aws-sdk-kms", "aws-types"] influx = ["influxdb"] local-db = ["diesel"] -splunk = ["reqwest", "serde_json"] +splunk = ["webhook"] +webhook = ["reqwest", "serde_json"] yubikey-support = ["sshcerts/yubikey-support"] [dependencies] @@ -24,15 +25,18 @@ futures-core = "0.3" futures-util = "0.3" hex = "0.4.2" log = "0.4.13" +minicbor = "0.13" prost = "0.7" ring = "0.16.20" serde = {version = "1.0", features = ["derive"]} +serde_cbor = "0.11.1" sha2 = "0.9.2" # For Production -sshcerts = { version = "0.10.1", default-features = false, features = ["x509-support"] } +sshcerts = {version = "0.11", default-features = false, features = ["fido-lite", "x509-support", "yubikey-lite"]} +#sshcerts = { version = "0.10.1", default-features = false, features = ["x509-support"] } # For Development -# sshcerts = {git = "https://github.com/obelisk/sshcerts", branch="more_versatile_certificate_signing", default-features = false, features = ["x509-support"]} -# sshcerts = {path = "../../sshcerts", features = ["x509-support"]} +#sshcerts = {git = "https://github.com/obelisk/sshcerts", branch="new_fido2", default-features = false, features = ["fido-lite", "x509-support", "yubikey-lite"]} +#sshcerts = {path = "../../sshcerts", features = ["x509-support"]} tokio = { version = "1", features = ["full"] } toml = "0.5" tonic = { version = "0.4", features = ["tls"] } @@ -49,7 +53,7 @@ diesel = { version = "1.4.4", features = ["sqlite"], optional = true } # Dependencies for Influx influxdb = { version = "0.5.1", optional = true } -# Dependencies for Splunk +# Dependencies for Splunk/Webhook reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"], optional = true } serde_json = { version = "1.0", optional = true } diff --git a/rustica/migrations/2022-02-23-205236_add_u2f/down.sql b/rustica/migrations/2022-02-23-205236_add_u2f/down.sql new file mode 100644 index 0000000..3a27f10 --- /dev/null +++ b/rustica/migrations/2022-02-23-205236_add_u2f/down.sql @@ -0,0 +1,12 @@ +DROP TABLE registered_keys; + +CREATE TABLE registered_keys ( + fingerprint TEXT PRIMARY KEY NOT NULL, + user TEXT NOT NULL, + pin_policy TEXT NULL, + touch_policy TEXT NULL, + hsm_serial TEXT NULL, + firmware TEXT NULL, + attestation_certificate TEXT NULL, + attestation_intermediate TEXT NULL +); \ No newline at end of file diff --git a/rustica/migrations/2022-02-23-205236_add_u2f/up.sql b/rustica/migrations/2022-02-23-205236_add_u2f/up.sql new file mode 100644 index 0000000..12a048b --- /dev/null +++ b/rustica/migrations/2022-02-23-205236_add_u2f/up.sql @@ -0,0 +1,6 @@ +ALTER TABLE registered_keys ADD COLUMN auth_data text; +ALTER TABLE registered_keys ADD COLUMN auth_data_signature text; +ALTER TABLE registered_keys ADD COLUMN aaguid text; +ALTER TABLE registered_keys ADD COLUMN challenge text; +ALTER TABLE registered_keys ADD COLUMN alg integer; +ALTER TABLE registered_keys ADD COLUMN application text; \ No newline at end of file diff --git a/rustica/src/auth/database.rs b/rustica/src/auth/database.rs index 96a1720..e0fcf2c 100644 --- a/rustica/src/auth/database.rs +++ b/rustica/src/auth/database.rs @@ -12,6 +12,7 @@ use super::{ AuthorizationError, AuthorizationRequestProperties, RegisterKeyRequestProperties, + KeyAttestation, }; use sshcerts::ssh::{Certificate, CertType}; @@ -94,7 +95,7 @@ impl LocalDatabase { } } - pub fn register_key(&self, req: &RegisterKeyRequestProperties) -> Result { + pub fn register_key(&self, req: &RegisterKeyRequestProperties) -> Result<(), AuthorizationError> { let connection = establish_connection(&self.path); let mut registered_key = models::RegisteredKey { fingerprint: req.fingerprint.clone(), @@ -105,16 +106,36 @@ impl LocalDatabase { pin_policy: None, attestation_certificate: None, attestation_intermediate: None, + auth_data: None, + auth_data_signature: None, + aaguid: None, + challenge: None, + alg: None, + application: None, }; - if let Some(attestation) = &req.attestation { - registered_key.firmware = Some(attestation.firmware.clone()); - registered_key.hsm_serial = Some(attestation.serial.to_string()); - registered_key.touch_policy = Some(attestation.touch_policy.to_string()); - registered_key.pin_policy = Some(attestation.pin_policy.to_string()); - registered_key.attestation_certificate = Some(hex::encode(&attestation.certificate)); - registered_key.attestation_intermediate = Some(hex::encode(&attestation.intermediate)); - } + match &req.attestation { + Some(KeyAttestation::Piv(attestation)) => { + registered_key.firmware = Some(attestation.firmware.clone()); + registered_key.hsm_serial = Some(attestation.serial.to_string()); + registered_key.touch_policy = Some(attestation.touch_policy.to_string()); + registered_key.pin_policy = Some(attestation.pin_policy.to_string()); + registered_key.attestation_certificate = Some(hex::encode(&attestation.certificate)); + registered_key.attestation_intermediate = Some(hex::encode(&attestation.intermediate)); + }, + Some(KeyAttestation::U2f(attestation)) => { + registered_key.firmware = Some(attestation.firmware.clone()); + registered_key.attestation_intermediate = Some(hex::encode(&attestation.intermediate)); + registered_key.auth_data = Some(hex::encode(&attestation.auth_data)); + registered_key.auth_data_signature = Some(hex::encode(&attestation.auth_data_signature)); + registered_key.aaguid = Some(hex::encode(&attestation.aaguid)); + registered_key.challenge = Some(hex::encode(&attestation.challenge)); + registered_key.alg = Some(attestation.alg); + registered_key.application = Some(hex::encode(&attestation.application)); + + } + _ => {}, + }; let result = { use schema::registered_keys::dsl::*; @@ -124,8 +145,8 @@ impl LocalDatabase { }; match result { - Ok(_) => Ok(true), - Err(_) => Ok(false), + Ok(_) => Ok(()), + Err(e) => Err(AuthorizationError::DatabaseError(format!("{}", e))), } } } diff --git a/rustica/src/auth/database/models.rs b/rustica/src/auth/database/models.rs index 60ea0e1..c75a61c 100644 --- a/rustica/src/auth/database/models.rs +++ b/rustica/src/auth/database/models.rs @@ -41,4 +41,10 @@ pub struct RegisteredKey { pub firmware: Option, pub attestation_certificate: Option, pub attestation_intermediate: Option, + pub auth_data: Option, + pub auth_data_signature: Option, + pub aaguid: Option, + pub challenge: Option, + pub alg: Option, + pub application: Option, } \ No newline at end of file diff --git a/rustica/src/auth/database/schema.rs b/rustica/src/auth/database/schema.rs index 0cd6426..54e7ede 100644 --- a/rustica/src/auth/database/schema.rs +++ b/rustica/src/auth/database/schema.rs @@ -60,6 +60,12 @@ table! { firmware -> Nullable, attestation_certificate -> Nullable, attestation_intermediate -> Nullable, + auth_data -> Nullable, + auth_data_signature -> Nullable, + aaguid -> Nullable, + challenge -> Nullable, + alg -> Nullable, + application -> Nullable, } } diff --git a/rustica/src/auth/external.rs b/rustica/src/auth/external.rs index c8dd581..0604cea 100644 --- a/rustica/src/auth/external.rs +++ b/rustica/src/auth/external.rs @@ -8,6 +8,7 @@ use super::{ Authorization, AuthorizationError, AuthorizationRequestProperties, + KeyAttestation, RegisterKeyRequestProperties, }; use std::collections::HashMap; @@ -57,8 +58,10 @@ impl AuthServer { error!("Could not open a channel to the authorization server: {}", e); return Err(AuthorizationError::AuthorizerError); }, - // TODO: @obelisk handle these TLS unwraps - }.tls_config(tls).unwrap().connect().await.unwrap(); + }.tls_config(tls) + .map_err(|_| AuthorizationError::ConnectionFailure)? + .connect().await + .map_err(|_| AuthorizationError::ConnectionFailure)?; let mut client = AuthorClient::new(channel); let response = client.authorize(request).await; @@ -107,18 +110,34 @@ impl AuthServer { }) } - pub async fn register_key(&self, req: &RegisterKeyRequestProperties) -> Result { + pub async fn register_key(&self, req: &RegisterKeyRequestProperties) -> Result<(), AuthorizationError> { let mut identities = HashMap::new(); identities.insert(String::from("requester_ip"), req.requester_ip.clone()); identities.insert(String::from("key_fingerprint"), req.fingerprint.clone()); identities.insert(String::from("mtls_identities"), req.mtls_identities.join(",")); let mut identity_data = HashMap::new(); - identity_data.insert(String::from("type"), String::from("ssh_key")); - if let Some(attestation) = &req.attestation { - identity_data.insert(String::from("certificate"), hex::encode(&attestation.certificate)); - identity_data.insert(String::from("intermediate_certificate"), hex::encode(&attestation.intermediate)); - } + + match &req.attestation { + Some(KeyAttestation::Piv(attestation)) => { + identity_data.insert(String::from("type"), String::from("ssh_key")); + identity_data.insert(String::from("certificate"), hex::encode(&attestation.certificate)); + identity_data.insert(String::from("intermediate_certificate"), hex::encode(&attestation.intermediate)); + }, + Some(KeyAttestation::U2f(attestation)) => { + identity_data.insert(String::from("type"), String::from("u2f_ssh_key")); + identity_data.insert(String::from("auth_data"), hex::encode(&attestation.auth_data)); + identity_data.insert(String::from("auth_data_signature"), hex::encode(&attestation.auth_data_signature)); + identity_data.insert(String::from("intermediate_certificate"), hex::encode(&attestation.intermediate)); + identity_data.insert(String::from("challenge"), hex::encode(&attestation.challenge)); + identity_data.insert(String::from("application"), hex::encode(&attestation.application)); + identity_data.insert(String::from("alg"), attestation.alg.to_string()); + identity_data.insert(String::from("aaguid"), attestation.aaguid.clone()); + }, + None => { + identity_data.insert(String::from("type"), String::from("ssh_key")); + }, + }; let request = tonic::Request::new(AddIdentityDataRequest { identities, @@ -135,19 +154,21 @@ impl AuthServer { Ok(c) => c, Err(e) => { error!("Could not open a channel to the authorization server: {}", e); - return Err(()); + return Err(AuthorizationError::ConnectionFailure); }, - // TODO: @obelisk handle these TLS unwraps - }.tls_config(tls).unwrap().connect().await.unwrap(); + }.tls_config(tls) + .map_err(|_| AuthorizationError::ConnectionFailure)? + .connect().await + .map_err(|_| AuthorizationError::ConnectionFailure)?; let mut client = AuthorClient::new(channel); let response = client.add_identity_data(request).await; match response { - Ok(_) => Ok(true), + Ok(_) => Ok(()), Err(e) => { error!("Server returned error: {}", e); - Ok(false) + Err(AuthorizationError::ExternalError(format!("{}", e))) }, } } diff --git a/rustica/src/auth/mod.rs b/rustica/src/auth/mod.rs index 21a25c7..2a8e1c6 100644 --- a/rustica/src/auth/mod.rs +++ b/rustica/src/auth/mod.rs @@ -16,6 +16,23 @@ pub enum AuthorizationError { CertType, NotAuthorized, AuthorizerError, + ConnectionFailure, + #[allow(dead_code)] + DatabaseError(String), + ExternalError(String), +} + +impl std::fmt::Display for AuthorizationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthorizationError::CertType => write!(f, "Not authorized for this certificate type"), + AuthorizationError::NotAuthorized => write!(f, "Not authorized for this certificate type"), + AuthorizationError::AuthorizerError => write!(f, "Not authorized for this certificate type"), + AuthorizationError::ConnectionFailure => write!(f, "Not authorized for this certificate type"), + AuthorizationError::DatabaseError(ref m) => write!(f, "Database error: {}", m), + AuthorizationError::ExternalError(ref m) => write!(f, "{}", m), + } + } } #[derive(Deserialize)] @@ -72,7 +89,7 @@ impl AuthorizationMechanism { } } - pub async fn register_key(&self, register_properties: &RegisterKeyRequestProperties) -> Result { + pub async fn register_key(&self, register_properties: &RegisterKeyRequestProperties) -> Result<(), AuthorizationError> { match &self { #[cfg(feature = "local-db")] AuthorizationMechanism::Local(local) => local.register_key(register_properties), diff --git a/rustica/src/config.rs b/rustica/src/config.rs index b00d110..f1efcaf 100644 --- a/rustica/src/config.rs +++ b/rustica/src/config.rs @@ -3,7 +3,10 @@ use crate::logging::{Log, LoggingConfiguration}; use crate::server::RusticaServer; use crate::signing::SigningConfiguration; -use clap::{App, Arg}; +use clap::{ + Arg, + Command, +}; use crossbeam_channel::{unbounded, Receiver}; @@ -13,6 +16,11 @@ use serde::Deserialize; use std::convert::TryInto; use std::net::SocketAddr; +use sshcerts::{ + ssh::KeyTypeKind, + PrivateKey, +}; + #[derive(Deserialize)] pub struct Configuration { @@ -23,6 +31,7 @@ pub struct Configuration { pub authorization: AuthorizationConfiguration, pub signing: SigningConfiguration, pub require_rustica_proof: bool, + pub require_attestation_chain: bool, pub logging: LoggingConfiguration, } @@ -79,7 +88,7 @@ impl std::fmt::Debug for ConfigurationError { pub async fn configure() -> Result { - let matches = App::new("Rustica") + let matches = Command::new("Rustica") .version(env!("CARGO_PKG_VERSION")) .author("Mitchell Grenier ") .about("Rustica is a Yubikey backed SSHCA") @@ -125,13 +134,16 @@ pub async fn configure() -> Result { let rng = rand::SystemRandom::new(); let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rng).unwrap(); + let challenge_key = PrivateKey::new(KeyTypeKind::Ed25519, "RusticaChallengeKey").unwrap(); let server = RusticaServer { log_sender, hmac_key, + challenge_key, authorizer, signer, require_rustica_proof: config.require_rustica_proof, + require_attestation_chain: config.require_attestation_chain, }; Ok(RusticaSettings { diff --git a/rustica/src/error.rs b/rustica/src/error.rs index 5b066f1..1a80427 100644 --- a/rustica/src/error.rs +++ b/rustica/src/error.rs @@ -5,7 +5,9 @@ pub enum RusticaServerError { Success = 0, TimeExpired = 1, BadChallenge = 2, + #[allow(dead_code)] InvalidKey = 3, + #[allow(dead_code)] UnsupportedKeyType = 4, BadCertOptions = 5, NotAuthorized = 6, @@ -18,7 +20,7 @@ impl From for RusticaServerError { match e { AuthorizationError::CertType => RusticaServerError::BadCertOptions, AuthorizationError::NotAuthorized => RusticaServerError::NotAuthorized, - AuthorizationError::AuthorizerError => RusticaServerError::Unknown, + _ => RusticaServerError::Unknown, } } } \ No newline at end of file diff --git a/rustica/src/key.rs b/rustica/src/key.rs index d7cb8da..f7f73b3 100644 --- a/rustica/src/key.rs +++ b/rustica/src/key.rs @@ -8,7 +8,7 @@ pub struct Key { } #[derive(Debug)] -pub struct KeyAttestation { +pub struct PIVAttestation { pub pin_policy: PinPolicy, pub touch_policy: TouchPolicy, pub serial: u64, @@ -17,6 +17,24 @@ pub struct KeyAttestation { pub intermediate: Vec, } +#[derive(Debug)] +pub struct U2fAttestation { + pub aaguid: String, + pub firmware: String, + pub auth_data: Vec, + pub auth_data_signature: Vec, + pub intermediate: Vec, + pub challenge: Vec, + pub application: Vec, + pub alg: i32, +} + +#[derive(Debug)] +pub enum KeyAttestation { + Piv(PIVAttestation), + U2f(U2fAttestation), +} + #[derive(Debug, PartialEq)] pub enum TouchPolicy { Never, diff --git a/rustica/src/logging/influx.rs b/rustica/src/logging/influx.rs index dca032f..b39afe3 100644 --- a/rustica/src/logging/influx.rs +++ b/rustica/src/logging/influx.rs @@ -3,7 +3,7 @@ use super::{Log, LoggingError, RusticaLogger, WrappedLog}; use influxdb::{Client, Timestamp}; use influxdb::InfluxDbWriteable; -use tokio::runtime::Runtime; +use tokio::runtime::Handle; use serde::Deserialize; @@ -20,17 +20,17 @@ pub struct Config { pub struct InfluxLogger { client: Client, - runtime: Runtime, + runtime: Handle, dataset: String, } impl InfluxLogger { /// Create a new InfluxDB logger from the provided configuration - pub fn new(config: Config) -> Self { + pub fn new(config: Config, handle: Handle) -> Self { Self { client: Client::new(config.address, config.database).with_auth(config.user, config.password), - runtime: Runtime::new().unwrap(), + runtime: handle, dataset: config.dataset, } } @@ -64,6 +64,7 @@ impl RusticaLogger for InfluxLogger { }); } Log::KeyRegistered(_kr) => (), + Log::KeyRegistrationFailure(_krf) => (), Log::InternalMessage(_im) => (), Log::Heartbeat(_) => (), } diff --git a/rustica/src/logging/mod.rs b/rustica/src/logging/mod.rs index 43cdecd..fd3bd08 100644 --- a/rustica/src/logging/mod.rs +++ b/rustica/src/logging/mod.rs @@ -2,11 +2,16 @@ mod influx; #[cfg(feature = "splunk")] mod splunk; +#[cfg(feature = "webhook")] +mod webhook; + mod stdout; use crossbeam_channel::{Receiver, RecvTimeoutError}; use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "influx", feature = "splunk", feature = "webhook"))] +use tokio::runtime::Runtime; use std::collections::HashMap; use std::time::Duration; @@ -54,13 +59,20 @@ pub struct CertificateIssued { /// Issued when a new key is registered with the service #[derive(Serialize)] -pub struct KeyRegistered { +pub struct KeyInfo { /// The fingerprint of a related key pub fingerprint: String, /// The MTLS identities of registree pub mtls_identities: Vec, } +/// Issued when a new key is registered with the service +#[derive(Serialize)] +pub struct KeyRegistrationFailure { + pub key_info: KeyInfo, + pub message: String, +} + /// Issued when errors or notable events occur within the system #[derive(Serialize)] pub struct InternalMessage { @@ -80,7 +92,11 @@ pub enum Log { /// A user has registered a new key with the Rustica system. This is /// emitted even if Rustica is not storing these keys locally and is /// only forwarding them on to an authorization service. - KeyRegistered(KeyRegistered), + KeyRegistered(KeyInfo), + /// When a user tries to register a new key but it fails for any reason. + /// This could be due to an external authorizor denying (again for any reason + /// it sees fit) or attestation/database errors. + KeyRegistrationFailure(KeyRegistrationFailure), /// Used for relaying status messages to a logging backend. Rustica errors /// or failures send messages of this type. InternalMessage(InternalMessage), @@ -111,7 +127,7 @@ pub struct LoggingConfiguration { /// that there are multiple Rustica instances in a single logging /// environment. identifier: Option, - /// If logs are received after this many seconds, the system will send an + /// If logs aren't received after this many seconds, the system will send an /// empty heartbeat log to the logging systems to signal it is still up /// and healthy. heartbeat_interval: Option, @@ -128,6 +144,11 @@ pub struct LoggingConfiguration { /// information on configuring this logger. #[cfg(feature = "splunk")] splunk: Option, + /// Log JSON to a POST endpoint. This is used for generic logging systems + /// so it's easy to operate on Rustica events. It's likely in future the + /// Splunk logger code will be a specific instantiation of this. + #[cfg(feature = "webhook")] + webhook: Option, } #[derive(Debug)] @@ -154,6 +175,8 @@ trait RusticaLogger { /// a noop and will not actually be sent to the backend (or logged to the /// screen). pub fn start_logging_thread(config: LoggingConfiguration, log_receiver: Receiver) { + #[cfg(any(feature = "influx", feature = "splunk", feature = "webhook"))] + let runtime = Runtime::new().unwrap(); let heartbeat_interval = config.heartbeat_interval.unwrap_or(300); // Configure the different loggers let stdout_logger = match config.stdout { @@ -171,7 +194,7 @@ pub fn start_logging_thread(config: LoggingConfiguration, log_receiver: Receiver let influx_logger = match config.influx { Some(config) => { println!("Configured logger: influx"); - Some(influx::InfluxLogger::new(config)) + Some(influx::InfluxLogger::new(config, runtime.handle().clone())) }, None => None, }; @@ -180,7 +203,16 @@ pub fn start_logging_thread(config: LoggingConfiguration, log_receiver: Receiver let splunk_logger = match config.splunk { Some(config) => { println!("Configured logger: splunk"); - Some(splunk::SplunkLogger::new(config)) + Some(splunk::SplunkLogger::new(config, runtime.handle().clone())) + }, + None => None, + }; + + #[cfg(feature = "webhook")] + let webhook_logger = match config.webhook { + Some(config) => { + println!("Configured logger: webhook"); + Some(webhook::WebhookLogger::new(config, runtime.handle().clone())) }, None => None, }; @@ -215,6 +247,13 @@ pub fn start_logging_thread(config: LoggingConfiguration, log_receiver: Receiver error!("Could not send logs to Splunk"); } } + + #[cfg(feature = "webhook")] + if let Some(logger) = &webhook_logger { + if let Err(_) = logger.send_log(&log) { + error!("Could not send logs to webhook"); + } + } } error!("Logging thread has gone away."); diff --git a/rustica/src/logging/splunk.rs b/rustica/src/logging/splunk.rs index e0cbed2..98fa505 100644 --- a/rustica/src/logging/splunk.rs +++ b/rustica/src/logging/splunk.rs @@ -5,7 +5,7 @@ use reqwest; use serde::{Deserialize, Serialize}; use std::time::Duration; -use tokio::runtime::Runtime; +use tokio::runtime::Handle; /// The struct that defines the Splunk specific configuration of the logging /// service. @@ -20,7 +20,7 @@ pub struct Config { /// `Config` struct. pub struct SplunkLogger { /// A tokio runtime to send logs on - runtime: Runtime, + runtime: Handle, /// A reqwest client configured with the Splunk endpoint and authentication client: reqwest::Client, /// An API token to send with our logs for authentication @@ -45,7 +45,7 @@ impl SplunkLogger { /// Implement the new function for the Splunk logger. This converts /// the configuration struct into a type that can handle sending /// logs directly to a Splunk HEC endpoint. - pub fn new(config: Config) -> Self { + pub fn new(config: Config, handle: Handle) -> Self { // I don't think this can fail with our settings so we do an unwrap let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) @@ -53,7 +53,7 @@ impl SplunkLogger { .build().unwrap(); Self { - runtime: Runtime::new().unwrap(), + runtime: handle, client, token: config.token.clone(), url: config.url.clone(), diff --git a/rustica/src/logging/stdout.rs b/rustica/src/logging/stdout.rs index dcc978d..ca3d0a0 100644 --- a/rustica/src/logging/stdout.rs +++ b/rustica/src/logging/stdout.rs @@ -29,7 +29,8 @@ impl RusticaLogger for StdoutLogger { ci.valid_before, ) } - Log::KeyRegistered(kr) => info!("Key registered with fingerprint: [{}] Identified by: [{}]", kr.fingerprint, kr.mtls_identities.join(", ")), + Log::KeyRegistered(kr) => info!("Key registered: [{}] Identified by: [{}]", kr.fingerprint, kr.mtls_identities.join(", ")), + Log::KeyRegistrationFailure(krf) => info!("Failed to register key: [{}] Identified by: [{}]", krf.key_info.fingerprint, krf.key_info.mtls_identities.join(", ")), Log::InternalMessage(im) => match im.severity { Severity::Error => error!("{}", im.message), Severity::Warning => warn!("{}", im.message), diff --git a/rustica/src/logging/webhook.rs b/rustica/src/logging/webhook.rs new file mode 100644 index 0000000..18faed9 --- /dev/null +++ b/rustica/src/logging/webhook.rs @@ -0,0 +1,79 @@ +use super::{LoggingError, RusticaLogger, WrappedLog}; + +use reqwest; + +use serde::Deserialize; +use std::time::Duration; + +use tokio::runtime::Handle; + +/// The struct that defines the Webhook specific configuration of the logging +/// service. +#[derive(Deserialize)] +pub struct Config { + pub auth_header: Option, + pub url: String, + pub timeout: u8, +} + +/// The specific logger that is configured from the `Config` struct. +pub struct WebhookLogger { + /// A tokio runtime to send logs on + runtime: Handle, + /// A reqwest client configured with the Splunk endpoint and authentication + client: reqwest::Client, + /// The configuration struct + config: Config, +} + + +impl WebhookLogger { + /// Implement the new function for the Splunk logger. This converts + /// the configuration struct into a type that can handle sending + /// logs directly to a Splunk HEC endpoint. + pub fn new(config: Config, handle: Handle) -> Self { + // I don't think this can fail with our settings so we do an unwrap + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(config.timeout.into())) + .build().unwrap(); + + Self { + runtime: handle, + client, + config, + } + } +} + +impl RusticaLogger for WebhookLogger { + /// Send a log to the webhook. Sending a log + /// will not block sending logs to other services (like stdout) but it + /// does mean we cannot return a proper LoggingError to the caller since + /// we cannot wait for it to complete. + fn send_log(&self, log: &WrappedLog) -> Result<(), LoggingError> { + let data = match serde_json::to_string(&log) { + Ok(json) => json, + Err(e) => return Err(LoggingError::SerializationError(e.to_string())) + }; + + let res = self.client.post(&self.config.url) + .header("Content-Type", "application/x-www-form-urlencoded") + .header("Content-Length", data.len()) + .body(data); + + let res = if let Some(auth) = &self.config.auth_header { + res.header("Authorization", auth) + } else { + res + }; + + self.runtime.spawn(async move { + match res.send().await { + Ok(_) => (), + Err(e) => error!("Could not log to webhook: {}", e.to_string()), + }; + }); + + Ok(()) + } +} \ No newline at end of file diff --git a/rustica/src/main.rs b/rustica/src/main.rs index fcad029..3b73a16 100644 --- a/rustica/src/main.rs +++ b/rustica/src/main.rs @@ -13,7 +13,7 @@ mod logging; mod server; mod signing; mod utils; -mod yubikey; +mod verification; use rustica::rustica_server::{RusticaServer as GRPCRusticaServer}; use sshcerts::ssh::CertType; diff --git a/rustica/src/server.rs b/rustica/src/server.rs index 9ce872b..a1f753a 100644 --- a/rustica/src/server.rs +++ b/rustica/src/server.rs @@ -1,6 +1,6 @@ use crate::auth::{AuthorizationMechanism, AuthorizationRequestProperties, RegisterKeyRequestProperties}; use crate::error::RusticaServerError; -use crate::logging::{CertificateIssued, KeyRegistered, InternalMessage, Log, Severity}; +use crate::logging::{CertificateIssued, KeyInfo, KeyRegistrationFailure, InternalMessage, Log, Severity}; use crate::rustica::{ CertificateRequest, CertificateResponse, @@ -9,19 +9,26 @@ use crate::rustica::{ ChallengeResponse, RegisterKeyRequest, RegisterKeyResponse, + RegisterU2fKeyRequest, + RegisterU2fKeyResponse, rustica_server::Rustica, }; use crate::signing::{SigningMechanism}; use crate::utils::build_login_script; -use crate::yubikey::verify_certificate_chain; +use crate::verification::{ + verify_piv_certificate_chain, + verify_u2f_certificate_chain, +}; use crossbeam_channel::Sender; use sshcerts::ssh::{ - CertType, Certificate, CurveKind, PublicKey as SSHPublicKey, PublicKeyKind as SSHPublicKeyKind + CertType, + Certificate, + PrivateKey, + PublicKey, }; -use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, ED25519}; use ring::hmac; use std::{ sync::Arc, @@ -37,9 +44,11 @@ use x509_parser::der_parser::oid; pub struct RusticaServer { pub log_sender: Sender, pub hmac_key: hmac::Key, + pub challenge_key: PrivateKey, pub authorizer: AuthorizationMechanism, pub signer: SigningMechanism, pub require_rustica_proof: bool, + pub require_attestation_chain: bool, } /// Macro for simplifying sending error logs to the Rustica logging system. @@ -109,9 +118,10 @@ fn extract_certificate_identities(peer_certs: &Arc>) -> Re /// Validates a request passes all the following checks in this order: /// - Validate the peer certs are the way we expect /// - Validate Time is not expired -/// - Validate Mac /// - Validate Signature -fn validate_request(hmac_key: &ring::hmac::Key, peer_certs: &Arc>, challenge: &Challenge, check_signature: bool) -> Result<(SSHPublicKey, Vec), RusticaServerError> { +/// - Validate HMAC +/// - Validate certificate parameters +fn validate_request(srv: &RusticaServer, hmac_key: &ring::hmac::Key, peer_certs: &Arc>, challenge: &Challenge, check_signature: bool) -> Result<(PublicKey, Vec), RusticaServerError> { let mtls_identities = extract_certificate_identities(peer_certs)?; // Get request time, and current time. Any issue causes request to fail @@ -120,62 +130,97 @@ fn validate_request(hmac_key: &ring::hmac::Key, peer_certs: &Arc return Err(RusticaServerError::Unknown) }; + // This is our operational window. A user must confirm they control the + // the private key within this window or else we will kick out and make + // them start again. This is so short because we don't want people to + // be able to "buffer" requests, where they presign them and then use + // them later. Admittedly, the period set here is exceedingly short but in + // practice it has not been too much of an issue. if (time - request_time) > 5 { + rustica_warning!(srv, format!("Expired challenge received from: {}", mtls_identities.join(","))); return Err(RusticaServerError::TimeExpired); } + // Since we need to parse a certificate which is not signed by us, we + // cannot validate integrity before taking an expensive parsing step. + // To prevent a malicious host serving us an enormous certificate that + // takes significant time to parse, we immiediately bail if it's much + // larger than we expect. + if challenge.challenge.len() > 1024 { + rustica_warning!(srv, format!("Received a certificate that is far too large from from: {}", mtls_identities.join(","))); + return Err(RusticaServerError::Unknown); + } + + // This step validates the signature on the certificate. If a user tries + // a malicious certificate which contains the correct public key but an + // invalid signature, that is caught here. + let parsed_certificate = Certificate::from_string(&challenge.challenge).map_err(|_| { + rustica_warning!(srv, format!("Received a bad certificate from: {}", mtls_identities.join(","))); + RusticaServerError::BadChallenge + })?; + + let hmac_challenge = &parsed_certificate.key_id; let hmac_verification = format!("{}-{}", request_time, challenge.pubkey); - let decoded_challenge = match hex::decode(&challenge.challenge) { - Ok(dc) => dc, - Err(_) => return Err(RusticaServerError::BadChallenge), - }; + let decoded_challenge = hex::decode(&hmac_challenge).map_err(|_| RusticaServerError::BadChallenge)?; if hmac::verify(hmac_key, hmac_verification.as_bytes(), &decoded_challenge).is_err() { - error!("Received a bad challenge from: {}", mtls_identities.join(",")); + rustica_warning!(srv, format!("Received a bad challenge from: {}", mtls_identities.join(","))); return Err(RusticaServerError::BadChallenge); } - // Request integrity confirmed, continue parsing knowing it has - // not been replayed significantly in time or its data tampered with since - // the initial request. - let ssh_pubkey = match SSHPublicKey::from_string(&challenge.pubkey) { - Ok(sshpk) => sshpk, - Err(_) => return Err(RusticaServerError::InvalidKey), - }; - + // This should never fail as the HMAC has passed so this cannot have been + // tampered with. It could only fail if we gave it a bad public key to + // start with. We check it for completeness. + let hmac_ssh_pubkey = PublicKey::from_string(&challenge.pubkey).map_err(|_| { + rustica_error!(srv, format!("Public key was invalid when negotiating with [{}]. Public key: [{}]", mtls_identities.join(","), &challenge.pubkey)); + RusticaServerError::BadChallenge + })?; + + // This functionality exists because when user certificates are FIDO or + // Yubikey PIV backed, SSHing into a remote host requires two taps: the + // first for this check, and then a second for the server being connected + // to. This check was made optional because in the event a user is + // compromised, there is still a requirement for physical interaction + // during the final step of the connection. The double tap is also + // confusing and annoying to some users. + // + // The benefit of enabling this is that a compromised host cannot fetch + // certificates to see what permissions they might be able to use after + // waiting for a user to initiate a connection themselves. if !check_signature { - return Ok((ssh_pubkey, mtls_identities)) + // Do an extra sanity check here that the certificate we received was signed by us + if parsed_certificate.signature_key.fingerprint().hash != srv.challenge_key.pubkey.fingerprint().hash { + rustica_warning!(srv, format!("Received an incorrect certificate from {}", mtls_identities.join(","))); + return Err(RusticaServerError::BadChallenge); + } + return Ok((hmac_ssh_pubkey, mtls_identities)) } - let result = match &ssh_pubkey.kind { - SSHPublicKeyKind::Ecdsa(key) => { - let (pubkey, alg) = match key.curve.kind { - CurveKind::Nistp256 => (key, &ECDSA_P256_SHA256_ASN1), - CurveKind::Nistp384 => (key, &ECDSA_P384_SHA384_ASN1), - _ => return Err(RusticaServerError::UnsupportedKeyType), - }; - - UnparsedPublicKey::new(alg, &pubkey.key).verify( - &hex::decode(&challenge.challenge).unwrap(), - &hex::decode(&challenge.challenge_signature).unwrap(), - ) - }, - SSHPublicKeyKind::Ed25519(key) => { - let peer_public_key = UnparsedPublicKey::new(&ED25519, &key.key); - peer_public_key.verify( - &hex::decode(&challenge.challenge).unwrap(), - &hex::decode(&challenge.challenge_signature).unwrap() - ) - }, - _ => return Err(RusticaServerError::UnsupportedKeyType), - }; + // We now know the request has not been replayed significantly in time. + // We also know the certificate is valid as it parsed. Now we need to + // check that the signature on the certificate is from the key we + // expect. - if result.is_err() { - error!("Could not verify signature on challenge: {}", mtls_identities.join(",")); - return Err(RusticaServerError::BadChallenge) + // We expect the client to resign the certificate we sent it with the + // key they are proving ownership of. + if parsed_certificate.key.fingerprint().hash != parsed_certificate.signature_key.fingerprint().hash { + rustica_warning!(srv, format!("User key did not equal CA key when talking to: {}", mtls_identities.join(","))); + return Err(RusticaServerError::BadChallenge); + } + + // We check that the user key in the certificate is the key that they + // should be proving ownership of. This is valid because the challenge + // pubkey was proved to be untamped with using the hmac. + if parsed_certificate.key.fingerprint().hash != hmac_ssh_pubkey.fingerprint().hash { + rustica_warning!(srv, format!("User key did not equal HMAC validated public key: {}", mtls_identities.join(","))); + return Err(RusticaServerError::BadChallenge); } - Ok((ssh_pubkey, mtls_identities)) + // We've proven user_fp == signing_fp == hmac_validated_fp. To get to + // this point the user must have received our challenge certificate + // containing our HMAC challenge, resigned it with their key, and + // sent it back for which it passed all checks. + Ok((hmac_ssh_pubkey, mtls_identities)) } #[tonic::async_trait] @@ -192,7 +237,7 @@ impl Rustica for RusticaServer { Err(_) => return Err(Status::permission_denied("")), }; - let ssh_pubkey = match SSHPublicKey::from_string(&request.pubkey) { + let ssh_pubkey = match PublicKey::from_string(&request.pubkey) { Ok(sshpk) => sshpk, Err(_) => return Err(Status::permission_denied("")), }; @@ -208,9 +253,17 @@ impl Rustica for RusticaServer { let challenge = format!("{}-{}", timestamp, pubkey); let tag = hmac::sign(&self.hmac_key, challenge.as_bytes()); + // Build an SSHCertificate as a challenge + let cert = Certificate::builder(&ssh_pubkey, CertType::Host, &self.challenge_key.pubkey).unwrap() + .serial(0xFEFEFEFEFEFEFEFE) + .key_id(hex::encode(tag)) + .valid_after(0) + .valid_before(0) + .sign(&self.challenge_key).unwrap(); + let reply = ChallengeResponse { time: timestamp, - challenge: hex::encode(tag), + challenge: format!("{}", cert), no_signature_required: !self.require_rustica_proof, }; @@ -228,7 +281,7 @@ impl Rustica for RusticaServer { _ => return Ok(create_response(RusticaServerError::BadRequest)), }; - let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, challenge, self.require_rustica_proof) { + let (ssh_pubkey, mtls_identities) = match validate_request(self, &self.hmac_key, &peer, challenge, self.require_rustica_proof) { Ok((ssh_pk, idents)) => (ssh_pk, idents), Err(e) => return Ok(create_response(e)), }; @@ -318,6 +371,8 @@ impl Rustica for RusticaServer { // Sanity check that we can parse the cert we just generated if let Err(e) = Certificate::from_string(&serialized) { + debug!("Offending Public Key: {}", ssh_pubkey); + debug!("Offending certificate: {}", serialized); rustica_error!(self, format!("Couldn't deserialize certificate: {}", e)); return Ok(create_response(RusticaServerError::BadCertOptions)); } @@ -341,8 +396,8 @@ impl Rustica for RusticaServer { certificate_type: req_cert_type.to_string(), mtls_identities, principals: authorization.principals, - extensions: authorization.extensions.into(), - critical_options: critical_options.into(), + extensions: authorization.extensions, + critical_options, valid_after: authorization.valid_after, valid_before: authorization.valid_before, })).unwrap(); @@ -351,7 +406,6 @@ impl Rustica for RusticaServer { } async fn register_key(&self, request: Request) -> Result, Status> { - debug!("Received register key request: {:?}", request); let requester_ip = match request.remote_addr() { Some(x) => x.to_string(), None => String::new(), @@ -365,14 +419,27 @@ impl Rustica for RusticaServer { _ => return Err(Status::permission_denied("")), }; - let (ssh_pubkey, mtls_identities) = match validate_request(&self.hmac_key, &peer, challenge, self.require_rustica_proof) { + let (ssh_pubkey, mtls_identities) = match validate_request(self, &self.hmac_key, &peer, challenge, self.require_rustica_proof) { Ok((ssh_pk, idents)) => (ssh_pk, idents), Err(e) => return Err(Status::cancelled(format!("{:?}", e))), }; - let (fingerprint, attestation) = match verify_certificate_chain(&request.certificate, &request.intermediate) { + let (fingerprint, attestation) = match verify_piv_certificate_chain(&request.certificate, &request.intermediate) { Ok(key) => (key.fingerprint, key.attestation), - _ => (ssh_pubkey.fingerprint().hash, None), + Err(_) => if !self.require_attestation_chain { + (ssh_pubkey.fingerprint().hash, None) + } else { + let key_info = KeyInfo { + fingerprint: ssh_pubkey.fingerprint().hash, + mtls_identities, + }; + + self.log_sender.send(Log::KeyRegistrationFailure(KeyRegistrationFailure{ + key_info, + message: "Attempt to register a key with an invalid attestation chain".to_string(), + })).unwrap(); + return Err(Status::unavailable("Could not register a key without valid attestation data")) + }, }; let register_properties = RegisterKeyRequestProperties { @@ -385,19 +452,92 @@ impl Rustica for RusticaServer { let response = self.authorizer.register_key(®ister_properties).await; match response { - Ok(true) => { - self.log_sender.send(Log::KeyRegistered(KeyRegistered { + Ok(_) => { + self.log_sender.send(Log::KeyRegistered(KeyInfo { fingerprint, mtls_identities, })).unwrap(); return Ok(Response::new(RegisterKeyResponse{})) }, - Ok(false) => { - rustica_warning!(self, format!("[{}] could not be registered with the authorizer. Identities: [{}]", fingerprint, mtls_identities.join(", "))); + Err(e) => { + let key_info = KeyInfo { + fingerprint, + mtls_identities, + }; + + self.log_sender.send(Log::KeyRegistrationFailure(KeyRegistrationFailure{ + key_info, + message: e.to_string(), + })).unwrap(); return Err(Status::unavailable("Could not register new key")) }, - Err(_) => { - rustica_error!(self, format!("Authorizer threw error registering fingerprint: [{}] with identities: [{}]", fingerprint, mtls_identities.join(", "))); + } + } + + async fn register_u2f_key(&self, request: Request) -> Result, Status> { + let requester_ip = match request.remote_addr() { + Some(x) => x.to_string(), + None => String::new(), + }; + + let peer = request.peer_certs(); + let request = request.into_inner(); + + let (challenge, peer) = match (&request.challenge, peer) { + (Some(challenge), Some(peer)) => (challenge, peer), + _ => return Err(Status::permission_denied("")), + }; + + let (ssh_pubkey, mtls_identities) = match validate_request(self, &self.hmac_key, &peer, challenge, self.require_rustica_proof) { + Ok((ssh_pk, idents)) => (ssh_pk, idents), + Err(e) => return Err(Status::cancelled(format!("{:?}", e))), + }; + + let (fingerprint, attestation) = match verify_u2f_certificate_chain(&request.auth_data, &request.auth_data_signature, &request.intermediate, request.alg, &request.u2f_challenge, &request.sk_application) { + Ok(key) => (key.fingerprint, key.attestation), + Err(_) => if !self.require_attestation_chain { + (ssh_pubkey.fingerprint().hash, None) + } else { + let key_info = KeyInfo { + fingerprint: ssh_pubkey.fingerprint().hash, + mtls_identities, + }; + + self.log_sender.send(Log::KeyRegistrationFailure(KeyRegistrationFailure{ + key_info, + message: "Attempt to register a key with an invalid attestation chain".to_string(), + })).unwrap(); + return Err(Status::unavailable("Could not register a key without valid attestation data")) + }, + }; + + let register_properties = RegisterKeyRequestProperties { + fingerprint: fingerprint.clone(), + mtls_identities: mtls_identities.clone(), + requester_ip, + attestation, + }; + + let response = self.authorizer.register_key(®ister_properties).await; + + match response { + Ok(_) => { + self.log_sender.send(Log::KeyRegistered(KeyInfo { + fingerprint, + mtls_identities, + })).unwrap(); + return Ok(Response::new(RegisterU2fKeyResponse{})) + }, + Err(e) => { + let key_info = KeyInfo { + fingerprint, + mtls_identities, + }; + + self.log_sender.send(Log::KeyRegistrationFailure(KeyRegistrationFailure{ + key_info, + message: e.to_string(), + })).unwrap(); return Err(Status::unavailable("Could not register new key")) }, } diff --git a/rustica/src/signing/amazon_kms.rs b/rustica/src/signing/amazon_kms.rs index e893448..4879b34 100644 --- a/rustica/src/signing/amazon_kms.rs +++ b/rustica/src/signing/amazon_kms.rs @@ -1,4 +1,9 @@ -use sshcerts::{Certificate, PublicKey, ssh::CertType}; +use sshcerts::{ + Certificate, + PublicKey, + ssh::CertType, + utils::format_signature_for_ssh, +}; use serde::Deserialize; use aws_sdk_kms::{Blob, Client, Credentials, Region}; @@ -113,9 +118,9 @@ impl AmazonKMSSigner { pub async fn sign(&self, cert: Certificate) -> Result { let data = cert.tbs_certificate(); - let (key_id, key_algo) = match &cert.cert_type { - CertType::User => (&self.user_key_id, &self.user_key_signing_algorithm), - CertType::Host => (&self.host_key_id, &self.host_key_signing_algorithm), + let (pubkey, key_id, key_algo) = match &cert.cert_type { + CertType::User => (&self.user_public_key, &self.user_key_id, &self.user_key_signing_algorithm), + CertType::Host => (&self.host_public_key, &self.host_key_id, &self.host_key_signing_algorithm), }; let result = self.client.sign().key_id(key_id).signing_algorithm(key_algo.clone()).message(Blob::new(data)).send().await; @@ -124,6 +129,11 @@ impl AmazonKMSSigner { Err(e) => return Err(SigningError::AccessError(e.to_string())), }; + let signature = match format_signature_for_ssh(pubkey, &signature) { + Some(s) => s, + None => return Err(SigningError::ParsingError), + }; + cert.add_signature(&signature).map_err(|_| SigningError::SigningFailure) } diff --git a/rustica/src/signing/mod.rs b/rustica/src/signing/mod.rs index 01acf96..d1137fd 100644 --- a/rustica/src/signing/mod.rs +++ b/rustica/src/signing/mod.rs @@ -38,7 +38,7 @@ pub struct SigningConfiguration { pub amazonkms: Option, } -/// A `SigningConfiguration` can be concerted into a `SigningMechanism` to +/// A `SigningConfiguration` can be coerced into a `SigningMechanism` to /// handle the signing operations as well as other convenience functions /// such as fetching public keys or printing info about how signing is ///configured. @@ -64,13 +64,19 @@ pub enum SigningError { /// sign the provided certificate. This could be because of a key /// incompatiblity or a corrupted private key. SigningFailure, + /// ParsingError represents any error that occurs from unexpected data + /// not being able to be parsed correctly, or code that fails to parse + /// expected data + #[allow(dead_code)] + ParsingError, } impl std::fmt::Display for SigningError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - SigningError::AccessError(e) => write!(f, "Could access the private key material: {}", e), + SigningError::AccessError(e) => write!(f, "Could not access the private key material: {}", e), SigningError::SigningFailure => write!(f, "The signing operation on the provided certificate failed"), + SigningError::ParsingError => write!(f, "The signature could not be parsed"), } } } diff --git a/rustica/src/verification.rs b/rustica/src/verification.rs new file mode 100644 index 0000000..c5d9331 --- /dev/null +++ b/rustica/src/verification.rs @@ -0,0 +1,52 @@ +use crate::key::{U2fAttestation, PIVAttestation}; +use crate::key::{Key, KeyAttestation, PinPolicy, TouchPolicy}; + +use crate::error::RusticaServerError; + +use sshcerts::{ + fido::verification::verify_auth_data, + yubikey::verification::verify_certificate_chain, +}; +use std::convert::TryFrom; + + +/// Verify a provided yubikey attestation certification and intermediate +/// certificate are valid against the Yubico attestation Root CA. +pub fn verify_piv_certificate_chain(client: &[u8], intermediate: &[u8]) -> Result { + // Extract the certificate public key and convert to an sshcerts PublicKey + let validated_piv_data = verify_certificate_chain(client, intermediate, None).map_err(|_| RusticaServerError::InvalidKey)?; + + Ok(Key { + fingerprint: validated_piv_data.public_key.fingerprint().hash, + attestation: Some(KeyAttestation::Piv(PIVAttestation { + firmware: validated_piv_data.firmware, + serial: validated_piv_data.serial, + pin_policy: PinPolicy::try_from(validated_piv_data.pin_policy).unwrap(), + touch_policy: TouchPolicy::try_from(validated_piv_data.touch_policy).unwrap(), + certificate: client.to_vec(), + intermediate: intermediate.to_vec(), + })) + }) +} + +/// Verify a provided U2F attestation, signature, and certificate are valid +/// against the Yubico U2F Root CA. +pub fn verify_u2f_certificate_chain(auth_data: &[u8], auth_data_signature: &[u8], intermediate: &[u8], alg: i32, challenge: &[u8], application: &[u8]) -> Result { + let validated_u2f_data = verify_auth_data(auth_data, auth_data_signature, challenge, alg, intermediate, None).map_err(|_| RusticaServerError::InvalidKey)?; + let parsed_application = String::from_utf8(application.to_vec()).map_err(|_| RusticaServerError::InvalidKey)?; + let ssh_public_key = validated_u2f_data.auth_data.ssh_public_key(&parsed_application).map_err(|_| RusticaServerError::InvalidKey)?; + + Ok(Key { + fingerprint: ssh_public_key.fingerprint().hash, + attestation: Some(KeyAttestation::U2f(U2fAttestation { + aaguid: hex::encode(validated_u2f_data.auth_data.aaguid), + firmware: validated_u2f_data.firmware.unwrap_or_else(|| "Unknown".to_string()), + auth_data: auth_data.to_vec(), + auth_data_signature: auth_data_signature.to_vec(), + intermediate: intermediate.to_vec(), + challenge: challenge.to_vec(), + alg, + application: application.to_vec(), + })), + }) +} \ No newline at end of file diff --git a/rustica/src/yubikey.rs b/rustica/src/yubikey.rs deleted file mode 100644 index a77fcca..0000000 --- a/rustica/src/yubikey.rs +++ /dev/null @@ -1,108 +0,0 @@ -use x509_parser::der_parser::oid; -use crate::key::{Key, KeyAttestation, PinPolicy, TouchPolicy}; - -use sshcerts::PublicKey; -use std::convert::TryFrom; -use std::convert::TryInto; -use x509_parser::prelude::*; - - -const ROOT_CA: &[u8] = "-----BEGIN CERTIFICATE----- -MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1 -YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY -DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg -U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2 -cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E -ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq -joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH -BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf -wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet -X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0 -1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s -XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2 -lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d -bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq -Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8 -SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 ------END CERTIFICATE-----".as_bytes(); - -#[derive(Debug)] -pub enum YubikeyValidationError { - ParseError, - ValidationError, -} - -impl From> for YubikeyValidationError { - fn from(_e: x509_parser::nom::Err) -> Self { - YubikeyValidationError::ParseError - } -} - -impl From for YubikeyValidationError { - fn from(_e: x509_parser::error::X509Error) -> Self { - YubikeyValidationError::ValidationError - } -} - -fn build_key(ssh_pubkey: PublicKey, certificate: X509Certificate, client: &[u8], intermediate: &[u8]) -> Key { - let extensions = certificate.extensions(); - - // Find the three things we need: Firmware, Yubikey serial, Usage Policies - let firmware = &extensions[&oid!(1.3.6.1.4.1.41482.3.3)].value; - let serial = &extensions[&oid!(1.3.6.1.4.1.41482.3.7)].value; - let policy = &extensions[&oid!(1.3.6.1.4.1.41482.3.8)].value; - if firmware.len() != 3 || serial.len() > 10 || policy.len() != 2 { - error!("The certificate has an unexpected format"); - Key { - fingerprint: ssh_pubkey.fingerprint().hash, - attestation: None, - } - } else { - let mut serial = vec![0; 8 - (serial.len() - 2)]; - serial.extend_from_slice(&extensions[&oid!(1.3.6.1.4.1.41482.3.7)].value[2..]); - let firmware = format!("{}.{}.{}", firmware[0] as u8, firmware[1] as u8, firmware[2] as u8); - let serial = u64::from_be_bytes(serial.try_into().unwrap()); - let pin_policy = PinPolicy::try_from(policy[0]).unwrap(); - let touch_policy = TouchPolicy::try_from(policy[1]).unwrap(); - Key { - fingerprint: ssh_pubkey.fingerprint().hash, - attestation: Some(KeyAttestation { - firmware, - serial, - pin_policy, - touch_policy, - certificate: client.to_vec(), - intermediate: intermediate.to_vec(), - }) - } - } -} - -/// Verify a provided yubikey attestation certification and intermediate -/// certificate are valid against the Yubico attestation root ca. -pub fn verify_certificate_chain(client: &[u8], intermediate: &[u8]) -> Result { - // Extract the certificate public key and convert to an sshcerts PublicKey - let ssh_pubkey = match sshcerts::x509::extract_ssh_pubkey_from_x509_certificate(client) { - Ok(ssh) => ssh, - Err(_) => return Err(YubikeyValidationError::ParseError), - }; - - // Parse the root ca. This should never fail - let (_, root_ca) = parse_x509_pem(ROOT_CA).unwrap(); - let root_ca = Pem::parse_x509(&root_ca).unwrap(); - - // Parse the certificates - let (_, parsed_intermediate) = parse_x509_certificate(intermediate)?; - let (_, parsed_client) = parse_x509_certificate(client)?; - debug!("Certificates parsed"); - - // Validate that the provided intermediate certificate is signed by the Yubico Attestation Root CA - parsed_intermediate.verify_signature(Some(&root_ca.tbs_certificate.subject_pki))?; - - // Validate that the provided certificate is signed by the intermediate CA - parsed_client.verify_signature(Some(&parsed_intermediate.tbs_certificate.subject_pki))?; - debug!("Certificate providence verified"); - - Ok(build_key(ssh_pubkey, parsed_client, client, intermediate)) -} \ No newline at end of file diff --git a/tests/integration.sh b/tests/integration.sh index 9817aa8..fd5cc2a 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -27,7 +27,7 @@ cd ../.. docker run --name rustica_test_ssh_server -p 2424:22 rustica_test_ssh_server:latest & # Verify that Rustica is not running and that this should fail -if ./target/debug/rustica-agent --config examples/rustica_agent_local.toml -i > /dev/null 2>&1; then +if ./target/debug/rustica-agent --config examples/rustica_agent_local.toml -i > /tmp/rustica_log 2>&1; then echo "FAIL: Some other Rustica instance is running!" exit 1 else @@ -40,10 +40,15 @@ RUSTICA_PID=$! sleep 2 # Test that we can fetch a certificate -if ./target/debug/rustica-agent --config examples/rustica_agent_local.toml -i > /dev/null 2>&1; then +if ./target/debug/rustica-agent --config examples/rustica_agent_local.toml -i > /tmp/rustica_agent_log 2>&1; then echo "PASS: Successfully pulled a certificate from Rustica" else echo "FAIL: Could not pull a certificate from Rustica" + echo "Rustica Log:" + cat /tmp/rustica_log + echo "" + echo "Rustica Agent Log" + cat /tmp/rustica_agent_log cleanup_and_exit 1 fi