diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 436f7e4..6330417 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,6 +22,14 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} + - run: RUSTFLAGS="-D warnings" cargo check --locked + + check-notlocked: + name: Check (not locked) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Cargo update run: cargo update - run: RUSTFLAGS="-D warnings" cargo check @@ -40,9 +48,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - name: Cargo update - run: cargo update - - run: RUSTFLAGS="-D warnings" cargo check --all-targets --all-features + - run: RUSTFLAGS="-D warnings" cargo check --locked --all-targets --all-features test: name: Test Suite @@ -51,7 +57,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo test --all-features + - run: cargo test --locked --all-features test_features: name: Test suite (with features) @@ -64,12 +70,13 @@ jobs: - --features=default - --all-features - --features=verify + - --features=verify-aws - --features=validate steps: - uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - - run: cargo test ${{ matrix.features }} + - run: cargo test --locked ${{ matrix.features }} fmt: name: Rustfmt @@ -90,7 +97,7 @@ jobs: - uses: dtolnay/rust-toolchain@nightly with: components: clippy - - run: cargo clippy --all-features -- -D warnings + - run: cargo clippy --locked --all-features -- -D warnings doc: name: Build documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index e813581..8f4b501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,32 @@ ### Thanks +## 0.18.0 + +### Added/Changed + +- Update lock file and dependencies +- Fix clippy warnings +- Visitor: add method to visit unknown extension and those with parse errors +- Add new feature `verify-aws` to used `aws-lc-rs` as crypto provider instead of `ring` + - The features are exclusive, so only one should be used + - If both are specified, `aws-lc-rs` is used (but both dependencies are included) +- Add `as_raw` methods to `X509Certificate`, `CertificateRevocationList` and `X509CertificationRequest` + - This method exposes the raw ASN.1 DER bytes used to build the object (#217) + +Extensions: +- Add support for SubjectInfoAccess extension +- GeneralName: add a new variant `Invalid` so an invalid entry does not stop + parsing for the entire list of names (for ex in SAN) + +### Fixed + +- PEM: ignore lines in comments which contain invalid UTF-8 characters (#180) + +### Thanks + +- Daniel McCarney + ## 0.17.0 ### Added/Changed/Fixed diff --git a/Cargo.lock b/Cargo.lock index 9b1b067..fc0081c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,20 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "asn1-rs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -43,30 +52,114 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cc" -version = "1.2.10" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] [[package]] name = "data-encoding" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "der-parser" @@ -84,9 +177,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -102,22 +195,96 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", ] [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] [[package]] name = "lazy_static" @@ -125,17 +292,45 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" @@ -189,52 +384,108 @@ dependencies = [ [[package]] name = "oid-registry" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "264c56d1492c13e769662197fb6b94e0a52abe52d27efac374615799a4bf453d" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ "asn1-rs", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", - "spin", - "untrusted", - "windows-sys", + "untrusted 0.9.0", + "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -244,20 +495,33 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -270,17 +534,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "syn" -version = "2.0.96" +version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", @@ -289,9 +547,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -300,18 +558,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", @@ -320,9 +578,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -335,15 +593,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -351,9 +609,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" @@ -363,9 +627,36 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "which" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" @@ -373,7 +664,25 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", ] [[package]] @@ -382,14 +691,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -398,53 +724,111 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "x509-parser" -version = "0.17.0" +version = "0.18.0" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", @@ -455,3 +839,9 @@ dependencies = [ "thiserror", "time", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 89c8286..38893fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "x509-parser" -version = "0.17.0" +version = "0.18.0" description = "Parser for the X.509 v3 format (RFC 5280 certificates)" license = "MIT OR Apache-2.0" keywords = ["X509","Certificate","parser","nom"] @@ -38,17 +38,20 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] +verify-aws = ["aws-lc-rs"] verify = ["ring"] validate = [] [dependencies] +aws-lc-rs = { version = "1.0", optional = true } asn1-rs = { version = "0.7.0", features=["datetime"] } data-encoding = "2.2.1" lazy_static = "1.4" nom = "7.0" -oid-registry = { version="0.8", features=["crypto", "x509", "x962"] } +oid-registry = { version="0.8.1", features=["crypto", "x509", "x962"] } rusticata-macros = "4.0" -ring = { version="0.17.7", optional=true } +ring = { version="0.17.12", optional=true } der-parser = { version = "10.0", features=["bigint"] } thiserror = "2.0" time = { version="0.3.35", features=["formatting"] } + diff --git a/examples/print-cert.rs b/examples/print-cert.rs index 9792085..00eb022 100644 --- a/examples/print-cert.rs +++ b/examples/print-cert.rs @@ -25,21 +25,22 @@ fn print_hex_dump(bytes: &[u8], max_len: usize) { fn format_oid(oid: &Oid) -> String { match oid2sn(oid, oid_registry()) { Ok(s) => s.to_owned(), - _ => format!("{}", oid), + _ => format!("{oid}"), } } fn generalname_to_string(gn: &GeneralName) -> String { match gn { - GeneralName::DNSName(name) => format!("DNSName:{}", name), - GeneralName::DirectoryName(n) => format!("DirName:{}", n), - GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{:?}", obj), - GeneralName::IPAddress(n) => format!("IPAddress:{:?}", n), - GeneralName::OtherName(oid, n) => format!("OtherName:{}, {:?}", oid, n), - GeneralName::RFC822Name(n) => format!("RFC822Name:{}", n), - GeneralName::RegisteredID(oid) => format!("RegisteredID:{}", oid), - GeneralName::URI(n) => format!("URI:{}", n), - GeneralName::X400Address(obj) => format!("X400Address:{:?}", obj), + GeneralName::DNSName(name) => format!("DNSName:{name}"), + GeneralName::DirectoryName(n) => format!("DirName:{n}"), + GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{obj:?}"), + GeneralName::IPAddress(n) => format!("IPAddress:{n:?}"), + GeneralName::OtherName(oid, n) => format!("OtherName:{oid}, {n:?}"), + GeneralName::RFC822Name(n) => format!("RFC822Name:{n}"), + GeneralName::RegisteredID(oid) => format!("RegisteredID:{oid}"), + GeneralName::URI(n) => format!("URI:{n}"), + GeneralName::X400Address(obj) => format!("X400Address:{obj:?}"), + GeneralName::Invalid(tag, b) => format!("Invalid:tag={tag},data={b:?}"), } } @@ -54,11 +55,11 @@ fn print_x509_extension(oid: &Oid, ext: &X509Extension) { ParsedExtension::AuthorityKeyIdentifier(aki) => { println!(" X509v3 Authority Key Identifier"); if let Some(key_id) = &aki.key_identifier { - println!(" Key Identifier: {:x}", key_id); + println!(" Key Identifier: {key_id:x}"); } if let Some(issuer) = &aki.authority_cert_issuer { for name in issuer { - println!(" Cert Issuer: {}", name); + println!(" Cert Issuer: {name}"); } } if let Some(serial) = aki.authority_cert_serial { @@ -72,10 +73,10 @@ fn print_x509_extension(oid: &Oid, ext: &X509Extension) { println!(" X509v3 CRL Distribution Points:"); for point in points.iter() { if let Some(name) = &point.distribution_point { - println!(" Full Name: {:?}", name); + println!(" Full Name: {name:?}"); } if let Some(reasons) = &point.reasons { - println!(" Reasons: {}", reasons); + println!(" Reasons: {reasons}"); } if let Some(crl_issuer) = &point.crl_issuer { print!(" CRL Issuer: "); @@ -88,44 +89,44 @@ fn print_x509_extension(oid: &Oid, ext: &X509Extension) { } } ParsedExtension::KeyUsage(ku) => { - println!(" X509v3 Key Usage: {}", ku); + println!(" X509v3 Key Usage: {ku}"); } ParsedExtension::NSCertType(ty) => { - println!(" Netscape Cert Type: {}", ty); + println!(" Netscape Cert Type: {ty}"); } ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { let s = match name { GeneralName::DNSName(s) => { - format!("DNS:{}", s) + format!("DNS:{s}") } GeneralName::IPAddress(b) => { let ip = match b.len() { 4 => { let b = <[u8; 4]>::try_from(*b).unwrap(); let ip = Ipv4Addr::from(b); - format!("{}", ip) + format!("{ip}") } 16 => { let b = <[u8; 16]>::try_from(*b).unwrap(); let ip = Ipv6Addr::from(b); - format!("{}", ip) + format!("{ip}") } - l => format!("invalid (len={})", l), + l => format!("invalid (len={l})"), }; - format!("IP Address:{}", ip) + format!("IP Address:{ip}") } _ => { - format!("{:?}", name) + format!("{name:?}") } }; - println!(" X509v3 SAN: {}", s); + println!(" X509v3 SAN: {s}"); } } ParsedExtension::SubjectKeyIdentifier(id) => { - println!(" X509v3 Subject Key Identifier: {:x}", id); + println!(" X509v3 Subject Key Identifier: {id:x}"); } - x => println!(" {:?}", x), + x => println!(" {x:?}"), } } @@ -155,7 +156,7 @@ fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { let version = x509.version(); if version.0 < 3 { - println!(" Version: {}", version); + println!(" Version: {version}"); } else { println!(" Version: INVALID({})", version.0); } @@ -172,7 +173,7 @@ fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { println!(" Signature Value:"); for l in format_number_to_hex_with_colon(&x509.signature_value.data, 16) { - println!(" {}", l); + println!(" {l}"); } println!(" Extensions:"); for ext in x509.extensions() { @@ -193,10 +194,10 @@ fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { println!("FAIL"); } for warning in logger.warnings() { - println!(" [W] {}", warning); + println!(" [W] {warning}"); } for error in logger.errors() { - println!(" [E] {}", error); + println!(" [E] {error}"); } println!(); if VALIDATE_ERRORS_FATAL && !logger.errors().is_empty() { @@ -243,7 +244,7 @@ fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, ind indent_s, format_oid(params.hash_algorithm_oid()), ); - print!("{}Mask Generation Function: ", indent_s); + print!("{indent_s}Mask Generation Function: "); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", @@ -263,7 +264,7 @@ fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, ind indent_s, format_oid(params.hash_algorithm_oid()), ); - print!("{}Mask Generation Function: ", indent_s); + print!("{indent_s}Mask Generation Function: "); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", @@ -282,7 +283,7 @@ fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, ind } } Err(e) => { - eprintln!("Could not parse signature algorithm: {}", e); + eprintln!("Could not parse signature algorithm: {e}"); println!(" Signature Algorithm:"); print_x509_digest_algorithm(signature_algorithm, indent); } @@ -297,10 +298,10 @@ fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { println!(" RSA Public Key: ({} bit)", rsa.key_size()); // print_hex_dump(rsa.modulus, 1024); for l in format_number_to_hex_with_colon(rsa.modulus, 16) { - println!(" {}", l); + println!(" {l}"); } if let Ok(e) = rsa.try_exponent() { - println!(" exponent: 0x{:x} ({})", e, e); + println!(" exponent: 0x{e:x} ({e})"); } else { println!(" exponent: :"); print_hex_dump(rsa.exponent, 32); @@ -309,7 +310,7 @@ fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { Ok(PublicKey::EC(ec)) => { println!(" EC Public Key: ({} bit)", ec.key_size()); for l in format_number_to_hex_with_colon(ec.data(), 16) { - println!(" {}", l); + println!(" {l}"); } // // identify curve // if let Some(params) = &public_key.algorithm.parameters { @@ -328,19 +329,19 @@ fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { Ok(PublicKey::DSA(y)) => { println!(" DSA Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { - println!(" {}", l); + println!(" {l}"); } } Ok(PublicKey::GostR3410(y)) => { println!(" GOST R 34.10-94 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { - println!(" {}", l); + println!(" {l}"); } } Ok(PublicKey::GostR3410_2012(y)) => { println!(" GOST R 34.10-2012 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { - println!(" {}", l); + println!(" {l}"); } } Ok(PublicKey::Unknown(b)) => { @@ -348,7 +349,7 @@ fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { print_hex_dump(b, 256); if let Ok((rem, res)) = der_parser::parse_der(b) { eprintln!("rem: {} bytes", rem.len()); - eprintln!("{:?}", res); + eprintln!("{res:?}"); } else { eprintln!(" "); } @@ -365,7 +366,7 @@ fn format_number_to_hex_with_colon(b: &[u8], row_size: usize) -> Vec { let mut v = Vec::with_capacity(1 + b.len() / row_size); for r in b.chunks(row_size) { let s = r.iter().fold(String::with_capacity(3 * r.len()), |a, b| { - a + &format!("{:02x}:", b) + a + &format!("{b:02x}:") }); v.push(s) } @@ -379,11 +380,11 @@ fn handle_certificate(file_name: &str, data: &[u8]) -> io::Result<()> { Ok(()) } Err(e) => { - let s = format!("Error while parsing {}: {}", file_name, e); + let s = format!("Error while parsing {file_name}: {e}"); if PARSE_ERRORS_FATAL { Err(io::Error::new(io::ErrorKind::Other, s)) } else { - eprintln!("{}", s); + eprintln!("{s}"); Ok(()) } } @@ -392,7 +393,7 @@ fn handle_certificate(file_name: &str, data: &[u8]) -> io::Result<()> { pub fn main() -> io::Result<()> { for file_name in env::args().skip(1) { - println!("File: {}", file_name); + println!("File: {file_name}"); let data = std::fs::read(file_name.clone()).expect("Unable to read file"); if matches!((data[0], data[1]), (0x30, 0x81..=0x83)) { // probably DER @@ -403,11 +404,11 @@ pub fn main() -> io::Result<()> { match pem { Ok(pem) => { let data = &pem.contents; - println!("Certificate [{}]", n); + println!("Certificate [{n}]"); handle_certificate(&file_name, data)?; } Err(e) => { - eprintln!("Error while decoding PEM entry {}: {}", n, e); + eprintln!("Error while decoding PEM entry {n}: {e}"); } } } diff --git a/examples/print-crl.rs b/examples/print-crl.rs index 73337cb..e36db5b 100644 --- a/examples/print-crl.rs +++ b/examples/print-crl.rs @@ -16,7 +16,7 @@ fn print_hex_dump(bytes: &[u8], max_len: usize) { fn format_oid(oid: &Oid) -> String { match oid2sn(oid, oid_registry()) { Ok(s) => s.to_owned(), - _ => format!("{}", oid), + _ => format!("{oid}"), } } @@ -136,7 +136,7 @@ pub fn main() -> io::Result<()> { // placeholder to store decoded PEM data, if needed let tmpdata; - println!("File: {}", file_name); + println!("File: {file_name}"); let data = std::fs::read(file_name.clone()).expect("Unable to read file"); let der_data: &[u8] = if (data[0], data[1]) == (0x30, 0x82) { // probably DER diff --git a/src/certificate.rs b/src/certificate.rs index 3abb5bf..75e46e7 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -66,9 +66,22 @@ pub struct X509Certificate<'a> { pub tbs_certificate: TbsCertificate<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, + + /// Complete raw ASN.1 DER content (TBS certificate, signature algorithm and signature). + pub(crate) raw: &'a [u8], } -impl X509Certificate<'_> { +impl<'a> X509Certificate<'a> { + /// Return the raw ASN.1 DER content of the complete signed certificate that was parsed. + /// + /// This includes the to-be-signed (TBS) certificate, the signature algorithm, and the signature. + /// If you want just the ASN.1 DER of the TBS certificate, prefer [`TbsCertificate::as_ref()`]. + /// + /// We avoid the `AsRef` trait in this instance to ensure the full lifetime of the `X509Certificate` is used. + pub fn as_raw(&self) -> &'a [u8] { + self.raw + } + /// Verify the cryptographic signature of this certificate /// /// `public_key` is the public key of the **signer**. For a self-signed certificate, @@ -95,6 +108,12 @@ impl X509Certificate<'_> { } } +impl<'a> AsRef<[u8]> for X509Certificate<'a> { + fn as_ref(&self) -> &[u8] { + self.as_raw() + } +} + impl<'a> Deref for X509Certificate<'a> { type Target = TbsCertificate<'a>; @@ -212,7 +231,8 @@ impl Default for X509CertificateParser { impl<'a> Parser<&'a [u8], X509Certificate<'a>, X509Error> for X509CertificateParser { fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], X509Certificate<'a>, X509Error> { - parse_der_sequence_defined_g(|i, _| { + let start_i = input; + let (rem, mut cert) = parse_der_sequence_defined_g(|i, _| { // pass options to TbsCertificate parser let mut tbs_parser = TbsCertificateParser::new().with_deep_parse_extensions(self.deep_parse_extensions); @@ -223,9 +243,13 @@ impl<'a> Parser<&'a [u8], X509Certificate<'a>, X509Error> for X509CertificatePar tbs_certificate, signature_algorithm, signature_value, + raw: &[], }; Ok((i, cert)) - })(input) + })(input)?; + let len = start_i.offset(rem); + cert.raw = &start_i[..len]; + Ok((rem, cert)) } } @@ -288,13 +312,13 @@ impl<'a> TbsCertificate<'a> { /// Get the certificate subject. #[inline] - pub fn subject(&self) -> &X509Name { + pub fn subject(&self) -> &X509Name<'_> { &self.subject } /// Get the certificate issuer. #[inline] - pub fn issuer(&self) -> &X509Name { + pub fn issuer(&self) -> &X509Name<'_> { &self.issuer } @@ -306,7 +330,7 @@ impl<'a> TbsCertificate<'a> { /// Get the certificate public key information. #[inline] - pub fn public_key(&self) -> &SubjectPublicKeyInfo { + pub fn public_key(&self) -> &SubjectPublicKeyInfo<'_> { &self.subject_pki } @@ -353,7 +377,7 @@ impl<'a> TbsCertificate<'a> { /// Builds and returns a map of extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. - pub fn extensions_map(&self) -> Result>, X509Error> { + pub fn extensions_map(&self) -> Result, &X509Extension<'a>>, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { @@ -403,7 +427,7 @@ impl<'a> TbsCertificate<'a> { /// or an error if the extension is invalid, or is present twice or more. pub fn extended_key_usage( &self, - ) -> Result>, X509Error> { + ) -> Result>>, X509Error> { self.get_extension_unique(&OID_X509_EXT_EXTENDED_KEY_USAGE)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::ExtendedKeyUsage(ref value) => { @@ -436,7 +460,7 @@ impl<'a> TbsCertificate<'a> { pub fn inhibit_anypolicy( &self, ) -> Result>, X509Error> { - self.get_extension_unique(&OID_X509_EXT_INHIBITANT_ANY_POLICY)? + self.get_extension_unique(&OID_X509_EXT_INHIBIT_ANY_POLICY)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::InhibitAnyPolicy(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) @@ -449,7 +473,9 @@ impl<'a> TbsCertificate<'a> { /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. - pub fn policy_mappings(&self) -> Result>, X509Error> { + pub fn policy_mappings( + &self, + ) -> Result>>, X509Error> { self.get_extension_unique(&OID_X509_EXT_POLICY_MAPPINGS)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::PolicyMappings(ref value) => { @@ -479,7 +505,9 @@ impl<'a> TbsCertificate<'a> { /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. - pub fn name_constraints(&self) -> Result>, X509Error> { + pub fn name_constraints( + &self, + ) -> Result>>, X509Error> { self.get_extension_unique(&OID_X509_EXT_NAME_CONSTRAINTS)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::NameConstraints(ref value) => { @@ -716,7 +744,7 @@ impl Validity { } impl FromDer<'_, X509Error> for Validity { - fn from_der(i: &[u8]) -> X509Result { + fn from_der(i: &[u8]) -> X509Result<'_, Self> { parse_der_sequence_defined_g(|i, _| { let (i, not_before) = ASN1Time::from_der(i)?; let (i, not_after) = ASN1Time::from_der(i)?; @@ -739,14 +767,14 @@ impl<'a> UniqueIdentifier<'a> { } // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL - fn from_der_subject(i: &[u8]) -> X509Result> { + fn from_der_subject(i: &[u8]) -> X509Result<'_, Option>> { Self::parse::<2>(i).map_err(|_| X509Error::InvalidSubjectUID.into()) } // Parse a [tag] UniqueIdentifier OPTIONAL // // UniqueIdentifier ::= BIT STRING - fn parse(i: &[u8]) -> BerResult> { + fn parse(i: &[u8]) -> BerResult<'_, Option>> { let (rem, unique_id) = OptTaggedImplicit::::from_der(i)?; let unique_id = unique_id.map(|u| UniqueIdentifier(u.into_inner())); Ok((rem, unique_id)) diff --git a/src/certification_request.rs b/src/certification_request.rs index 1f16beb..83af7a6 100644 --- a/src/certification_request.rs +++ b/src/certification_request.rs @@ -19,10 +19,22 @@ pub struct X509CertificationRequest<'a> { pub certification_request_info: X509CertificationRequestInfo<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, + + /// Complete raw ASN.1 DER content (request info, signature algorithm and signature). + pub(crate) raw: &'a [u8], } -impl X509CertificationRequest<'_> { - pub fn requested_extensions(&self) -> Option> { +impl<'a> X509CertificationRequest<'a> { + /// Return the raw ASN.1 DER content of the complete signed certification request that was parsed. + /// + /// This includes the certification request info, the signature algorithm, and the signature. + /// + /// We avoid the `AsRef` trait in this instance to ensure the full lifetime of the `X509CertificationRequest` is used. + pub fn as_raw(&self) -> &'a [u8] { + self.raw + } + + pub fn requested_extensions(&self) -> Option>> { self.certification_request_info .iter_attributes() .find_map(|attr| { @@ -50,6 +62,12 @@ impl X509CertificationRequest<'_> { } } +impl<'a> AsRef<[u8]> for X509CertificationRequest<'a> { + fn as_ref(&self) -> &[u8] { + self.as_raw() + } +} + ///
 /// CertificationRequest ::= SEQUENCE {
 ///     certificationRequestInfo CertificationRequestInfo,
@@ -59,7 +77,8 @@ impl X509CertificationRequest<'_> {
 /// 
impl<'a> FromDer<'a, X509Error> for X509CertificationRequest<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parse_der_sequence_defined_g(|i, _| { + let start_i = i; + let (rem, mut req) = parse_der_sequence_defined_g(|i, _| { let (i, certification_request_info) = X509CertificationRequestInfo::from_der(i)?; let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, signature_value) = parse_signature_value(i)?; @@ -67,9 +86,13 @@ impl<'a> FromDer<'a, X509Error> for X509CertificationRequest<'a> { certification_request_info, signature_algorithm, signature_value, + raw: &[], }; Ok((i, cert)) - })(i) + })(i)?; + let len = start_i.offset(rem); + req.raw = &start_i[..len]; + Ok((rem, req)) } } @@ -102,27 +125,27 @@ pub struct X509CertificationRequestInfo<'a> { impl X509CertificationRequestInfo<'_> { /// Get the CRL entry extensions. #[inline] - pub fn attributes(&self) -> &[X509CriAttribute] { + pub fn attributes(&self) -> &[X509CriAttribute<'_>] { &self.attributes } /// Returns an iterator over the CRL entry extensions #[inline] - pub fn iter_attributes(&self) -> impl Iterator { + pub fn iter_attributes(&self) -> impl Iterator> { self.attributes.iter() } /// Searches for a CRL entry extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. - pub fn find_attribute(&self, oid: &Oid) -> Option<&X509CriAttribute> { + pub fn find_attribute(&self, oid: &Oid) -> Option<&X509CriAttribute<'_>> { self.attributes.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of CRL entry extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. - pub fn attributes_map(&self) -> Result, X509Error> { + pub fn attributes_map(&self) -> Result, &X509CriAttribute<'_>>, X509Error> { self.attributes .iter() .try_fold(HashMap::new(), |mut m, ext| { diff --git a/src/cri_attributes.rs b/src/cri_attributes.rs index 78cb036..dc819b6 100644 --- a/src/cri_attributes.rs +++ b/src/cri_attributes.rs @@ -116,12 +116,12 @@ pub(crate) mod parser { } } - pub(super) fn parse_extension_request(i: &[u8]) -> X509Result { + pub(super) fn parse_extension_request(i: &[u8]) -> X509Result<'_, ExtensionRequest<'_>> { crate::extensions::parse_extension_sequence(i) .map(|(i, extensions)| (i, ExtensionRequest { extensions })) } - fn parse_extension_request_attr(i: &[u8]) -> X509Result { + fn parse_extension_request_attr(i: &[u8]) -> X509Result<'_, ParsedCriAttribute<'_>> { map( parse_extension_request, ParsedCriAttribute::ExtensionRequest, @@ -143,7 +143,7 @@ pub(crate) mod parser { // utf8String UTF8String (SIZE (1..MAX)), // bmpString BMPString (SIZE (1..MAX)) // } - pub(super) fn parse_challenge_password(i: &[u8]) -> X509Result { + pub(super) fn parse_challenge_password(i: &[u8]) -> X509Result<'_, ChallengePassword> { let (rem, obj) = match alt(( parse_der_utf8string, parse_der_printablestring, @@ -161,7 +161,7 @@ pub(crate) mod parser { } } - fn parse_challenge_password_attr(i: &[u8]) -> X509Result { + fn parse_challenge_password_attr(i: &[u8]) -> X509Result<'_, ParsedCriAttribute<'_>> { map( parse_challenge_password, ParsedCriAttribute::ChallengePassword, @@ -169,7 +169,7 @@ pub(crate) mod parser { } } -pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result> { +pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<'_, Vec>> { let (i, hdr) = Header::from_der(i).map_err(|_| Err::Error(X509Error::InvalidAttributes))?; if hdr.is_contextspecific() && hdr.tag().0 == 0 { all_consuming(many0(complete(X509CriAttribute::from_der)))(i) diff --git a/src/extensions/generalname.rs b/src/extensions/generalname.rs index 894e3ba..83a5b18 100644 --- a/src/extensions/generalname.rs +++ b/src/extensions/generalname.rs @@ -1,7 +1,7 @@ use crate::error::{X509Error, X509Result}; use crate::prelude::format_serial; use crate::x509::X509Name; -use asn1_rs::{Any, CheckDerConstraints, Class, Error, FromDer, Oid, Sequence}; +use asn1_rs::{Any, CheckDerConstraints, Class, Error, FromDer, Oid, Sequence, Tag}; use core::convert::TryFrom; use nom::combinator::all_consuming; use nom::{Err, IResult}; @@ -11,7 +11,7 @@ use std::fmt; /// Represents a GeneralName as defined in RFC5280. There /// is no support X.400 addresses and EDIPartyName. /// -/// String formats are not validated. +/// String formats are not validated (except for valid UTF-8). pub enum GeneralName<'a> { OtherName(Oid<'a>, &'a [u8]), /// More or less an e-mail, the format is not checked. @@ -30,6 +30,8 @@ pub enum GeneralName<'a> { /// An ip address, provided as encoded. IPAddress(&'a [u8]), RegisteredID(Oid<'a>), + /// Invalid data (for ex. invalid UTF-8 data in DNSName entry) + Invalid(Tag, &'a [u8]), } impl<'a> TryFrom> for GeneralName<'a> { @@ -37,49 +39,54 @@ impl<'a> TryFrom> for GeneralName<'a> { fn try_from(any: Any<'a>) -> Result { any.class().assert_eq(Class::ContextSpecific)?; - fn ia5str(any: Any) -> Result<&str, Err> { - // Relax constraints from RFC here: we are expecting an IA5String, but many certificates - // are using unicode characters - std::str::from_utf8(any.data).map_err(|_| Err::Failure(Error::BerValueError)) - } - let name = match any.tag().0 { - 0 => { - // otherName SEQUENCE { OID, [0] explicit any defined by oid } - let (rest, oid) = Oid::from_der(any.data)?; - GeneralName::OtherName(oid, rest) - } - 1 => GeneralName::RFC822Name(ia5str(any)?), - 2 => GeneralName::DNSName(ia5str(any)?), - 3 => { - // XXX Not yet implemented - GeneralName::X400Address(any) - } - 4 => { - // directoryName, name - let (_, name) = all_consuming(X509Name::from_der)(any.data) - .or(Err(Error::Unsupported)) // XXX remove me - ?; - GeneralName::DirectoryName(name) - } - 5 => { - // XXX Not yet implemented - GeneralName::EDIPartyName(any) - } - 6 => GeneralName::URI(ia5str(any)?), - 7 => { - // IPAddress, OctetString - GeneralName::IPAddress(any.data) - } - 8 => { - let oid = Oid::new(any.data.into()); - GeneralName::RegisteredID(oid) - } - _ => return Err(Error::unexpected_tag(None, any.tag())), + + let name = match parse_generalname_entry(any.clone()) { + Ok(name) => name, + Err(_) => GeneralName::Invalid(any.tag(), any.data), }; Ok(name) } } +fn parse_generalname_entry( + any: Any<'_>, +) -> Result, as TryFrom>>::Error> { + Ok(match any.tag().0 { + 0 => { + // otherName SEQUENCE { OID, [0] explicit any defined by oid } + let (rest, oid) = Oid::from_der(any.data)?; + GeneralName::OtherName(oid, rest) + } + 1 => GeneralName::RFC822Name(ia5str(any)?), + 2 => GeneralName::DNSName(ia5str(any)?), + 3 => { + // XXX Not yet implemented + GeneralName::X400Address(any) + } + 4 => { + // directoryName, name + let (_, name) = all_consuming(X509Name::from_der)(any.data) + .or(Err(Error::Unsupported)) // XXX remove me + ?; + GeneralName::DirectoryName(name) + } + 5 => { + // XXX Not yet implemented + GeneralName::EDIPartyName(any) + } + 6 => GeneralName::URI(ia5str(any)?), + 7 => { + // IPAddress, OctetString + GeneralName::IPAddress(any.data) + } + 8 => { + let oid = Oid::new(any.data.into()); + GeneralName::RegisteredID(oid) + } + _ => return Err(Error::unexpected_tag(None, any.tag())), + }) +} + impl CheckDerConstraints for GeneralName<'_> { fn check_constraints(any: &Any) -> asn1_rs::Result<()> { Sequence::check_constraints(any) @@ -95,20 +102,29 @@ impl<'a> FromDer<'a, X509Error> for GeneralName<'a> { impl fmt::Display for GeneralName<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - GeneralName::OtherName(oid, _) => write!(f, "OtherName({}, [...])", oid), - GeneralName::RFC822Name(s) => write!(f, "RFC822Name({})", s), - GeneralName::DNSName(s) => write!(f, "DNSName({})", s), + GeneralName::OtherName(oid, _) => write!(f, "OtherName({oid}, [...])"), + GeneralName::RFC822Name(s) => write!(f, "RFC822Name({s})"), + GeneralName::DNSName(s) => write!(f, "DNSName({s})"), GeneralName::X400Address(_) => write!(f, "X400Address()"), - GeneralName::DirectoryName(dn) => write!(f, "DirectoryName({})", dn), + GeneralName::DirectoryName(dn) => write!(f, "DirectoryName({dn})"), GeneralName::EDIPartyName(_) => write!(f, "EDIPartyName()"), - GeneralName::URI(s) => write!(f, "URI({})", s), + GeneralName::URI(s) => write!(f, "URI({s})"), GeneralName::IPAddress(b) => write!(f, "IPAddress({})", format_serial(b)), - GeneralName::RegisteredID(oid) => write!(f, "RegisteredID({})", oid), + GeneralName::RegisteredID(oid) => write!(f, "RegisteredID({oid})"), + GeneralName::Invalid(tag, b) => { + write!(f, "Invalid(tag={}, data={})", tag, format_serial(b)) + } } } } -pub(crate) fn parse_generalname(i: &[u8]) -> IResult<&[u8], GeneralName, Error> { +fn ia5str(any: Any<'_>) -> Result<&str, Err> { + // Relax constraints from RFC here: we are expecting an IA5String, but many certificates + // are using unicode characters + std::str::from_utf8(any.data).map_err(|_| Err::Failure(Error::BerValueError)) +} + +pub(crate) fn parse_generalname(i: &[u8]) -> IResult<&[u8], GeneralName<'_>, Error> { let (rest, any) = Any::from_der(i)?; let gn = GeneralName::try_from(any)?; Ok((rest, gn)) diff --git a/src/extensions/keyusage.rs b/src/extensions/keyusage.rs index ffb7f76..e68e198 100644 --- a/src/extensions/keyusage.rs +++ b/src/extensions/keyusage.rs @@ -110,7 +110,7 @@ pub(crate) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> { Ok((rest, KeyUsage { flags })) } -pub(crate) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> { +pub(crate) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage<'_>, BerError> { let (ret, seq) = >::from_der(i)?; let mut seen = std::collections::HashSet::new(); let mut eku = ExtendedKeyUsage { diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index 025ed89..3bd4922 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -216,6 +216,8 @@ pub enum ParsedExtension<'a> { InhibitAnyPolicy(InhibitAnyPolicy), /// Section 4.2.2.1 of rfc 5280 AuthorityInfoAccess(AuthorityInfoAccess<'a>), + /// Section 4.2.2.2 of rfc 5280 + SubjectInfoAccess(SubjectInfoAccess<'a>), /// Netscape certificate type (subject is SSL client, an SSL server, or a CA) NSCertType(NSCertType), /// Netscape certificate comment @@ -440,6 +442,68 @@ impl<'a> FromDer<'a, X509Error> for AuthorityInfoAccess<'a> { } } +#[derive(Clone, Debug, PartialEq)] +pub struct SubjectInfoAccess<'a> { + pub accessdescs: Vec>, +} + +impl<'a> SubjectInfoAccess<'a> { + /// Returns an iterator over the Access Descriptors + pub fn iter(&self) -> impl Iterator> { + self.accessdescs.iter() + } + + /// Returns a `HashMap` mapping `Oid` to the list of references to `GeneralNames` + /// + /// If several names match the same `Oid`, they are merged in the same entry. + pub fn as_hashmap(&self) -> HashMap, Vec<&GeneralName<'a>>> { + // create the hashmap and merge entries with same OID + let mut m: HashMap> = HashMap::new(); + for desc in &self.accessdescs { + let AccessDescription { + access_method: oid, + access_location: gn, + } = desc; + if let Some(general_names) = m.get_mut(oid) { + general_names.push(gn); + } else { + m.insert(oid.clone(), vec![gn]); + } + } + m + } + + /// Returns a `HashMap` mapping `Oid` to the list of `GeneralNames` (consuming the input) + /// + /// If several names match the same `Oid`, they are merged in the same entry. + pub fn into_hashmap(self) -> HashMap, Vec>> { + let mut aia_list = self.accessdescs; + // create the hashmap and merge entries with same OID + let mut m: HashMap> = HashMap::new(); + for desc in aia_list.drain(..) { + let AccessDescription { + access_method: oid, + access_location: gn, + } = desc; + if let Some(general_names) = m.get_mut(&oid) { + general_names.push(gn); + } else { + m.insert(oid, vec![gn]); + } + } + m + } +} + +impl<'a> FromDer<'a, X509Error> for SubjectInfoAccess<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_subjectinfoaccess(i).map_err(Err::convert) + } +} + +/// AccessDescription ::= SEQUENCE { +/// accessMethod OBJECT IDENTIFIER, +/// accessLocation GeneralName } #[derive(Clone, Debug, PartialEq)] pub struct AccessDescription<'a> { pub access_method: Oid<'a>, @@ -672,7 +736,7 @@ pub(crate) mod parser { ); add!( m, - OID_X509_EXT_INHIBITANT_ANY_POLICY, + OID_X509_EXT_INHIBIT_ANY_POLICY, parse_inhibitanypolicy_ext ); add!( @@ -680,6 +744,7 @@ pub(crate) mod parser { OID_PKIX_AUTHORITY_INFO_ACCESS, parse_authorityinfoaccess_ext ); + add!(m, OID_PKIX_SUBJECT_INFO_ACCESS, parse_subjectinfoaccess_ext); add!( m, OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, @@ -778,17 +843,17 @@ pub(crate) mod parser { } } - fn parse_basicconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_basicconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_basicconstraints, ParsedExtension::BasicConstraints)(i) } - fn parse_nameconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_nameconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_nameconstraints, ParsedExtension::NameConstraints)(i) } pub(super) fn parse_subjectalternativename_ext( i: &[u8], - ) -> IResult<&[u8], ParsedExtension, BerError> { + ) -> IResult<&[u8], ParsedExtension<'_>, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; Ok(( @@ -800,7 +865,7 @@ pub(crate) mod parser { pub(super) fn parse_issueralternativename_ext( i: &[u8], - ) -> IResult<&[u8], ParsedExtension, BerError> { + ) -> IResult<&[u8], ParsedExtension<'_>, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; Ok(( @@ -828,15 +893,15 @@ pub(crate) mod parser { })(i) } - fn parse_policyconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_policyconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_policyconstraints, ParsedExtension::PolicyConstraints)(i) } - fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_policymappings, ParsedExtension::PolicyMappings)(i) } - fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { let (ret, skip_certs) = parse_der_u32(i)?; Ok(( ret, @@ -844,14 +909,16 @@ pub(crate) mod parser { )) } - fn parse_extendedkeyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_extendedkeyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_extendedkeyusage, ParsedExtension::ExtendedKeyUsage)(i) } // DistributionPointName ::= CHOICE { // fullName [0] GeneralNames, // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } - fn parse_distributionpointname(i: &[u8]) -> IResult<&[u8], DistributionPointName, BerError> { + fn parse_distributionpointname( + i: &[u8], + ) -> IResult<&[u8], DistributionPointName<'_>, BerError> { let (rem, header) = der_read_element_header(i)?; match header.tag().0 { 0 => { @@ -897,7 +964,7 @@ pub(crate) mod parser { } } - fn parse_crlissuer_content(i: &[u8]) -> BerResult> { + fn parse_crlissuer_content(i: &[u8]) -> BerResult<'_, Vec>> { many1(complete(parse_generalname))(i) } @@ -907,7 +974,7 @@ pub(crate) mod parser { // cRLIssuer [2] GeneralNames OPTIONAL } pub(super) fn parse_crldistributionpoint( i: &[u8], - ) -> IResult<&[u8], CRLDistributionPoint, BerError> { + ) -> IResult<&[u8], CRLDistributionPoint<'_>, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, distribution_point) = opt(complete(parse_der_tagged_explicit_g(0, |b, _| { @@ -928,12 +995,12 @@ pub(crate) mod parser { pub(super) fn parse_crldistributionpoints( i: &[u8], - ) -> IResult<&[u8], CRLDistributionPoints, BerError> { + ) -> IResult<&[u8], CRLDistributionPoints<'_>, BerError> { let (ret, crldps) = parse_der_sequence_of_v(parse_crldistributionpoint)(i)?; Ok((ret, CRLDistributionPoints { points: crldps })) } - fn parse_crldistributionpoints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_crldistributionpoints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_crldistributionpoints, ParsedExtension::CRLDistributionPoints, @@ -949,7 +1016,7 @@ pub(crate) mod parser { // onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE } pub(super) fn parse_issuingdistributionpoint( i: &[u8], - ) -> IResult<&[u8], IssuingDistributionPoint, BerError> { + ) -> IResult<&[u8], IssuingDistributionPoint<'_>, BerError> { parse_der_sequence_defined_g(|content, _| { let parse_tagged_bool = |tag: u32, rem| -> IResult<&[u8], bool, BerError> { let (rem, value) = opt(complete(|_| { @@ -982,7 +1049,9 @@ pub(crate) mod parser { })(i) } - fn parse_issuingdistributionpoint_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_issuingdistributionpoint_ext( + i: &[u8], + ) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_issuingdistributionpoint, ParsedExtension::IssuingDistributionPoint, @@ -997,8 +1066,8 @@ pub(crate) mod parser { // accessLocation GeneralName } pub(super) fn parse_authorityinfoaccess( i: &[u8], - ) -> IResult<&[u8], AuthorityInfoAccess, BerError> { - fn parse_aia(i: &[u8]) -> IResult<&[u8], AccessDescription, BerError> { + ) -> IResult<&[u8], AuthorityInfoAccess<'_>, BerError> { + fn parse_aia(i: &[u8]) -> IResult<&[u8], AccessDescription<'_>, BerError> { parse_der_sequence_defined_g(|content, _| { // Read first element, an oid. let (gn, oid) = Oid::from_der(content)?; @@ -1011,13 +1080,39 @@ pub(crate) mod parser { Ok((ret, AuthorityInfoAccess { accessdescs })) } - fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_authorityinfoaccess, ParsedExtension::AuthorityInfoAccess, )(i) } + // SubjectInfoAccessSyntax ::= + // SEQUENCE SIZE (1..MAX) OF AccessDescription + // + // AccessDescription ::= SEQUENCE { + // accessMethod OBJECT IDENTIFIER, + // accessLocation GeneralName } + pub(super) fn parse_subjectinfoaccess( + i: &[u8], + ) -> IResult<&[u8], SubjectInfoAccess<'_>, BerError> { + fn parse_sia(i: &[u8]) -> IResult<&[u8], AccessDescription<'_>, BerError> { + parse_der_sequence_defined_g(|content, _| { + // Read first element, an oid. + let (gn, oid) = Oid::from_der(content)?; + // Parse second element + let (rest, gn) = parse_generalname(gn)?; + Ok((rest, AccessDescription::new(oid, gn))) + })(i) + } + let (ret, accessdescs) = parse_der_sequence_of_v(parse_sia)(i)?; + Ok((ret, SubjectInfoAccess { accessdescs })) + } + + fn parse_subjectinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { + map(parse_subjectinfoaccess, ParsedExtension::SubjectInfoAccess)(i) + } + fn parse_aki_content<'a>( i: &'a [u8], _hdr: Header<'_>, @@ -1045,29 +1140,29 @@ pub(crate) mod parser { // RFC 5280 section 4.2.1.1: Authority Key Identifier pub(super) fn parse_authoritykeyidentifier( i: &[u8], - ) -> IResult<&[u8], AuthorityKeyIdentifier, BerError> { + ) -> IResult<&[u8], AuthorityKeyIdentifier<'_>, BerError> { let (rem, aki) = parse_der_sequence_defined_g(parse_aki_content)(i)?; Ok((rem, aki)) } - fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_authoritykeyidentifier, ParsedExtension::AuthorityKeyIdentifier, )(i) } - pub(super) fn parse_keyidentifier(i: &[u8]) -> IResult<&[u8], KeyIdentifier, BerError> { + pub(super) fn parse_keyidentifier(i: &[u8]) -> IResult<&[u8], KeyIdentifier<'_>, BerError> { let (rest, id) = <&[u8]>::from_der(i)?; let ki = KeyIdentifier(id); Ok((rest, ki)) } - fn parse_keyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_keyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i) } - fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_keyusage, ParsedExtension::KeyUsage)(i) } @@ -1085,11 +1180,11 @@ pub(crate) mod parser { Ok((rest, NSCertType(flags))) } - fn parse_nscerttype_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_nscerttype_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map(parse_nscerttype, ParsedExtension::NSCertType)(i) } - fn parse_nscomment_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_nscomment_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { match parse_der_ia5string(i) { Ok((i, obj)) => { let s = obj.as_str()?; @@ -1126,8 +1221,10 @@ pub(crate) mod parser { // PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice ) pub(super) fn parse_certificatepolicies( i: &[u8], - ) -> IResult<&[u8], Vec, BerError> { - fn parse_policy_qualifier_info(i: &[u8]) -> IResult<&[u8], PolicyQualifierInfo, BerError> { + ) -> IResult<&[u8], Vec>, BerError> { + fn parse_policy_qualifier_info( + i: &[u8], + ) -> IResult<&[u8], PolicyQualifierInfo<'_>, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_qualifier_id) = Oid::from_der(content)?; let info = PolicyQualifierInfo { @@ -1137,7 +1234,7 @@ pub(crate) mod parser { Ok((&[], info)) })(i) } - fn parse_policy_information(i: &[u8]) -> IResult<&[u8], PolicyInformation, BerError> { + fn parse_policy_information(i: &[u8]) -> IResult<&[u8], PolicyInformation<'_>, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_id) = Oid::from_der(content)?; let (rem, policy_qualifiers) = @@ -1154,7 +1251,7 @@ pub(crate) mod parser { parse_der_sequence_of_v(parse_policy_information)(i) } - fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_certificatepolicies, ParsedExtension::CertificatePolicies, @@ -1162,7 +1259,7 @@ pub(crate) mod parser { } // CRLReason ::= ENUMERATED { ... - fn parse_reason_code(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_reason_code(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { let (rest, obj) = parse_der_enum(i)?; let code = obj .content @@ -1176,7 +1273,7 @@ pub(crate) mod parser { } // invalidityDate ::= GeneralizedTime - fn parse_invalidity_date(i: &[u8]) -> ParseResult { + fn parse_invalidity_date(i: &[u8]) -> ParseResult<'_, ParsedExtension<'_>> { let (rest, t) = GeneralizedTime::from_der(i)?; let dt = t.utc_datetime()?; Ok((rest, ParsedExtension::InvalidityDate(ASN1Time::new(dt)))) @@ -1184,12 +1281,12 @@ pub(crate) mod parser { // CRLNumber ::= INTEGER (0..MAX) // Note from RFC 3280: "CRL verifiers MUST be able to handle CRLNumber values up to 20 octets." - fn parse_crl_number(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_crl_number(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { let (rest, num) = map_res(parse_der_integer, |obj| obj.as_biguint())(i)?; Ok((rest, ParsedExtension::CRLNumber(num))) } - fn parse_sct_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_sct_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension<'_>, BerError> { map( parse_ct_signed_certificate_timestamp_list, ParsedExtension::SCT, @@ -1198,13 +1295,16 @@ pub(crate) mod parser { } /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension -pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result> { +pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result<'_, Vec>> { parse_der_sequence_defined_g(|a, _| all_consuming(many0(complete(X509Extension::from_der)))(a))( i, ) } -pub(crate) fn parse_extensions(i: &[u8], explicit_tag: Tag) -> X509Result> { +pub(crate) fn parse_extensions( + i: &[u8], + explicit_tag: Tag, +) -> X509Result<'_, Vec>> { if i.is_empty() { return Ok((i, Vec::new())); } @@ -1221,7 +1321,9 @@ pub(crate) fn parse_extensions(i: &[u8], explicit_tag: Tag) -> X509Result X509Result> { +pub(crate) fn parse_extension_envelope_sequence( + i: &[u8], +) -> X509Result<'_, Vec>> { let parser = X509ExtensionParser::new().with_deep_parse_extensions(false); parse_der_sequence_defined_g(move |a, _| all_consuming(many0(complete(parser)))(a))(i) @@ -1230,7 +1332,7 @@ pub(crate) fn parse_extension_envelope_sequence(i: &[u8]) -> X509Result X509Result> { +) -> X509Result<'_, Vec>> { if i.is_empty() { return Ok((i, Vec::new())); } @@ -1246,7 +1348,7 @@ pub(crate) fn parse_extensions_envelope( } } -fn der_read_critical(i: &[u8]) -> BerResult { +fn der_read_critical(i: &[u8]) -> BerResult<'_, bool> { // Some certificates do not respect the DER BOOLEAN constraint (true must be encoded as 0xff) // so we attempt to parse as BER let (rem, obj) = opt(parse_ber_bool)(i)?; @@ -1501,7 +1603,7 @@ mod tests { assert!(!reasons.privelege_withdrawn()); assert!(reasons.aa_compromise()); assert_eq!( - format!("{}", reasons), + format!("{reasons}"), "Key Compromise, CA Compromise, AA Compromise" ); let issuers = crl[0].crl_issuer.as_ref().unwrap(); @@ -1532,7 +1634,7 @@ mod tests { assert!(!reasons.certificate_hold()); assert!(!reasons.privelege_withdrawn()); assert!(!reasons.aa_compromise()); - assert_eq!(format!("{}", reasons), "Key Compromise, CA Compromise"); + assert_eq!(format!("{reasons}"), "Key Compromise, CA Compromise"); assert!(crl[1].crl_issuer.is_none()); let distribution_point = crl[1].distribution_point.as_ref().unwrap(); assert!(matches!( diff --git a/src/extensions/nameconstraints.rs b/src/extensions/nameconstraints.rs index da9a999..3dadbcb 100644 --- a/src/extensions/nameconstraints.rs +++ b/src/extensions/nameconstraints.rs @@ -29,13 +29,13 @@ pub struct GeneralSubtree<'a> { // maximum: Option, } -pub(crate) fn parse_nameconstraints(i: &[u8]) -> IResult<&[u8], NameConstraints, BerError> { - fn parse_subtree(i: &[u8]) -> IResult<&[u8], GeneralSubtree, BerError> { +pub(crate) fn parse_nameconstraints(i: &[u8]) -> IResult<&[u8], NameConstraints<'_>, BerError> { + fn parse_subtree(i: &[u8]) -> IResult<&[u8], GeneralSubtree<'_>, BerError> { parse_der_sequence_defined_g(|input, _| { map(parse_generalname, |base| GeneralSubtree { base })(input) })(i) } - fn parse_subtrees(i: &[u8]) -> IResult<&[u8], Vec, BerError> { + fn parse_subtrees(i: &[u8]) -> IResult<&[u8], Vec>, BerError> { all_consuming(many1(complete(parse_subtree)))(i) } diff --git a/src/extensions/policymappings.rs b/src/extensions/policymappings.rs index 2bd367d..380da8f 100644 --- a/src/extensions/policymappings.rs +++ b/src/extensions/policymappings.rs @@ -75,7 +75,7 @@ impl<'a> PolicyMapping<'a> { // PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { // issuerDomainPolicy CertPolicyId, // subjectDomainPolicy CertPolicyId } -pub(crate) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, Error> { +pub(crate) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings<'_>, Error> { let (ret, pairs) = >::from_der(i)?; // let mut mappings: HashMap> = HashMap::new(); let mappings = pairs; diff --git a/src/extensions/sct.rs b/src/extensions/sct.rs index 646f774..f2db52a 100644 --- a/src/extensions/sct.rs +++ b/src/extensions/sct.rs @@ -52,7 +52,7 @@ pub struct DigitallySigned<'a> { /// Parses a list of Signed Certificate Timestamp entries pub fn parse_ct_signed_certificate_timestamp_list( i: &[u8], -) -> IResult<&[u8], Vec, BerError> { +) -> IResult<&[u8], Vec>, BerError> { // use nom::HexDisplay; // eprintln!("{}", i.to_hex(16)); let (rem, b) = <&[u8]>::from_der(i)?; @@ -67,7 +67,7 @@ pub fn parse_ct_signed_certificate_timestamp_list( /// Parses as single Signed Certificate Timestamp entry pub fn parse_ct_signed_certificate_timestamp( i: &[u8], -) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { +) -> IResult<&[u8], SignedCertificateTimestamp<'_>, BerError> { map_parser( length_data(be_u16), parse_ct_signed_certificate_timestamp_content, @@ -76,7 +76,7 @@ pub fn parse_ct_signed_certificate_timestamp( pub(crate) fn parse_ct_signed_certificate_timestamp_content( i: &[u8], -) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { +) -> IResult<&[u8], SignedCertificateTimestamp<'_>, BerError> { let (i, version) = be_u8(i)?; let (i, id) = parse_log_id(i)?; let (i, timestamp) = be_u64(i)?; @@ -93,7 +93,7 @@ pub(crate) fn parse_ct_signed_certificate_timestamp_content( } // Safety: cannot fail, take() returns exactly 32 bytes -fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, BerError> { +fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID<'_>, BerError> { let (i, key_id) = take(32usize)(i)?; Ok(( i, @@ -105,13 +105,13 @@ fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, BerError> { )) } -fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions, BerError> { +fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions<'_>, BerError> { let (i, ext_len) = be_u16(i)?; let (i, ext_data) = take(ext_len as usize)(i)?; Ok((i, CtExtensions(ext_data))) } -fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned, BerError> { +fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned<'_>, BerError> { let (i, hash_alg_id) = be_u8(i)?; let (i, sign_alg_id) = be_u8(i)?; let (i, data) = length_data(be_u16)(i)?; diff --git a/src/lib.rs b/src/lib.rs index e695030..1817746 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,13 @@ //! } //! ``` //! +//! - The `verify-aws` feature offers the same support for signature verification, but based on +//! `aws-lc-rs` instead of `ring`. +//! +//! - _Note_: if both `verify` and `verify-aws` features are enabled (which happens when using +//! `--all-features`), the verification will use `aws-lc-rs`. It also has the side-effect of +//! having a dependency on `ring`, even if it is not used. +//! //! - The `validate` features add methods to run more validation functions on the certificate structure //! and values using the [`Validate`](validate/trait.Validate.html) trait. //! It does not validate any cryptographic parameter (see `verify` above). @@ -150,8 +157,8 @@ pub mod utils; #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] pub mod validate; -#[cfg(feature = "verify")] -#[cfg_attr(docsrs, doc(cfg(feature = "verify")))] +#[cfg(any(feature = "verify", feature = "verify-aws"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "verify", feature = "verify-aws"))))] pub mod verify; pub mod visitor; pub mod x509; @@ -177,7 +184,7 @@ use revocation_list::CertificateRevocationList; /// /// For PEM-encoded certificates, use the [`pem`](pem/index.html) module. #[inline] -pub fn parse_x509_certificate(i: &[u8]) -> X509Result { +pub fn parse_x509_certificate(i: &[u8]) -> X509Result<'_, X509Certificate<'_>> { X509Certificate::from_der(i) } @@ -187,7 +194,7 @@ pub fn parse_x509_certificate(i: &[u8]) -> X509Result { /// This function is an alias to [CertificateRevocationList::from_der](revocation_list::CertificateRevocationList::from_der). See this function /// for more information. #[inline] -pub fn parse_x509_crl(i: &[u8]) -> X509Result { +pub fn parse_x509_crl(i: &[u8]) -> X509Result<'_, CertificateRevocationList<'_>> { CertificateRevocationList::from_der(i) } @@ -197,7 +204,7 @@ pub fn parse_x509_crl(i: &[u8]) -> X509Result { note = "please use `parse_x509_certificate` or `X509Certificate::from_der` instead" )] #[inline] -pub fn parse_x509_der(i: &[u8]) -> X509Result { +pub fn parse_x509_der(i: &[u8]) -> X509Result<'_, X509Certificate<'_>> { X509Certificate::from_der(i) } @@ -208,6 +215,6 @@ pub fn parse_x509_der(i: &[u8]) -> X509Result { note = "please use `parse_x509_crl` or `CertificateRevocationList::from_der` instead" )] #[inline] -pub fn parse_crl_der(i: &[u8]) -> X509Result { +pub fn parse_crl_der(i: &[u8]) -> X509Result<'_, CertificateRevocationList<'_>> { CertificateRevocationList::from_der(i) } diff --git a/src/pem.rs b/src/pem.rs index 9b8e12f..f2f930f 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -61,7 +61,7 @@ use crate::certificate::X509Certificate; use crate::error::{PEMError, X509Error}; use crate::parse_x509_certificate; use nom::{Err, IResult}; -use std::io::{BufRead, Cursor, Seek}; +use std::io::{BufRead, Cursor, ErrorKind, Seek}; /// Representation of PEM data #[derive(Clone, PartialEq, Eq, Debug)] @@ -119,7 +119,16 @@ impl Pem { pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> { let mut line = String::new(); let label = loop { - let num_bytes = r.read_line(&mut line)?; + let num_bytes = match r.read_line(&mut line) { + Ok(line) => line, + Err(e) if e.kind() == ErrorKind::InvalidData => { + // some tools put invalid UTF-8 data in PEM comment section. Ignore line + continue; + } + Err(e) => { + return Err(e.into()); + } + }; if num_bytes == 0 { // EOF return Err(PEMError::MissingHeader); @@ -161,7 +170,7 @@ impl Pem { } /// Decode the PEM contents into a X.509 object - pub fn parse_x509(&self) -> Result> { + pub fn parse_x509(&self) -> Result, Err> { parse_x509_certificate(&self.contents).map(|(_, x509)| x509) } diff --git a/src/public_key.rs b/src/public_key.rs index 9a6fe7a..413ffe1 100644 --- a/src/public_key.rs +++ b/src/public_key.rs @@ -73,7 +73,7 @@ impl RSAPublicKey<'_> { } // helper function to parse with error type BerError -fn parse_rsa_key(bytes: &[u8]) -> BerResult { +fn parse_rsa_key(bytes: &[u8]) -> BerResult<'_, RSAPublicKey<'_>> { parse_der_sequence_defined_g(move |i, _| { let (i, obj_modulus) = parse_der_integer(i)?; let (i, obj_exponent) = parse_der_integer(i)?; diff --git a/src/revocation_list.rs b/src/revocation_list.rs index 903e87b..f154dec 100644 --- a/src/revocation_list.rs +++ b/src/revocation_list.rs @@ -51,6 +51,9 @@ pub struct CertificateRevocationList<'a> { pub tbs_cert_list: TbsCertList<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, + + /// Complete raw ASN.1 DER content (TBS certificate list, signature algorithm and signature). + pub(crate) raw: &'a [u8], } impl<'a> CertificateRevocationList<'a> { @@ -61,7 +64,7 @@ impl<'a> CertificateRevocationList<'a> { /// Get the certificate issuer. #[inline] - pub fn issuer(&self) -> &X509Name { + pub fn issuer(&self) -> &X509Name<'_> { &self.tbs_cert_list.issuer } @@ -84,7 +87,7 @@ impl<'a> CertificateRevocationList<'a> { /// Get the CRL extensions. #[inline] - pub fn extensions(&self) -> &[X509Extension] { + pub fn extensions(&self) -> &[X509Extension<'_>] { &self.tbs_cert_list.extensions } @@ -121,6 +124,23 @@ impl<'a> CertificateRevocationList<'a> { self.tbs_cert_list.raw, ) } + + /// + /// Return the raw ASN.1 DER content of the complete signed certificate revocation list that was parsed. + /// + /// This includes the to-be-signed (TBS) certificate list, the signature algorithm, and the signature. + /// If you want just the ASN.1 DER of the TBS certificate list, prefer [`TbsCertList::as_ref()`]. + /// + /// We avoid the `AsRef` trait in this instance to ensure the full lifetime of the `CertificateRevocationList` is used. + pub fn as_raw(&self) -> &'a [u8] { + self.raw + } +} + +impl<'a> AsRef<[u8]> for CertificateRevocationList<'a> { + fn as_ref(&self) -> &[u8] { + self.as_raw() + } } ///
@@ -131,7 +151,8 @@ impl<'a> CertificateRevocationList<'a> {
 /// 
impl<'a> FromDer<'a, X509Error> for CertificateRevocationList<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parse_der_sequence_defined_g(|i, _| { + let start_i = i; + let (rem, mut crl) = parse_der_sequence_defined_g(|i, _| { let (i, tbs_cert_list) = TbsCertList::from_der(i)?; let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, signature_value) = parse_signature_value(i)?; @@ -139,9 +160,13 @@ impl<'a> FromDer<'a, X509Error> for CertificateRevocationList<'a> { tbs_cert_list, signature_algorithm, signature_value, + raw: &[], }; Ok((i, crl)) - })(i) + })(i)?; + let len = start_i.offset(rem); + crl.raw = &start_i[..len]; + Ok((rem, crl)) } } @@ -183,27 +208,27 @@ pub struct TbsCertList<'a> { impl TbsCertList<'_> { /// Returns the certificate extensions #[inline] - pub fn extensions(&self) -> &[X509Extension] { + pub fn extensions(&self) -> &[X509Extension<'_>] { &self.extensions } /// Returns an iterator over the certificate extensions #[inline] - pub fn iter_extensions(&self) -> impl Iterator { + pub fn iter_extensions(&self) -> impl Iterator> { self.extensions.iter() } /// Searches for an extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. - pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension> { + pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension<'_>> { self.extensions.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. - pub fn extensions_map(&self) -> Result, X509Error> { + pub fn extensions_map(&self) -> Result, &X509Extension<'_>>, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { @@ -269,27 +294,27 @@ impl RevokedCertificate<'_> { /// Get the CRL entry extensions. #[inline] - pub fn extensions(&self) -> &[X509Extension] { + pub fn extensions(&self) -> &[X509Extension<'_>] { &self.extensions } /// Returns an iterator over the CRL entry extensions #[inline] - pub fn iter_extensions(&self) -> impl Iterator { + pub fn iter_extensions(&self) -> impl Iterator> { self.extensions.iter() } /// Searches for a CRL entry extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. - pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension> { + pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension<'_>> { self.extensions.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of CRL entry extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. - pub fn extensions_map(&self) -> Result, X509Error> { + pub fn extensions_map(&self) -> Result, &X509Extension<'_>>, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { @@ -356,7 +381,7 @@ impl<'a> FromDer<'a, X509Error> for RevokedCertificate<'a> { } } -fn parse_revoked_certificates(i: &[u8]) -> X509Result> { +fn parse_revoked_certificates(i: &[u8]) -> X509Result<'_, Vec>> { parse_der_sequence_defined_g(|a, _| { all_consuming(many0(complete(RevokedCertificate::from_der)))(a) })(i) diff --git a/src/signature_algorithm.rs b/src/signature_algorithm.rs index c833aa5..f8adb98 100644 --- a/src/signature_algorithm.rs +++ b/src/signature_algorithm.rs @@ -91,12 +91,12 @@ pub struct RsaSsaPssParams<'a> { impl<'a> RsaSsaPssParams<'a> { /// Get a reference to the rsa ssa pss params's hash algorithm. - pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { + pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier<'_>> { self.hash_alg.as_ref() } /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) - pub fn hash_algorithm_oid(&self) -> &'a Oid { + pub fn hash_algorithm_oid(&self) -> &'a Oid<'_> { const SHA1: &Oid = &OID_HASH_SHA1; self.hash_alg .as_ref() @@ -105,14 +105,14 @@ impl<'a> RsaSsaPssParams<'a> { } /// Get a reference to the rsa ssa pss params's mask generation algorithm. - pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { + pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier<'_>> { self.mask_gen_algorithm.as_ref() } /// Get the rsa ssa pss params's mask generation algorithm. /// /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` - pub fn mask_gen_algorithm(&self) -> Result { + pub fn mask_gen_algorithm(&self) -> Result, X509Error> { match self.mask_gen_algorithm.as_ref() { Some(alg) => { let (_, hash) = alg @@ -222,12 +222,12 @@ impl<'a> RsaAesOaepParams<'a> { ); /// Get a reference to the rsa aes oaep params's hash algorithm. - pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { + pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier<'_>> { self.hash_alg.as_ref() } /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) - pub fn hash_algorithm_oid(&self) -> &'a Oid { + pub fn hash_algorithm_oid(&self) -> &'a Oid<'_> { const SHA1: &Oid = &OID_HASH_SHA1; self.hash_alg .as_ref() @@ -236,14 +236,14 @@ impl<'a> RsaAesOaepParams<'a> { } /// Get a reference to the rsa ssa pss params's mask generation algorithm. - pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { + pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier<'_>> { self.mask_gen_alg.as_ref() } /// Get the rsa ssa pss params's mask generation algorithm. /// /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` - pub fn mask_gen_algorithm(&self) -> Result { + pub fn mask_gen_algorithm(&self) -> Result, X509Error> { match self.mask_gen_alg.as_ref() { Some(alg) => { let (_, hash) = alg diff --git a/src/time.rs b/src/time.rs index caa0927..73086dc 100644 --- a/src/time.rs +++ b/src/time.rs @@ -16,7 +16,7 @@ pub struct ASN1Time { } impl ASN1Time { - pub(crate) fn from_der_opt(i: &[u8]) -> X509Result> { + pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<'_, Option> { if i.is_empty() { return Ok((i, None)); } @@ -110,13 +110,13 @@ impl ASN1Time { } impl FromDer<'_, X509Error> for ASN1Time { - fn from_der(i: &[u8]) -> X509Result { + fn from_der(i: &[u8]) -> X509Result<'_, Self> { let (rem, time) = parse_choice_of_time(i).map_err(|_| X509Error::InvalidDate)?; Ok((rem, time)) } } -pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult { +pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult<'_, ASN1Time> { if let Ok((rem, t)) = UtcTime::from_der(i) { let dt = t.utc_adjusted_datetime()?; return Ok((rem, ASN1Time::new_utc(dt))); @@ -129,7 +129,7 @@ pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult { } // allow relaxed parsing of UTCTime (ex: 370116130016+0000) -fn parse_malformed_date(i: &[u8]) -> ParseResult { +fn parse_malformed_date(i: &[u8]) -> ParseResult<'_, ASN1Time> { #[allow(clippy::trivially_copy_pass_by_ref)] // fn check_char(b: &u8) -> bool { // (0x20 <= *b && *b <= 0x7f) || (*b == b'+') @@ -164,7 +164,7 @@ impl fmt::Display for ASN1Time { let s = self .time .format(format) - .unwrap_or_else(|e| format!("Invalid date: {}", e)); + .unwrap_or_else(|e| format!("Invalid date: {e}")); f.write_str(&s) } } diff --git a/src/utils.rs b/src/utils.rs index 72b4d99..f1754c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ /// Formats a slice to a colon-separated hex string (for ex `01:02:ff:ff`) pub fn format_serial(i: &[u8]) -> String { let mut s = i.iter().fold(String::with_capacity(3 * i.len()), |a, b| { - a + &format!("{:02x}:", b) + a + &format!("{b:02x}:") }); s.pop(); s diff --git a/src/validate/extensions.rs b/src/validate/extensions.rs index a838f6e..7f7780e 100644 --- a/src/validate/extensions.rs +++ b/src/validate/extensions.rs @@ -100,7 +100,7 @@ impl<'a> Validator<'a> for X509ExtensionsValidator { GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { // should be an ia5string if !s.as_bytes().iter().all(u8::is_ascii) { - l.warn(&format!("Invalid charset in 'SAN' entry '{}'", s)); + l.warn(&format!("Invalid charset in 'SAN' entry '{s}'")); } } _ => (), diff --git a/src/validate/loggers.rs b/src/validate/loggers.rs index 0052f8d..988bdbd 100644 --- a/src/validate/loggers.rs +++ b/src/validate/loggers.rs @@ -39,11 +39,11 @@ pub struct StderrLogger; impl Logger for StderrLogger { fn warn(&mut self, message: &str) { - eprintln!("[W] {}", message); + eprintln!("[W] {message}"); } fn err(&mut self, message: &str) { - eprintln!("[E] {}", message); + eprintln!("[E] {message}"); } } diff --git a/src/validate/structure.rs b/src/validate/structure.rs index 9896e38..f8f33c9 100644 --- a/src/validate/structure.rs +++ b/src/validate/structure.rs @@ -143,7 +143,7 @@ impl<'a> Validator<'a> for TbsCertificateStructureValidator { GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { // should be an ia5string if !s.as_bytes().iter().all(u8::is_ascii) { - l.warn(&format!("Invalid charset in 'SAN' entry '{}'", s)); + l.warn(&format!("Invalid charset in 'SAN' entry '{s}'")); } } _ => (), diff --git a/src/verify.rs b/src/verify.rs index 0e5288b..94a2fce 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -9,19 +9,25 @@ use oid_registry::{ }; use std::convert::TryFrom; +// Since the `signature` object is similar in ring and in aws-lc-rs, we just use simple logic +// to determine which one to use. +// If both verify and verify-aws features are enabled, aws will be used. +#[cfg(feature = "verify-aws")] +use aws_lc_rs::signature; +#[cfg(all(feature = "verify", not(feature = "verify-aws")))] +use ring::signature; + /// Verify the cryptographic signature of the raw data (can be a certificate, a CRL or a CSR). /// /// `public_key` is the public key of the **signer**. /// -/// Not all algorithms are supported, this function is limited to what `ring` supports. +/// Not all algorithms are supported, this function is limited to what `aws_lc_rs` or `ring` supports. pub fn verify_signature( public_key: &SubjectPublicKeyInfo, signature_algorithm: &AlgorithmIdentifier, signature_value: &BitString, raw_data: &[u8], ) -> Result<(), X509Error> { - use ring::signature; - let AlgorithmIdentifier { algorithm: signature_algorithm, parameters: signature_algorithm_parameters, @@ -63,12 +69,11 @@ pub fn verify_signature( /// Find the verification algorithm for the given EC curve and SHA digest size /// -/// Not all algorithms are supported, we are limited to what `ring` supports. +/// Not all algorithms are supported, we are limited to what `aws_lc_rs` or `ring`supports. fn get_ec_curve_sha( pubkey_alg: &AlgorithmIdentifier, sha_len: usize, -) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { - use ring::signature; +) -> Option<&'static dyn signature::VerificationAlgorithm> { let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; // let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; if curve_oid == OID_EC_P256 { @@ -90,13 +95,11 @@ fn get_ec_curve_sha( /// Find the verification algorithm for the given RSA-PSS parameters /// -/// Not all algorithms are supported, we are limited to what `ring` supports. +/// Not all algorithms are supported, we are limited to what `aws_lc_rs` or `ring` supports. /// Notably, the SHA-1 hash algorithm is not supported. fn get_rsa_pss_verification_algo( params: &Option, -) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { - use ring::signature; - +) -> Option<&'static dyn signature::VerificationAlgorithm> { let params = params.as_ref()?; let params = RsaSsaPssParams::try_from(params).ok()?; let hash_algo = params.hash_algorithm_oid(); diff --git a/src/visitor/certificate_visitor.rs b/src/visitor/certificate_visitor.rs index c6df8e2..a2178cc 100644 --- a/src/visitor/certificate_visitor.rs +++ b/src/visitor/certificate_visitor.rs @@ -110,47 +110,75 @@ pub trait X509CertificateVisitor { /// Invoked for extensions, after visiting children fn post_visit_extensions(&mut self, _extensions: &[X509Extension]) {} - /// Invoked for the "Authority Key Identifier" (if present) + /// Invoked for the "Authority Key Identifier" extension (if present) fn visit_extension_aki(&mut self, _aki: &AuthorityKeyIdentifier) {} - /// Invoked for the "Subject Key Identifier" (if present) + /// Invoked for the "Subject Key Identifier" extension (if present) fn visit_extension_ski(&mut self, _id: &KeyIdentifier) {} - /// Invoked for the "Key Usage" (if present) + /// Invoked for the "Key Usage" extension (if present) fn visit_extension_key_usage(&mut self, _usage: &KeyUsage) {} - /// Invoked for the "Certificate Policies" (if present) + /// Invoked for the "Certificate Policies" extension (if present) fn visit_extension_certificate_policies(&mut self, _policies: &CertificatePolicies) {} - /// Invoked for the "Subject Alternative Name" (if present) + /// Invoked for the "Subject Alternative Name" extension (if present) fn visit_extension_subject_alternative_name(&mut self, _san: &SubjectAlternativeName) {} - /// Invoked for the "Issuer Alternative Name" (if present) + /// Invoked for the "Issuer Alternative Name" extension (if present) fn visit_extension_issuer_alternative_name(&mut self, _ian: &IssuerAlternativeName) {} - /// Invoked for the "Basic Constraints" (if present) + /// Invoked for the "Basic Constraints" extension (if present) fn visit_extension_basic_constraints(&mut self, _bc: &BasicConstraints) {} - /// Invoked for the "Name Constraints" (if present) + /// Invoked for the "Name Constraints" extension (if present) fn visit_extension_name_constraints(&mut self, _constraints: &NameConstraints) {} - /// Invoked for the "Policy Constraints" (if present) + /// Invoked for the "Name Constraints" extension (if present) + fn visit_extension_nscert_comment(&mut self, _nscert_comment: &str) {} + + /// Invoked for the "Name Constraints" extension (if present) + fn visit_extension_nscert_type(&mut self, _nscert_type: &NSCertType) {} + + /// Invoked for the "Policy Constraints" extension (if present) fn visit_extension_policy_constraints(&mut self, _constraints: &PolicyConstraints) {} - /// Invoked for the "Extended Key Usage" (if present) + /// Invoked for the "Policy Mappings" extension (if present) + fn visit_extension_policy_mappings(&mut self, _mappings: &PolicyMappings) {} + + /// Invoked for the "Extended Key Usage" extension (if present) fn visit_extension_extended_key_usage(&mut self, _usage: &ExtendedKeyUsage) {} - /// Invoked for the "CRL Distribution Points" (if present) + /// Invoked for the "CRL Distribution Points" extension (if present) fn visit_extension_crl_distribution_points(&mut self, _crl: &CRLDistributionPoints) {} - /// Invoked for the "Inhibit anyPolicy" (if present) + /// Invoked for the "Inhibit anyPolicy" extension (if present) fn visit_extension_inhibit_anypolicy(&mut self, _policy: &InhibitAnyPolicy) {} - /// Invoked for the "Authority Information Access" (if present) + /// Invoked for the "Authority Information Access" extension (if present) fn visit_extension_authority_information_access(&mut self, _info: &AuthorityInfoAccess) {} - /// Invoked for the "Signed Certificate Timestamp" (SCT) (if present) + /// Invoked for the "Signed Certificate Timestamp" (SCT) extension (if present) fn visit_extension_sct(&mut self, _sct: &[SignedCertificateTimestamp]) {} + + /// Invoked for any other extension than the specific (recognized) types + /// + /// This can happen for several reasons: + /// - the parser did not recognize the extension content + /// - the parser was explicitly asked to not parse extension content + /// - the extension could be correct (for ex in a CRL), but is not supposed to be part of a Certificate + fn visit_extension_unknown(&mut self, _ext: &X509Extension) {} + + /// Invoked for any extension than caused a parse error + /// + /// Normally, this should not match anything except for invalid data. + /// This could match any known extension malformed or wrongly encoded. + fn visit_extension_parse_error( + &mut self, + _extension: &X509Extension, + _error: &asn1_rs::Err, + ) { + } } impl X509Certificate<'_> { @@ -166,80 +194,70 @@ impl X509Certificate<'_> { impl TbsCertificate<'_> { /// Run the provided `visitor` over the [`TbsCertificate`] object pub fn walk(&self, visitor: &mut V) { - visitor.visit_version(&self.version); - visitor.visit_serial_number(self.raw_serial()); - visitor.visit_tbs_signature_algorithm(&self.signature); - visitor.visit_issuer(&self.issuer); - visitor.visit_validity(&self.validity); - visitor.visit_subject(&self.subject); - visitor.visit_subject_public_key_info(&self.subject_pki); - visitor.visit_issuer_unique_id(self.issuer_uid.as_ref()); - visitor.visit_subject_unique_id(self.subject_uid.as_ref()); - visitor.pre_visit_extensions(self.extensions()); + // shorten name to reduce line length + let v = visitor; + v.visit_version(&self.version); + v.visit_serial_number(self.raw_serial()); + v.visit_tbs_signature_algorithm(&self.signature); + v.visit_issuer(&self.issuer); + v.visit_validity(&self.validity); + v.visit_subject(&self.subject); + v.visit_subject_public_key_info(&self.subject_pki); + v.visit_issuer_unique_id(self.issuer_uid.as_ref()); + v.visit_subject_unique_id(self.subject_uid.as_ref()); + v.pre_visit_extensions(self.extensions()); for extension in self.extensions() { - visitor.visit_extension(extension); + v.visit_extension(extension); - if extension.oid == OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER { - if let ParsedExtension::AuthorityKeyIdentifier(aki) = &extension.parsed_extension { - visitor.visit_extension_aki(aki); - } - } else if extension.oid == OID_X509_EXT_SUBJECT_KEY_IDENTIFIER { - if let ParsedExtension::SubjectKeyIdentifier(id) = &extension.parsed_extension { - visitor.visit_extension_ski(id); + match extension.parsed_extension() { + ParsedExtension::AuthorityInfoAccess(info) => { + v.visit_extension_authority_information_access(info) } - } else if extension.oid == OID_X509_EXT_KEY_USAGE { - if let ParsedExtension::KeyUsage(usage) = &extension.parsed_extension { - visitor.visit_extension_key_usage(usage); + ParsedExtension::AuthorityKeyIdentifier(aki) => v.visit_extension_aki(aki), + ParsedExtension::BasicConstraints(bc) => v.visit_extension_basic_constraints(bc), + ParsedExtension::CertificatePolicies(policies) => { + v.visit_extension_certificate_policies(policies) } - } else if extension.oid == OID_X509_EXT_CERTIFICATE_POLICIES { - if let ParsedExtension::CertificatePolicies(policies) = &extension.parsed_extension - { - visitor.visit_extension_certificate_policies(policies); + ParsedExtension::CRLDistributionPoints(crl) => { + v.visit_extension_crl_distribution_points(crl) } - } else if extension.oid == OID_X509_EXT_SUBJECT_ALT_NAME { - if let ParsedExtension::SubjectAlternativeName(san) = &extension.parsed_extension { - visitor.visit_extension_subject_alternative_name(san); + ParsedExtension::ExtendedKeyUsage(usage) => { + v.visit_extension_extended_key_usage(usage) } - } else if extension.oid == OID_X509_EXT_ISSUER_ALT_NAME { - if let ParsedExtension::IssuerAlternativeName(ian) = &extension.parsed_extension { - visitor.visit_extension_issuer_alternative_name(ian); + ParsedExtension::InhibitAnyPolicy(policy) => { + v.visit_extension_inhibit_anypolicy(policy) } - } else if extension.oid == OID_X509_EXT_BASIC_CONSTRAINTS { - if let ParsedExtension::BasicConstraints(bc) = &extension.parsed_extension { - visitor.visit_extension_basic_constraints(bc); + ParsedExtension::IssuerAlternativeName(ian) => { + v.visit_extension_issuer_alternative_name(ian) } - } else if extension.oid == OID_X509_EXT_NAME_CONSTRAINTS { - if let ParsedExtension::NameConstraints(constraints) = &extension.parsed_extension { - visitor.visit_extension_name_constraints(constraints); + ParsedExtension::KeyUsage(usage) => v.visit_extension_key_usage(usage), + ParsedExtension::NSCertType(nscert_type) => { + v.visit_extension_nscert_type(nscert_type) } - } else if extension.oid == OID_X509_EXT_POLICY_CONSTRAINTS { - if let ParsedExtension::PolicyConstraints(constraints) = &extension.parsed_extension - { - visitor.visit_extension_policy_constraints(constraints); + ParsedExtension::NameConstraints(constraints) => { + v.visit_extension_name_constraints(constraints) } - } else if extension.oid == OID_X509_EXT_EXTENDED_KEY_USAGE { - if let ParsedExtension::ExtendedKeyUsage(usage) = &extension.parsed_extension { - visitor.visit_extension_extended_key_usage(usage); + ParsedExtension::NsCertComment(comment) => { + v.visit_extension_nscert_comment(comment) } - } else if extension.oid == OID_X509_EXT_CRL_DISTRIBUTION_POINTS { - if let ParsedExtension::CRLDistributionPoints(crl) = &extension.parsed_extension { - visitor.visit_extension_crl_distribution_points(crl); + ParsedExtension::PolicyConstraints(constraints) => { + v.visit_extension_policy_constraints(constraints) } - } else if extension.oid == OID_X509_EXT_INHIBITANT_ANY_POLICY { - if let ParsedExtension::InhibitAnyPolicy(policy) = &extension.parsed_extension { - visitor.visit_extension_inhibit_anypolicy(policy); + ParsedExtension::PolicyMappings(mappings) => { + v.visit_extension_policy_mappings(mappings) } - } else if extension.oid == OID_PKIX_AUTHORITY_INFO_ACCESS { - if let ParsedExtension::AuthorityInfoAccess(info) = &extension.parsed_extension { - visitor.visit_extension_authority_information_access(info); + ParsedExtension::SCT(sct) => v.visit_extension_sct(sct), + ParsedExtension::SubjectAlternativeName(san) => { + v.visit_extension_subject_alternative_name(san) } - } else if extension.oid == OID_CT_LIST_SCT { - if let ParsedExtension::SCT(sct) = &extension.parsed_extension { - visitor.visit_extension_sct(sct); + ParsedExtension::SubjectKeyIdentifier(id) => v.visit_extension_ski(id), + ParsedExtension::ParseError { error } => { + v.visit_extension_parse_error(extension, error) } + _ => v.visit_extension_unknown(extension), } } - visitor.post_visit_extensions(self.extensions()); + v.post_visit_extensions(self.extensions()); } } diff --git a/src/visitor/crl_visitor.rs b/src/visitor/crl_visitor.rs index c67b77b..edaff65 100644 --- a/src/visitor/crl_visitor.rs +++ b/src/visitor/crl_visitor.rs @@ -122,6 +122,25 @@ pub trait CertificateRevocationListVisitor { /// Invoked for the "Signed Certificate Timestamp" (SCT) (if present) fn visit_extension_sct(&mut self, _sct: &[SignedCertificateTimestamp]) {} + + /// Invoked for any other extension than the specific (recognized) types + /// + /// This can happen for several reasons: + /// - the parser did not recognize the extension content + /// - the parser was explicitly asked to not parse extension content + /// - the extension could be correct (for ex in a CRL), but is not supposed to be part of a Certificate + fn visit_extension_unknown(&mut self, _ext: &X509Extension) {} + + /// Invoked for any extension than caused a parse error + /// + /// Normally, this should not match anything except for invalid data. + /// This could match any known extension malformed or wrongly encoded. + fn visit_extension_parse_error( + &mut self, + _extension: &X509Extension, + _error: &asn1_rs::Err, + ) { + } } impl CertificateRevocationList<'_> { @@ -137,54 +156,43 @@ impl CertificateRevocationList<'_> { impl TbsCertList<'_> { /// Run the provided `visitor` over the [`TbsCertList`] object pub fn walk(&self, visitor: &mut V) { - visitor.visit_version(self.version.as_ref()); - visitor.visit_tbs_signature_algorithm(&self.signature); - visitor.visit_issuer(&self.issuer); - visitor.visit_this_update(&self.this_update); - visitor.visit_next_update(self.next_update.as_ref()); - visitor.visit_revoked_certificates(&self.revoked_certificates); + // shorten name to reduce line length + let v = visitor; + v.visit_version(self.version.as_ref()); + v.visit_tbs_signature_algorithm(&self.signature); + v.visit_issuer(&self.issuer); + v.visit_this_update(&self.this_update); + v.visit_next_update(self.next_update.as_ref()); + v.visit_revoked_certificates(&self.revoked_certificates); for certificate in &self.revoked_certificates { - visitor.visit_revoked_certificate(certificate); + v.visit_revoked_certificate(certificate); } - visitor.pre_visit_extensions(self.extensions()); + v.pre_visit_extensions(self.extensions()); for extension in self.extensions() { - visitor.visit_extension(extension); + v.visit_extension(extension); - if extension.oid == OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER { - if let ParsedExtension::AuthorityKeyIdentifier(aki) = &extension.parsed_extension { - visitor.visit_extension_aki(aki); - } - } else if extension.oid == OID_X509_EXT_ISSUER_ALT_NAME { - if let ParsedExtension::IssuerAlternativeName(ian) = &extension.parsed_extension { - visitor.visit_extension_issuer_alternative_name(ian); - } - } else if extension.oid == OID_X509_EXT_CRL_NUMBER { - if let ParsedExtension::CRLNumber(number) = &extension.parsed_extension { - visitor.visit_extension_crl_number(number); - } - } else if extension.oid == OID_X509_EXT_ISSUER_DISTRIBUTION_POINT { - if let ParsedExtension::IssuingDistributionPoint(dp) = &extension.parsed_extension { - visitor.visit_extension_issuing_distribution_point(dp); - } - } else if extension.oid == OID_PKIX_AUTHORITY_INFO_ACCESS { - if let ParsedExtension::AuthorityInfoAccess(info) = &extension.parsed_extension { - visitor.visit_extension_authority_information_access(info); + match extension.parsed_extension() { + ParsedExtension::AuthorityInfoAccess(info) => { + v.visit_extension_authority_information_access(info) } - } else if extension.oid == OID_X509_EXT_REASON_CODE { - if let ParsedExtension::ReasonCode(code) = &extension.parsed_extension { - visitor.visit_extension_reason_code(code); + ParsedExtension::AuthorityKeyIdentifier(aki) => v.visit_extension_aki(aki), + ParsedExtension::CRLNumber(number) => v.visit_extension_crl_number(number), + ParsedExtension::InvalidityDate(time) => v.visit_extension_invalidity_date(time), + ParsedExtension::IssuerAlternativeName(ian) => { + v.visit_extension_issuer_alternative_name(ian) } - } else if extension.oid == OID_X509_EXT_INVALIDITY_DATE { - if let ParsedExtension::InvalidityDate(time) = &extension.parsed_extension { - visitor.visit_extension_invalidity_date(time); + ParsedExtension::IssuingDistributionPoint(dp) => { + v.visit_extension_issuing_distribution_point(dp) } - } else if extension.oid == OID_CT_LIST_SCT { - if let ParsedExtension::SCT(sct) = &extension.parsed_extension { - visitor.visit_extension_sct(sct); + ParsedExtension::ReasonCode(code) => v.visit_extension_reason_code(code), + ParsedExtension::SCT(sct) => v.visit_extension_sct(sct), + ParsedExtension::ParseError { error } => { + v.visit_extension_parse_error(extension, error) } + _ => v.visit_extension_unknown(extension), } } - visitor.post_visit_extensions(self.extensions()); + v.post_visit_extensions(self.extensions()); } } diff --git a/src/x509.rs b/src/x509.rs index b4f1df9..711e47c 100644 --- a/src/x509.rs +++ b/src/x509.rs @@ -40,7 +40,7 @@ pub struct X509Version(pub u32); impl X509Version { /// Parse `[0]` EXPLICIT Version DEFAULT v1 - pub(crate) fn from_der_tagged_0(i: &[u8]) -> X509Result { + pub(crate) fn from_der_tagged_0(i: &[u8]) -> X509Result<'_, X509Version> { let (rem, opt_version) = OptTaggedParser::from(0) .parse_der(i, |_, data| Self::from_der(data)) .map_err(Err::convert)?; @@ -153,11 +153,11 @@ impl<'a> FromDer<'a, X509Error> for AttributeTypeAndValue<'a> { // AttributeValue ::= ANY -- DEFINED BY AttributeType #[inline] -fn parse_attribute_value(i: &[u8]) -> ParseResult { +fn parse_attribute_value(i: &[u8]) -> ParseResult<'_, Any<'_>, Error> { alt((Any::from_der, parse_malformed_string))(i) } -fn parse_malformed_string(i: &[u8]) -> ParseResult { +fn parse_malformed_string(i: &[u8]) -> ParseResult<'_, Any<'_>, Error> { let (rem, hdr) = Header::from_der(i)?; let len = hdr.length().definite()?; if len > MAX_OBJECT_SIZE { @@ -231,7 +231,7 @@ pub struct SubjectPublicKeyInfo<'a> { impl SubjectPublicKeyInfo<'_> { /// Attempt to parse the public key, and return the parsed version or an error - pub fn parsed(&self) -> Result { + pub fn parsed(&self) -> Result, X509Error> { let b = &self.subject_public_key.data; if self.algorithm.algorithm == OID_PKCS1_RSAENCRYPTION { let (_, key) = RSAPublicKey::from_der(b).map_err(|_| X509Error::InvalidSPKI)?; @@ -335,7 +335,7 @@ pub struct X509Name<'a> { impl fmt::Display for X509Name<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match x509name_to_string(&self.rdn_seq, oid_registry()) { - Ok(o) => write!(f, "{}", o), + Ok(o) => write!(f, "{o}"), Err(_) => write!(f, ""), } } @@ -558,7 +558,7 @@ fn x509name_to_string( Ok(s) => String::from(s), _ => format!("{:?}", attr.attr_type), }; - let rdn = format!("{}={}", abbrev, val_str); + let rdn = format!("{abbrev}={val_str}"); match acc2.len() { 0 => Ok(rdn), _ => Ok(acc2 + " + " + &rdn), @@ -571,11 +571,11 @@ fn x509name_to_string( }) } -pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result { +pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result<'_, BitString<'_>> { BitString::from_der(i).or(Err(Err::Error(X509Error::InvalidSignatureValue))) } -pub(crate) fn parse_serial(i: &[u8]) -> X509Result<(&[u8], BigUint)> { +pub(crate) fn parse_serial(i: &[u8]) -> X509Result<'_, (&[u8], BigUint)> { let (rem, any) = Any::from_ber(i).map_err(|_| X509Error::InvalidSerial)?; // RFC 5280 4.1.2.2: "The serial number MUST be a positive integer" // however, many CAs do not respect this and send integers with MSB set, diff --git a/tests/readcert.rs b/tests/readcert.rs index 05fc80a..9e8c4a8 100644 --- a/tests/readcert.rs +++ b/tests/readcert.rs @@ -123,6 +123,8 @@ fn test_x509_parser() { assert!(tbs_cert.is_ca()); // assert_eq!(tbs_cert.as_ref(), &IGCA_DER[4..(8 + 746)]); + // check that cert.as_raw() returns the certificate bytes + assert_eq!(cert.as_raw(), IGCA_DER); } _ => panic!("x509 parsing failed: {:?}", res), } diff --git a/tests/readcrl.rs b/tests/readcrl.rs index 3bc88d2..710d5d7 100644 --- a/tests/readcrl.rs +++ b/tests/readcrl.rs @@ -9,8 +9,11 @@ fn read_crl_verify() { let (_, x509_ca) = X509Certificate::from_der(CA_DATA).expect("could not parse certificate"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let res = crl.verify_signature(&x509_ca.tbs_certificate.subject_pki); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); + + // check that `.as_raw()` returns the input bytes + assert_eq!(crl.as_raw(), CRL_DATA); } fn crl_idp<'a>(crl: &'a CertificateRevocationList) -> &'a IssuingDistributionPoint<'a> { diff --git a/tests/readcsr.rs b/tests/readcsr.rs index 1124c48..fc61ffd 100644 --- a/tests/readcsr.rs +++ b/tests/readcsr.rs @@ -109,8 +109,8 @@ fn read_csr_with_challenge_password() { #[cfg(feature = "verify")] #[test] fn read_csr_verify() { - let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; - let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); + let pem = pem::parse_x509_pem(CSR_DATA).unwrap().1; + let (_, csr) = X509CertificationRequest::from_der(&pem.contents).expect("could not parse CSR"); csr.verify_signature().unwrap(); let mut der = pem::parse_x509_pem(CSR_DATA).unwrap().1; @@ -122,4 +122,7 @@ fn read_csr_verify() { let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); csr.verify_signature().unwrap_err(); + + // check that `.as_raw()` returns the input bytes + assert_eq!(csr.as_raw(), &der.contents); } diff --git a/tests/verify.rs b/tests/verify.rs index 848239d..e51d119 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -11,7 +11,7 @@ fn test_signature_verification() { // for a root CA, verify self-signature let (_, x509_ca) = parse_x509_certificate(CA_DER).expect("could not parse certificate"); let res = x509_ca.verify_signature(None); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); // for a standard certificate, first load the authority, then the certificate, and verify it @@ -19,7 +19,7 @@ fn test_signature_verification() { parse_x509_certificate(CA_LETSENCRYPT_X3).expect("could not parse certificate"); let (_, x509_cert) = parse_x509_certificate(CERT_DER).expect("could not parse certificate"); let res = x509_cert.verify_signature(Some(&x509_ca.tbs_certificate.subject_pki)); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); } @@ -30,7 +30,7 @@ fn test_signature_verification_ed25519() { // this certificate is self-signed let (_, x509_ca) = parse_x509_certificate(ED25519_DER).expect("could not parse certificate"); let res = x509_ca.verify_signature(None); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); } @@ -46,7 +46,7 @@ fn test_signature_verification_rsa_pss_sha256() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA256).expect("could not parse certificate"); let res = x509.verify_signature(None); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); } @@ -55,7 +55,7 @@ fn test_signature_verification_rsa_pss_sha384() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA384).expect("could not parse certificate"); let res = x509.verify_signature(None); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); } @@ -64,6 +64,6 @@ fn test_signature_verification_rsa_pss_sha512() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA512).expect("could not parse certificate"); let res = x509.verify_signature(None); - eprintln!("Verification: {:?}", res); + eprintln!("Verification: {res:?}"); assert!(res.is_ok()); }