From a9fa1e429cdc9e952e0507f20951bbd4dae6c35c Mon Sep 17 00:00:00 2001 From: Philip Tricca Date: Tue, 14 Feb 2023 12:32:36 -0800 Subject: [PATCH] Add crate for prototype that checks our DICE cert chains. --- Cargo.lock | 719 ++++++++++++++++++++++++++++++++---- Cargo.toml | 1 + dice-cert-check/Cargo.toml | 25 ++ dice-cert-check/README.md | 7 + dice-cert-check/src/lib.rs | 359 ++++++++++++++++++ dice-cert-check/src/main.rs | 216 +++++++++++ 6 files changed, 1262 insertions(+), 65 deletions(-) create mode 100644 dice-cert-check/Cargo.toml create mode 100644 dice-cert-check/README.md create mode 100644 dice-cert-check/src/lib.rs create mode 100644 dice-cert-check/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 206be81..3246a56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,16 @@ dependencies = [ "mach 0.1.2", ] +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -32,6 +42,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + [[package]] name = "atty" version = "0.2.14" @@ -43,18 +59,50 @@ dependencies = [ "winapi", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + +[[package]] +name = "base64ct" +version = "1.5.3" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + [[package]] name = "byteorder" version = "1.4.3" @@ -63,9 +111,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -75,9 +123,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.0.29" +version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ "bitflags", "clap_derive", @@ -90,9 +138,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -103,19 +151,114 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" dependencies = [ "os_str_bytes", ] +[[package]] +name = "const-oid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" + +[[package]] +name = "const-oid" +version = "0.10.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" + [[package]] name = "corncobs" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9236877021b66ad90f833d8a73a7acb702b985b64c5986682d9f1f1a184f0fb" +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid 0.9.1", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" +dependencies = [ + "const-oid 0.10.0-pre", + "der_derive", + "flagset", + "pem-rfc7468 0.7.0-pre", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.7.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dice-cert-check" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "const-oid 0.10.0-pre", + "ecdsa", + "env_logger 0.10.0", + "hex", + "log", + "p384", + "pem-rfc7468 0.7.0-pre", + "ring-compat", + "sha2", + "thiserror", + "x509-cert", +] + [[package]] name = "dice-cert-tmpl" version = "0.1.0" @@ -132,7 +275,7 @@ version = "0.1.0" dependencies = [ "clap", "dice-mfg-msgs", - "env_logger", + "env_logger 0.9.3", "log", "pem", "serialport", @@ -152,13 +295,67 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e" +dependencies = [ + "der 0.6.1", + "elliptic-curve", + "rfc6979", + "signature 2.0.0", +] + [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", +] + +[[package]] +name = "ed25519" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf420a7ec85d98495b0c34aa4a58ca117f982ffbece111aeb545160148d7010" +dependencies = [ + "signature 2.0.0", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der 0.6.1", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468 0.6.0", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -174,6 +371,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.2.8" @@ -197,18 +407,66 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -221,11 +479,32 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "libc", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", ] [[package]] @@ -266,9 +545,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", "windows-sys", @@ -276,21 +555,30 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.3.1", "io-lifetimes", "rustix", "windows-sys", ] +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libudev" @@ -364,9 +652,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" @@ -374,21 +668,80 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "p256" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", +] + +[[package]] +name = "p384" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630a4a9b2618348ececfae61a4905f564b817063bf2d66cdfc2ced523fe1d2d4" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ "base64", ] +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" +dependencies = [ + "base64ct 1.5.3 (git+https://github.com/RustCrypto/formats)", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkg-config" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "primeorder" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -415,22 +768,31 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -442,9 +804,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -466,11 +828,56 @@ dependencies = [ "winapi", ] +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ring-compat" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9339924785350cc2a705ee5485c5e748c585b268ee9f9162a770bd91dae4ea" +dependencies = [ + "aead", + "digest", + "ecdsa", + "ed25519 2.1.0", + "generic-array", + "opaque-debug", + "p256", + "p384", + "pkcs8", + "ring", + "signature 2.0.0", +] + [[package]] name = "rustix" -version = "0.36.5" +version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", @@ -486,16 +893,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77cdd38ed8bfe51e53ee991aae0791b94349d0a05cfdecd283835a8a965d4c37" dependencies = [ - "ed25519", + "ed25519 1.5.3", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der 0.6.1", + "generic-array", + "pkcs8", "subtle", "zeroize", ] [[package]] name = "serde" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -511,9 +932,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -537,12 +958,58 @@ dependencies = [ "winapi", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" +dependencies = [ + "base64ct 1.5.3 (git+https://github.com/RustCrypto/formats)", + "der 0.7.0-pre", +] + [[package]] name = "string-error" version = "0.1.0" @@ -563,9 +1030,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -588,18 +1055,50 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "version_check" @@ -607,6 +1106,76 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -640,9 +1209,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -655,45 +1233,56 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "x509-cert" +version = "0.2.0-pre" +source = "git+https://github.com/RustCrypto/formats#9c3ad4366d7f6132d770fe1ead839fbca295cff9" +dependencies = [ + "const-oid 0.10.0-pre", + "der 0.7.0-pre", + "flagset", + "spki 0.7.0-pre", +] [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index 129b3f4..34f7b1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "dice-cert-check", "dice-cert-tmpl", "dice-mfg", "dice-mfg-msgs", diff --git a/dice-cert-check/Cargo.toml b/dice-cert-check/Cargo.toml new file mode 100644 index 0000000..4c79451 --- /dev/null +++ b/dice-cert-check/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "dice-cert-check" +version = "0.1.0" +edition = "2021" + +# NOTE: We're currently using crates from the main development branch of the +# rust crypto formats git repo. We do this because: +# - const-oid doesn't have the OIDs from RFC 8410 in a release yet +# - the latest x509-cert release has a bug that prevents us from consuming +# PEM encoded certs. This has been fixed on the development branch but +# hasn't made it into a release yet. +[dependencies] +anyhow = "1.0.68" +clap = { version = "4.1.4", features = ["derive"] } +const-oid = { git = "https://github.com/RustCrypto/formats" } +ecdsa = "0.15.1" +env_logger = "0.10.0" +hex = "0.4.3" +log = "0.4.17" +p384 = "0.12.0" +pem-rfc7468 = { git = "https://github.com/RustCrypto/formats" } +ring-compat = { version = "0.6.0", features = ["std"] } +sha2 = "0.10.6" +thiserror = "1.0.38" +x509-cert = { git = "https://github.com/RustCrypto/formats", features = ["pem", "std"] } diff --git a/dice-cert-check/README.md b/dice-cert-check/README.md new file mode 100644 index 0000000..b76d964 --- /dev/null +++ b/dice-cert-check/README.md @@ -0,0 +1,7 @@ +This is a crate hosting a prototype validation mechanism for the certificate +hierarchies created by Hubris on boot for DICE keys. We're doing this +validation manually / with new code instead of an existing implementation +like webpki because DICE certs contain at least one component (policy OID or v3 +extension) that, if marked as 'critical', cause webpki to balk as it should. +Similarly webpki requires fields like 'subjectAltName' that we don't need +(yet?) and havne't included for size reasons. diff --git a/dice-cert-check/src/lib.rs b/dice-cert-check/src/lib.rs new file mode 100644 index 0000000..98d5f41 --- /dev/null +++ b/dice-cert-check/src/lib.rs @@ -0,0 +1,359 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use const_oid::{ + db::{ + rfc5912::{ECDSA_WITH_SHA_384, ID_EC_PUBLIC_KEY, SECP_384_R_1}, + rfc8410::ID_ED_25519, + }, + ObjectIdentifier, +}; +use hex::ToHex; +use log::{error, warn}; +use sha2::Digest; +use std::fmt; +use std::result; +use x509_cert::{ + der::{ + self, + asn1::{Any, BitString}, + Encode, Tag, Tagged, + }, + spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}, +}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Signature is not octet aligned")] + BadSignature, + #[error("SPKI has parameters incompatible with Verifier.")] + IncompatibleParams, + #[error("Verifier is not compatible with provided signature")] + IncompatibleSignature, + #[error("Required parameters missing from SPKI.")] + MissingParams, + #[error("Failed to verify signature.")] + P384VerificationFail { + #[from] + source: ecdsa::Error, + }, + #[error("Public key is not octet aligned")] + UnalignedPublicKey, + #[error("SPKI algorithm has parameters where none were expected")] + UnexpectedParams, + #[error("Algorithm not supported")] + UnsupportedAlgorithm, + #[error("Parameter not supported")] + UnsupportedParameter, + #[error("Signature not supported")] + UnsupportedSignature, + #[error("Parameters has unspported tag.")] + UnsupportedTag, + #[error("Unsupported OID in algorith parameter.")] + UnsupportedOid { + #[from] + source: der::Error, + }, + #[error("Wrong key type for verifier")] + WrongAlgorithm, +} + +type Result = result::Result; + +#[derive(Debug, Eq, PartialEq)] +pub enum AlgorithmType { + EcPublicKey, + Ed25519, +} + +impl TryFrom<&ObjectIdentifier> for AlgorithmType { + type Error = Error; + + fn try_from(oid: &ObjectIdentifier) -> result::Result { + match *oid { + ID_EC_PUBLIC_KEY => Ok(AlgorithmType::EcPublicKey), + ID_ED_25519 => Ok(AlgorithmType::Ed25519), + _ => Err(Error::UnsupportedAlgorithm), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ParameterType { + P384, +} + +impl TryFrom<&Any> for ParameterType { + type Error = Error; + + fn try_from(param: &Any) -> result::Result { + match param.tag() { + Tag::ObjectIdentifier => { + let oid: ObjectIdentifier = param.decode_as()?; + match oid { + SECP_384_R_1 => Ok(ParameterType::P384), + _ => Err(Error::UnsupportedParameter), + } + } + _ => Err(Error::UnsupportedTag), + } + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum SignatureType { + EcdsaWithSha384, + Ed25519, +} + +impl TryFrom<&ObjectIdentifier> for SignatureType { + type Error = Error; + + fn try_from(oid: &ObjectIdentifier) -> result::Result { + match *oid { + ECDSA_WITH_SHA_384 => Ok(SignatureType::EcdsaWithSha384), + ID_ED_25519 => Ok(SignatureType::Ed25519), + _ => { + warn!("Unsupported signature w/ oid: {}", oid); + Err(Error::UnsupportedSignature) + } + } + } +} + +pub struct Signature { + pub bytes: Vec, + pub kind: SignatureType, + params: Option, +} + +impl Signature { + pub fn new( + algorithm: &AlgorithmIdentifierOwned, + signature: &BitString, + ) -> Result { + let params = match &algorithm.parameters { + Some(params) => Some(ParameterType::try_from(params)?), + None => None, + }; + + let bytes = match signature.as_bytes() { + // copy DER encoded signature + Some(bytes) => bytes.to_vec(), + // we get None back if the ANS.1 BIT STRING has unused bits + None => Err(Error::BadSignature)?, + }; + + Ok(Self { + bytes, + kind: SignatureType::try_from(&algorithm.oid)?, + params, + }) + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "kind: {:?}, params: {:?}, bytes: {}", + self.kind, + self.params, + self.bytes.encode_hex::(), + ) + } +} + +pub trait Verifier { + fn verify(&self, msg: &[u8], sig: &Signature) -> Result<()>; +} + +pub struct P384Verifier { + verifying_key: p384::ecdsa::VerifyingKey, +} + +impl TryFrom<&SubjectPublicKeyInfoOwned> for P384Verifier { + type Error = Error; + + fn try_from( + spki: &SubjectPublicKeyInfoOwned, + ) -> result::Result { + let params = match &spki.algorithm.parameters { + Some(params) => ParameterType::try_from(params)?, + None => return Err(Error::MissingParams), + }; + + if params != ParameterType::P384 { + warn!("P384Verifier: incompatible algorithm parameters"); + return Err(Error::IncompatibleParams); + } + + use p384::{ecdsa, pkcs8::DecodePublicKey}; + + // the trait `std::error::Error` is not implemented for + // `p384::pkcs8::spki::Error` + // what feature do I need to enable? + // p384 has std enabled by default ... no clue + let verifying_key = + ecdsa::VerifyingKey::from_public_key_der(&spki.to_vec()?) + .expect("p384 VerifyingKey"); + Ok(Self { verifying_key }) + } +} + +impl Verifier for P384Verifier { + fn verify(&self, msg: &[u8], sig: &Signature) -> Result<()> { + if sig.kind != SignatureType::EcdsaWithSha384 { + warn!( + "P384Verifier: incompatible signature algorithm: {:?}", + sig.kind + ); + return Err(Error::IncompatibleSignature); + } + + let digest = sha2::Sha384::digest(msg); + + use p384::ecdsa::{self, signature::hazmat::PrehashVerifier}; + + let signature = ecdsa::Signature::from_der(&sig.bytes)?; + + Ok(self.verifying_key.verify_prehash(&digest, &signature)?) + } +} + +pub struct Ed25519Verifier { + verifying_key: ring_compat::signature::ed25519::VerifyingKey, +} + +impl TryFrom<&SubjectPublicKeyInfoOwned> for Ed25519Verifier { + type Error = Error; + + fn try_from(spki: &SubjectPublicKeyInfoOwned) -> Result { + let algorithm = AlgorithmType::try_from(&spki.algorithm.oid)?; + if algorithm != AlgorithmType::Ed25519 { + return Err(Error::WrongAlgorithm); + } + + if spki.algorithm.parameters.is_some() { + return Err(Error::UnexpectedParams); + } + + use ring_compat::signature::ed25519::VerifyingKey; + + // The ring type behind the VerifyingKey expects the public key + // as the raw bits, not the DER encoded SPKI like the rust crypto + // ecc VerifyingKey. This requires dealing with the ASN.1 BIT + // STRING type and the fact that it may not be byte aligned. + let key_bytes = match spki.subject_public_key.as_bytes() { + Some(b) => b, + None => return Err(Error::UnalignedPublicKey), + }; + + let verifying_key = VerifyingKey::new(key_bytes)?; + Ok(Self { verifying_key }) + } +} + +impl Verifier for Ed25519Verifier { + fn verify(&self, msg: &[u8], sig: &Signature) -> Result<()> { + if sig.kind != SignatureType::Ed25519 { + warn!("Ed25519Verifier: incompatible signature"); + return Err(Error::IncompatibleSignature); + } + + use ring_compat::signature::ed25519::Signature; + use ring_compat::signature::Verifier; + + let signature = Signature::from_slice(&sig.bytes)?; + + match self.verifying_key.verify(msg, &signature) { + Ok(_) => Ok(()), + Err(e) => { + // not sure failed signature checks are worth + // spamming the logs + warn!("Signature verification failed: {}", e); + Err(e.into()) + } + } + } +} + +pub struct VerifierFactory; + +impl VerifierFactory { + pub fn get_verifier( + spki: &SubjectPublicKeyInfoOwned, + ) -> Result> { + match AlgorithmType::try_from(&spki.algorithm.oid)? { + AlgorithmType::EcPublicKey => match &spki.algorithm.parameters { + Some(params) => match ParameterType::try_from(params)? { + ParameterType::P384 => { + Ok(Box::new(P384Verifier::try_from(spki)?)) + } + }, + None => Err(Error::MissingParams), + }, + AlgorithmType::Ed25519 => { + Ok(Box::new(Ed25519Verifier::try_from(spki)?)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use x509_cert::der::asn1::Any; + + #[test] + fn test_alg_type_ecpublickey() -> Result<()> { + let alg_oid = ID_EC_PUBLIC_KEY; + + let alg = AlgorithmType::try_from(&alg_oid)?; + assert_eq!(alg, AlgorithmType::EcPublicKey); + + Ok(()) + } + + #[test] + fn test_alg_type_ed25519() -> Result<()> { + let alg_oid = ID_ED_25519; + + let alg = AlgorithmType::try_from(&alg_oid)?; + assert_eq!(alg, AlgorithmType::Ed25519); + + Ok(()) + } + + #[test] + fn test_param_type() -> Result<()> { + let param_oid = SECP_384_R_1; + let param = Any::from(¶m_oid); + + let param = ParameterType::try_from(¶m)?; + assert_eq!(param, ParameterType::P384); + + Ok(()) + } + + #[test] + fn test_sig_type_ecdsa384() -> Result<()> { + let sig_oid = ECDSA_WITH_SHA_384; + let sig = SignatureType::try_from(&sig_oid)?; + + assert_eq!(sig, SignatureType::EcdsaWithSha384); + + Ok(()) + } + + #[test] + fn test_sig_type_ed25519() -> Result<()> { + let sig_oid = ID_ED_25519; + let sig = SignatureType::try_from(&sig_oid)?; + + assert_eq!(sig, SignatureType::Ed25519); + + Ok(()) + } +} diff --git a/dice-cert-check/src/main.rs b/dice-cert-check/src/main.rs new file mode 100644 index 0000000..094c3fe --- /dev/null +++ b/dice-cert-check/src/main.rs @@ -0,0 +1,216 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::Result; +use clap::{Parser, ValueEnum}; +use env_logger::Builder; +use hex::ToHex; +use log::{debug, error, info, LevelFilter}; +use pem_rfc7468::{LineEnding, PemLabel}; +use std::{fs, path::PathBuf, process}; +use x509_cert::{ + der::{Decode, DecodePem, Encode, EncodePem}, + Certificate, +}; + +use dice_cert_check::{Signature, VerifierFactory}; + +/// Transform an x509 cert between DER and PEM encoding. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Noisy output. + #[clap(long)] + verbose: bool, + + #[clap(subcommand)] + command: Command, +} + +#[derive(Debug, Parser)] +#[clap(name = "command")] +enum Command { + /// Convert the provided cert to the requested format. + Convert { + /// Input file holding certificate. + #[clap(long = "in")] + infile: PathBuf, + + /// Output file for certificate. + #[clap(long = "out")] + outfile: PathBuf, + + /// Write certificate out in this format. + #[clap(long)] + encoding: Encoding, + }, + /// Verify signatures in the provided cert chain back to the provided + /// root CA. + Verify { + /// Input file holding certificate chain. + #[clap(long = "in")] + infile: PathBuf, + + /// Input file holding certificate chain. + #[clap(long = "root")] + cafile: PathBuf, + }, +} + +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Debug, ValueEnum)] +enum Encoding { + DER, + PEM, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let mut builder = Builder::from_default_env(); + + let level = if args.verbose { + LevelFilter::Debug + } else { + LevelFilter::Error + }; + builder.filter(None, level).init(); + + match args.command { + Command::Convert { + infile, + outfile, + encoding, + } => convert(&infile, &outfile, encoding), + Command::Verify { infile, cafile } => { + let mut cert_chain = parse_pem_cert_chain(&infile)?; + let ca_cert = fs::read_to_string(cafile)?; + let ca_cert = Certificate::from_pem(ca_cert)?; + cert_chain.push(ca_cert); + + let code = match verify(&cert_chain) { + Ok(_) => 0, + Err(e) => { + error!("Error: {}", e); + 1 + } + }; + process::exit(code) + } + } +} + +fn convert( + infile: &PathBuf, + outfile: &PathBuf, + encoding: Encoding, +) -> Result<()> { + let cert = fs::read(infile)?; + + let cert = match Certificate::from_pem(&cert) { + Ok(c) => c, + Err(_) => { + info!("failed to parse input as PEM, trying DER ..."); + Certificate::from_der(&cert)? + } + }; + + // this is a bit weird, but there's a reason ... + let cert_vec = match encoding { + Encoding::DER => cert.to_vec()?, + // If we get the PEM encoded cert by just calling 'to_vec' it will + // be preceded by 5 bytes of ... something not PEM. We work around + // this by calling 'as_bytes' first. + Encoding::PEM => { + cert.to_pem(LineEnding::default())?.as_bytes().to_vec() + } + }; + + fs::write(outfile, cert_vec)?; + + Ok(()) +} + +// look into rfc 6066 PkiPath encoding? +// This is gross but ... it works +fn parse_pem_cert_chain(infile: &PathBuf) -> Result> { + let mut tmp = String::new(); + let boundary: String = + String::from("-----END ") + Certificate::PEM_LABEL + "-----"; + let mut chain = Vec::::new(); + + for line in fs::read_to_string(infile)?.lines() { + tmp.push_str(line); + tmp.push('\n'); + if line.contains(&boundary) { + // tmp string now holds one PEM encoded Certificate + chain.push(Certificate::from_pem(&tmp)?); + tmp.clear(); + } + } + + Ok(chain) +} + +fn verify(cert_chain: &[Certificate]) -> Result<()> { + // It's possible we may be handed a self signed cert and asked to verify + // it. For now this is an edge case. + if cert_chain.len() < 2 { + todo!("not enough certs"); + } + + let signature = Signature::new( + &cert_chain[0].signature_algorithm, + &cert_chain[0].signature, + )?; + debug!("Initial signature: {}", signature); + + let message = cert_chain[0].tbs_certificate.to_vec()?; + debug!("Initial message: {}", message.encode_hex::()); + + _verify(&cert_chain[1..], &message, &signature) +} + +/// This function should only be called from the 'verify' function. Where +/// 'verify' handles the initial condition for recusrion, this function is +/// the primary recursion. +fn _verify( + cert_chain: &[Certificate], + message: &[u8], + signature: &Signature, +) -> Result<()> { + debug!("cert_chain.len(): {}", cert_chain.len()); + if cert_chain.is_empty() { + error!("Cert chain has no certs. This shouldn't happen."); + process::exit(1); + } + + let verifier = VerifierFactory::get_verifier( + &cert_chain[0].tbs_certificate.subject_public_key_info, + )?; + + // verify signature from previous cert with verifier from current cert + verifier.verify(message, signature)?; + + // prepare data to be verified by next recursion or terminal condition + let signature = Signature::new( + &cert_chain[0].signature_algorithm, + &cert_chain[0].signature, + )?; + let message = cert_chain[0].tbs_certificate.to_vec()?; + + if cert_chain.len() > 1 { + _verify(&cert_chain[1..], &message, &signature) + } else { + // the terminal condition (no more certs in the chain) requires that + // we verify the signature on the final cert with the private key from + // the same cert. We've already prepared the signature & message from + // this cert in preparation for the next recursion. Instead of calling + // ourselves again however we do the verification and return. + info!("terminal condition"); + + // verify signature from previous cert with verifier from current cert + Ok(verifier.verify(&message, &signature)?) + } +}