diff options
Diffstat (limited to '')
67 files changed, 8908 insertions, 0 deletions
diff --git a/rust/vendor/x509-parser/.cargo-checksum.json b/rust/vendor/x509-parser/.cargo-checksum.json new file mode 100644 index 0000000..23e09c4 --- /dev/null +++ b/rust/vendor/x509-parser/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"26cff1f987bc08f232bc82e844639cdf0321ce27620d94e93799b61ffefcb5fd","Cargo.lock":"50420468b592973ebb987b43fb88fb6212764b5f00f9cf6d1aa35a7bd1504500","Cargo.toml":"ae21317b0dc1957f8879fece77c4b20bf84ad34649e53c32c2a6ccef091ee57f","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"a5c61b93b6ee1d104af9920cf020ff3c7efe818e31fe562c72261847a728f513","README.md":"067be566b938ab29678debf9b618ff23efee1af25b3807be7d7f9cf74184b3e2","assets/IGC_A.der":"b9bea7860a962ea3611dab97ab6da3e21c1068b97d55575ed0e11279c11c8932","assets/IGC_A.pem":"0a2172389aefb412d58187f4707d1947e9ca1e77e30669c4ce03401ad90e6bcc","assets/ca_minimalcrl.der":"e62b689fd79c7e1208fa910df96839ca458671e0aae92573abb37f43d90dc5c7","assets/certificate.der":"8fa4cb4e938b5a855fb1497fa288ac55723e0109d0518b505481348dec0de471","assets/certificate.pem":"ed18153fca7a0ba64c52a2df4d9199e91aa2536621ce7fa7dbcb2365f27199e7","assets/crl-ext/crl-complex.der":"64acfbe4da92d28b6f1f498b21d2f9364529b73743c916c75fbf5eb1b14b6217","assets/crl-ext/crl-no-crl.der":"54f9d02e1d4b148cdbc0c09166d9d9648fd8a7078a734a52b490829775f6afb0","assets/crl-ext/crl-simple.der":"27250bbac66b8120931e1eca3db17a041f5e57a2fa9d4505fb7e14cb36b8dc21","assets/csr-challenge-password.pem":"647eec9fe35374b15633ae47501b1aed5c397bf79e31b3eb6187abb11f6347b6","assets/csr-empty-attributes.csr":"c9590910a25b9de5dabe2cee92c6a776829a3f4517e04a82940f8d63ed7aa28b","assets/duplicate_value_in_authority_info_access.der":"91efafdd6648ddf460a0df21c5606eb497e8499d76486c4ee1d68d7f83f25033","assets/ed25519.der":"d38693b676f3b80b183e7ab61d14182a9d207c131a35ed67696cf8955972551d","assets/empty.crl":"546c5c5659b1712b5a75d56283c1c8b430c27a92380a4d1336cd10304c9f7576","assets/example.crl":"0117fb79eddef3e4fb98ab1fdddbd234227add49f1f68eabdd1a49065b8cff59","assets/extension1.der":"3b9ee8d9070277fdddf85e92d99651ccc4a1736c2079ba492797e34c3cf3b47f","assets/extension2.der":"b62badf95878a9961e26bbcd306711c87f8edbd9c3913070ce328dd90c94fec6","assets/lets-encrypt-x3-cross-signed.der":"25847d668eb4f04fdd40b12b6b0740c567da7d024308eb6c2c96fe41d9de218d","assets/minimal.crl":"8400125989ec8f08e3e42411f36d05887b1a75a28f2ba22a3c0224fde3c6d871","assets/no_end.pem":"e5dcdff988479c74facc6718a11eded6e1f9d496482771ee76358153abb0729f","assets/no_extensions.der":"39f41ff9385a90df31a90fc6c0c686cf2343dea2f18cd058339a0d00a07a51cb","assets/no_extensions.pem":"77f53d745e6391c60360f3e55700ab32a9b0f6f44896412791f0a8ca8a0209e1","assets/test.csr":"591f651ba48a18237103057972ca250cc50640cb760466bfe3a511d9ed19332d","assets/v1.der":"3d6da682d33093d85aa632846ba9fabf31fe90edc091a27fd7f62e514f858c57","examples/print-cert.rs":"036605671f29b612ab08ff1ad42a7a73888e8623fd57b6bd214c4ee0ee828ec1","examples/print-crl.rs":"07ad03a276e11e71a2770215998033268c3e0e7b2457e041359a5d47125feec1","src/certificate.rs":"f1516d8d9a29c0f7c9c6e72d8878acbc8d76b52adae2028fe10d30004d05897e","src/certification_request.rs":"e72df52b00957ae4c94e49697a9c6fc20116f965edd08ca97670e6f1615df69e","src/cri_attributes.rs":"1f9a4c2f8563e1f6e0d39435c74708a43272fa01e10bcb933328afd1592cf3fc","src/error.rs":"071ec0426c9b066d7cc3f9ab41023dda40ffd16fbf93b5d88b710b462505d324","src/extensions/generalname.rs":"38da2f80d50a6222b8ac70ebec7cd84c413a2a56fe7509a0842e354e9ba64ca7","src/extensions/keyusage.rs":"e03fa7fbb5054c321c78c42fd5260f5f5f535fc02155aa51d1af4c8937662de6","src/extensions/mod.rs":"ba6473c8afba47f996b49bad18a61089f5aa3255093d92a05b121e25d72ea431","src/extensions/nameconstraints.rs":"00532ef23fa172cbe80b6ac7ec34725cbb11a01983ccbc9bfe94256e024d4fb2","src/extensions/policymappings.rs":"c1b1d36ffad3bfd2354d80c59d7977ac5d17c0b4fd1a224894c4ebdb1c5d7a73","src/extensions/sct.rs":"04e44fde5c2532fc9276c00e3eb2a7fe1e42335f2dad77b8da137817bb3f6a96","src/lib.rs":"b49ce3a01c6ac209f8106808abfc13b5ff90f015c651cfdab232a0693302f7d3","src/objects.rs":"102ece2f6ce7c484f4daf73665d36f417432db75ab90c5718bda1f4093d4a985","src/pem.rs":"cab9937201c909c9b92946dbabd98dbd06bc5684638d3cdeec2dd829bc0dffa9","src/prelude.rs":"82471321b16588e25df2a6d277acb53ce342de962c61ab2fa4bd3e4c202562aa","src/public_key.rs":"f9c593759ffccb1db9c766ff79b4041c85dcd0e42d10d6ef22d012b3134692df","src/revocation_list.rs":"c7fe79225d53b6a96103d2849451a5a84f37f4da4c4cb7fb5a2df05c685a5739","src/signature_algorithm.rs":"0ee7f84b1ad2004f4cb59ad2ca68d3828ffd8cbc42228bf05f15171d5a590c93","src/signature_value.rs":"b1c1f51429f13ada0a1e9b75b075f53f70e3aa44c5edb5e93ed8d0dff116467b","src/time.rs":"570aef0b70cdebce8adcffbadc3d67c4e4085106b2153c92d4b3ddd976d8acde","src/utils.rs":"59f0f22026da24998225ddb443960e8fda22ae94bb79cb3fe2c523deadfb0395","src/validate/certificate.rs":"9d3c74a1f886392cc10fe7d0a6ba4e0ded2fd195354c649689e7f58bee0ff971","src/validate/extensions.rs":"e7043d5a24ba63f3eb0515a477a757846af9a1f68eb33ef87c6ffd717799cbae","src/validate/loggers.rs":"24b00af5e4c43d5c7a029d8ea7674ec070c985ac092a928471a1d3141c9e3a2d","src/validate/mod.rs":"d748ab37ce2057e23a4b09b3f4c053c6c9d4047a9f53875f5d54c73724dfb146","src/validate/name.rs":"26543fe1a2401e8eb7890c8ae77933d53a3b8dc0b932f6d6b6370589af702048","src/validate/structure.rs":"0b6046c021bd780abcebc47abebc29023f6a2b6f0e3123ea0622dab5610c3512","src/verify.rs":"d6e798ac095395835d460f63a60f64dbbd18081cb2be979b4edb385ef5ccda2f","src/x509.rs":"e3d5bcee663faddad4a93a89d32217ea222ae8ad08daeec342aa757bdbd28a5b","tests/pem.rs":"eb568a214fcfaa0822dca524f75f1ffd6381e92897728eeba237ae584e870a21","tests/readcert.rs":"a97110e9d0572a307155495dedbe14ad401750c7d27a26f96dc89923e8c42bdf","tests/readcrl.rs":"4671c737cb93acbe1ced61f14985d3a173f779929f13048725734623a505ce47","tests/readcsr.rs":"82b6e6352672a960eca44d7f88b2c39eb3583b2d22e0e1467de5ba178b92df95","tests/run_all_fuzz_files.rs":"bc3a4c1b9792f7d6bf5767775be07c3720c1ae8567b2c865b4e204c3a70d7359","tests/test01.rs":"fcb258a7204ac7a4c62ab46e12126c11edf23b62a7f24ef40ae7d843482e1341","tests/verify.rs":"a6c454e84809cdd8b6faeb2bab94719d0ffbfe9fbe01fc68a69e622cda3517f1"},"package":"7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da"}
\ No newline at end of file diff --git a/rust/vendor/x509-parser/CHANGELOG.md b/rust/vendor/x509-parser/CHANGELOG.md new file mode 100644 index 0000000..36c4f63 --- /dev/null +++ b/rust/vendor/x509-parser/CHANGELOG.md @@ -0,0 +1,355 @@ +# ChangeLog + +## [Unreleased][unreleased] + +### Added/Changed/Fixed + +### Thanks + +## 0.15.1 + +### Added/Changed/Fixed + +- Attribute: fix parsing of BmpString string type to use UTF-16 (Closes #143) +- `revocation_list`: use correct OID for CRL number. +- Fix receiver lifetimes in `AttributeTypeAndValue` + +### Thanks + +- Sergio Benitez, Daniel McCarney, Lily Ballard + +## 0.15.0 + +### Added/Changed/Fixed + +Global: +- Use SPDX license format (#137) +- Set MSRV to 1.57 (due to `ring`/`once_cell`) +- Switch base64 decoding to `data-encoding` crate (#136) + +Code: +- Add `verify` feature to verify a certificate revocation list by a public key +- Fixed CriAttributes parser (#131) +- Refactor code for parsing X509Version +- Add verify signature method to revocation list (#130) +- Add support for parsing challenge password attribute in CSR's (#129) +- Add support for multi-word PEM labels (C#135) + +Docs: +- Fix broken FromDer trait link in README + +### Thanks + +- Bernd Krietenstein, Florian Zipperle, Jean-Baptiste Trystram, Daniel McCarney, + Jeff Hiner, Campbell He, Sebastian Dröge + +## 0.14.0 + +### Added/Changed + +- Add support for parsing signature parameters and value (closes #94) + +- Change `ASN1Time::to_rfc2822()` to return a Result +- ASN1Time: modify `from_timestamp` to return a Result +- ASN1Time: implement Display +- Upgrade versions of asn1-rs, oid-registry and der-parser +- AlgorithmIdentifier: add const methods to create object/access fields +- Globally: start using `asn1-rs` types, simplify parsers: + - AlgorithmIdentifier: automatically derive struct, use type ANY + - Merge old FromDer trait into `asn1_rs::FromDer` (using X509Error) + - Replace BitStringObject with BitString + - AttributeTypeAndValue: use Any instead of DerObject + - Extensions: replace UnparsedObject with Any + - X509Error: add methods to simplify conversions + - CRI Attributes: rewrite and simplify parsers + - Simplify parsers for multiple types and extensions + +### Fixed + +- Fix ECDSA signature verification when CA and certificate use different curves + +### Thanks + +## 0.13.2 + +### Fixed + +- Fix panic in ASN1Time::to_rfc2822() when year is less than 1900 + +## 0.13.1 + +### Fixed + +- Fix regression with certificate verification for ECDSA signatures using the P-256 curve and SHA-384 (#118) +- Set minimum version of `time` to 0.3.7 (#119) +- Allow empty SEQUENCE when OPTIONAL, for ex in CRL extensions (#120) + +### Thanks + +- @SergioBenitez, @flavio, @acarlson0000 + +## 0.13.0 + +### Added/Changed/Fixed + +Crate: +- Update to der-parser 7.0 and asn1-rs +- Remove chrono (#111) +- Set MSRV to 1.53 + +Validators: +- Add `Deref<Target=TbsCertificate>` trait to `X509Certificate` +- Add `Validator` trait and deprecate `Validate` + * The previous validation is implemented in `X509StructureValidator` + * Split some checks (not on structure) to `X509CertificateValidator` + +Extensions: +- add support for nsComment +- add support for IssuerAltName +- start adding support for CT Signed Certificate Timestamp (rfc6962) +- raise error if a SAN entry cannot be parsed +- deprecate `TbsCertificate::find_extension()` and add preferred method `TbsCertificate::get_extension_unique()`: + the latter checks for duplicate extensions (#113) + +Signatures: +- Fix signature verification for EC curves (#116) + +Public Keys: +- Add base functions for parsing public keys (RSA, DSA, GOST) + +### Thanks + +- @lilyball, @g2p + +## 0.12.0 + +### Added/Changed/Fixed + +- Upgrade to nom 7 + +## 0.11.0 + +### Added + +- Add SubjectPublicKeyInfo::raw field + +### Changed/Fixed + +- Fix der-parser dependency (#102) +- Update oid-registry dependency (#77) +- Set MSRV to 1.46 (indirect dependency on lexical-core and bitvec) +- Extend the lifetimes exposed on TbsCertificate (#104) +- Add missing test assets (#103) + +### Thanks + +- @jgalenson, @g2p, @kpp + +## 0.10.0 + +### Added + +- Add the `Validate` trait to run post-parsing validations of X.509 structure +- Add the `FromDer` trait to unify parsing methods and visibility (#85) +- Add method to format X509Name using a given registry +- Add `X509Certificate::public_key()` method +- Add ED25519 as a signature algorithm (#95) +- Add support for extensions (#86): + - CRL Distribution Points +- Add `X509CertificateParser` builder to allow specifying parsing options + +### Changed/Fixed + +- Extensions are now stored in order of appearance in the certificate/CRL (#80) + - `.extensions` field is not public anymore, but methods `.extensions()` and `.extensions_map()` + have been added +- Store CRI attributes in order +- Fix parsing of CertificatePolicies, and use named types (closes #82) +- Allow specifying registry in oid2sn and similar functions (closes #88) +- Mark X509Extension::new as const fn + inline +- Allow leading zeroes in serial number +- Derive `Clone` for all types (when possible) (#89) +- Fix certificate validity period check to be inclusive (#90) +- Do not fail GeneralName parsing for x400Address and ediPartyName, read it as unparsed objects (#87) +- Change visibility of fields in `X509Name` (replaced by accessors) + +### Thanks + +- @lilyball for numerous issues, ideas and comments +- @SergioBenitez for lifetimes fixes (#93) and validity period check fixes (#90) +- @rappet for Ed25519 signature verification support (#95) +- @xonatius for the work on CRLDistributionPoints (#96, #98) + +## 0.9.3 + +### Added/Changed/Fixed + +- Add functions oid2description() and oid_registry() (closes #79) +- Fix typo 'ocsp_signing' (closes #84) +- Extension: use specific variant if unsupported or failed to parse (closes #83) +- Relax constrains on parsing to accept certificates that do not strictly respect + DER encoding, but are widely accepted by other X.509 libraries: + - SubjectAltName: accept non-ia5string characters + - Extensions: accept boolean values not enoded as `00` or `ff` + - Serial: build BigUint from raw bytes (do not check sign) + +## 0.9.2 + +### Added/Changed/Fixed + +- Remove der-oid-macro from dependencies, not used directly +- Use der_parser::num_bigint, remove it from direct dependencies +- Add methods to iterate all blocks from a PEM file (#75) +- Update MSRV to 1.45.0 + +## 0.9.1 + +### Added/Changed/Fixed + +- Fix: X509Name::iter_state_or_province OID value +- Re-export oid-registry, and add doc to show how to access OID + +### Thanks + +- @0xazure for fixing X509Name::iter_state_or_province + +## 0.9.0 + +### Added/Changed/Fixed + +- Upgrade to `nom` 6.0 +- Upgrade to `der-parser` 5.0 +- Upgrade MSRV to 1.44.0 +- Re-export crates so crate users do not have to import them + +- Add function parse_x509_pem and deprecate pem_to_der (#53) +- Add helper methods to X509Name and simplify accessing values +- Add support for ReasonCode extension +- Add support for InvalidityDate extension +- Add support for CRL Number extension +- Add support for Certificate Signing Request (#58) + +- Change type of X509Version (now directly using the u32 value) +- X509Name: relax check, allow some non-rfc compliant strings (#50) +- Relax some constraints for invalid dates +- CRL: extract raw serial, and add methods to access it +- CRL: add method to iterate revoked certificates +- RevokedCertificate: convert extensions list to hashmap + +- Refactor crate modules and visibility +- Rename top-level functions to `parse_x509_certificate` and parse_x509_crl` + +- Refactor error handling, return meaningful errors when possible +- Make many more functions public (parse_tbs_certificate, etc.) + +### Thanks + +- Dirkjan Ochtman (@djc): support for Certificate Signing Request (CSR), code refactoring, etc. + +## 0.8.0 + +### Added/Changed + +- Upgrade to `der-parser` 4.0 +- Move from `time` to `chrono` + - `time 0.1 is very old, and time 0.2 broke compatibility and cannot parse timezones + - Add public type `ASN1Time` object to abstract implementation + - *this breaks API for direct access to `not_before`, `not_after` etc.* +- Fix clippy warnings + - `nid2obj` argument is now passed by copy, not reference +- Add method to get a formatted string of the certificate serial number +- Add method to get decoded version +- Add convenience methods to access the most common fields (subject, issuer, etc.) +- Expose the raw DER of an X509Name +- Make `parse_x509_name` public, for parsing distinguished names +- Make OID objects public +- Implement parsing for some extensions + - Support for extensions is not complete, support for more types will be added later +- Add example to decode and print certificates +- Add `verify` feature to verify cryptographic signature by a public key + +### Fixed + +- Fix parsing of types not representable by string in X509Name (#36) +- Fix parsing of certificates with empty subject (#37) + +### Thanks + +- @jannschu, @g2p for the extensions parsing +- @wayofthepie for the tests and contributions +- @nicholasbishop for contributions + +## 0.7.0 + +- Expose raw bytes of the certificate serial number +- Set edition to 2018 + +## 0.6.4 + +- Fix infinite loop when certificate has no END mark + +## 0.6.3 + +- Fix infinite loop when reading non-pem data (#28) + +## 0.6.2 + +- Remove debug code left in `Pem::read` + +## 0.6.1 + +- Add CRL parser +- Expose CRL tbs bytes +- PEM: ignore lines before BEGIN label (#21) +- Fix parsing default values for TbsCertificate version field (#24) +- Use BerResult from der-parser for simpler function signatures +- Expose tbsCertificate bytes +- Upgrade dependencies (base64) + +## 0.6.0 + +- Update to der-parser 3.0 and nom 5 +- Breaks API, cleaner error types + +## 0.5.1 + +- Add `time_to_expiration` to `Validity` object +- Add method to read a `Pem` object from `BufRead + Seek` +- Add method to `Pem` to decode and extract certificate + +## 0.5.0 + +- Update to der-parser 2.0 + +## 0.4.3 + +- Make `parse_subject_public_key_info` public +- Add function `sn2oid` (get an OID by short name) + +## 0.4.2 + +- Support GeneralizedTime conversion + +## 0.4.1 + +- Fix case where certificate has no extensions + +## 0.4.0 + +- Upgrade to der-parser 1.1, and Use num-bigint over num +- Rename x509_parser to parse_x509_der +- Do not export subparsers +- Improve documentation + +## 0.3.0 + +- Upgrade to nom 4 + +## 0.2.0 + +- Rewrite X.509 structures and parsing code to work in one pass + **Warning: this is a breaking change** +- Add support for PEM-encoded certificates +- Add some documentation + + diff --git a/rust/vendor/x509-parser/Cargo.lock b/rust/vendor/x509-parser/Cargo.lock new file mode 100644 index 0000000..d8e6a17 --- /dev/null +++ b/rust/vendor/x509-parser/Cargo.lock @@ -0,0 +1,462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "cc" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "serde" +version = "1.0.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "time" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +dependencies = [ + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "x509-parser" +version = "0.15.1" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", + "time", +] diff --git a/rust/vendor/x509-parser/Cargo.toml b/rust/vendor/x509-parser/Cargo.toml new file mode 100644 index 0000000..dbc6a42 --- /dev/null +++ b/rust/vendor/x509-parser/Cargo.toml @@ -0,0 +1,100 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "x509-parser" +version = "0.15.1" +authors = ["Pierre Chifflier <chifflier@wzdftpd.net>"] +include = [ + "CHANGELOG.md", + "LICENSE-*", + "README.md", + ".gitignore", + ".travis.yml", + "Cargo.toml", + "src/*.rs", + "src/extensions/*.rs", + "src/validate/*.rs", + "tests/*.rs", + "assets/*.crl", + "assets/*.csr", + "assets/*.der", + "assets/*.pem", + "assets/crl-ext/*.der", + "examples/*.rs", +] +description = "Parser for the X.509 v3 format (RFC 5280 certificates)" +homepage = "https://github.com/rusticata/x509-parser" +readme = "README.md" +keywords = [ + "X509", + "Certificate", + "parser", + "nom", +] +categories = [ + "parser-implementations", + "cryptography", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/rusticata/x509-parser.git" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[dependencies.asn1-rs] +version = "0.5" +features = ["datetime"] + +[dependencies.data-encoding] +version = "2.2.1" + +[dependencies.der-parser] +version = "8.1.0" +features = ["bigint"] + +[dependencies.lazy_static] +version = "1.4" + +[dependencies.nom] +version = "7.0" + +[dependencies.oid-registry] +version = "0.6" +features = [ + "crypto", + "x509", + "x962", +] + +[dependencies.ring] +version = "0.16.11" +optional = true + +[dependencies.rusticata-macros] +version = "4.0" + +[dependencies.thiserror] +version = "1.0.2" + +[dependencies.time] +version = "0.3.7" +features = ["formatting"] + +[features] +default = [] +validate = [] +verify = ["ring"] diff --git a/rust/vendor/x509-parser/LICENSE-APACHE b/rust/vendor/x509-parser/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/rust/vendor/x509-parser/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/rust/vendor/x509-parser/LICENSE-MIT b/rust/vendor/x509-parser/LICENSE-MIT new file mode 100644 index 0000000..290e7b9 --- /dev/null +++ b/rust/vendor/x509-parser/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2017 Pierre Chifflier + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/rust/vendor/x509-parser/README.md b/rust/vendor/x509-parser/README.md new file mode 100644 index 0000000..cf055f2 --- /dev/null +++ b/rust/vendor/x509-parser/README.md @@ -0,0 +1,135 @@ +<!-- cargo-sync-readme start --> + +[](./LICENSE-MIT) +[](./LICENSE-APACHE) +[](https://docs.rs/x509-parser) +[](https://crates.io/crates/x509-parser) +[](https://crates.io/crates/x509-parser) +[](https://github.com/rusticata/x509-parser/actions) +[](#rust-version-requirements) + +# X.509 Parser + +A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) +parser combinator framework. + +It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken +to ensure security and safety of this crate, including design (recursion limit, defensive +programming), tests, and fuzzing. It also aims to be panic-free. + +The code is available on [Github](https://github.com/rusticata/x509-parser) +and is part of the [Rusticata](https://github.com/rusticata) project. + +Certificates are usually encoded in two main formats: PEM (usually the most common format) or +DER. A PEM-encoded certificate is a container, storing a DER object. See the +[`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module for more documentation. + +To decode a DER-encoded certificate, the main parsing method is +`X509Certificate::from_der` ( +part of the [`FromDer`](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html) trait +), which builds a +[`X509Certificate`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html) object. + +An alternative method is to use [`X509CertificateParser`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509CertificateParser.html), +which allows specifying parsing options (for example, not automatically parsing option contents). + +The returned objects for parsers follow the definitions of the RFC. This means that accessing +fields is done by accessing struct members recursively. Some helper functions are provided, for +example [`X509Certificate::issuer()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.issuer) returns the +same as accessing `<object>.tbs_certificate.issuer`. + +For PEM-encoded certificates, use the [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module. + +# Examples + +Parsing a certificate in DER format: + +```rust +use x509_parser::prelude::*; + +static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); + +let res = X509Certificate::from_der(IGCA_DER); +match res { + Ok((rem, cert)) => { + assert!(rem.is_empty()); + // + assert_eq!(cert.version(), X509Version::V3); + }, + _ => panic!("x509 parsing failed: {:?}", res), +} +``` + +To parse a CRL and print information about revoked certificates: + +```rust +# +# +let res = CertificateRevocationList::from_der(DER); +match res { + Ok((_rem, crl)) => { + for revoked in crl.iter_revoked_certificates() { + println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); + println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); + } + }, + _ => panic!("CRL parsing failed: {:?}", res), +} +``` + +See also `examples/print-cert.rs`. + +# Features + +- The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. + It adds the + [`X509Certificate::verify_signature()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.verify_signature) + to `X509Certificate`. + +```rust +/// Cryptographic signature verification: returns true if certificate was signed by issuer +#[cfg(feature = "verify")] +pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { + let issuer_public_key = issuer.public_key(); + cert + .verify_signature(Some(issuer_public_key)) + .is_ok() +} +``` + +- The `validate` features add methods to run more validation functions on the certificate structure + and values using the [`Validate`](https://docs.rs/x509-parser/latest/x509_parser/validate/trait.Validate.html) trait. + It does not validate any cryptographic parameter (see `verify` above). + +## Rust version requirements + +`x509-parser` requires **Rustc version 1.57 or greater**, based on der-parser +dependencies and for proc-macro attributes support. + +Note that due to breaking changes in the `time` crate, a specific version of this +crate must be specified for compiler versions <= 1.57: +`cargo update -p time --precise 0.3.9` + +[RFC5280]: https://tools.ietf.org/html/rfc5280 +<!-- cargo-sync-readme end --> + +## Changes + +See [CHANGELOG.md](CHANGELOG.md) + +# License + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rust/vendor/x509-parser/assets/IGC_A.der b/rust/vendor/x509-parser/assets/IGC_A.der Binary files differnew file mode 100644 index 0000000..2c55538 --- /dev/null +++ b/rust/vendor/x509-parser/assets/IGC_A.der diff --git a/rust/vendor/x509-parser/assets/IGC_A.pem b/rust/vendor/x509-parser/assets/IGC_A.pem new file mode 100644 index 0000000..8940ef5 --- /dev/null +++ b/rust/vendor/x509-parser/assets/IGC_A.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT +AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ +TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG +9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw +MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM +BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO +MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 +LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI +s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 +xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 +u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b +F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx +Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd +PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV +HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx +NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF +AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ +L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY +YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a +NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R +0982gaEbeC9xs/FZTEYYKKuF0mBWWg== +-----END CERTIFICATE----- diff --git a/rust/vendor/x509-parser/assets/ca_minimalcrl.der b/rust/vendor/x509-parser/assets/ca_minimalcrl.der Binary files differnew file mode 100644 index 0000000..7635da4 --- /dev/null +++ b/rust/vendor/x509-parser/assets/ca_minimalcrl.der diff --git a/rust/vendor/x509-parser/assets/certificate.der b/rust/vendor/x509-parser/assets/certificate.der Binary files differnew file mode 100644 index 0000000..e5f48f5 --- /dev/null +++ b/rust/vendor/x509-parser/assets/certificate.der diff --git a/rust/vendor/x509-parser/assets/certificate.pem b/rust/vendor/x509-parser/assets/certificate.pem new file mode 100644 index 0000000..3e08af7 --- /dev/null +++ b/rust/vendor/x509-parser/assets/certificate.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWzCCBEOgAwIBAgISAyBIAwu7NBD5CTxX8suDCMgFMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA3MTIxMTEyMzBaFw0x +OTEwMTAxMTEyMzBaMB0xGzAZBgNVBAMTEmxpc3RzLmZvci1vdXIuaW5mbzCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVoti34X46DaI2nX24C+aZ2Ofkm +hKbidiXiRTon1MLSMGl1oNW9MyRyYYCzP4j6DNKChJnr8ZnVShh2oZD+yHWP9lpn +XMGkbsUxejRMU9hnaAB50pXRIDAzavkVFCguFlJ8nKkv/Y1Avlw7tc2aZOd3lOZB +Er8gJ8mRDGqqsNU+Z12I6slEstzGMpsq6AewCVw4lMjdWWgugzUrxQTRAsG87on6 +gOiQH2cMODN3L7Fq4KOLQIjb3/luQhAQhpdKmEGFLin3c+f5or3thCDuwwDtOU1l +Zf+8t9S8pZPLrZrIs6H2xjXqCRuUY7iRNbO18Ukc6rlDYhBj9LT+cpmBbHECAwEA +AaOCAmYwggJiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI +KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUJj2pvRtl3GloH3He6FX1 +ds3X0VEwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUH +AQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5 +cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5 +cHQub3JnLzAdBgNVHREEFjAUghJsaXN0cy5mb3Itb3VyLmluZm8wTAYDVR0gBEUw +QzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDov +L2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdgAp +PFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0eAAAAWvmGV7yAAAEAwBHMEUC +ICQL2Sm14aCMLxX9a9RbySgyBfichMRdbu6QA2Mbrl4eAiEA1vgJ7snqUWCgoqEE +3SEfK3ioMopzWBsPvG6LdCuCMRAAdQBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkG +jbIImjfZEwAAAWvmGV9oAAAEAwBGMEQCIExGqw3Lo0nSCyUuTRf92FgGASwWYji5 +UGnXuYnpJrAvAiBw8AWVag8fzZ4ogAhY9EFRNdLrUcBjStipL888vyuxKzANBgkq +hkiG9w0BAQsFAAOCAQEAF8BBLDvSWZg57B6aDtzfUTSGetCYs3k0vJqCJlL+Pz7/ +UruCSsojQzp5R6jvvgYQ83MaIdwe2mgt+OCQB5v7ylctyBzBmYIw9nPnxEC7HlcJ +L2K/k5ZjJFRnv4kV1Si8+TIpEAV0ksf39KGKemG8kGi4GXV1v03zSv0p8aCarpuo +SKBJ4qlB0CvmS2MqV4KnzO0O2h0c/ZQ4jg7l53eiN7VPdRMMO1DRw+MaW6I/hEZp ++oZQ7hhKXgKUBvF4IGwyrfyIZ8AeWKG4IP98COgyRbz7qtrAVevRKCM0ZC2t04A2 +Fcix40FKEeiE093Aj3cweMYxNLPgwgQP8Xu3kA5QEw== +-----END CERTIFICATE----- diff --git a/rust/vendor/x509-parser/assets/crl-ext/crl-complex.der b/rust/vendor/x509-parser/assets/crl-ext/crl-complex.der Binary files differnew file mode 100644 index 0000000..f5140f1 --- /dev/null +++ b/rust/vendor/x509-parser/assets/crl-ext/crl-complex.der diff --git a/rust/vendor/x509-parser/assets/crl-ext/crl-no-crl.der b/rust/vendor/x509-parser/assets/crl-ext/crl-no-crl.der Binary files differnew file mode 100644 index 0000000..03f7357 --- /dev/null +++ b/rust/vendor/x509-parser/assets/crl-ext/crl-no-crl.der diff --git a/rust/vendor/x509-parser/assets/crl-ext/crl-simple.der b/rust/vendor/x509-parser/assets/crl-ext/crl-simple.der Binary files differnew file mode 100644 index 0000000..224a743 --- /dev/null +++ b/rust/vendor/x509-parser/assets/crl-ext/crl-simple.der diff --git a/rust/vendor/x509-parser/assets/csr-challenge-password.pem b/rust/vendor/x509-parser/assets/csr-challenge-password.pem new file mode 100644 index 0000000..a48e99d --- /dev/null +++ b/rust/vendor/x509-parser/assets/csr-challenge-password.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIFuDCCA6ACAQAwgb4xCzAJBgNVBAYTAkdCMR8wHQYDVQQIDBZUZXN0IFN0YXRl +IG9yIFByb3ZpbmNlMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRowGAYDVQQKDBFP +cmdhbml6YXRpb24gTmFtZTEhMB8GA1UECwwYT3JnYW5pemF0aW9uYWwgVW5pdCBO +YW1lMRQwEgYDVQQDDAtDb21tb24gTmFtZTEhMB8GCSqGSIb3DQEJARYSdGVzdEBl +bWFpbC5hZGRyZXNzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2pTx +B0wQkImcsMaHCO1jYsFRii4JhWJQT326yzI/1FM7pWA/s/kecMOkWVn/KtGDroqE +9Jw6UQOEsmFpLJEOTzAaGbNMM2BkJBNdhO2Vmmhyjks1sJljTftA+aKu4zpxs0i6 +r83oMc6iJ08kMMyDYrKCt4oJjln7dpJXc8OoV9INoRpyU2KhTiqJdw0+4V8lB3BI +QGP6o4mN1Jx/ElJogWyS4CB3wt9I7n1RH08+f/StCs0iAkASKFxAoCLrdIKSk4CM +yUFilaZz6SvHXiLLi+NoLkc+nDaDu4Nu7Pj/e2fYtq8dLRB8A25v5wqQplF7s/ZJ +wiFHVfFJDY8wtdBJ12bgLOPQbqmpBiWVrnBZHGMQTsc5YSMHy6EPv33w16cUWneu +Ho/ryqBwTWww+fCnjOapkSajckmVpm8e4fijhNmq6N6VTpfgOZ3loCjVOp4/EW3M +L+qsYQyM5Y4trY4h0zZh9hZZmQig6vLMIl9n1r+580rLeGsdeyHwHAc6jfwsy4Zz +/jOfbWF3rB1xNgSBHL0po02k8PWFr+uBfKPmfTK8Yvlz3fsTJfzLr6u6y3XAejw/ +ZZjjPydwLn3hL/q+J9SAhA3wxnCu3puwiyjhQQHgUHLkQsYpivdF1a7Pbz+l1D89 +cTOwwkYonkReosLI5QiKbQAX2NbSfVqv8ALn09sCAwEAAaCBszAjBgkqhkiG9w0B +CQcxFgwUQSBjaGFsbGVuZ2UgcGFzc3dvcmQwgYsGCSqGSIb3DQEJDjF+MHwwDgYD +VR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV +HQ4EFgQUkDknz2bwybBXuXEG1RmUpP84GxMwLAYDVR0RBCUwI4cEfwAAAYcQAAAA +AAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4ICAQAll1fo +6YIKQHINhdj6aKRHKZ/CluFSTAvctga5+JQG8clbWWxjI+RDUZhnw/r5Jr+8OaBW +rm7c/aDePP9AOQ93tBfIEv4OMwP4jQVOrZJuAOuyrUoSTSehMPZoR7IZv2QcyRk+ +RUi2aqZbYLPFptsJ6+8FlX/ieSM+GEBmj2W9U5LsvI2y6B34N0uy/if9IbQO8OHj +afrjZc2jZ1QYj69OUJekEjPZzrb3kSIVELyWfZEocq+e0hLgQEdI0ZCIdzKFeH8i +yQfCtJ7g7k/hVkrQcyd3JwFvhIJRlyhw8ORxOHqFsyZ2XWvg0lGVWk9ksRDGFcf3 +g38Erl1sU3jPzEfb/B2aio2DO+42ZY65P7nuLNB5Tatdyy+yYmd3OlxL0XtUb/VW +DS9zKjNrjBLvgN9w5QUE0hc4HOHfcceEcPljALoeZWF/XmPjEUjbiKSIP7vIKDi2 +bkx1LiLnuDzjvapG/C6YmuFrTxoqjc9qZs/e2NjrxJ5tI3s/d9R9UET+jh/1swLH +wz35SohLLDLc2ShaNlNuMOwWOySFV93RwjxcP6OsEFyCZrVsQ6UXz6odXk509A5e +w3xhpMdQFlsNRpH0f7CjcxsTUx3qFEXMwq0iYt9w0wrTezOTL25oGouHH11JQsZh +g7iXkOHZIfzZVIF3sagZhdp7stnJTWnUIhJvXw== +-----END CERTIFICATE REQUEST----- diff --git a/rust/vendor/x509-parser/assets/csr-empty-attributes.csr b/rust/vendor/x509-parser/assets/csr-empty-attributes.csr Binary files differnew file mode 100644 index 0000000..bfb84c8 --- /dev/null +++ b/rust/vendor/x509-parser/assets/csr-empty-attributes.csr diff --git a/rust/vendor/x509-parser/assets/duplicate_value_in_authority_info_access.der b/rust/vendor/x509-parser/assets/duplicate_value_in_authority_info_access.der Binary files differnew file mode 100644 index 0000000..1379255 --- /dev/null +++ b/rust/vendor/x509-parser/assets/duplicate_value_in_authority_info_access.der diff --git a/rust/vendor/x509-parser/assets/ed25519.der b/rust/vendor/x509-parser/assets/ed25519.der Binary files differnew file mode 100644 index 0000000..cae76a1 --- /dev/null +++ b/rust/vendor/x509-parser/assets/ed25519.der diff --git a/rust/vendor/x509-parser/assets/empty.crl b/rust/vendor/x509-parser/assets/empty.crl Binary files differnew file mode 100644 index 0000000..fc3f6b3 --- /dev/null +++ b/rust/vendor/x509-parser/assets/empty.crl diff --git a/rust/vendor/x509-parser/assets/example.crl b/rust/vendor/x509-parser/assets/example.crl Binary files differnew file mode 100644 index 0000000..3df0b6f --- /dev/null +++ b/rust/vendor/x509-parser/assets/example.crl diff --git a/rust/vendor/x509-parser/assets/extension1.der b/rust/vendor/x509-parser/assets/extension1.der Binary files differnew file mode 100644 index 0000000..d3f14be --- /dev/null +++ b/rust/vendor/x509-parser/assets/extension1.der diff --git a/rust/vendor/x509-parser/assets/extension2.der b/rust/vendor/x509-parser/assets/extension2.der Binary files differnew file mode 100644 index 0000000..b0f7006 --- /dev/null +++ b/rust/vendor/x509-parser/assets/extension2.der diff --git a/rust/vendor/x509-parser/assets/lets-encrypt-x3-cross-signed.der b/rust/vendor/x509-parser/assets/lets-encrypt-x3-cross-signed.der Binary files differnew file mode 100644 index 0000000..e08466c --- /dev/null +++ b/rust/vendor/x509-parser/assets/lets-encrypt-x3-cross-signed.der diff --git a/rust/vendor/x509-parser/assets/minimal.crl b/rust/vendor/x509-parser/assets/minimal.crl Binary files differnew file mode 100644 index 0000000..e77d446 --- /dev/null +++ b/rust/vendor/x509-parser/assets/minimal.crl diff --git a/rust/vendor/x509-parser/assets/no_end.pem b/rust/vendor/x509-parser/assets/no_end.pem new file mode 100644 index 0000000..9bd844e --- /dev/null +++ b/rust/vendor/x509-parser/assets/no_end.pem @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF +YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD +DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW +ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 +HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl +AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 +K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 +iV6NtZU= diff --git a/rust/vendor/x509-parser/assets/no_extensions.der b/rust/vendor/x509-parser/assets/no_extensions.der Binary files differnew file mode 100644 index 0000000..39278c6 --- /dev/null +++ b/rust/vendor/x509-parser/assets/no_extensions.der diff --git a/rust/vendor/x509-parser/assets/no_extensions.pem b/rust/vendor/x509-parser/assets/no_extensions.pem new file mode 100644 index 0000000..de3731f --- /dev/null +++ b/rust/vendor/x509-parser/assets/no_extensions.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF +YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD +DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW +ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 +HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl +AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 +K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 +iV6NtZU= +-----END CERTIFICATE----- diff --git a/rust/vendor/x509-parser/assets/test.csr b/rust/vendor/x509-parser/assets/test.csr new file mode 100644 index 0000000..729df67 --- /dev/null +++ b/rust/vendor/x509-parser/assets/test.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST-----
+MIIBBjCBrQIBADAcMRowGAYDVQQDDBF0ZXN0LnJ1c3RpY2F0YS5mcjBZMBMGByqG
+SM49AgEGCCqGSM49AwEHA0IABMP1frFxwJLXiLU6UoqOPf31ucCm2NqR2yqpcHo6
+W7iWJe31OzYs0izP2qeUvdKfz2fpAbuGiRjwvN+H10dQQEGgLzAtBgkqhkiG9w0B
+CQ4xIDAeMBwGA1UdEQQVMBOCEXRlc3QucnVzdGljYXRhLmZyMAoGCCqGSM49BAMC
+A0gAMEUCIGqQHPHgpeyZa5YMLP2X5IwfmrvpIcg5fQ2xkXotGAa0AiEAydeBwr4r
+Iu7XDe015h8uz8xZs2QUEgRdr73lJXTX+Ck=
+-----END CERTIFICATE REQUEST-----
diff --git a/rust/vendor/x509-parser/assets/v1.der b/rust/vendor/x509-parser/assets/v1.der Binary files differnew file mode 100644 index 0000000..f749852 --- /dev/null +++ b/rust/vendor/x509-parser/assets/v1.der diff --git a/rust/vendor/x509-parser/examples/print-cert.rs b/rust/vendor/x509-parser/examples/print-cert.rs new file mode 100644 index 0000000..9792085 --- /dev/null +++ b/rust/vendor/x509-parser/examples/print-cert.rs @@ -0,0 +1,417 @@ +use der_parser::der::Tag; +use der_parser::oid::Oid; +use nom::HexDisplay; +use std::cmp::min; +use std::convert::TryFrom; +use std::env; +use std::io; +use std::net::{Ipv4Addr, Ipv6Addr}; +use x509_parser::prelude::*; +use x509_parser::public_key::PublicKey; +use x509_parser::signature_algorithm::SignatureAlgorithm; + +const PARSE_ERRORS_FATAL: bool = false; +#[cfg(feature = "validate")] +const VALIDATE_ERRORS_FATAL: bool = false; + +fn print_hex_dump(bytes: &[u8], max_len: usize) { + let m = min(bytes.len(), max_len); + print!("{}", &bytes[..m].to_hex(16)); + if bytes.len() > max_len { + println!("... <continued>"); + } +} + +fn format_oid(oid: &Oid) -> String { + match oid2sn(oid, oid_registry()) { + Ok(s) => s.to_owned(), + _ => 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), + } +} + +fn print_x509_extension(oid: &Oid, ext: &X509Extension) { + println!( + " [crit:{} l:{}] {}: ", + ext.critical, + ext.value.len(), + format_oid(oid) + ); + match ext.parsed_extension() { + ParsedExtension::AuthorityKeyIdentifier(aki) => { + println!(" X509v3 Authority Key Identifier"); + if let Some(key_id) = &aki.key_identifier { + println!(" Key Identifier: {:x}", key_id); + } + if let Some(issuer) = &aki.authority_cert_issuer { + for name in issuer { + println!(" Cert Issuer: {}", name); + } + } + if let Some(serial) = aki.authority_cert_serial { + println!(" Cert Serial: {}", format_serial(serial)); + } + } + ParsedExtension::BasicConstraints(bc) => { + println!(" X509v3 CA: {}", bc.ca); + } + ParsedExtension::CRLDistributionPoints(points) => { + println!(" X509v3 CRL Distribution Points:"); + for point in points.iter() { + if let Some(name) = &point.distribution_point { + println!(" Full Name: {:?}", name); + } + if let Some(reasons) = &point.reasons { + println!(" Reasons: {}", reasons); + } + if let Some(crl_issuer) = &point.crl_issuer { + print!(" CRL Issuer: "); + for gn in crl_issuer { + print!("{} ", generalname_to_string(gn)); + } + println!(); + } + println!(); + } + } + ParsedExtension::KeyUsage(ku) => { + println!(" X509v3 Key Usage: {}", ku); + } + ParsedExtension::NSCertType(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) + } + GeneralName::IPAddress(b) => { + let ip = match b.len() { + 4 => { + let b = <[u8; 4]>::try_from(*b).unwrap(); + let ip = Ipv4Addr::from(b); + format!("{}", ip) + } + 16 => { + let b = <[u8; 16]>::try_from(*b).unwrap(); + let ip = Ipv6Addr::from(b); + format!("{}", ip) + } + l => format!("invalid (len={})", l), + }; + format!("IP Address:{}", ip) + } + _ => { + format!("{:?}", name) + } + }; + println!(" X509v3 SAN: {}", s); + } + } + ParsedExtension::SubjectKeyIdentifier(id) => { + println!(" X509v3 Subject Key Identifier: {:x}", id); + } + x => println!(" {:?}", x), + } +} + +fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { + println!( + "{:indent$}Oid: {}", + "", + format_oid(&alg.algorithm), + indent = level + ); + if let Some(parameter) = &alg.parameters { + let s = match parameter.tag() { + Tag::Oid => { + let oid = parameter.as_oid().unwrap(); + format_oid(&oid) + } + _ => format!("{}", parameter.tag()), + }; + println!("{:indent$}Parameter: <PRESENT> {}", "", s, indent = level); + let bytes = parameter.as_bytes(); + print_hex_dump(bytes, 32); + } else { + println!("{:indent$}Parameter: <ABSENT>", "", indent = level); + } +} + +fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { + let version = x509.version(); + if version.0 < 3 { + println!(" Version: {}", version); + } else { + println!(" Version: INVALID({})", version.0); + } + println!(" Serial: {}", x509.tbs_certificate.raw_serial_as_string()); + println!(" Subject: {}", x509.subject()); + println!(" Issuer: {}", x509.issuer()); + println!(" Validity:"); + println!(" NotBefore: {}", x509.validity().not_before); + println!(" NotAfter: {}", x509.validity().not_after); + println!(" is_valid: {}", x509.validity().is_valid()); + println!(" Subject Public Key Info:"); + print_x509_ski(x509.public_key()); + print_x509_signature_algorithm(&x509.signature_algorithm, 4); + + println!(" Signature Value:"); + for l in format_number_to_hex_with_colon(&x509.signature_value.data, 16) { + println!(" {}", l); + } + println!(" Extensions:"); + for ext in x509.extensions() { + print_x509_extension(&ext.oid, ext); + } + println!(); + print!("Structure validation status: "); + #[cfg(feature = "validate")] + { + let mut logger = VecLogger::default(); + // structure validation status + let ok = X509StructureValidator + .chain(X509CertificateValidator) + .validate(x509, &mut logger); + if ok { + println!("Ok"); + } else { + println!("FAIL"); + } + for warning in logger.warnings() { + println!(" [W] {}", warning); + } + for error in logger.errors() { + println!(" [E] {}", error); + } + println!(); + if VALIDATE_ERRORS_FATAL && !logger.errors().is_empty() { + return Err(io::Error::new(io::ErrorKind::Other, "validation failed")); + } + } + #[cfg(not(feature = "validate"))] + { + println!("Unknown (feature 'validate' not enabled)"); + } + #[cfg(feature = "verify")] + { + print!("Signature verification: "); + if x509.subject() == x509.issuer() { + if x509.verify_signature(None).is_ok() { + println!("OK"); + println!(" [I] certificate is self-signed"); + } else if x509.subject() == x509.issuer() { + println!("FAIL"); + println!(" [W] certificate looks self-signed, but signature verification failed"); + } + } else { + // if subject is different from issuer, we cannot verify certificate without the public key of the issuer + println!("N/A"); + } + } + Ok(()) +} + +fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, indent: usize) { + match SignatureAlgorithm::try_from(signature_algorithm) { + Ok(sig_alg) => { + print!(" Signature Algorithm: "); + match sig_alg { + SignatureAlgorithm::DSA => println!("DSA"), + SignatureAlgorithm::ECDSA => println!("ECDSA"), + SignatureAlgorithm::ED25519 => println!("ED25519"), + SignatureAlgorithm::RSA => println!("RSA"), + SignatureAlgorithm::RSASSA_PSS(params) => { + println!("RSASSA-PSS"); + let indent_s = format!("{:indent$}", "", indent = indent + 2); + println!( + "{}Hash Algorithm: {}", + indent_s, + format_oid(params.hash_algorithm_oid()), + ); + print!("{}Mask Generation Function: ", indent_s); + if let Ok(mask_gen) = params.mask_gen_algorithm() { + println!( + "{}/{}", + format_oid(&mask_gen.mgf), + format_oid(&mask_gen.hash), + ); + } else { + println!("INVALID"); + } + println!("{}Salt Length: {}", indent_s, params.salt_length()); + } + SignatureAlgorithm::RSAAES_OAEP(params) => { + println!("RSAAES-OAEP"); + let indent_s = format!("{:indent$}", "", indent = indent + 2); + println!( + "{}Hash Algorithm: {}", + indent_s, + format_oid(params.hash_algorithm_oid()), + ); + print!("{}Mask Generation Function: ", indent_s); + if let Ok(mask_gen) = params.mask_gen_algorithm() { + println!( + "{}/{}", + format_oid(&mask_gen.mgf), + format_oid(&mask_gen.hash), + ); + } else { + println!("INVALID"); + } + println!( + "{}pSourceFunc: {}", + indent_s, + format_oid(¶ms.p_source_alg().algorithm), + ); + } + } + } + Err(e) => { + eprintln!("Could not parse signature algorithm: {}", e); + println!(" Signature Algorithm:"); + print_x509_digest_algorithm(signature_algorithm, indent); + } + } +} + +fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { + println!(" Public Key Algorithm:"); + print_x509_digest_algorithm(&public_key.algorithm, 6); + match public_key.parsed() { + Ok(PublicKey::RSA(rsa)) => { + 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); + } + if let Ok(e) = rsa.try_exponent() { + println!(" exponent: 0x{:x} ({})", e, e); + } else { + println!(" exponent: <INVALID>:"); + print_hex_dump(rsa.exponent, 32); + } + } + 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); + } + // // identify curve + // if let Some(params) = &public_key.algorithm.parameters { + // let curve_oid = params.as_oid(); + // let curve = curve_oid + // .map(|oid| { + // oid_registry() + // .get(oid) + // .map(|entry| entry.sn()) + // .unwrap_or("<UNKNOWN>") + // }) + // .unwrap_or("<ERROR: NOT AN OID>"); + // println!(" Curve: {}", curve); + // } + } + 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); + } + } + 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); + } + } + 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); + } + } + Ok(PublicKey::Unknown(b)) => { + println!(" Unknown key type"); + print_hex_dump(b, 256); + if let Ok((rem, res)) = der_parser::parse_der(b) { + eprintln!("rem: {} bytes", rem.len()); + eprintln!("{:?}", res); + } else { + eprintln!(" <Could not parse key as DER>"); + } + } + Err(_) => { + println!(" INVALID PUBLIC KEY"); + } + } + // dbg!(&public_key); + // todo!(); +} + +fn format_number_to_hex_with_colon(b: &[u8], row_size: usize) -> Vec<String> { + 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) + }); + v.push(s) + } + v +} + +fn handle_certificate(file_name: &str, data: &[u8]) -> io::Result<()> { + match parse_x509_certificate(data) { + Ok((_, x509)) => { + print_x509_info(&x509)?; + Ok(()) + } + Err(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); + Ok(()) + } + } + } +} + +pub fn main() -> io::Result<()> { + for file_name in env::args().skip(1) { + 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 + handle_certificate(&file_name, &data)?; + } else { + // try as PEM + for (n, pem) in Pem::iter_from_buffer(&data).enumerate() { + match pem { + Ok(pem) => { + let data = &pem.contents; + println!("Certificate [{}]", n); + handle_certificate(&file_name, data)?; + } + Err(e) => { + eprintln!("Error while decoding PEM entry {}: {}", n, e); + } + } + } + } + } + Ok(()) +} diff --git a/rust/vendor/x509-parser/examples/print-crl.rs b/rust/vendor/x509-parser/examples/print-crl.rs new file mode 100644 index 0000000..73337cb --- /dev/null +++ b/rust/vendor/x509-parser/examples/print-crl.rs @@ -0,0 +1,154 @@ +use der_parser::oid::Oid; +use nom::HexDisplay; +use std::cmp::min; +use std::env; +use std::io; +use x509_parser::prelude::*; + +fn print_hex_dump(bytes: &[u8], max_len: usize) { + let m = min(bytes.len(), max_len); + print!("{}", &bytes[..m].to_hex(16)); + if bytes.len() > max_len { + println!("... <continued>"); + } +} + +fn format_oid(oid: &Oid) -> String { + match oid2sn(oid, oid_registry()) { + Ok(s) => s.to_owned(), + _ => format!("{}", oid), + } +} + +fn print_authority_key_identifier(aki: &AuthorityKeyIdentifier, level: usize) { + if let Some(id) = &aki.key_identifier { + println!("{:indent$}keyid: {:x}", "", id, indent = level); + } + if aki.authority_cert_issuer.is_some() { + unimplemented!(); + } + if let Some(serial) = aki.authority_cert_serial { + let s = format_serial(serial); + println!("{:indent$}serial: {}", "", &s, indent = level); + } +} + +fn print_x509_extension(oid: &Oid, ext: &X509Extension, level: usize) { + match ext.parsed_extension() { + ParsedExtension::CRLNumber(num) => { + println!("{:indent$}X509v3 CRL Number: {}", "", num, indent = level); + } + ParsedExtension::ReasonCode(code) => { + println!( + "{:indent$}X509v3 CRL Reason Code: {}", + "", + code, + indent = level + ); + } + ParsedExtension::InvalidityDate(date) => { + println!("{:indent$}Invalidity Date: {}", "", date, indent = level); + } + ParsedExtension::AuthorityKeyIdentifier(aki) => { + println!( + "{:indent$}X509v3 Authority Key Identifier:", + "", + indent = level + ); + print_authority_key_identifier(aki, level + 2); + } + x => { + print!("{:indent$}{}:", "", format_oid(oid), indent = level); + print!(" Critical={}", ext.critical); + print!(" len={}", ext.value.len()); + println!(); + println!(" {:indent$}{:?}", "", x, indent = level); + } + } +} + +fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { + println!( + "{:indent$}Oid: {}", + "", + format_oid(&alg.algorithm), + indent = level + ); + if let Some(parameter) = &alg.parameters { + println!( + "{:indent$}Parameter: <PRESENT> {:?}", + "", + parameter.tag(), + indent = level + ); + let bytes = parameter.as_bytes(); + print_hex_dump(bytes, 32); + } else { + println!("{:indent$}Parameter: <ABSENT>", "", indent = level); + } +} + +fn print_revoked_certificate(revoked: &RevokedCertificate, level: usize) { + println!( + "{:indent$}Serial number: {}", + "", + revoked.raw_serial_as_string(), + indent = level + ); + println!( + "{:indent$}Revocation Date: {}", + "", + revoked.revocation_date, + indent = level + 2 + ); + println!("{:indent$}CRL Extensions:", "", indent = level + 2); + for ext in revoked.extensions() { + print_x509_extension(&ext.oid, ext, level + 4); + } +} + +fn print_crl_info(crl: &CertificateRevocationList) { + println!(" Version: {}", crl.version().unwrap_or(X509Version(0))); + // println!(" Subject: {}", crl.subject()); + println!(" Signature Algorithm:"); + print_x509_digest_algorithm(&crl.signature_algorithm, 4); + println!(" Issuer: {}", crl.issuer()); + // println!(" Serial: {}", crl.tbs_certificate.raw_serial_as_string()); + println!(" Last Update: {}", crl.last_update()); + println!( + " Next Update: {}", + crl.next_update() + .map_or_else(|| "NONE".to_string(), |d| d.to_string()) + ); + println!("{:indent$}CRL Extensions:", "", indent = 2); + for ext in crl.extensions() { + print_x509_extension(&ext.oid, ext, 4); + } + println!(" Revoked certificates:"); + for revoked in crl.iter_revoked_certificates() { + print_revoked_certificate(revoked, 4); + } + println!(); +} + +pub fn main() -> io::Result<()> { + for file_name in env::args().skip(1) { + // placeholder to store decoded PEM data, if needed + let tmpdata; + + 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 + &data + } else { + // try as PEM + let (_, data) = parse_x509_pem(&data).expect("Could not decode the PEM file"); + tmpdata = data; + &tmpdata.contents + }; + let (_, crl) = parse_x509_crl(der_data).expect("Could not decode DER data"); + print_crl_info(&crl); + } + Ok(()) +} diff --git a/rust/vendor/x509-parser/src/certificate.rs b/rust/vendor/x509-parser/src/certificate.rs new file mode 100644 index 0000000..3bd6b8e --- /dev/null +++ b/rust/vendor/x509-parser/src/certificate.rs @@ -0,0 +1,782 @@ +//! X.509 Certificate object definitions and operations + +use crate::error::{X509Error, X509Result}; +use crate::extensions::*; +use crate::time::ASN1Time; +use crate::utils::format_serial; +#[cfg(feature = "validate")] +use crate::validate::*; +use crate::x509::{ + parse_serial, parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, + X509Version, +}; + +#[cfg(feature = "verify")] +use crate::verify::verify_signature; +use asn1_rs::{BitString, FromDer, OptTaggedExplicit}; +use core::ops::Deref; +use der_parser::ber::Tag; +use der_parser::der::*; +use der_parser::error::*; +use der_parser::num_bigint::BigUint; +use der_parser::*; +use nom::{Offset, Parser}; +use oid_registry::Oid; +use oid_registry::*; +use std::collections::HashMap; +use time::Duration; + +/// An X.509 v3 Certificate. +/// +/// X.509 v3 certificates are defined in [RFC5280](https://tools.ietf.org/html/rfc5280), section +/// 4.1. This object uses the same structure for content, so for ex the subject can be accessed +/// using the path `x509.tbs_certificate.subject`. +/// +/// `X509Certificate` also contains convenience methods to access the most common fields (subject, +/// issuer, etc.). These are provided using `Deref<Target = TbsCertificate>`, so documentation for +/// these methods can be found in the [`TbsCertificate`] object. +/// +/// A `X509Certificate` is a zero-copy view over a buffer, so the lifetime is the same as the +/// buffer containing the binary representation. +/// +/// ```rust +/// # use x509_parser::prelude::FromDer; +/// # use x509_parser::certificate::X509Certificate; +/// # +/// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); +/// # +/// fn display_x509_info(x509: &X509Certificate<'_>) { +/// let subject = x509.subject(); +/// let issuer = x509.issuer(); +/// println!("X.509 Subject: {}", subject); +/// println!("X.509 Issuer: {}", issuer); +/// println!("X.509 serial: {}", x509.tbs_certificate.raw_serial_as_string()); +/// } +/// # +/// # fn main() { +/// # let res = X509Certificate::from_der(DER); +/// # match res { +/// # Ok((_rem, x509)) => { +/// # display_x509_info(&x509); +/// # }, +/// # _ => panic!("x509 parsing failed: {:?}", res), +/// # } +/// # } +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct X509Certificate<'a> { + pub tbs_certificate: TbsCertificate<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature_value: BitString<'a>, +} + +impl<'a> X509Certificate<'a> { + /// Verify the cryptographic signature of this certificate + /// + /// `public_key` is the public key of the **signer**. For a self-signed certificate, + /// (for ex. a public root certificate authority), this is the key from the certificate, + /// so you can use `None`. + /// + /// For a leaf certificate, this is the public key of the certificate that signed it. + /// It is usually an intermediate authority. + /// + /// Not all algorithms are supported, this function is limited to what `ring` supports. + #[cfg(feature = "verify")] + #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] + pub fn verify_signature( + &self, + public_key: Option<&SubjectPublicKeyInfo>, + ) -> Result<(), X509Error> { + let spki = public_key.unwrap_or_else(|| self.public_key()); + verify_signature( + spki, + &self.signature_algorithm, + &self.signature_value, + self.tbs_certificate.raw, + ) + } +} + +impl<'a> Deref for X509Certificate<'a> { + type Target = TbsCertificate<'a>; + + fn deref(&self) -> &Self::Target { + &self.tbs_certificate + } +} + +impl<'a> FromDer<'a, X509Error> for X509Certificate<'a> { + /// Parse a DER-encoded X.509 Certificate, and return the remaining of the input and the built + /// object. + /// + /// The returned object uses zero-copy, and so has the same lifetime as the input. + /// + /// Note that only parsing is done, not validation. + /// + /// <pre> + /// Certificate ::= SEQUENCE { + /// tbsCertificate TBSCertificate, + /// signatureAlgorithm AlgorithmIdentifier, + /// signatureValue BIT STRING } + /// </pre> + /// + /// # Example + /// + /// To parse a certificate and print the subject and issuer: + /// + /// ```rust + /// # use x509_parser::parse_x509_certificate; + /// # + /// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); + /// # + /// # fn main() { + /// let res = parse_x509_certificate(DER); + /// match res { + /// Ok((_rem, x509)) => { + /// let subject = x509.subject(); + /// let issuer = x509.issuer(); + /// println!("X.509 Subject: {}", subject); + /// println!("X.509 Issuer: {}", issuer); + /// }, + /// _ => panic!("x509 parsing failed: {:?}", res), + /// } + /// # } + /// ``` + fn from_der(i: &'a [u8]) -> X509Result<Self> { + // run parser with default options + X509CertificateParser::new().parse(i) + } +} + +/// X.509 Certificate parser +/// +/// This object is a parser builder, and allows specifying parsing options. +/// Currently, the only option is to control deep parsing of X.509v3 extensions: +/// a parser can decide to skip deep-parsing to be faster (the structure of extensions is still +/// parsed, and the contents can be parsed later using the [`from_der`](FromDer::from_der) +/// method from individual extension objects). +/// +/// This object uses the `nom::Parser` trait, which must be imported. +/// +/// # Example +/// +/// To parse a certificate without parsing extensions: +/// +/// ```rust +/// use x509_parser::certificate::X509CertificateParser; +/// use x509_parser::nom::Parser; +/// +/// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); +/// # +/// # fn main() { +/// // create a parser that will not parse extensions +/// let mut parser = X509CertificateParser::new() +/// .with_deep_parse_extensions(false); +/// let res = parser.parse(DER); +/// match res { +/// Ok((_rem, x509)) => { +/// let subject = x509.subject(); +/// let issuer = x509.issuer(); +/// println!("X.509 Subject: {}", subject); +/// println!("X.509 Issuer: {}", issuer); +/// }, +/// _ => panic!("x509 parsing failed: {:?}", res), +/// } +/// # } +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct X509CertificateParser { + deep_parse_extensions: bool, + // strict: bool, +} + +impl X509CertificateParser { + #[inline] + pub const fn new() -> Self { + X509CertificateParser { + deep_parse_extensions: true, + } + } + + #[inline] + pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { + X509CertificateParser { + deep_parse_extensions, + } + } +} + +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, _| { + // pass options to TbsCertificate parser + let mut tbs_parser = + TbsCertificateParser::new().with_deep_parse_extensions(self.deep_parse_extensions); + let (i, tbs_certificate) = tbs_parser.parse(i)?; + let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; + let (i, signature_value) = parse_signature_value(i)?; + let cert = X509Certificate { + tbs_certificate, + signature_algorithm, + signature_value, + }; + Ok((i, cert)) + })(input) + } +} + +#[allow(deprecated)] +#[cfg(feature = "validate")] +#[cfg_attr(docsrs, doc(cfg(feature = "validate")))] +impl Validate for X509Certificate<'_> { + fn validate<W, E>(&self, warn: W, err: E) -> bool + where + W: FnMut(&str), + E: FnMut(&str), + { + X509StructureValidator.validate(self, &mut CallbackLogger::new(warn, err)) + } +} + +/// The sequence `TBSCertificate` contains information associated with the +/// subject of the certificate and the CA that issued it. +/// +/// RFC5280 definition: +/// +/// <pre> +/// TBSCertificate ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// serialNumber CertificateSerialNumber, +/// signature AlgorithmIdentifier, +/// issuer Name, +/// validity Validity, +/// subject Name, +/// subjectPublicKeyInfo SubjectPublicKeyInfo, +/// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, +/// -- If present, version MUST be v2 or v3 +/// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, +/// -- If present, version MUST be v2 or v3 +/// extensions [3] EXPLICIT Extensions OPTIONAL +/// -- If present, version MUST be v3 +/// } +/// </pre> +#[derive(Clone, Debug, PartialEq)] +pub struct TbsCertificate<'a> { + pub version: X509Version, + pub serial: BigUint, + pub signature: AlgorithmIdentifier<'a>, + pub issuer: X509Name<'a>, + pub validity: Validity, + pub subject: X509Name<'a>, + pub subject_pki: SubjectPublicKeyInfo<'a>, + pub issuer_uid: Option<UniqueIdentifier<'a>>, + pub subject_uid: Option<UniqueIdentifier<'a>>, + extensions: Vec<X509Extension<'a>>, + pub(crate) raw: &'a [u8], + pub(crate) raw_serial: &'a [u8], +} + +impl<'a> TbsCertificate<'a> { + /// Get the version of the encoded certificate + pub fn version(&self) -> X509Version { + self.version + } + + /// Get the certificate subject. + #[inline] + pub fn subject(&self) -> &X509Name { + &self.subject + } + + /// Get the certificate issuer. + #[inline] + pub fn issuer(&self) -> &X509Name { + &self.issuer + } + + /// Get the certificate validity. + #[inline] + pub fn validity(&self) -> &Validity { + &self.validity + } + + /// Get the certificate public key information. + #[inline] + pub fn public_key(&self) -> &SubjectPublicKeyInfo { + &self.subject_pki + } + + /// Returns the certificate extensions + #[inline] + pub fn extensions(&self) -> &[X509Extension<'a>] { + &self.extensions + } + + /// Returns an iterator over the certificate extensions + #[inline] + pub fn iter_extensions(&self) -> impl Iterator<Item = &X509Extension<'a>> { + self.extensions.iter() + } + + /// Searches for an extension with the given `Oid`. + /// + /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, + /// or an error `DuplicateExtensions` if the extension is present twice or more. + #[inline] + pub fn get_extension_unique(&self, oid: &Oid) -> Result<Option<&X509Extension<'a>>, X509Error> { + get_extension_unique(&self.extensions, oid) + } + + /// Searches for an extension with the given `Oid`. + /// + /// ## Duplicate extensions + /// + /// Note: if there are several extensions with the same `Oid`, the first one is returned, masking other values. + /// + /// RFC5280 forbids having duplicate extensions, but does not specify how errors should be handled. + /// + /// **Because of this, the `find_extension` method is not safe and should not be used!** + /// The [`get_extension_unique`](Self::get_extension_unique) method checks for duplicate extensions and should be + /// preferred. + #[deprecated( + since = "0.13.0", + note = "Do not use this function (duplicate extensions are not checked), use `get_extension_unique`" + )] + pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension<'a>> { + 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<HashMap<Oid, &X509Extension<'a>>, X509Error> { + self.extensions + .iter() + .try_fold(HashMap::new(), |mut m, ext| { + if m.contains_key(&ext.oid) { + return Err(X509Error::DuplicateExtensions); + } + m.insert(ext.oid.clone(), ext); + Ok(m) + }) + } + + /// Attempt to get the certificate Basic Constraints extension + /// + /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, + /// or an error if the extension is present twice or more. + pub fn basic_constraints( + &self, + ) -> Result<Option<BasicExtension<&BasicConstraints>>, X509Error> { + let r = self + .get_extension_unique(&OID_X509_EXT_BASIC_CONSTRAINTS)? + .and_then(|ext| match ext.parsed_extension { + ParsedExtension::BasicConstraints(ref bc) => { + Some(BasicExtension::new(ext.critical, bc)) + } + _ => None, + }); + Ok(r) + } + + /// Attempt to get the certificate Key Usage extension + /// + /// 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 key_usage(&self) -> Result<Option<BasicExtension<&KeyUsage>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_KEY_USAGE)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::KeyUsage(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Extended Key Usage extension + /// + /// 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 extended_key_usage( + &self, + ) -> Result<Option<BasicExtension<&ExtendedKeyUsage>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_EXTENDED_KEY_USAGE)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::ExtendedKeyUsage(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Policy Constraints extension + /// + /// 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_constraints( + &self, + ) -> Result<Option<BasicExtension<&PolicyConstraints>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_POLICY_CONSTRAINTS)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::PolicyConstraints(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Policy Constraints extension + /// + /// 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 inhibit_anypolicy( + &self, + ) -> Result<Option<BasicExtension<&InhibitAnyPolicy>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_INHIBITANT_ANY_POLICY)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::InhibitAnyPolicy(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Policy Mappings extension + /// + /// 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<Option<BasicExtension<&PolicyMappings>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_POLICY_MAPPINGS)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::PolicyMappings(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Subject Alternative Name extension + /// + /// 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 subject_alternative_name( + &self, + ) -> Result<Option<BasicExtension<&SubjectAlternativeName>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_SUBJECT_ALT_NAME)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::SubjectAlternativeName(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Attempt to get the certificate Name Constraints extension + /// + /// 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<Option<BasicExtension<&NameConstraints>>, X509Error> { + self.get_extension_unique(&OID_X509_EXT_NAME_CONSTRAINTS)? + .map_or(Ok(None), |ext| match ext.parsed_extension { + ParsedExtension::NameConstraints(ref value) => { + Ok(Some(BasicExtension::new(ext.critical, value))) + } + _ => Err(X509Error::InvalidExtensions), + }) + } + + /// Returns true if certificate has `basicConstraints CA:true` + pub fn is_ca(&self) -> bool { + self.basic_constraints() + .unwrap_or(None) + .map(|ext| ext.value.ca) + .unwrap_or(false) + } + + /// Get the raw bytes of the certificate serial number + pub fn raw_serial(&self) -> &'a [u8] { + self.raw_serial + } + + /// Get a formatted string of the certificate serial number, separated by ':' + pub fn raw_serial_as_string(&self) -> String { + format_serial(self.raw_serial) + } +} + +/// Searches for an extension with the given `Oid`. +/// +/// Note: if there are several extensions with the same `Oid`, an error `DuplicateExtensions` is returned. +fn get_extension_unique<'a, 'b>( + extensions: &'a [X509Extension<'b>], + oid: &Oid, +) -> Result<Option<&'a X509Extension<'b>>, X509Error> { + let mut res = None; + for ext in extensions { + if ext.oid == *oid { + if res.is_some() { + return Err(X509Error::DuplicateExtensions); + } + res = Some(ext); + } + } + Ok(res) +} + +impl<'a> AsRef<[u8]> for TbsCertificate<'a> { + #[inline] + fn as_ref(&self) -> &[u8] { + self.raw + } +} + +impl<'a> FromDer<'a, X509Error> for TbsCertificate<'a> { + /// Parse a DER-encoded TbsCertificate object + /// + /// <pre> + /// TBSCertificate ::= SEQUENCE { + /// version [0] Version DEFAULT v1, + /// serialNumber CertificateSerialNumber, + /// signature AlgorithmIdentifier, + /// issuer Name, + /// validity Validity, + /// subject Name, + /// subjectPublicKeyInfo SubjectPublicKeyInfo, + /// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, + /// -- If present, version MUST be v2 or v3 + /// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, + /// -- If present, version MUST be v2 or v3 + /// extensions [3] Extensions OPTIONAL + /// -- If present, version MUST be v3 -- } + /// </pre> + fn from_der(i: &'a [u8]) -> X509Result<TbsCertificate<'a>> { + let start_i = i; + parse_der_sequence_defined_g(move |i, _| { + let (i, version) = X509Version::from_der_tagged_0(i)?; + let (i, serial) = parse_serial(i)?; + let (i, signature) = AlgorithmIdentifier::from_der(i)?; + let (i, issuer) = X509Name::from_der(i)?; + let (i, validity) = Validity::from_der(i)?; + let (i, subject) = X509Name::from_der(i)?; + let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; + let (i, issuer_uid) = UniqueIdentifier::from_der_issuer(i)?; + let (i, subject_uid) = UniqueIdentifier::from_der_subject(i)?; + let (i, extensions) = parse_extensions(i, Tag(3))?; + let len = start_i.offset(i); + let tbs = TbsCertificate { + version, + serial: serial.1, + signature, + issuer, + validity, + subject, + subject_pki, + issuer_uid, + subject_uid, + extensions, + + raw: &start_i[..len], + raw_serial: serial.0, + }; + Ok((i, tbs)) + })(i) + } +} + +/// `TbsCertificate` parser builder +#[derive(Clone, Copy, Debug)] +pub struct TbsCertificateParser { + deep_parse_extensions: bool, +} + +impl TbsCertificateParser { + #[inline] + pub const fn new() -> Self { + TbsCertificateParser { + deep_parse_extensions: true, + } + } + + #[inline] + pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { + TbsCertificateParser { + deep_parse_extensions, + } + } +} + +impl<'a> Parser<&'a [u8], TbsCertificate<'a>, X509Error> for TbsCertificateParser { + fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], TbsCertificate<'a>, X509Error> { + let start_i = input; + parse_der_sequence_defined_g(move |i, _| { + let (i, version) = X509Version::from_der_tagged_0(i)?; + let (i, serial) = parse_serial(i)?; + let (i, signature) = AlgorithmIdentifier::from_der(i)?; + let (i, issuer) = X509Name::from_der(i)?; + let (i, validity) = Validity::from_der(i)?; + let (i, subject) = X509Name::from_der(i)?; + let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; + let (i, issuer_uid) = UniqueIdentifier::from_der_issuer(i)?; + let (i, subject_uid) = UniqueIdentifier::from_der_subject(i)?; + let (i, extensions) = if self.deep_parse_extensions { + parse_extensions(i, Tag(3))? + } else { + parse_extensions_envelope(i, Tag(3))? + }; + let len = start_i.offset(i); + let tbs = TbsCertificate { + version, + serial: serial.1, + signature, + issuer, + validity, + subject, + subject_pki, + issuer_uid, + subject_uid, + extensions, + + raw: &start_i[..len], + raw_serial: serial.0, + }; + Ok((i, tbs)) + })(input) + } +} + +#[allow(deprecated)] +#[cfg(feature = "validate")] +#[cfg_attr(docsrs, doc(cfg(feature = "validate")))] +impl Validate for TbsCertificate<'_> { + fn validate<W, E>(&self, warn: W, err: E) -> bool + where + W: FnMut(&str), + E: FnMut(&str), + { + TbsCertificateStructureValidator.validate(self, &mut CallbackLogger::new(warn, err)) + } +} + +/// Basic extension structure, used in search results +#[derive(Debug, PartialEq, Eq)] +pub struct BasicExtension<T> { + pub critical: bool, + pub value: T, +} + +impl<T> BasicExtension<T> { + pub const fn new(critical: bool, value: T) -> Self { + Self { critical, value } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Validity { + pub not_before: ASN1Time, + pub not_after: ASN1Time, +} + +impl Validity { + /// The time left before the certificate expires. + /// + /// If the certificate is not currently valid, then `None` is + /// returned. Otherwise, the `Duration` until the certificate + /// expires is returned. + pub fn time_to_expiration(&self) -> Option<Duration> { + let now = ASN1Time::now(); + if !self.is_valid_at(now) { + return None; + } + // Note that the duration below is guaranteed to be positive, + // since we just checked that now < na + self.not_after - now + } + + /// Check the certificate time validity for the provided date/time + #[inline] + pub fn is_valid_at(&self, time: ASN1Time) -> bool { + time >= self.not_before && time <= self.not_after + } + + /// Check the certificate time validity + #[inline] + pub fn is_valid(&self) -> bool { + self.is_valid_at(ASN1Time::now()) + } +} + +impl<'a> FromDer<'a, X509Error> for Validity { + 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)?; + let v = Validity { + not_before, + not_after, + }; + Ok((i, v)) + })(i) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UniqueIdentifier<'a>(pub BitString<'a>); + +impl<'a> UniqueIdentifier<'a> { + // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL + fn from_der_issuer(i: &'a [u8]) -> X509Result<Option<Self>> { + Self::parse::<1>(i).map_err(|_| X509Error::InvalidIssuerUID.into()) + } + + // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL + fn from_der_subject(i: &[u8]) -> X509Result<Option<UniqueIdentifier>> { + Self::parse::<2>(i).map_err(|_| X509Error::InvalidSubjectUID.into()) + } + + // Parse a [tag] UniqueIdentifier OPTIONAL + // + // UniqueIdentifier ::= BIT STRING + fn parse<const TAG: u32>(i: &[u8]) -> BerResult<Option<UniqueIdentifier>> { + let (rem, unique_id) = OptTaggedExplicit::<BitString, Error, TAG>::from_der(i)?; + let unique_id = unique_id.map(|u| UniqueIdentifier(u.into_inner())); + Ok((rem, unique_id)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_validity_expiration() { + let mut v = Validity { + not_before: ASN1Time::now(), + not_after: ASN1Time::now(), + }; + assert_eq!(v.time_to_expiration(), None); + v.not_after = (v.not_after + Duration::new(60, 0)).unwrap(); + assert!(v.time_to_expiration().is_some()); + assert!(v.time_to_expiration().unwrap() <= Duration::new(60, 0)); + // The following assumes this timing won't take 10 seconds... I + // think that is safe. + assert!(v.time_to_expiration().unwrap() > Duration::new(50, 0)); + } + + #[test] + fn extension_duplication() { + let extensions = vec![ + X509Extension::new(oid! {1.2}, true, &[], ParsedExtension::Unparsed), + X509Extension::new(oid! {1.3}, true, &[], ParsedExtension::Unparsed), + X509Extension::new(oid! {1.2}, true, &[], ParsedExtension::Unparsed), + X509Extension::new(oid! {1.4}, true, &[], ParsedExtension::Unparsed), + X509Extension::new(oid! {1.4}, true, &[], ParsedExtension::Unparsed), + ]; + + let r2 = get_extension_unique(&extensions, &oid! {1.2}); + assert!(r2.is_err()); + let r3 = get_extension_unique(&extensions, &oid! {1.3}); + assert!(r3.is_ok()); + let r4 = get_extension_unique(&extensions, &oid! {1.4}); + assert!(r4.is_err()); + } +} diff --git a/rust/vendor/x509-parser/src/certification_request.rs b/rust/vendor/x509-parser/src/certification_request.rs new file mode 100644 index 0000000..e56d05a --- /dev/null +++ b/rust/vendor/x509-parser/src/certification_request.rs @@ -0,0 +1,166 @@ +use crate::cri_attributes::*; +use crate::error::{X509Error, X509Result}; +use crate::extensions::*; +use crate::x509::{ + parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, +}; + +#[cfg(feature = "verify")] +use crate::verify::verify_signature; +use asn1_rs::{BitString, FromDer}; +use der_parser::der::*; +use der_parser::oid::Oid; +use der_parser::*; +use nom::Offset; +use std::collections::HashMap; + +/// Certification Signing Request (CSR) +#[derive(Debug, PartialEq)] +pub struct X509CertificationRequest<'a> { + pub certification_request_info: X509CertificationRequestInfo<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature_value: BitString<'a>, +} + +impl<'a> X509CertificationRequest<'a> { + pub fn requested_extensions(&self) -> Option<impl Iterator<Item = &ParsedExtension>> { + self.certification_request_info + .iter_attributes() + .find_map(|attr| { + if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute { + Some(requested.extensions.iter().map(|ext| &ext.parsed_extension)) + } else { + None + } + }) + } + + /// Verify the cryptographic signature of this certification request + /// + /// Uses the public key contained in the CSR, which must be the one of the entity + /// requesting the certification for this verification to succeed. + #[cfg(feature = "verify")] + pub fn verify_signature(&self) -> Result<(), X509Error> { + let spki = &self.certification_request_info.subject_pki; + verify_signature( + spki, + &self.signature_algorithm, + &self.signature_value, + self.certification_request_info.raw, + ) + } +} + +/// <pre> +/// CertificationRequest ::= SEQUENCE { +/// certificationRequestInfo CertificationRequestInfo, +/// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, +/// signature BIT STRING +/// } +/// </pre> +impl<'a> FromDer<'a, X509Error> for X509CertificationRequest<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + 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)?; + let cert = X509CertificationRequest { + certification_request_info, + signature_algorithm, + signature_value, + }; + Ok((i, cert)) + })(i) + } +} + +/// Certification Request Info structure +/// +/// Certification request information is defined by the following ASN.1 structure: +/// +/// <pre> +/// CertificationRequestInfo ::= SEQUENCE { +/// version INTEGER { v1(0) } (v1,...), +/// subject Name, +/// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, +/// attributes [0] Attributes{{ CRIAttributes }} +/// } +/// </pre> +/// +/// version is the version number; subject is the distinguished name of the certificate +/// subject; subject_pki contains information about the public key being certified, and +/// attributes is a collection of attributes providing additional information about the +/// subject of the certificate. +#[derive(Debug, PartialEq)] +pub struct X509CertificationRequestInfo<'a> { + pub version: X509Version, + pub subject: X509Name<'a>, + pub subject_pki: SubjectPublicKeyInfo<'a>, + attributes: Vec<X509CriAttribute<'a>>, + pub raw: &'a [u8], +} + +impl<'a> X509CertificationRequestInfo<'a> { + /// Get the CRL entry extensions. + #[inline] + pub fn attributes(&self) -> &[X509CriAttribute] { + &self.attributes + } + + /// Returns an iterator over the CRL entry extensions + #[inline] + pub fn iter_attributes(&self) -> impl Iterator<Item = &X509CriAttribute> { + 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> { + 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<HashMap<Oid, &X509CriAttribute>, X509Error> { + self.attributes + .iter() + .try_fold(HashMap::new(), |mut m, ext| { + if m.contains_key(&ext.oid) { + return Err(X509Error::DuplicateAttributes); + } + m.insert(ext.oid.clone(), ext); + Ok(m) + }) + } +} + +/// <pre> +/// CertificationRequestInfo ::= SEQUENCE { +/// version INTEGER { v1(0) } (v1,...), +/// subject Name, +/// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, +/// attributes [0] Attributes{{ CRIAttributes }} +/// } +/// </pre> +impl<'a> FromDer<'a, X509Error> for X509CertificationRequestInfo<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + let start_i = i; + parse_der_sequence_defined_g(move |i, _| { + let (i, version) = X509Version::from_der(i)?; + let (i, subject) = X509Name::from_der(i)?; + let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; + let (i, attributes) = parse_cri_attributes(i)?; + let len = start_i.offset(i); + let tbs = X509CertificationRequestInfo { + version, + subject, + subject_pki, + attributes, + raw: &start_i[..len], + }; + Ok((i, tbs)) + })(i) + } +} diff --git a/rust/vendor/x509-parser/src/cri_attributes.rs b/rust/vendor/x509-parser/src/cri_attributes.rs new file mode 100644 index 0000000..1092200 --- /dev/null +++ b/rust/vendor/x509-parser/src/cri_attributes.rs @@ -0,0 +1,179 @@ +use crate::{ + error::{X509Error, X509Result}, + extensions::X509Extension, +}; + +use asn1_rs::{Error, FromDer, Header, Oid, Sequence, Tag}; +use nom::combinator::{all_consuming, complete}; +use nom::multi::many0; +use nom::Err; +use oid_registry::*; +use std::collections::HashMap; + +/// Attributes for Certification Request +#[derive(Clone, Debug, PartialEq)] +pub struct X509CriAttribute<'a> { + pub oid: Oid<'a>, + pub value: &'a [u8], + pub(crate) parsed_attribute: ParsedCriAttribute<'a>, +} + +impl<'a> FromDer<'a, X509Error> for X509CriAttribute<'a> { + fn from_der(i: &'a [u8]) -> X509Result<X509CriAttribute> { + Sequence::from_ber_and_then(i, |i| { + let (i, oid) = Oid::from_der(i)?; + let value_start = i; + let (i, hdr) = Header::from_der(i)?; + if hdr.tag() != Tag::Set { + return Err(Err::Error(Error::BerTypeError)); + }; + + let (i, parsed_attribute) = crate::cri_attributes::parser::parse_attribute(i, &oid) + .map_err(|_| Err::Error(Error::BerValueError))?; + let attribute = X509CriAttribute { + oid, + value: &value_start[..value_start.len() - i.len()], + parsed_attribute, + }; + Ok((i, attribute)) + }) + .map_err(|_| X509Error::InvalidAttributes.into()) + } +} + +impl<'a> X509CriAttribute<'a> { + /// Return the attribute type or `UnsupportedAttribute` if the attribute is unknown. + #[inline] + pub fn parsed_attribute(&self) -> &ParsedCriAttribute<'a> { + &self.parsed_attribute + } +} + +/// Section 3.1 of rfc 5272 +#[derive(Clone, Debug, PartialEq)] +pub struct ExtensionRequest<'a> { + pub extensions: Vec<X509Extension<'a>>, +} + +impl<'a> FromDer<'a, X509Error> for ExtensionRequest<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_extension_request(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ChallengePassword(pub String); + +/// Attributes for Certification Request +#[derive(Clone, Debug, PartialEq)] +pub enum ParsedCriAttribute<'a> { + ChallengePassword(ChallengePassword), + ExtensionRequest(ExtensionRequest<'a>), + UnsupportedAttribute, +} + +pub(crate) mod parser { + use crate::cri_attributes::*; + use der_parser::der::{ + parse_der_bmpstring, parse_der_printablestring, parse_der_t61string, + parse_der_universalstring, parse_der_utf8string, + }; + use lazy_static::lazy_static; + use nom::branch::alt; + use nom::combinator::map; + + type AttrParser = fn(&[u8]) -> X509Result<ParsedCriAttribute>; + + lazy_static! { + static ref ATTRIBUTE_PARSERS: HashMap<Oid<'static>, AttrParser> = { + macro_rules! add { + ($m:ident, $oid:ident, $p:ident) => { + $m.insert($oid, $p as AttrParser); + }; + } + + let mut m = HashMap::new(); + add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_attr); + add!( + m, + OID_PKCS9_CHALLENGE_PASSWORD, + parse_challenge_password_attr + ); + m + }; + } + + // look into the parser map if the extension is known, and parse it + // otherwise, leave it as UnsupportedExtension + pub(crate) fn parse_attribute<'a>( + i: &'a [u8], + oid: &Oid, + ) -> X509Result<'a, ParsedCriAttribute<'a>> { + if let Some(parser) = ATTRIBUTE_PARSERS.get(oid) { + parser(i) + } else { + Ok((i, ParsedCriAttribute::UnsupportedAttribute)) + } + } + + 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<ParsedCriAttribute> { + map( + parse_extension_request, + ParsedCriAttribute::ExtensionRequest, + )(i) + } + + // RFC 2985, 5.4.1 Challenge password + // challengePassword ATTRIBUTE ::= { + // WITH SYNTAX DirectoryString {pkcs-9-ub-challengePassword} + // EQUALITY MATCHING RULE caseExactMatch + // SINGLE VALUE TRUE + // ID pkcs-9-at-challengePassword + // } + // RFC 5280, 4.1.2.4. Issuer + // DirectoryString ::= CHOICE { + // teletexString TeletexString (SIZE (1..MAX)), + // printableString PrintableString (SIZE (1..MAX)), + // universalString UniversalString (SIZE (1..MAX)), + // utf8String UTF8String (SIZE (1..MAX)), + // bmpString BMPString (SIZE (1..MAX)) + // } + pub(super) fn parse_challenge_password(i: &[u8]) -> X509Result<ChallengePassword> { + let (rem, obj) = match alt(( + parse_der_utf8string, + parse_der_printablestring, + parse_der_universalstring, + parse_der_bmpstring, + parse_der_t61string, // == teletexString + ))(i) + { + Ok((rem, obj)) => (rem, obj), + Err(_) => return Err(Err::Error(X509Error::InvalidAttributes)), + }; + match obj.content.as_str() { + Ok(s) => Ok((rem, ChallengePassword(s.to_string()))), + Err(_) => Err(Err::Error(X509Error::InvalidAttributes)), + } + } + + fn parse_challenge_password_attr(i: &[u8]) -> X509Result<ParsedCriAttribute> { + map( + parse_challenge_password, + ParsedCriAttribute::ChallengePassword, + )(i) + } +} + +pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<Vec<X509CriAttribute>> { + 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) + } else { + Err(Err::Error(X509Error::InvalidAttributes)) + } +} diff --git a/rust/vendor/x509-parser/src/error.rs b/rust/vendor/x509-parser/src/error.rs new file mode 100644 index 0000000..85def3f --- /dev/null +++ b/rust/vendor/x509-parser/src/error.rs @@ -0,0 +1,123 @@ +//! X.509 errors + +use der_parser::error::BerError; +use nom::error::{ErrorKind, ParseError}; +use nom::IResult; + +/// An error that can occur while converting an OID to a Nid. +#[derive(Debug, PartialEq, Eq)] +pub struct NidError; + +/// Holds the result of parsing functions (X.509) +/// +/// Note that this type is also a `Result`, so usual functions (`map`, `unwrap` etc.) are available. +pub type X509Result<'a, T> = IResult<&'a [u8], T, X509Error>; + +/// An error that can occur while parsing or validating a certificate. +#[derive(Clone, Debug, PartialEq, thiserror::Error)] +pub enum X509Error { + #[error("generic error")] + Generic, + + #[error("invalid version")] + InvalidVersion, + #[error("invalid serial")] + InvalidSerial, + #[error("invalid algorithm identifier")] + InvalidAlgorithmIdentifier, + #[error("invalid X.509 name")] + InvalidX509Name, + #[error("invalid date")] + InvalidDate, + #[error("invalid X.509 Subject Public Key Info")] + InvalidSPKI, + #[error("invalid X.509 Subject Unique ID")] + InvalidSubjectUID, + #[error("invalid X.509 Issuer Unique ID")] + InvalidIssuerUID, + #[error("invalid extensions")] + InvalidExtensions, + #[error("invalid attributes")] + InvalidAttributes, + #[error("duplicate extensions")] + DuplicateExtensions, + #[error("duplicate attributes")] + DuplicateAttributes, + #[error("invalid Signature DER Value")] + InvalidSignatureValue, + #[error("invalid TBS certificate")] + InvalidTbsCertificate, + + // error types from CRL + #[error("invalid User certificate")] + InvalidUserCertificate, + + /// Top-level certificate structure is invalid + #[error("invalid certificate")] + InvalidCertificate, + + #[error("signature verification error")] + SignatureVerificationError, + #[error("signature unsupported algorithm")] + SignatureUnsupportedAlgorithm, + + #[error("invalid number")] + InvalidNumber, + + #[error("BER error: {0}")] + Der(#[from] BerError), + #[error("nom error: {0:?}")] + NomError(ErrorKind), +} + +impl From<nom::Err<BerError>> for X509Error { + fn from(e: nom::Err<BerError>) -> Self { + Self::Der(BerError::from(e)) + } +} + +impl From<nom::Err<X509Error>> for X509Error { + fn from(e: nom::Err<X509Error>) -> Self { + match e { + nom::Err::Error(e) | nom::Err::Failure(e) => e, + nom::Err::Incomplete(i) => Self::Der(BerError::Incomplete(i)), + } + } +} + +impl From<X509Error> for nom::Err<X509Error> { + fn from(e: X509Error) -> nom::Err<X509Error> { + nom::Err::Error(e) + } +} + +impl From<ErrorKind> for X509Error { + fn from(e: ErrorKind) -> X509Error { + X509Error::NomError(e) + } +} + +impl<I> ParseError<I> for X509Error { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + X509Error::NomError(kind) + } + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + X509Error::NomError(kind) + } +} + +/// An error that can occur while parsing or validating a certificate. +#[derive(Debug, thiserror::Error)] +pub enum PEMError { + #[error("base64 decode error")] + Base64DecodeError, + #[error("incomplete PEM")] + IncompletePEM, + #[error("invalid header")] + InvalidHeader, + #[error("missing header")] + MissingHeader, + + #[error("IO error: {0}")] + IOError(#[from] std::io::Error), +} diff --git a/rust/vendor/x509-parser/src/extensions/generalname.rs b/rust/vendor/x509-parser/src/extensions/generalname.rs new file mode 100644 index 0000000..37e6f18 --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/generalname.rs @@ -0,0 +1,115 @@ +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 core::convert::TryFrom; +use nom::combinator::all_consuming; +use nom::{Err, IResult}; +use std::fmt; + +#[derive(Clone, Debug, PartialEq)] +/// Represents a GeneralName as defined in RFC5280. There +/// is no support X.400 addresses and EDIPartyName. +/// +/// String formats are not validated. +pub enum GeneralName<'a> { + OtherName(Oid<'a>, &'a [u8]), + /// More or less an e-mail, the format is not checked. + RFC822Name(&'a str), + /// A hostname, format is not checked. + DNSName(&'a str), + /// X400Address, + X400Address(Any<'a>), + /// RFC5280 defines several string types, we always try to parse as utf-8 + /// which is more or less a superset of the string types. + DirectoryName(X509Name<'a>), + /// EDIPartyName + EDIPartyName(Any<'a>), + /// An uniform resource identifier. The format is not checked. + URI(&'a str), + /// An ip address, provided as encoded. + IPAddress(&'a [u8]), + RegisteredID(Oid<'a>), +} + +impl<'a> TryFrom<Any<'a>> for GeneralName<'a> { + type Error = Error; + + fn try_from(any: Any<'a>) -> Result<Self, Self::Error> { + any.class().assert_eq(Class::ContextSpecific)?; + fn ia5str(any: Any) -> Result<&str, Err<Error>> { + // 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(|_| nom::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())), + }; + Ok(name) + } +} + +impl CheckDerConstraints for GeneralName<'_> { + fn check_constraints(any: &Any) -> asn1_rs::Result<()> { + Sequence::check_constraints(any) + } +} + +impl<'a> FromDer<'a, X509Error> for GeneralName<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_generalname(i).map_err(Err::convert) + } +} + +impl<'a> fmt::Display for GeneralName<'a> { + 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::X400Address(_) => write!(f, "X400Address(<unparsed>)"), + GeneralName::DirectoryName(dn) => write!(f, "DirectoryName({})", dn), + GeneralName::EDIPartyName(_) => write!(f, "EDIPartyName(<unparsed>)"), + GeneralName::URI(s) => write!(f, "URI({})", s), + GeneralName::IPAddress(b) => write!(f, "IPAddress({})", format_serial(b)), + GeneralName::RegisteredID(oid) => write!(f, "RegisteredID({})", oid), + } + } +} + +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/rust/vendor/x509-parser/src/extensions/keyusage.rs b/rust/vendor/x509-parser/src/extensions/keyusage.rs new file mode 100644 index 0000000..81a07f1 --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/keyusage.rs @@ -0,0 +1,150 @@ +use crate::error::{X509Error, X509Result}; +use asn1_rs::FromDer; +use der_parser::der::*; +use der_parser::error::BerError; +use der_parser::{oid, oid::Oid}; +use nom::{Err, IResult}; +use std::fmt; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct KeyUsage { + pub flags: u16, +} + +impl KeyUsage { + pub fn digital_signature(&self) -> bool { + self.flags & 1 == 1 + } + pub fn non_repudiation(&self) -> bool { + (self.flags >> 1) & 1u16 == 1 + } + pub fn key_encipherment(&self) -> bool { + (self.flags >> 2) & 1u16 == 1 + } + pub fn data_encipherment(&self) -> bool { + (self.flags >> 3) & 1u16 == 1 + } + pub fn key_agreement(&self) -> bool { + (self.flags >> 4) & 1u16 == 1 + } + pub fn key_cert_sign(&self) -> bool { + (self.flags >> 5) & 1u16 == 1 + } + pub fn crl_sign(&self) -> bool { + (self.flags >> 6) & 1u16 == 1 + } + pub fn encipher_only(&self) -> bool { + (self.flags >> 7) & 1u16 == 1 + } + pub fn decipher_only(&self) -> bool { + (self.flags >> 8) & 1u16 == 1 + } +} + +// This list must have the same order as KeyUsage flags declaration (4.2.1.3) +const KEY_USAGE_FLAGS: &[&str] = &[ + "Digital Signature", + "Non Repudiation", + "Key Encipherment", + "Data Encipherment", + "Key Agreement", + "Key Cert Sign", + "CRL Sign", + "Encipher Only", + "Decipher Only", +]; + +impl fmt::Display for KeyUsage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = KEY_USAGE_FLAGS + .iter() + .enumerate() + .fold(String::new(), |acc, (idx, s)| { + if self.flags >> idx & 1 != 0 { + acc + s + ", " + } else { + acc + } + }); + s.pop(); + s.pop(); + f.write_str(&s) + } +} + +impl<'a> FromDer<'a, X509Error> for KeyUsage { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_keyusage(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtendedKeyUsage<'a> { + pub any: bool, + pub server_auth: bool, + pub client_auth: bool, + pub code_signing: bool, + pub email_protection: bool, + pub time_stamping: bool, + pub ocsp_signing: bool, + pub other: Vec<Oid<'a>>, +} + +impl<'a> FromDer<'a, X509Error> for ExtendedKeyUsage<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_extendedkeyusage(i).map_err(Err::convert) + } +} + +pub(crate) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> { + let (rest, obj) = parse_der_bitstring(i)?; + let bitstring = obj + .content + .as_bitstring() + .or(Err(Err::Error(BerError::BerTypeError)))?; + let flags = bitstring + .data + .iter() + .rev() + .fold(0, |acc, x| acc << 8 | (x.reverse_bits() as u16)); + Ok((rest, KeyUsage { flags })) +} + +pub(crate) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> { + let (ret, seq) = <Vec<Oid>>::from_der(i)?; + let mut seen = std::collections::HashSet::new(); + let mut eku = ExtendedKeyUsage { + any: false, + server_auth: false, + client_auth: false, + code_signing: false, + email_protection: false, + time_stamping: false, + ocsp_signing: false, + other: Vec::new(), + }; + for oid in &seq { + if !seen.insert(oid.clone()) { + continue; + } + let asn1 = oid.as_bytes(); + if asn1 == oid!(raw 2.5.29.37.0) { + eku.any = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.1) { + eku.server_auth = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.2) { + eku.client_auth = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.3) { + eku.code_signing = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.4) { + eku.email_protection = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.8) { + eku.time_stamping = true; + } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.9) { + eku.ocsp_signing = true; + } else { + eku.other.push(oid.clone()); + } + } + Ok((ret, eku)) +} diff --git a/rust/vendor/x509-parser/src/extensions/mod.rs b/rust/vendor/x509-parser/src/extensions/mod.rs new file mode 100644 index 0000000..868dc77 --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/mod.rs @@ -0,0 +1,1484 @@ +//! X.509 Extensions objects and types + +use crate::error::{X509Error, X509Result}; +use crate::time::ASN1Time; +use crate::utils::format_serial; +use crate::x509::{ReasonCode, RelativeDistinguishedName}; + +use asn1_rs::FromDer; +use der_parser::ber::parse_ber_bool; +use der_parser::der::*; +use der_parser::error::{BerError, BerResult}; +use der_parser::num_bigint::BigUint; +use der_parser::oid::Oid; +use nom::combinator::{all_consuming, complete, cut, map, map_res, opt}; +use nom::multi::{many0, many1}; +use nom::{Err, IResult, Parser}; +use oid_registry::*; +use std::collections::HashMap; +use std::fmt::{self, LowerHex}; + +mod generalname; +mod keyusage; +mod nameconstraints; +mod policymappings; +mod sct; + +pub use generalname::*; +pub use keyusage::*; +pub use nameconstraints::*; +pub use policymappings::*; +pub use sct::*; + +/// X.509 version 3 extension +/// +/// X.509 extensions allow adding attributes to objects like certificates or revocation lists. +/// +/// Each extension in a certificate is designated as either critical or non-critical. A +/// certificate using system MUST reject the certificate if it encounters a critical extension it +/// does not recognize; however, a non-critical extension MAY be ignored if it is not recognized. +/// +/// Each extension includes an OID and an ASN.1 structure. When an extension appears in a +/// certificate, the OID appears as the field extnID and the corresponding ASN.1 encoded structure +/// is the value of the octet string extnValue. A certificate MUST NOT include more than one +/// instance of a particular extension. +/// +/// When parsing an extension, the global extension structure (described above) is parsed, +/// and the object is returned if it succeeds. +/// During this step, it also attempts to parse the content of the extension, if known. +/// The returned object has a +/// [`X509Extension::parsed_extension()`] method. The returned +/// enum is either a known extension, or the special value `ParsedExtension::UnsupportedExtension`. +/// +/// # Example +/// +/// ```rust +/// use x509_parser::prelude::FromDer; +/// use x509_parser::extensions::{X509Extension, ParsedExtension}; +/// +/// static DER: &[u8] = &[ +/// 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xA3, 0x05, 0x2F, 0x18, +/// 0x60, 0x50, 0xC2, 0x89, 0x0A, 0xDD, 0x2B, 0x21, 0x4F, 0xFF, 0x8E, 0x4E, 0xA8, 0x30, 0x31, +/// 0x36 ]; +/// +/// # fn main() { +/// let res = X509Extension::from_der(DER); +/// match res { +/// Ok((_rem, ext)) => { +/// println!("Extension OID: {}", ext.oid); +/// println!(" Critical: {}", ext.critical); +/// let parsed_ext = ext.parsed_extension(); +/// assert!(!parsed_ext.unsupported()); +/// assert!(parsed_ext.error().is_none()); +/// if let ParsedExtension::SubjectKeyIdentifier(key_id) = parsed_ext { +/// assert!(key_id.0.len() > 0); +/// } else { +/// panic!("Extension has wrong type"); +/// } +/// }, +/// _ => panic!("x509 extension parsing failed: {:?}", res), +/// } +/// # } +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct X509Extension<'a> { + /// OID describing the extension content + pub oid: Oid<'a>, + /// Boolean value describing the 'critical' attribute of the extension + /// + /// An extension includes the boolean critical, with a default value of FALSE. + pub critical: bool, + /// Raw content of the extension + pub value: &'a [u8], + pub(crate) parsed_extension: ParsedExtension<'a>, +} + +impl<'a> X509Extension<'a> { + /// Creates a new extension with the provided values. + #[inline] + pub const fn new( + oid: Oid<'a>, + critical: bool, + value: &'a [u8], + parsed_extension: ParsedExtension<'a>, + ) -> X509Extension<'a> { + X509Extension { + oid, + critical, + value, + parsed_extension, + } + } + + /// Return the extension type or `UnsupportedExtension` if the extension is not implemented. + #[inline] + pub fn parsed_extension(&self) -> &ParsedExtension<'a> { + &self.parsed_extension + } +} + +/// <pre> +/// Extension ::= SEQUENCE { +/// extnID OBJECT IDENTIFIER, +/// critical BOOLEAN DEFAULT FALSE, +/// extnValue OCTET STRING } +/// </pre> +impl<'a> FromDer<'a, X509Error> for X509Extension<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + X509ExtensionParser::new().parse(i) + } +} + +/// `X509Extension` parser builder +#[derive(Clone, Copy, Debug)] +pub struct X509ExtensionParser { + deep_parse_extensions: bool, +} + +impl X509ExtensionParser { + #[inline] + pub const fn new() -> Self { + X509ExtensionParser { + deep_parse_extensions: true, + } + } + + #[inline] + pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { + X509ExtensionParser { + deep_parse_extensions, + } + } +} + +impl<'a> Parser<&'a [u8], X509Extension<'a>, X509Error> for X509ExtensionParser { + fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], X509Extension<'a>, X509Error> { + parse_der_sequence_defined_g(|i, _| { + let (i, oid) = Oid::from_der(i)?; + let (i, critical) = der_read_critical(i)?; + let (i, value) = <&[u8]>::from_der(i)?; + let (i, parsed_extension) = if self.deep_parse_extensions { + parser::parse_extension(i, value, &oid)? + } else { + (&[] as &[_], ParsedExtension::Unparsed) + }; + let ext = X509Extension { + oid, + critical, + value, + parsed_extension, + }; + Ok((i, ext)) + })(input) + .map_err(|_| X509Error::InvalidExtensions.into()) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ParsedExtension<'a> { + /// Crate parser does not support this extension (yet) + UnsupportedExtension { + oid: Oid<'a>, + }, + ParseError { + error: Err<BerError>, + }, + /// Section 4.2.1.1 of rfc 5280 + AuthorityKeyIdentifier(AuthorityKeyIdentifier<'a>), + /// Section 4.2.1.2 of rfc 5280 + SubjectKeyIdentifier(KeyIdentifier<'a>), + /// Section 4.2.1.3 of rfc 5280 + KeyUsage(KeyUsage), + /// Section 4.2.1.4 of rfc 5280 + CertificatePolicies(CertificatePolicies<'a>), + /// Section 4.2.1.5 of rfc 5280 + PolicyMappings(PolicyMappings<'a>), + /// Section 4.2.1.6 of rfc 5280 + SubjectAlternativeName(SubjectAlternativeName<'a>), + /// Section 4.2.1.7 of rfc 5280 + IssuerAlternativeName(IssuerAlternativeName<'a>), + /// Section 4.2.1.9 of rfc 5280 + BasicConstraints(BasicConstraints), + /// Section 4.2.1.10 of rfc 5280 + NameConstraints(NameConstraints<'a>), + /// Section 4.2.1.11 of rfc 5280 + PolicyConstraints(PolicyConstraints), + /// Section 4.2.1.12 of rfc 5280 + ExtendedKeyUsage(ExtendedKeyUsage<'a>), + /// Section 4.2.1.13 of rfc 5280 + CRLDistributionPoints(CRLDistributionPoints<'a>), + /// Section 4.2.1.14 of rfc 5280 + InhibitAnyPolicy(InhibitAnyPolicy), + /// Section 4.2.2.1 of rfc 5280 + AuthorityInfoAccess(AuthorityInfoAccess<'a>), + /// Netscape certificate type (subject is SSL client, an SSL server, or a CA) + NSCertType(NSCertType), + /// Netscape certificate comment + NsCertComment(&'a str), + /// Section 5.3.1 of rfc 5280 + CRLNumber(BigUint), + /// Section 5.3.1 of rfc 5280 + ReasonCode(ReasonCode), + /// Section 5.3.3 of rfc 5280 + InvalidityDate(ASN1Time), + /// rfc 6962 + SCT(Vec<SignedCertificateTimestamp<'a>>), + /// Unparsed extension (was not requested in parsing options) + Unparsed, +} + +impl<'a> ParsedExtension<'a> { + /// Return `true` if the extension is unsupported + pub fn unsupported(&self) -> bool { + matches!(self, &ParsedExtension::UnsupportedExtension { .. }) + } + + /// Return a reference on the parsing error if the extension parsing failed + pub fn error(&self) -> Option<&Err<BerError>> { + match self { + ParsedExtension::ParseError { error } => Some(error), + _ => None, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AuthorityKeyIdentifier<'a> { + pub key_identifier: Option<KeyIdentifier<'a>>, + pub authority_cert_issuer: Option<Vec<GeneralName<'a>>>, + pub authority_cert_serial: Option<&'a [u8]>, +} + +impl<'a> FromDer<'a, X509Error> for AuthorityKeyIdentifier<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_authoritykeyidentifier(i).map_err(Err::convert) + } +} + +pub type CertificatePolicies<'a> = Vec<PolicyInformation<'a>>; + +// impl<'a> FromDer<'a> for CertificatePolicies<'a> { +// fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { +// parser::parse_certificatepolicies(i).map_err(Err::convert) +// } +// } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyInformation<'a> { + pub policy_id: Oid<'a>, + pub policy_qualifiers: Option<Vec<PolicyQualifierInfo<'a>>>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyQualifierInfo<'a> { + pub policy_qualifier_id: Oid<'a>, + pub qualifier: &'a [u8], +} + +/// Identifies whether the subject of the certificate is a CA, and the max validation depth. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BasicConstraints { + pub ca: bool, + pub path_len_constraint: Option<u32>, +} + +impl<'a> FromDer<'a, X509Error> for BasicConstraints { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_basicconstraints(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KeyIdentifier<'a>(pub &'a [u8]); + +impl<'a> FromDer<'a, X509Error> for KeyIdentifier<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_keyidentifier(i).map_err(Err::convert) + } +} + +impl<'a> LowerHex for KeyIdentifier<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = format_serial(self.0); + f.write_str(&s) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NSCertType(u8); + +// The value is a bit-string, where the individual bit positions are defined as: +// +// bit-0 SSL client - this cert is certified for SSL client authentication use +// bit-1 SSL server - this cert is certified for SSL server authentication use +// bit-2 S/MIME - this cert is certified for use by clients (New in PR3) +// bit-3 Object Signing - this cert is certified for signing objects such as Java applets and plugins(New in PR3) +// bit-4 Reserved - this bit is reserved for future use +// bit-5 SSL CA - this cert is certified for issuing certs for SSL use +// bit-6 S/MIME CA - this cert is certified for issuing certs for S/MIME use (New in PR3) +// bit-7 Object Signing CA - this cert is certified for issuing certs for Object Signing (New in PR3) +impl NSCertType { + pub fn ssl_client(&self) -> bool { + self.0 & 0x1 == 1 + } + pub fn ssl_server(&self) -> bool { + (self.0 >> 1) & 1 == 1 + } + pub fn smime(&self) -> bool { + (self.0 >> 2) & 1 == 1 + } + pub fn object_signing(&self) -> bool { + (self.0 >> 3) & 1 == 1 + } + pub fn ssl_ca(&self) -> bool { + (self.0 >> 5) & 1 == 1 + } + pub fn smime_ca(&self) -> bool { + (self.0 >> 6) & 1 == 1 + } + pub fn object_signing_ca(&self) -> bool { + (self.0 >> 7) & 1 == 1 + } +} + +const NS_CERT_TYPE_FLAGS: &[&str] = &[ + "SSL CLient", + "SSL Server", + "S/MIME", + "Object Signing", + "Reserved", + "SSL CA", + "S/MIME CA", + "Object Signing CA", +]; + +impl fmt::Display for NSCertType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = String::new(); + let mut acc = self.0; + for flag_text in NS_CERT_TYPE_FLAGS { + if acc & 1 != 0 { + s = s + flag_text + ", "; + } + acc >>= 1; + } + s.pop(); + s.pop(); + f.write_str(&s) + } +} + +impl<'a> FromDer<'a, X509Error> for NSCertType { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_nscerttype(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AuthorityInfoAccess<'a> { + pub accessdescs: Vec<AccessDescription<'a>>, +} + +impl<'a> AuthorityInfoAccess<'a> { + /// Returns an iterator over the Access Descriptors + pub fn iter(&self) -> impl Iterator<Item = &AccessDescription<'a>> { + 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<Oid<'a>, Vec<&GeneralName<'a>>> { + // create the hashmap and merge entries with same OID + let mut m: HashMap<Oid, Vec<&GeneralName>> = 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<Oid<'a>, Vec<GeneralName<'a>>> { + let mut aia_list = self.accessdescs; + // create the hashmap and merge entries with same OID + let mut m: HashMap<Oid, Vec<GeneralName>> = 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 AuthorityInfoAccess<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_authorityinfoaccess(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct AccessDescription<'a> { + pub access_method: Oid<'a>, + pub access_location: GeneralName<'a>, +} + +impl<'a> AccessDescription<'a> { + pub const fn new(access_method: Oid<'a>, access_location: GeneralName<'a>) -> Self { + AccessDescription { + access_method, + access_location, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InhibitAnyPolicy { + pub skip_certs: u32, +} + +impl<'a> FromDer<'a, X509Error> for InhibitAnyPolicy { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + map(parse_der_u32, |skip_certs| InhibitAnyPolicy { skip_certs })(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyConstraints { + pub require_explicit_policy: Option<u32>, + pub inhibit_policy_mapping: Option<u32>, +} + +impl<'a> FromDer<'a, X509Error> for PolicyConstraints { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_policyconstraints(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SubjectAlternativeName<'a> { + pub general_names: Vec<GeneralName<'a>>, +} + +impl<'a> FromDer<'a, X509Error> for SubjectAlternativeName<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_der_sequence_defined_g(|input, _| { + let (i, general_names) = + all_consuming(many0(complete(cut(GeneralName::from_der))))(input)?; + Ok((i, SubjectAlternativeName { general_names })) + })(i) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct IssuerAlternativeName<'a> { + pub general_names: Vec<GeneralName<'a>>, +} + +impl<'a> FromDer<'a, X509Error> for IssuerAlternativeName<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_der_sequence_defined_g(|input, _| { + let (i, general_names) = + all_consuming(many0(complete(cut(GeneralName::from_der))))(input)?; + Ok((i, IssuerAlternativeName { general_names })) + })(i) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CRLDistributionPoints<'a> { + pub points: Vec<CRLDistributionPoint<'a>>, +} + +impl<'a> std::ops::Deref for CRLDistributionPoints<'a> { + type Target = Vec<CRLDistributionPoint<'a>>; + + fn deref(&self) -> &Self::Target { + &self.points + } +} + +impl<'a> FromDer<'a, X509Error> for CRLDistributionPoints<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parser::parse_crldistributionpoints(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CRLDistributionPoint<'a> { + pub distribution_point: Option<DistributionPointName<'a>>, + pub reasons: Option<ReasonFlags>, + pub crl_issuer: Option<Vec<GeneralName<'a>>>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DistributionPointName<'a> { + FullName(Vec<GeneralName<'a>>), + NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ReasonFlags { + pub flags: u16, +} + +impl ReasonFlags { + pub fn key_compromise(&self) -> bool { + (self.flags >> 1) & 1 == 1 + } + pub fn ca_compromise(&self) -> bool { + (self.flags >> 2) & 1 == 1 + } + pub fn affilation_changed(&self) -> bool { + (self.flags >> 3) & 1 == 1 + } + pub fn superseded(&self) -> bool { + (self.flags >> 4) & 1 == 1 + } + pub fn cessation_of_operation(&self) -> bool { + (self.flags >> 5) & 1 == 1 + } + pub fn certificate_hold(&self) -> bool { + (self.flags >> 6) & 1 == 1 + } + pub fn privelege_withdrawn(&self) -> bool { + (self.flags >> 7) & 1 == 1 + } + pub fn aa_compromise(&self) -> bool { + (self.flags >> 8) & 1 == 1 + } +} + +const REASON_FLAGS: &[&str] = &[ + "Unused", + "Key Compromise", + "CA Compromise", + "Affiliation Changed", + "Superseded", + "Cessation Of Operation", + "Certificate Hold", + "Privilege Withdrawn", + "AA Compromise", +]; + +impl fmt::Display for ReasonFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = String::new(); + let mut acc = self.flags; + for flag_text in REASON_FLAGS { + if acc & 1 != 0 { + s = s + flag_text + ", "; + } + acc >>= 1; + } + s.pop(); + s.pop(); + f.write_str(&s) + } +} + +pub(crate) mod parser { + use crate::extensions::*; + use crate::time::ASN1Time; + use asn1_rs::{GeneralizedTime, ParseResult}; + use der_parser::error::BerError; + use der_parser::{oid::Oid, *}; + use lazy_static::lazy_static; + use nom::combinator::{cut, map}; + use nom::{Err, IResult}; + + type ExtParser = fn(&[u8]) -> IResult<&[u8], ParsedExtension, BerError>; + + lazy_static! { + static ref EXTENSION_PARSERS: HashMap<Oid<'static>, ExtParser> = { + macro_rules! add { + ($m:ident, $oid:ident, $p:ident) => { + $m.insert($oid, $p as ExtParser); + }; + } + + let mut m = HashMap::new(); + add!( + m, + OID_X509_EXT_SUBJECT_KEY_IDENTIFIER, + parse_keyidentifier_ext + ); + add!(m, OID_X509_EXT_KEY_USAGE, parse_keyusage_ext); + add!( + m, + OID_X509_EXT_SUBJECT_ALT_NAME, + parse_subjectalternativename_ext + ); + add!( + m, + OID_X509_EXT_ISSUER_ALT_NAME, + parse_issueralternativename_ext + ); + add!( + m, + OID_X509_EXT_BASIC_CONSTRAINTS, + parse_basicconstraints_ext + ); + add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints_ext); + add!( + m, + OID_X509_EXT_CERTIFICATE_POLICIES, + parse_certificatepolicies_ext + ); + add!(m, OID_X509_EXT_POLICY_MAPPINGS, parse_policymappings_ext); + add!( + m, + OID_X509_EXT_POLICY_CONSTRAINTS, + parse_policyconstraints_ext + ); + add!( + m, + OID_X509_EXT_EXTENDED_KEY_USAGE, + parse_extendedkeyusage_ext + ); + add!( + m, + OID_X509_EXT_CRL_DISTRIBUTION_POINTS, + parse_crldistributionpoints_ext + ); + add!( + m, + OID_X509_EXT_INHIBITANT_ANY_POLICY, + parse_inhibitanypolicy_ext + ); + add!( + m, + OID_PKIX_AUTHORITY_INFO_ACCESS, + parse_authorityinfoaccess_ext + ); + add!( + m, + OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, + parse_authoritykeyidentifier_ext + ); + add!(m, OID_CT_LIST_SCT, parse_sct_ext); + add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype_ext); + add!(m, OID_X509_EXT_CERT_COMMENT, parse_nscomment_ext); + add!(m, OID_X509_EXT_CRL_NUMBER, parse_crl_number); + add!(m, OID_X509_EXT_REASON_CODE, parse_reason_code); + add!(m, OID_X509_EXT_INVALIDITY_DATE, parse_invalidity_date); + m + }; + } + + // look into the parser map if the extension is known, and parse it + // otherwise, leave it as UnsupportedExtension + fn parse_extension0<'a>( + orig_i: &'a [u8], + i: &'a [u8], + oid: &Oid, + ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { + if let Some(parser) = EXTENSION_PARSERS.get(oid) { + match parser(i) { + Ok((_, ext)) => Ok((orig_i, ext)), + Err(error) => Ok((orig_i, ParsedExtension::ParseError { error })), + } + } else { + Ok(( + orig_i, + ParsedExtension::UnsupportedExtension { + oid: oid.to_owned(), + }, + )) + } + } + + pub(crate) fn parse_extension<'a>( + orig_i: &'a [u8], + i: &'a [u8], + oid: &Oid, + ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { + parse_extension0(orig_i, i, oid) + } + + /// Parse a "Basic Constraints" extension + /// + /// <pre> + /// id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } + /// BasicConstraints ::= SEQUENCE { + /// cA BOOLEAN DEFAULT FALSE, + /// pathLenConstraint INTEGER (0..MAX) OPTIONAL } + /// </pre> + /// + /// Note the maximum length of the `pathLenConstraint` field is limited to the size of a 32-bits + /// unsigned integer, and parsing will fail if value if larger. + pub(super) fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], BasicConstraints, BerError> { + let (rem, obj) = parse_der_sequence(i)?; + if let Ok(seq) = obj.as_sequence() { + let (ca, path_len_constraint) = match seq.len() { + 0 => (false, None), + 1 => { + if let Ok(b) = seq[0].as_bool() { + (b, None) + } else if let Ok(u) = seq[0].as_u32() { + (false, Some(u)) + } else { + return Err(nom::Err::Error(BerError::InvalidTag)); + } + } + 2 => { + let ca = seq[0] + .as_bool() + .or(Err(nom::Err::Error(BerError::InvalidLength)))?; + let pl = seq[1] + .as_u32() + .or(Err(nom::Err::Error(BerError::InvalidLength)))?; + (ca, Some(pl)) + } + _ => return Err(nom::Err::Error(BerError::InvalidLength)), + }; + Ok(( + rem, + BasicConstraints { + ca, + path_len_constraint, + }, + )) + } else { + Err(nom::Err::Error(BerError::InvalidLength)) + } + } + + 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> { + map(parse_nameconstraints, ParsedExtension::NameConstraints)(i) + } + + pub(super) fn parse_subjectalternativename_ext( + i: &[u8], + ) -> IResult<&[u8], ParsedExtension, BerError> { + parse_der_sequence_defined_g(|input, _| { + let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; + Ok(( + i, + ParsedExtension::SubjectAlternativeName(SubjectAlternativeName { general_names }), + )) + })(i) + } + + pub(super) fn parse_issueralternativename_ext( + i: &[u8], + ) -> IResult<&[u8], ParsedExtension, BerError> { + parse_der_sequence_defined_g(|input, _| { + let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; + Ok(( + i, + ParsedExtension::IssuerAlternativeName(IssuerAlternativeName { general_names }), + )) + })(i) + } + + pub(super) fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], PolicyConstraints, BerError> { + parse_der_sequence_defined_g(|input, _| { + let (i, require_explicit_policy) = opt(complete(map_res( + parse_der_tagged_implicit(0, parse_der_content(Tag::Integer)), + |x| x.as_u32(), + )))(input)?; + let (i, inhibit_policy_mapping) = all_consuming(opt(complete(map_res( + parse_der_tagged_implicit(1, parse_der_content(Tag::Integer)), + |x| x.as_u32(), + ))))(i)?; + let policy_constraint = PolicyConstraints { + require_explicit_policy, + inhibit_policy_mapping, + }; + Ok((i, policy_constraint)) + })(i) + } + + 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> { + map(parse_policymappings, ParsedExtension::PolicyMappings)(i) + } + + fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + let (ret, skip_certs) = parse_der_u32(i)?; + Ok(( + ret, + ParsedExtension::InhibitAnyPolicy(InhibitAnyPolicy { skip_certs }), + )) + } + + 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> { + let (rem, header) = der_read_element_header(i)?; + match header.tag().0 { + 0 => { + let (rem, names) = many1(complete(parse_generalname))(rem)?; + Ok((rem, DistributionPointName::FullName(names))) + } + 1 => { + let (rem, rdn) = RelativeDistinguishedName::from_der(rem) + .map_err(|_| BerError::BerValueError)?; + Ok((rem, DistributionPointName::NameRelativeToCRLIssuer(rdn))) + } + _ => Err(Err::Error(BerError::InvalidTag)), + } + } + + // ReasonFlags ::= BIT STRING { + // unused (0), + // keyCompromise (1), + // cACompromise (2), + // affiliationChanged (3), + // superseded (4), + // cessationOfOperation (5), + // certificateHold (6), + // privilegeWithdrawn (7), + // aACompromise (8) } + fn parse_tagged1_reasons(i: &[u8]) -> BerResult<ReasonFlags> { + let (rem, obj) = parse_der_tagged_implicit(1, parse_der_content(Tag::BitString))(i)?; + if let DerObjectContent::BitString(_, b) = obj.content { + let flags = b + .data + .iter() + .rev() + .fold(0, |acc, x| acc << 8 | (x.reverse_bits() as u16)); + Ok((rem, ReasonFlags { flags })) + } else { + Err(nom::Err::Failure(BerError::InvalidTag)) + } + } + + fn parse_crlissuer_content(i: &[u8]) -> BerResult<Vec<GeneralName>> { + many1(complete(parse_generalname))(i) + } + + // DistributionPoint ::= SEQUENCE { + // distributionPoint [0] DistributionPointName OPTIONAL, + // reasons [1] ReasonFlags OPTIONAL, + // cRLIssuer [2] GeneralNames OPTIONAL } + pub(super) fn parse_crldistributionpoint( + i: &[u8], + ) -> IResult<&[u8], CRLDistributionPoint, BerError> { + parse_der_sequence_defined_g(|content, _| { + let (rem, distribution_point) = + opt(complete(parse_der_tagged_explicit_g(0, |b, _| { + parse_distributionpointname(b) + })))(content)?; + let (rem, reasons) = opt(complete(parse_tagged1_reasons))(rem)?; + let (rem, crl_issuer) = opt(complete(parse_der_tagged_implicit_g(2, |i, _, _| { + parse_crlissuer_content(i) + })))(rem)?; + let crl_dp = CRLDistributionPoint { + distribution_point, + reasons, + crl_issuer, + }; + Ok((rem, crl_dp)) + })(i) + } + + pub(super) fn parse_crldistributionpoints( + i: &[u8], + ) -> 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> { + map( + parse_crldistributionpoints, + ParsedExtension::CRLDistributionPoints, + )(i) + } + + // AuthorityInfoAccessSyntax ::= + // SEQUENCE SIZE (1..MAX) OF AccessDescription + // + // AccessDescription ::= SEQUENCE { + // accessMethod OBJECT IDENTIFIER, + // accessLocation GeneralName } + pub(super) fn parse_authorityinfoaccess( + i: &[u8], + ) -> 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)?; + // 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_aia)(i)?; + Ok((ret, AuthorityInfoAccess { accessdescs })) + } + + fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + map( + parse_authorityinfoaccess, + ParsedExtension::AuthorityInfoAccess, + )(i) + } + + fn parse_aki_content<'a>( + i: &'a [u8], + _hdr: Header<'_>, + ) -> IResult<&'a [u8], AuthorityKeyIdentifier<'a>, BerError> { + let (i, key_identifier) = opt(complete(parse_der_tagged_implicit_g(0, |d, _, _| { + Ok((&[], KeyIdentifier(d))) + })))(i)?; + let (i, authority_cert_issuer) = + opt(complete(parse_der_tagged_implicit_g(1, |d, _, _| { + many0(complete(parse_generalname))(d) + })))(i)?; + let (i, authority_cert_serial) = opt(complete(parse_der_tagged_implicit( + 2, + parse_der_content(Tag::Integer), + )))(i)?; + let authority_cert_serial = authority_cert_serial.and_then(|o| o.as_slice().ok()); + let aki = AuthorityKeyIdentifier { + key_identifier, + authority_cert_issuer, + authority_cert_serial, + }; + Ok((i, aki)) + } + + // RFC 5280 section 4.2.1.1: Authority Key Identifier + pub(super) fn parse_authoritykeyidentifier( + i: &[u8], + ) -> 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> { + map( + parse_authoritykeyidentifier, + ParsedExtension::AuthorityKeyIdentifier, + )(i) + } + + 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> { + map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i) + } + + fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + map(parse_keyusage, ParsedExtension::KeyUsage)(i) + } + + pub(super) fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], NSCertType, BerError> { + let (rest, obj) = parse_der_bitstring(i)?; + let bitstring = obj + .content + .as_bitstring() + .or(Err(Err::Error(BerError::BerTypeError)))?; + // bitstring should be 1 byte long + if bitstring.data.len() != 1 { + return Err(Err::Error(BerError::BerValueError)); + } + let flags = bitstring.data[0].reverse_bits(); + Ok((rest, NSCertType(flags))) + } + + 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> { + match parse_der_ia5string(i) { + Ok((i, obj)) => { + let s = obj.as_str()?; + Ok((i, ParsedExtension::NsCertComment(s))) + } + Err(e) => { + // Some implementations encode the comment directly, without + // wrapping it in an IA5String + if let Ok(s) = std::str::from_utf8(i) { + Ok((&[], ParsedExtension::NsCertComment(s))) + } else { + Err(e) + } + } + } + } + + // CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation + // + // PolicyInformation ::= SEQUENCE { + // policyIdentifier CertPolicyId, + // policyQualifiers SEQUENCE SIZE (1..MAX) OF + // PolicyQualifierInfo OPTIONAL } + // + // CertPolicyId ::= OBJECT IDENTIFIER + // + // PolicyQualifierInfo ::= SEQUENCE { + // policyQualifierId PolicyQualifierId, + // qualifier ANY DEFINED BY policyQualifierId } + // + // -- Implementations that recognize additional policy qualifiers MUST + // -- augment the following definition for PolicyQualifierId + // + // PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice ) + pub(super) fn parse_certificatepolicies( + i: &[u8], + ) -> IResult<&[u8], Vec<PolicyInformation>, 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 { + policy_qualifier_id, + qualifier: rem, + }; + Ok((&[], info)) + })(i) + } + 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) = + opt(complete(parse_der_sequence_defined_g(|content, _| { + many1(complete(parse_policy_qualifier_info))(content) + })))(rem)?; + let info = PolicyInformation { + policy_id, + policy_qualifiers, + }; + Ok((rem, info)) + })(i) + } + parse_der_sequence_of_v(parse_policy_information)(i) + } + + fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + map( + parse_certificatepolicies, + ParsedExtension::CertificatePolicies, + )(i) + } + + // CRLReason ::= ENUMERATED { ... + fn parse_reason_code(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + let (rest, obj) = parse_der_enum(i)?; + let code = obj + .content + .as_u32() + .or(Err(Err::Error(BerError::BerValueError)))?; + if code > 10 { + return Err(Err::Error(BerError::BerValueError)); + } + let ret = ParsedExtension::ReasonCode(ReasonCode(code as u8)); + Ok((rest, ret)) + } + + // invalidityDate ::= GeneralizedTime + 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)))) + } + + // 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> { + 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> { + map( + parse_ct_signed_certificate_timestamp_list, + ParsedExtension::SCT, + )(i) + } +} + +/// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension +pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result<Vec<X509Extension>> { + 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<Vec<X509Extension>> { + if i.is_empty() { + return Ok((i, Vec::new())); + } + + match der_read_element_header(i) { + Ok((rem, hdr)) => { + if hdr.tag() != explicit_tag { + return Err(Err::Error(X509Error::InvalidExtensions)); + } + all_consuming(parse_extension_sequence)(rem) + } + Err(_) => Err(X509Error::InvalidExtensions.into()), + } +} + +/// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension +pub(crate) fn parse_extension_envelope_sequence(i: &[u8]) -> X509Result<Vec<X509Extension>> { + let parser = X509ExtensionParser::new().with_deep_parse_extensions(false); + + parse_der_sequence_defined_g(move |a, _| all_consuming(many0(complete(parser)))(a))(i) +} + +pub(crate) fn parse_extensions_envelope( + i: &[u8], + explicit_tag: Tag, +) -> X509Result<Vec<X509Extension>> { + if i.is_empty() { + return Ok((i, Vec::new())); + } + + match der_read_element_header(i) { + Ok((rem, hdr)) => { + if hdr.tag() != explicit_tag { + return Err(Err::Error(X509Error::InvalidExtensions)); + } + all_consuming(parse_extension_envelope_sequence)(rem) + } + Err(_) => Err(X509Error::InvalidExtensions.into()), + } +} + +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)?; + let value = obj + .map(|o| o.as_bool().unwrap_or_default()) // unwrap cannot fail, we just read a bool + .unwrap_or(false) // default critical value + ; + Ok((rem, value)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_keyusage_flags() { + let ku = KeyUsage { flags: 98 }; + assert!(!ku.digital_signature()); + assert!(ku.non_repudiation()); + assert!(!ku.key_encipherment()); + assert!(!ku.data_encipherment()); + assert!(!ku.key_agreement()); + assert!(ku.key_cert_sign()); + assert!(ku.crl_sign()); + assert!(!ku.encipher_only()); + assert!(!ku.decipher_only()); + } + + #[test] + fn test_extensions1() { + use der_parser::oid; + let crt = crate::parse_x509_certificate(include_bytes!("../../assets/extension1.der")) + .unwrap() + .1; + let tbs = &crt.tbs_certificate; + let bc = crt + .basic_constraints() + .expect("could not get basic constraints") + .expect("no basic constraints found"); + assert_eq!( + bc.value, + &BasicConstraints { + ca: true, + path_len_constraint: Some(1) + } + ); + { + let ku = tbs + .key_usage() + .expect("could not get key usage") + .expect("no key usage found") + .value; + assert!(ku.digital_signature()); + assert!(!ku.non_repudiation()); + assert!(ku.key_encipherment()); + assert!(ku.data_encipherment()); + assert!(ku.key_agreement()); + assert!(!ku.key_cert_sign()); + assert!(!ku.crl_sign()); + assert!(ku.encipher_only()); + assert!(ku.decipher_only()); + } + { + let eku = tbs + .extended_key_usage() + .expect("could not get extended key usage") + .expect("no extended key usage found") + .value; + assert!(!eku.any); + assert!(eku.server_auth); + assert!(!eku.client_auth); + assert!(eku.code_signing); + assert!(!eku.email_protection); + assert!(eku.time_stamping); + assert!(!eku.ocsp_signing); + assert_eq!(eku.other, vec![oid!(1.2.3 .4 .0 .42)]); + } + assert_eq!( + tbs.policy_constraints() + .expect("could not get policy constraints") + .expect("no policy constraints found") + .value, + &PolicyConstraints { + require_explicit_policy: None, + inhibit_policy_mapping: Some(10) + } + ); + let val = tbs + .inhibit_anypolicy() + .expect("could not get inhibit_anypolicy") + .expect("no inhibit_anypolicy found") + .value; + assert_eq!(val, &InhibitAnyPolicy { skip_certs: 2 }); + { + let alt_names = &tbs + .subject_alternative_name() + .expect("could not get subject alt names") + .expect("no subject alt names found") + .value + .general_names; + assert_eq!(alt_names[0], GeneralName::RFC822Name("foo@example.com")); + assert_eq!(alt_names[1], GeneralName::URI("http://my.url.here/")); + assert_eq!( + alt_names[2], + GeneralName::IPAddress([192, 168, 7, 1].as_ref()) + ); + assert_eq!( + format!( + "{}", + match alt_names[3] { + GeneralName::DirectoryName(ref dn) => dn, + _ => unreachable!(), + } + ), + "C=UK, O=My Organization, OU=My Unit, CN=My Name" + ); + assert_eq!(alt_names[4], GeneralName::DNSName("localhost")); + assert_eq!(alt_names[5], GeneralName::RegisteredID(oid!(1.2.90 .0))); + assert_eq!( + alt_names[6], + GeneralName::OtherName(oid!(1.2.3 .4), b"\xA0\x17\x0C\x15some other identifier") + ); + } + + { + let name_constraints = &tbs + .name_constraints() + .expect("could not get name constraints") + .expect("no name constraints found") + .value; + assert_eq!(name_constraints.permitted_subtrees, None); + assert_eq!( + name_constraints.excluded_subtrees, + Some(vec![ + GeneralSubtree { + base: GeneralName::IPAddress([192, 168, 0, 0, 255, 255, 0, 0].as_ref()) + }, + GeneralSubtree { + base: GeneralName::RFC822Name("foo.com") + }, + ]) + ); + } + } + + #[test] + fn test_extensions2() { + use der_parser::oid; + let crt = crate::parse_x509_certificate(include_bytes!("../../assets/extension2.der")) + .unwrap() + .1; + let tbs = crt.tbs_certificate; + assert_eq!( + tbs.policy_constraints() + .expect("could not get policy constraints") + .expect("no policy constraints found") + .value, + &PolicyConstraints { + require_explicit_policy: Some(5000), + inhibit_policy_mapping: None + } + ); + { + let pm = tbs + .policy_mappings() + .expect("could not get policy_mappings") + .expect("no policy_mappings found") + .value + .clone() + .into_hashmap(); + let mut pm_ref = HashMap::new(); + pm_ref.insert(oid!(2.34.23), vec![oid!(2.2)]); + pm_ref.insert(oid!(1.1), vec![oid!(0.0.4)]); + pm_ref.insert(oid!(2.2), vec![oid!(2.2.1), oid!(2.2.3)]); + assert_eq!(pm, pm_ref); + } + } + + #[test] + fn test_extensions_crl_distribution_points() { + // Extension not present + { + let crt = crate::parse_x509_certificate(include_bytes!( + "../../assets/crl-ext/crl-no-crl.der" + )) + .unwrap() + .1; + assert!(crt + .tbs_certificate + .extensions_map() + .unwrap() + .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) + .is_none()); + } + // CRLDistributionPoints has 1 entry with 1 URI + { + let crt = crate::parse_x509_certificate(include_bytes!( + "../../assets/crl-ext/crl-simple.der" + )) + .unwrap() + .1; + let crl = crt + .tbs_certificate + .extensions_map() + .unwrap() + .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) + .unwrap() + .parsed_extension(); + assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); + if let ParsedExtension::CRLDistributionPoints(crl) = crl { + assert_eq!(crl.len(), 1); + assert!(crl[0].reasons.is_none()); + assert!(crl[0].crl_issuer.is_none()); + let distribution_point = crl[0].distribution_point.as_ref().unwrap(); + assert!(matches!( + distribution_point, + DistributionPointName::FullName(_) + )); + if let DistributionPointName::FullName(names) = distribution_point { + assert_eq!(names.len(), 1); + assert!(matches!(names[0], GeneralName::URI(_))); + if let GeneralName::URI(uri) = names[0] { + assert_eq!(uri, "http://example.com/myca.crl") + } + } + } + } + // CRLDistributionPoints has 2 entries + { + let crt = crate::parse_x509_certificate(include_bytes!( + "../../assets/crl-ext/crl-complex.der" + )) + .unwrap() + .1; + let crl = crt + .tbs_certificate + .extensions_map() + .unwrap() + .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) + .unwrap() + .parsed_extension(); + assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); + if let ParsedExtension::CRLDistributionPoints(crl) = crl { + assert_eq!(crl.len(), 2); + // First CRL Distribution point + let reasons = crl[0].reasons.as_ref().unwrap(); + assert!(reasons.key_compromise()); + assert!(reasons.ca_compromise()); + assert!(!reasons.affilation_changed()); + assert!(!reasons.superseded()); + assert!(!reasons.cessation_of_operation()); + assert!(!reasons.certificate_hold()); + assert!(!reasons.privelege_withdrawn()); + assert!(reasons.aa_compromise()); + assert_eq!( + format!("{}", reasons), + "Key Compromise, CA Compromise, AA Compromise" + ); + let issuers = crl[0].crl_issuer.as_ref().unwrap(); + assert_eq!(issuers.len(), 1); + assert!(matches!(issuers[0], GeneralName::DirectoryName(_))); + if let GeneralName::DirectoryName(name) = &issuers[0] { + assert_eq!(name.to_string(), "C=US, O=Organisation, CN=Some Name"); + } + let distribution_point = crl[0].distribution_point.as_ref().unwrap(); + assert!(matches!( + distribution_point, + DistributionPointName::FullName(_) + )); + if let DistributionPointName::FullName(names) = distribution_point { + assert_eq!(names.len(), 1); + assert!(matches!(names[0], GeneralName::URI(_))); + if let GeneralName::URI(uri) = names[0] { + assert_eq!(uri, "http://example.com/myca.crl") + } + } + // Second CRL Distribution point + let reasons = crl[1].reasons.as_ref().unwrap(); + assert!(reasons.key_compromise()); + assert!(reasons.ca_compromise()); + assert!(!reasons.affilation_changed()); + assert!(!reasons.superseded()); + assert!(!reasons.cessation_of_operation()); + assert!(!reasons.certificate_hold()); + assert!(!reasons.privelege_withdrawn()); + assert!(!reasons.aa_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!( + distribution_point, + DistributionPointName::FullName(_) + )); + if let DistributionPointName::FullName(names) = distribution_point { + assert_eq!(names.len(), 1); + assert!(matches!(names[0], GeneralName::URI(_))); + if let GeneralName::URI(uri) = names[0] { + assert_eq!(uri, "http://example.com/myca2.crl") + } + } + } + } + } + + // Test cases for: + // - parsing SubjectAlternativeName + // - parsing NameConstraints +} diff --git a/rust/vendor/x509-parser/src/extensions/nameconstraints.rs b/rust/vendor/x509-parser/src/extensions/nameconstraints.rs new file mode 100644 index 0000000..da9a999 --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/nameconstraints.rs @@ -0,0 +1,59 @@ +use super::GeneralName; +use crate::error::{X509Error, X509Result}; +use crate::extensions::parse_generalname; +use asn1_rs::FromDer; +use der_parser::der::*; +use der_parser::error::BerError; +use nom::combinator::{all_consuming, complete, map, opt}; +use nom::multi::many1; +use nom::{Err, IResult}; + +#[derive(Clone, Debug, PartialEq)] +pub struct NameConstraints<'a> { + pub permitted_subtrees: Option<Vec<GeneralSubtree<'a>>>, + pub excluded_subtrees: Option<Vec<GeneralSubtree<'a>>>, +} + +impl<'a> FromDer<'a, X509Error> for NameConstraints<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_nameconstraints(i).map_err(Err::convert) + } +} + +#[derive(Clone, Debug, PartialEq)] +/// Represents the structure used in the name constraints extensions. +/// The fields minimum and maximum are not supported (openssl also has no support). +pub struct GeneralSubtree<'a> { + pub base: GeneralName<'a>, + // minimum: u32, + // maximum: Option<u32>, +} + +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<GeneralSubtree>, BerError> { + all_consuming(many1(complete(parse_subtree)))(i) + } + + let (ret, named_constraints) = parse_der_sequence_defined_g(|input, _| { + let (rem, permitted_subtrees) = + opt(complete(parse_der_tagged_explicit_g(0, |input, _| { + parse_subtrees(input) + })))(input)?; + let (rem, excluded_subtrees) = + opt(complete(parse_der_tagged_explicit_g(1, |input, _| { + parse_subtrees(input) + })))(rem)?; + let named_constraints = NameConstraints { + permitted_subtrees, + excluded_subtrees, + }; + Ok((rem, named_constraints)) + })(i)?; + + Ok((ret, named_constraints)) +} diff --git a/rust/vendor/x509-parser/src/extensions/policymappings.rs b/rust/vendor/x509-parser/src/extensions/policymappings.rs new file mode 100644 index 0000000..2bd367d --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/policymappings.rs @@ -0,0 +1,92 @@ +use crate::error::{X509Error, X509Result}; +use asn1_rs::{DerSequence, Error, FromDer, Oid}; +use nom::{Err, IResult}; +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyMappings<'a> { + pub mappings: Vec<PolicyMapping<'a>>, +} + +impl<'a> FromDer<'a, X509Error> for PolicyMappings<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_policymappings(i).map_err(Err::convert) + } +} + +impl<'a> PolicyMappings<'a> { + /// Returns a `HashMap` mapping `Oid` to the list of references to `Oid` + /// + /// If several names match the same `Oid`, they are merged in the same entry. + pub fn as_hashmap(&self) -> HashMap<Oid<'a>, Vec<&Oid<'a>>> { + // create the hashmap and merge entries with same OID + let mut m: HashMap<Oid, Vec<&_>> = HashMap::new(); + for desc in &self.mappings { + let PolicyMapping { + issuer_domain_policy: left, + subject_domain_policy: right, + } = desc; + if let Some(l) = m.get_mut(left) { + l.push(right); + } else { + m.insert(left.clone(), vec![right]); + } + } + m + } + + /// Returns a `HashMap` mapping `Oid` to the list of `Oid` (consuming the input) + /// + /// If several names match the same `Oid`, they are merged in the same entry. + pub fn into_hashmap(self) -> HashMap<Oid<'a>, Vec<Oid<'a>>> { + let mut l = self.mappings; + // create the hashmap and merge entries with same OID + let mut m: HashMap<Oid, Vec<_>> = HashMap::new(); + for mapping in l.drain(..) { + let PolicyMapping { + issuer_domain_policy: left, + subject_domain_policy: right, + } = mapping; + if let Some(general_names) = m.get_mut(&left) { + general_names.push(right); + } else { + m.insert(left, vec![right]); + } + } + m + } +} + +#[derive(Clone, Debug, PartialEq, Eq, DerSequence)] +pub struct PolicyMapping<'a> { + pub issuer_domain_policy: Oid<'a>, + pub subject_domain_policy: Oid<'a>, +} + +impl<'a> PolicyMapping<'a> { + pub const fn new(issuer_domain_policy: Oid<'a>, subject_domain_policy: Oid<'a>) -> Self { + PolicyMapping { + issuer_domain_policy, + subject_domain_policy, + } + } +} + +// PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { +// issuerDomainPolicy CertPolicyId, +// subjectDomainPolicy CertPolicyId } +pub(crate) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, Error> { + let (ret, pairs) = <Vec<PolicyMapping>>::from_der(i)?; + // let mut mappings: HashMap<Oid, Vec<Oid>> = HashMap::new(); + let mappings = pairs; + // let mut mappings = Vec::new(); + // for pair in pairs.iter() { + // // XXX this should go to Validate + // // if left.bytes() == oid!(raw 2.5.29.32.0) || right.bytes() == oid!(raw 2.5.29.32.0) { + // // // mapping to or from anyPolicy is not allowed + // // return Err(Err::Failure(BerError::InvalidTag)); + // // } + // mappings.push(PolicyMapping::new(left, right)); + // } + Ok((ret, PolicyMappings { mappings })) +} diff --git a/rust/vendor/x509-parser/src/extensions/sct.rs b/rust/vendor/x509-parser/src/extensions/sct.rs new file mode 100644 index 0000000..646f774 --- /dev/null +++ b/rust/vendor/x509-parser/src/extensions/sct.rs @@ -0,0 +1,124 @@ +//! Certificate transparency [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962) +//! +//! Code borrowed from tls-parser crate (file <https://github.com/rusticata/tls-parser/blob/tls-parser-0.11.0/src/certificate_transparency.rs>) + +use std::convert::TryInto; + +use asn1_rs::FromDer; +use der_parser::error::BerError; +use nom::bytes::streaming::take; +use nom::combinator::{complete, map_parser}; +use nom::multi::{length_data, many1}; +use nom::number::streaming::{be_u16, be_u64, be_u8}; +use nom::IResult; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignedCertificateTimestamp<'a> { + pub version: CtVersion, + pub id: CtLogID<'a>, + pub timestamp: u64, + pub extensions: CtExtensions<'a>, + pub signature: DigitallySigned<'a>, +} + +/// Certificate Transparency Version as defined in +/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CtVersion(pub u8); + +impl CtVersion { + pub const V1: CtVersion = CtVersion(0); +} + +/// LogID as defined in +/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CtLogID<'a> { + pub key_id: &'a [u8; 32], +} + +/// CtExtensions as defined in +/// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CtExtensions<'a>(pub &'a [u8]); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DigitallySigned<'a> { + pub hash_alg_id: u8, + pub sign_alg_id: u8, + pub data: &'a [u8], +} + +/// Parses a list of Signed Certificate Timestamp entries +pub fn parse_ct_signed_certificate_timestamp_list( + i: &[u8], +) -> IResult<&[u8], Vec<SignedCertificateTimestamp>, BerError> { + // use nom::HexDisplay; + // eprintln!("{}", i.to_hex(16)); + let (rem, b) = <&[u8]>::from_der(i)?; + let (b, sct_len) = be_u16(b)?; + let (_, sct_list) = map_parser( + take(sct_len as usize), + many1(complete(parse_ct_signed_certificate_timestamp)), + )(b)?; + Ok((rem, sct_list)) +} + +/// Parses as single Signed Certificate Timestamp entry +pub fn parse_ct_signed_certificate_timestamp( + i: &[u8], +) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { + map_parser( + length_data(be_u16), + parse_ct_signed_certificate_timestamp_content, + )(i) +} + +pub(crate) fn parse_ct_signed_certificate_timestamp_content( + i: &[u8], +) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { + let (i, version) = be_u8(i)?; + let (i, id) = parse_log_id(i)?; + let (i, timestamp) = be_u64(i)?; + let (i, extensions) = parse_ct_extensions(i)?; + let (i, signature) = parse_digitally_signed(i)?; + let sct = SignedCertificateTimestamp { + version: CtVersion(version), + id, + timestamp, + extensions, + signature, + }; + Ok((i, sct)) +} + +// Safety: cannot fail, take() returns exactly 32 bytes +fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, BerError> { + let (i, key_id) = take(32usize)(i)?; + Ok(( + i, + CtLogID { + key_id: key_id + .try_into() + .expect("take(32) is in sync with key_id size"), + }, + )) +} + +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> { + let (i, hash_alg_id) = be_u8(i)?; + let (i, sign_alg_id) = be_u8(i)?; + let (i, data) = length_data(be_u16)(i)?; + let signed = DigitallySigned { + hash_alg_id, + sign_alg_id, + data, + }; + Ok((i, signed)) +} diff --git a/rust/vendor/x509-parser/src/lib.rs b/rust/vendor/x509-parser/src/lib.rs new file mode 100644 index 0000000..1ddd357 --- /dev/null +++ b/rust/vendor/x509-parser/src/lib.rs @@ -0,0 +1,213 @@ +//! [](./LICENSE-MIT) +//! [](./LICENSE-APACHE) +//! [](https://docs.rs/x509-parser) +//! [](https://crates.io/crates/x509-parser) +//! [](https://crates.io/crates/x509-parser) +//! [](https://github.com/rusticata/x509-parser/actions) +//! [](#rust-version-requirements) +//! +//! # X.509 Parser +//! +//! A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) +//! parser combinator framework. +//! +//! It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken +//! to ensure security and safety of this crate, including design (recursion limit, defensive +//! programming), tests, and fuzzing. It also aims to be panic-free. +//! +//! The code is available on [Github](https://github.com/rusticata/x509-parser) +//! and is part of the [Rusticata](https://github.com/rusticata) project. +//! +//! Certificates are usually encoded in two main formats: PEM (usually the most common format) or +//! DER. A PEM-encoded certificate is a container, storing a DER object. See the +//! [`pem`](pem/index.html) module for more documentation. +//! +//! To decode a DER-encoded certificate, the main parsing method is +//! `X509Certificate::from_der` ( +//! part of the [`FromDer`](prelude/trait.FromDer.html) trait +//! ), which builds a +//! [`X509Certificate`](certificate/struct.X509Certificate.html) object. +//! +//! An alternative method is to use [`X509CertificateParser`](certificate/struct.X509CertificateParser.html), +//! which allows specifying parsing options (for example, not automatically parsing option contents). +//! +//! The returned objects for parsers follow the definitions of the RFC. This means that accessing +//! fields is done by accessing struct members recursively. Some helper functions are provided, for +//! example [`X509Certificate::issuer()`](certificate/struct.X509Certificate.html#method.issuer) returns the +//! same as accessing `<object>.tbs_certificate.issuer`. +//! +//! For PEM-encoded certificates, use the [`pem`](pem/index.html) module. +//! +//! # Examples +//! +//! Parsing a certificate in DER format: +//! +//! ```rust +//! use x509_parser::prelude::*; +//! +//! static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); +//! +//! # fn main() { +//! let res = X509Certificate::from_der(IGCA_DER); +//! match res { +//! Ok((rem, cert)) => { +//! assert!(rem.is_empty()); +//! // +//! assert_eq!(cert.version(), X509Version::V3); +//! }, +//! _ => panic!("x509 parsing failed: {:?}", res), +//! } +//! # } +//! ``` +//! +//! To parse a CRL and print information about revoked certificates: +//! +//! ```rust +//! # use x509_parser::prelude::*; +//! # +//! # static DER: &[u8] = include_bytes!("../assets/example.crl"); +//! # +//! # fn main() { +//! let res = CertificateRevocationList::from_der(DER); +//! match res { +//! Ok((_rem, crl)) => { +//! for revoked in crl.iter_revoked_certificates() { +//! println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); +//! println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); +//! } +//! }, +//! _ => panic!("CRL parsing failed: {:?}", res), +//! } +//! # } +//! ``` +//! +//! See also `examples/print-cert.rs`. +//! +//! # Features +//! +//! - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. +//! It adds the +//! [`X509Certificate::verify_signature()`](certificate/struct.X509Certificate.html#method.verify_signature) +//! to `X509Certificate`. +//! +//! ```rust +//! # #[cfg(feature = "verify")] +//! # use x509_parser::certificate::X509Certificate; +//! /// Cryptographic signature verification: returns true if certificate was signed by issuer +//! #[cfg(feature = "verify")] +//! pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { +//! let issuer_public_key = issuer.public_key(); +//! cert +//! .verify_signature(Some(issuer_public_key)) +//! .is_ok() +//! } +//! ``` +//! +//! - 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). +//! +//! ## Rust version requirements +//! +//! `x509-parser` requires **Rustc version 1.57 or greater**, based on der-parser +//! dependencies and for proc-macro attributes support. +//! +//! Note that due to breaking changes in the `time` crate, a specific version of this +//! crate must be specified for compiler versions <= 1.57: +//! `cargo update -p time --precise 0.3.9` +//! +//! [RFC5280]: https://tools.ietf.org/html/rfc5280 + +#![deny(/*missing_docs,*/ + unstable_features, + unused_import_braces, unused_qualifications)] +#![warn( + missing_debug_implementations, + /* missing_docs, + rust_2018_idioms,*/ + unreachable_pub +)] +#![forbid(unsafe_code)] +#![deny(rustdoc::broken_intra_doc_links)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +pub mod certificate; +pub mod certification_request; +pub mod cri_attributes; +pub mod error; +pub mod extensions; +pub mod objects; +pub mod pem; +pub mod prelude; +pub mod public_key; +pub mod revocation_list; +pub mod signature_algorithm; +pub mod signature_value; +pub mod time; +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")))] +pub mod verify; +pub mod x509; + +// reexports +pub use der_parser; +pub use der_parser::num_bigint; +pub use nom; +pub use oid_registry; + +use asn1_rs::FromDer; +use certificate::X509Certificate; +use error::X509Result; +use revocation_list::CertificateRevocationList; + +/// Parse a **DER-encoded** X.509 Certificate, and return the remaining of the input and the built +/// object. +/// +/// +/// This function is an alias to [X509Certificate::from_der](certificate::X509Certificate::from_der). See this function +/// for more information. +/// +/// For PEM-encoded certificates, use the [`pem`](pem/index.html) module. +#[inline] +pub fn parse_x509_certificate(i: &[u8]) -> X509Result<X509Certificate> { + X509Certificate::from_der(i) +} + +/// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built +/// object. +/// +/// 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<CertificateRevocationList> { + CertificateRevocationList::from_der(i) +} + +/// Parse a DER-encoded X.509 Certificate, and return the remaining of the input and the built +#[deprecated( + since = "0.9.0", + note = "please use `parse_x509_certificate` or `X509Certificate::from_der` instead" +)] +#[inline] +pub fn parse_x509_der(i: &[u8]) -> X509Result<X509Certificate> { + X509Certificate::from_der(i) +} + +/// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built +/// object. +#[deprecated( + since = "0.9.0", + note = "please use `parse_x509_crl` or `CertificateRevocationList::from_der` instead" +)] +#[inline] +pub fn parse_crl_der(i: &[u8]) -> X509Result<CertificateRevocationList> { + CertificateRevocationList::from_der(i) +} diff --git a/rust/vendor/x509-parser/src/objects.rs b/rust/vendor/x509-parser/src/objects.rs new file mode 100644 index 0000000..9e3dc63 --- /dev/null +++ b/rust/vendor/x509-parser/src/objects.rs @@ -0,0 +1,92 @@ +//! X.509 helper objects definitions and registry +//! +//! All OID objects and definitions are now stored in the [oid-registry](https://crates.io/crates/oid-registry) crate. +//! +//! This crate is re-exporting `oid-registry`, so to access the OID constants the +//! `x509_parser::oid_oid_registry` namespace can be used (see example below). +//! +//! ## Example +//! +//! To get the short name for a given OID: +//! +//! ```rust +//! use x509_parser::objects::*; +//! use x509_parser::oid_registry::*; +//! +//! let oid = &OID_X509_COMMON_NAME; +//! let sn = oid2sn(oid, oid_registry()); +//! assert_eq!(sn, Ok("commonName")); +//! ``` + +use crate::error::NidError; +use asn1_rs::{oid, Oid}; +use lazy_static::lazy_static; +use oid_registry::*; +use std::collections::HashMap; + +lazy_static! { + static ref OID_REGISTRY: OidRegistry<'static> = { + let mut reg = OidRegistry::default().with_all_crypto().with_x509(); + // OIDs not in the default registry can be added here + let entry = OidEntry::new("id-mgf1", "Mask Generator Function 1 (MGF1)"); + reg.insert(oid! {1.2.840.113549.1.1.8}, entry); + reg + }; + static ref ABBREV_MAP: HashMap<Oid<'static>, &'static str> = { + let mut m = HashMap::new(); + m.insert(OID_X509_COMMON_NAME, "CN"); + m.insert(OID_X509_COUNTRY_NAME, "C"); + m.insert(OID_X509_LOCALITY_NAME, "L"); + m.insert(OID_X509_STATE_OR_PROVINCE_NAME, "ST"); + m.insert(OID_X509_ORGANIZATION_NAME, "O"); + m.insert(OID_X509_ORGANIZATIONAL_UNIT, "OU"); + m.insert(OID_DOMAIN_COMPONENT, "DC"); + m.insert(OID_PKCS9_EMAIL_ADDRESS, "Email"); + m + }; +} + +/// Return the abbreviation (for ex. CN for Common Name), or if not found, the OID short name +pub fn oid2abbrev<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { + if let Some(abbrev) = ABBREV_MAP.get(oid) { + return Ok(abbrev); + } + registry.get(oid).map(|entry| entry.sn()).ok_or(NidError) +} + +/// Returns the short name corresponding to the OID +pub fn oid2sn<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { + registry.get(oid).map(|o| o.sn()).ok_or(NidError) +} + +/// Returns the description corresponding to the OID +pub fn oid2description<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { + registry.get(oid).map(|o| o.description()).ok_or(NidError) +} + +/// Return a reference to the default registry of known OIDs +pub fn oid_registry() -> &'static OidRegistry<'static> { + &OID_REGISTRY +} + +#[cfg(test)] +mod tests { + use super::*; + use der_parser::oid; + + // This test is meant to check syntax of pattern matching with OID objects + #[test] + fn test_oid_match() { + let oid = oid!(1.2.840 .113549 .1 .1 .5); + if oid == OID_PKCS1_SHA1WITHRSA { + // ok + } + // matching is not possible with Cow constants in pattern, + // see https://rust-lang.github.io/rfcs/1445-restrict-constants-in-patterns.html + // + // match oid { + // OID_PKCS1_SHA1WITHRSA => (), + // _ => (), + // } + } +} diff --git a/rust/vendor/x509-parser/src/pem.rs b/rust/vendor/x509-parser/src/pem.rs new file mode 100644 index 0000000..f8bfc7a --- /dev/null +++ b/rust/vendor/x509-parser/src/pem.rs @@ -0,0 +1,255 @@ +//! Decoding functions for PEM-encoded data +//! +//! A PEM object is a container, which can store (amongst other formats) a public X.509 +//! Certificate, or a CRL, etc. It contains only printable characters. +//! PEM-encoded binary data is essentially a beginning and matching end tag that encloses +//! base64-encoded binary data (see: +//! <https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail>). +//! +//! # Examples +//! +//! To parse a certificate in PEM format, first create the `Pem` object, then decode +//! contents: +//! +//! ```rust,no_run +//! use x509_parser::pem::Pem; +//! use x509_parser::x509::X509Version; +//! +//! static IGCA_PEM: &str = "../assets/IGC_A.pem"; +//! +//! # fn main() { +//! let data = std::fs::read(IGCA_PEM).expect("Could not read file"); +//! for pem in Pem::iter_from_buffer(&data) { +//! let pem = pem.expect("Reading next PEM block failed"); +//! let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); +//! assert_eq!(x509.tbs_certificate.version, X509Version::V3); +//! } +//! # } +//! ``` +//! +//! This is the most direct method to parse PEM data. +//! +//! Another method to parse the certificate is to use `parse_x509_pem`: +//! +//! ```rust,no_run +//! use x509_parser::pem::parse_x509_pem; +//! use x509_parser::parse_x509_certificate; +//! +//! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); +//! +//! # fn main() { +//! let res = parse_x509_pem(IGCA_PEM); +//! match res { +//! Ok((rem, pem)) => { +//! assert!(rem.is_empty()); +//! // +//! assert_eq!(pem.label, String::from("CERTIFICATE")); +//! // +//! let res_x509 = parse_x509_certificate(&pem.contents); +//! assert!(res_x509.is_ok()); +//! }, +//! _ => panic!("PEM parsing failed: {:?}", res), +//! } +//! # } +//! ``` +//! +//! Note that all methods require to store the `Pem` object in a variable, mainly because decoding +//! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will +//! be bound to these buffers. + +use crate::certificate::X509Certificate; +use crate::error::{PEMError, X509Error}; +use crate::parse_x509_certificate; +use nom::{Err, IResult}; +use std::io::{BufRead, Cursor, Seek}; + +/// Representation of PEM data +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Pem { + /// The PEM label + pub label: String, + /// The PEM decoded data + pub contents: Vec<u8>, +} + +#[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")] +pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> { + parse_x509_pem(i) +} + +/// Read a PEM-encoded structure, and decode the base64 data +/// +/// Return a structure describing the PEM object: the enclosing tag, and the data. +/// Allocates a new buffer for the decoded data. +/// +/// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data, +/// use [`Pem::iter_from_buffer`]. +/// +/// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the +/// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`. +pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> { + let reader = Cursor::new(i); + let res = Pem::read(reader); + match res { + Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)), + Err(e) => Err(Err::Error(e)), + } +} + +impl Pem { + /// Read the next PEM-encoded structure, and decode the base64 data + /// + /// Returns the certificate (encoded in DER) and the number of bytes read. + /// Allocates a new buffer for the decoded data. + /// + /// Note that a PEM file can contain multiple PEM blocks. This function returns the + /// *first* decoded object, starting from the current reader position. + /// To get all objects, call this function repeatedly until `PEMError::MissingHeader` + /// is returned. + /// + /// # Examples + /// ``` + /// let file = std::fs::File::open("assets/certificate.pem").unwrap(); + /// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file)) + /// .unwrap().0 + /// .parse_x509().unwrap() + /// .tbs_certificate.subject.to_string(); + /// assert_eq!(subject, "CN=lists.for-our.info"); + /// ``` + 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)?; + if num_bytes == 0 { + // EOF + return Err(PEMError::MissingHeader); + } + if !line.starts_with("-----BEGIN ") { + line.clear(); + continue; + } + let v: Vec<&str> = line.split("-----").collect(); + if v.len() < 3 || !v[0].is_empty() { + return Err(PEMError::InvalidHeader); + } + let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?; + break label; + }; + let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?; + let mut s = String::new(); + loop { + let mut l = String::new(); + let num_bytes = r.read_line(&mut l)?; + if num_bytes == 0 { + return Err(PEMError::IncompletePEM); + } + if l.starts_with("-----END ") { + // finished reading + break; + } + s.push_str(l.trim_end()); + } + + let contents = data_encoding::BASE64 + .decode(s.as_bytes()) + .or(Err(PEMError::Base64DecodeError))?; + let pem = Pem { + label: label.to_string(), + contents, + }; + Ok((pem, r.stream_position()? as usize)) + } + + /// Decode the PEM contents into a X.509 object + pub fn parse_x509(&self) -> Result<X509Certificate, ::nom::Err<X509Error>> { + parse_x509_certificate(&self.contents).map(|(_, x509)| x509) + } + + /// Returns an iterator over the PEM-encapsulated parts of a buffer + /// + /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` + /// and ending with `-----END xxx-----` will be considered. + /// Lines before, between or after such blocks will be ignored. + /// + /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. + /// An error indicates a block is present but invalid. + /// + /// If the buffer does not contain any block, iterator will be empty. + pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> { + let reader = Cursor::new(i); + PemIterator { reader } + } + + /// Returns an iterator over the PEM-encapsulated parts of a reader + /// + /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` + /// and ending with `-----END xxx-----` will be considered. + /// Lines before, between or after such blocks will be ignored. + /// + /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. + /// An error indicates a block is present but invalid. + /// + /// If the reader does not contain any block, iterator will be empty. + pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> { + PemIterator { reader } + } +} + +/// Iterator over PEM-encapsulated blocks +/// +/// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` +/// and ending with `-----END xxx-----` will be considered. +/// Lines before, between or after such blocks will be ignored. +/// +/// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object. +/// An error indicates a block is present but invalid. +/// +/// If the buffer does not contain any block, iterator will be empty. +#[allow(missing_debug_implementations)] +pub struct PemIterator<Reader: BufRead + Seek> { + reader: Reader, +} + +impl<R: BufRead + Seek> Iterator for PemIterator<R> { + type Item = Result<Pem, PEMError>; + + fn next(&mut self) -> Option<Self::Item> { + if let Ok(&[]) = self.reader.fill_buf() { + return None; + } + let reader = self.reader.by_ref(); + let r = Pem::read(reader).map(|(pem, _)| pem); + if let Err(PEMError::MissingHeader) = r { + None + } else { + Some(r) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_pem_from_file() { + let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap()); + let subject = Pem::read(file) + .unwrap() + .0 + .parse_x509() + .unwrap() + .tbs_certificate + .subject + .to_string(); + assert_eq!(subject, "CN=lists.for-our.info"); + } + + #[test] + fn pem_multi_word_label() { + const PEM_BYTES: &[u8] = + b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----"; + let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem"); + assert_eq!(pem.label, "MULTI WORD LABEL"); + } +} diff --git a/rust/vendor/x509-parser/src/prelude.rs b/rust/vendor/x509-parser/src/prelude.rs new file mode 100644 index 0000000..a1749ce --- /dev/null +++ b/rust/vendor/x509-parser/src/prelude.rs @@ -0,0 +1,18 @@ +//! A "prelude" for users of the x509-parser crate. + +pub use crate::certificate::*; +pub use crate::certification_request::*; +pub use crate::cri_attributes::*; +pub use crate::error::*; +pub use crate::extensions::*; +pub use crate::objects::*; +pub use crate::pem::*; +pub use crate::revocation_list::*; +pub use crate::time::*; +pub use crate::utils::*; +#[cfg(feature = "validate")] +pub use crate::validate::*; +pub use crate::x509::*; +pub use crate::*; + +pub use asn1_rs::FromDer; diff --git a/rust/vendor/x509-parser/src/public_key.rs b/rust/vendor/x509-parser/src/public_key.rs new file mode 100644 index 0000000..4016a9f --- /dev/null +++ b/rust/vendor/x509-parser/src/public_key.rs @@ -0,0 +1,132 @@ +use crate::error::*; +use asn1_rs::FromDer; +use der_parser::{ + der::{parse_der_integer, parse_der_sequence_defined_g}, + error::BerResult, +}; + +/// Public Key value +#[derive(Debug, PartialEq, Eq)] +pub enum PublicKey<'a> { + RSA(RSAPublicKey<'a>), + EC(ECPoint<'a>), + /// DSAPublicKey ::= INTEGER -- public key, Y (RFC 3279) + DSA(&'a [u8]), + /// GostR3410-94-PublicKey ::= OCTET STRING -- public key, Y (RFC 4491) + GostR3410(&'a [u8]), + /// GostR3410-2012-256-PublicKey ::= OCTET STRING (64), + /// GostR3410-2012-512-PublicKey ::= OCTET STRING (128). (RFC 4491-bis) + GostR3410_2012(&'a [u8]), + + Unknown(&'a [u8]), +} + +impl<'a> PublicKey<'a> { + /// Return the key size (in bits) or 0 + pub fn key_size(&self) -> usize { + match self { + Self::EC(ec) => ec.key_size(), + Self::RSA(rsa) => rsa.key_size(), + Self::DSA(y) | Self::GostR3410(y) => y.len() * 8, + _ => 0, + } + } +} + +/// RSA public Key, defined in rfc3279 +#[derive(Debug, PartialEq, Eq)] +pub struct RSAPublicKey<'a> { + /// Raw bytes of the modulus + /// + /// This possibly includes a leading 0 if the MSB is 1 + pub modulus: &'a [u8], + /// Raw bytes of the exponent + /// + /// This possibly includes a leading 0 if the MSB is 1 + pub exponent: &'a [u8], +} + +impl<'a> RSAPublicKey<'a> { + /// Attempt to convert exponent to u64 + /// + /// Returns an error if integer is too large, empty, or negative + pub fn try_exponent(&self) -> Result<u64, X509Error> { + let mut buf = [0u8; 8]; + if self.exponent.is_empty() || self.exponent[0] & 0x80 != 0 || self.exponent.len() > 8 { + return Err(X509Error::InvalidNumber); + } + buf[8_usize.saturating_sub(self.exponent.len())..].copy_from_slice(self.exponent); + let int = <u64>::from_be_bytes(buf); + Ok(int) + } + + /// Return the key size (in bits) or 0 + pub fn key_size(&self) -> usize { + if !self.modulus.is_empty() && self.modulus[0] & 0x80 == 0 { + // XXX len must substract leading zeroes + let modulus = &self.modulus[1..]; + 8 * modulus.len() + } else { + 0 + } + } +} + +// helper function to parse with error type BerError +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)?; + let modulus = obj_modulus.as_slice()?; + let exponent = obj_exponent.as_slice()?; + let key = RSAPublicKey { modulus, exponent }; + Ok((i, key)) + })(bytes) +} + +impl<'a> FromDer<'a, X509Error> for RSAPublicKey<'a> { + fn from_der(bytes: &'a [u8]) -> X509Result<'a, Self> { + parse_rsa_key(bytes).map_err(|_| nom::Err::Error(X509Error::InvalidSPKI)) + } +} + +/// Elliptic Curve point, as defined in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) +#[derive(Debug, PartialEq, Eq)] +pub struct ECPoint<'a> { + data: &'a [u8], +} + +impl<'a> ECPoint<'a> { + /// EC Point content (See Standards for Efficient Cryptography Group (SECG), "SEC1: Elliptic Curve Cryptography") + pub fn data(&'a self) -> &'a [u8] { + self.data + } + + /// Return the key size (in bits) or 0 + pub fn key_size(&self) -> usize { + match self.data { + [] => { + // empty + 0 + } + [4, rem @ ..] => { + // uncompressed + rem.len() * 8 / 2 + } + [2..=3, rem @ ..] => { + // compressed + rem.len() * 8 + } + _ => { + // invalid + 0 + } + } + } +} + +impl<'a> From<&'a [u8]> for ECPoint<'a> { + fn from(data: &'a [u8]) -> Self { + ECPoint { data } + } +} diff --git a/rust/vendor/x509-parser/src/revocation_list.rs b/rust/vendor/x509-parser/src/revocation_list.rs new file mode 100644 index 0000000..55cf8f3 --- /dev/null +++ b/rust/vendor/x509-parser/src/revocation_list.rs @@ -0,0 +1,365 @@ +use crate::error::{X509Error, X509Result}; +use crate::extensions::*; +use crate::time::ASN1Time; +use crate::utils::format_serial; +use crate::x509::{ + parse_serial, parse_signature_value, AlgorithmIdentifier, ReasonCode, X509Name, X509Version, +}; + +#[cfg(feature = "verify")] +use crate::verify::verify_signature; +#[cfg(feature = "verify")] +use crate::x509::SubjectPublicKeyInfo; +use asn1_rs::{BitString, FromDer}; +use der_parser::ber::Tag; +use der_parser::der::*; +use der_parser::num_bigint::BigUint; +use der_parser::oid::Oid; +use nom::combinator::{all_consuming, complete, map, opt}; +use nom::multi::many0; +use nom::Offset; +use oid_registry::*; +use std::collections::HashMap; + +/// An X.509 v2 Certificate Revocation List (CRL). +/// +/// X.509 v2 CRLs are defined in [RFC5280](https://tools.ietf.org/html/rfc5280). +/// +/// # Example +/// +/// To parse a CRL and print information about revoked certificates: +/// +/// ```rust +/// use x509_parser::prelude::FromDer; +/// use x509_parser::revocation_list::CertificateRevocationList; +/// +/// # static DER: &'static [u8] = include_bytes!("../assets/example.crl"); +/// # +/// # fn main() { +/// let res = CertificateRevocationList::from_der(DER); +/// match res { +/// Ok((_rem, crl)) => { +/// for revoked in crl.iter_revoked_certificates() { +/// println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); +/// println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); +/// } +/// }, +/// _ => panic!("CRL parsing failed: {:?}", res), +/// } +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct CertificateRevocationList<'a> { + pub tbs_cert_list: TbsCertList<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature_value: BitString<'a>, +} + +impl<'a> CertificateRevocationList<'a> { + /// Get the version of the encoded certificate + pub fn version(&self) -> Option<X509Version> { + self.tbs_cert_list.version + } + + /// Get the certificate issuer. + #[inline] + pub fn issuer(&self) -> &X509Name { + &self.tbs_cert_list.issuer + } + + /// Get the date and time of the last (this) update. + #[inline] + pub fn last_update(&self) -> ASN1Time { + self.tbs_cert_list.this_update + } + + /// Get the date and time of the next update, if present. + #[inline] + pub fn next_update(&self) -> Option<ASN1Time> { + self.tbs_cert_list.next_update + } + + /// Return an iterator over the `RevokedCertificate` objects + pub fn iter_revoked_certificates(&self) -> impl Iterator<Item = &RevokedCertificate<'a>> { + self.tbs_cert_list.revoked_certificates.iter() + } + + /// Get the CRL extensions. + #[inline] + pub fn extensions(&self) -> &[X509Extension] { + &self.tbs_cert_list.extensions + } + + /// Get the CRL number, if present + /// + /// Note that the returned value is a `BigUint`, because of the following RFC specification: + /// <pre> + /// Given the requirements above, CRL numbers can be expected to contain long integers. CRL + /// verifiers MUST be able to handle CRLNumber values up to 20 octets. Conformant CRL issuers + /// MUST NOT use CRLNumber values longer than 20 octets. + /// </pre> + pub fn crl_number(&self) -> Option<&BigUint> { + self.extensions() + .iter() + .find(|&ext| ext.oid == OID_X509_EXT_CRL_NUMBER) + .and_then(|ext| match ext.parsed_extension { + ParsedExtension::CRLNumber(ref num) => Some(num), + _ => None, + }) + } + + /// Verify the cryptographic signature of this certificate revocation list + /// + /// `public_key` is the public key of the **signer**. + /// + /// Not all algorithms are supported, this function is limited to what `ring` supports. + #[cfg(feature = "verify")] + #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] + pub fn verify_signature(&self, public_key: &SubjectPublicKeyInfo) -> Result<(), X509Error> { + verify_signature( + public_key, + &self.signature_algorithm, + &self.signature_value, + self.tbs_cert_list.raw, + ) + } +} + +/// <pre> +/// CertificateList ::= SEQUENCE { +/// tbsCertList TBSCertList, +/// signatureAlgorithm AlgorithmIdentifier, +/// signatureValue BIT STRING } +/// </pre> +impl<'a> FromDer<'a, X509Error> for CertificateRevocationList<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + 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)?; + let crl = CertificateRevocationList { + tbs_cert_list, + signature_algorithm, + signature_value, + }; + Ok((i, crl)) + })(i) + } +} + +/// The sequence TBSCertList contains information about the certificates that have +/// been revoked by the CA that issued the CRL. +/// +/// RFC5280 definition: +/// +/// <pre> +/// TBSCertList ::= SEQUENCE { +/// version Version OPTIONAL, +/// -- if present, MUST be v2 +/// signature AlgorithmIdentifier, +/// issuer Name, +/// thisUpdate Time, +/// nextUpdate Time OPTIONAL, +/// revokedCertificates SEQUENCE OF SEQUENCE { +/// userCertificate CertificateSerialNumber, +/// revocationDate Time, +/// crlEntryExtensions Extensions OPTIONAL +/// -- if present, version MUST be v2 +/// } OPTIONAL, +/// crlExtensions [0] EXPLICIT Extensions OPTIONAL +/// -- if present, version MUST be v2 +/// } +/// </pre> +#[derive(Clone, Debug, PartialEq)] +pub struct TbsCertList<'a> { + pub version: Option<X509Version>, + pub signature: AlgorithmIdentifier<'a>, + pub issuer: X509Name<'a>, + pub this_update: ASN1Time, + pub next_update: Option<ASN1Time>, + pub revoked_certificates: Vec<RevokedCertificate<'a>>, + extensions: Vec<X509Extension<'a>>, + pub(crate) raw: &'a [u8], +} + +impl<'a> TbsCertList<'a> { + /// Returns the certificate extensions + #[inline] + pub fn extensions(&self) -> &[X509Extension] { + &self.extensions + } + + /// Returns an iterator over the certificate extensions + #[inline] + pub fn iter_extensions(&self) -> impl Iterator<Item = &X509Extension> { + 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> { + 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<HashMap<Oid, &X509Extension>, X509Error> { + self.extensions + .iter() + .try_fold(HashMap::new(), |mut m, ext| { + if m.contains_key(&ext.oid) { + return Err(X509Error::DuplicateExtensions); + } + m.insert(ext.oid.clone(), ext); + Ok(m) + }) + } +} + +impl<'a> AsRef<[u8]> for TbsCertList<'a> { + fn as_ref(&self) -> &[u8] { + self.raw + } +} + +impl<'a> FromDer<'a, X509Error> for TbsCertList<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + let start_i = i; + parse_der_sequence_defined_g(move |i, _| { + let (i, version) = + opt(map(parse_der_u32, X509Version))(i).or(Err(X509Error::InvalidVersion))?; + let (i, signature) = AlgorithmIdentifier::from_der(i)?; + let (i, issuer) = X509Name::from_der(i)?; + let (i, this_update) = ASN1Time::from_der(i)?; + let (i, next_update) = ASN1Time::from_der_opt(i)?; + let (i, revoked_certificates) = opt(complete(parse_revoked_certificates))(i)?; + let (i, extensions) = parse_extensions(i, Tag(0))?; + let len = start_i.offset(i); + let tbs = TbsCertList { + version, + signature, + issuer, + this_update, + next_update, + revoked_certificates: revoked_certificates.unwrap_or_default(), + extensions, + raw: &start_i[..len], + }; + Ok((i, tbs)) + })(i) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct RevokedCertificate<'a> { + /// The Serial number of the revoked certificate + pub user_certificate: BigUint, + /// The date on which the revocation occurred is specified. + pub revocation_date: ASN1Time, + /// Additional information about revocation + extensions: Vec<X509Extension<'a>>, + pub(crate) raw_serial: &'a [u8], +} + +impl<'a> RevokedCertificate<'a> { + /// Return the serial number of the revoked certificate + pub fn serial(&self) -> &BigUint { + &self.user_certificate + } + + /// Get the CRL entry extensions. + #[inline] + pub fn extensions(&self) -> &[X509Extension] { + &self.extensions + } + + /// Returns an iterator over the CRL entry extensions + #[inline] + pub fn iter_extensions(&self) -> impl Iterator<Item = &X509Extension> { + 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> { + 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<HashMap<Oid, &X509Extension>, X509Error> { + self.extensions + .iter() + .try_fold(HashMap::new(), |mut m, ext| { + if m.contains_key(&ext.oid) { + return Err(X509Error::DuplicateExtensions); + } + m.insert(ext.oid.clone(), ext); + Ok(m) + }) + } + + /// Get the raw bytes of the certificate serial number + pub fn raw_serial(&self) -> &[u8] { + self.raw_serial + } + + /// Get a formatted string of the certificate serial number, separated by ':' + pub fn raw_serial_as_string(&self) -> String { + format_serial(self.raw_serial) + } + + /// Get the code identifying the reason for the revocation, if present + pub fn reason_code(&self) -> Option<(bool, ReasonCode)> { + self.find_extension(&OID_X509_EXT_REASON_CODE) + .and_then(|ext| match ext.parsed_extension { + ParsedExtension::ReasonCode(code) => Some((ext.critical, code)), + _ => None, + }) + } + + /// Get the invalidity date, if present + /// + /// The invalidity date is the date on which it is known or suspected that the private + /// key was compromised or that the certificate otherwise became invalid. + pub fn invalidity_date(&self) -> Option<(bool, ASN1Time)> { + self.find_extension(&OID_X509_EXT_INVALIDITY_DATE) + .and_then(|ext| match ext.parsed_extension { + ParsedExtension::InvalidityDate(date) => Some((ext.critical, date)), + _ => None, + }) + } +} + +// revokedCertificates SEQUENCE OF SEQUENCE { +// userCertificate CertificateSerialNumber, +// revocationDate Time, +// crlEntryExtensions Extensions OPTIONAL +// -- if present, MUST be v2 +// } OPTIONAL, +impl<'a> FromDer<'a, X509Error> for RevokedCertificate<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + parse_der_sequence_defined_g(|i, _| { + let (i, (raw_serial, user_certificate)) = parse_serial(i)?; + let (i, revocation_date) = ASN1Time::from_der(i)?; + let (i, extensions) = opt(complete(parse_extension_sequence))(i)?; + let revoked = RevokedCertificate { + user_certificate, + revocation_date, + extensions: extensions.unwrap_or_default(), + raw_serial, + }; + Ok((i, revoked)) + })(i) + } +} + +fn parse_revoked_certificates(i: &[u8]) -> X509Result<Vec<RevokedCertificate>> { + parse_der_sequence_defined_g(|a, _| { + all_consuming(many0(complete(RevokedCertificate::from_der)))(a) + })(i) +} diff --git a/rust/vendor/x509-parser/src/signature_algorithm.rs b/rust/vendor/x509-parser/src/signature_algorithm.rs new file mode 100644 index 0000000..24a37a4 --- /dev/null +++ b/rust/vendor/x509-parser/src/signature_algorithm.rs @@ -0,0 +1,326 @@ +use crate::error::X509Error; +use crate::x509::AlgorithmIdentifier; +use asn1_rs::{ + oid, Any, CheckDerConstraints, Class, DerAutoDerive, Error, FromDer, Oid, OptTaggedExplicit, + OptTaggedParser, Tag, +}; +use core::convert::TryFrom; +use oid_registry::*; + +#[allow(non_camel_case_types)] +#[derive(Debug, PartialEq)] +pub enum SignatureAlgorithm<'a> { + RSA, + RSASSA_PSS(Box<RsaSsaPssParams<'a>>), + RSAAES_OAEP(Box<RsaAesOaepParams<'a>>), + DSA, + ECDSA, + ED25519, +} + +impl<'a, 'b> TryFrom<&'b AlgorithmIdentifier<'a>> for SignatureAlgorithm<'a> { + type Error = X509Error; + + fn try_from(value: &'b AlgorithmIdentifier<'a>) -> Result<Self, Self::Error> { + if value.algorithm.starts_with(&oid! {1.2.840.113549.1.1}) { + // children of PKCS1 are all RSA + // test if RSASSA-PSS + if value.algorithm == OID_PKCS1_RSASSAPSS { + let params = match value.parameters.as_ref() { + Some(any) => any, + None => return Err(X509Error::InvalidSignatureValue), + }; + let params = RsaSsaPssParams::try_from(params) + .map_err(|_| X509Error::InvalidSignatureValue)?; + Ok(SignatureAlgorithm::RSASSA_PSS(Box::new(params))) + } else { + // rfc3279#section-2.2.1: the parameters component of that type SHALL be + // the ASN.1 type NULL + // We could enforce presence of NULL, but that would make a strict parser + // so it would best go to a verifier. + Ok(SignatureAlgorithm::RSA) + } + } else if test_ecdsa_oid(&value.algorithm) { + // parameter should be NULL - see above + Ok(SignatureAlgorithm::ECDSA) + } else if value.algorithm.starts_with(&oid! {1.2.840.10040.4}) { + // parameter should be NULL - see above + Ok(SignatureAlgorithm::DSA) + } else if value.algorithm == OID_SIG_ED25519 { + Ok(SignatureAlgorithm::ED25519) + } else if value.algorithm == oid! {1.2.840.113549.1.1.7} { + let params = match value.parameters.as_ref() { + Some(any) => any, + None => return Err(X509Error::InvalidSignatureValue), + }; + let params = + RsaAesOaepParams::try_from(params).map_err(|_| X509Error::InvalidSignatureValue)?; + Ok(SignatureAlgorithm::RSAAES_OAEP(Box::new(params))) + } else { + if cfg!(debug_assertions) { + // TODO: remove debug + eprintln!("bad Signature AlgorithmIdentifier: {}", value.algorithm); + } + Err(X509Error::InvalidSignatureValue) + } + } +} + +#[inline] +fn test_ecdsa_oid(oid: &Oid) -> bool { + // test if oid is a child from {ansi-x962 signatures} + oid.starts_with(&oid! {1.2.840.10045.4}) +} + +// RSASSA-PSS public keys [RFC4055](https://www.rfc-editor.org/rfc/rfc4055.html) + +// RSASSA-PSS-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT +// sha1Identifier, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT +// mgf1SHA1Identifier, +// saltLength [2] INTEGER DEFAULT 20, +// trailerField [3] INTEGER DEFAULT 1 } +#[derive(Debug, PartialEq)] +pub struct RsaSsaPssParams<'a> { + hash_alg: Option<AlgorithmIdentifier<'a>>, + mask_gen_algorithm: Option<AlgorithmIdentifier<'a>>, + salt_length: Option<u32>, + trailer_field: Option<u32>, +} + +impl<'a> RsaSsaPssParams<'a> { + /// Get a reference to the rsa ssa pss params's hash algorithm. + 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 { + const SHA1: &Oid = &OID_HASH_SHA1; + self.hash_alg + .as_ref() + .map(|alg| &alg.algorithm) + .unwrap_or(SHA1) + } + + /// Get a reference to the rsa ssa pss params's mask generation algorithm. + 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<MaskGenAlgorithm, X509Error> { + match self.mask_gen_algorithm.as_ref() { + Some(alg) => { + let (_, hash) = alg + .parameters() + .and_then(|any| Oid::from_der(any.data).ok()) + .ok_or(X509Error::InvalidAlgorithmIdentifier)?; + Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) + } + _ => { + Ok(MaskGenAlgorithm::new( + oid! {1.2.840.113549.1.1.8}, // id-mgf1 + OID_HASH_SHA1, + )) + } + } + } + + /// Return the salt length + pub fn salt_length(&self) -> u32 { + self.salt_length.unwrap_or(20) + } + + /// Return the trailer field (value must be `1` according to RFC4055) + pub fn trailer_field(&self) -> u32 { + self.trailer_field.unwrap_or(1) + } +} + +impl<'a> TryFrom<Any<'a>> for RsaSsaPssParams<'a> { + type Error = X509Error; + + fn try_from(value: Any<'a>) -> Result<Self, Self::Error> { + Self::try_from(&value) + } +} + +impl<'a, 'b> TryFrom<&'b Any<'a>> for RsaSsaPssParams<'a> { + type Error = X509Error; + + fn try_from(value: &'b Any<'a>) -> Result<Self, Self::Error> { + value.tag().assert_eq(Tag::Sequence)?; + let i = &value.data; + // let (i, hash_alg) = OptTaggedExplicit::<_, X509Error, 0>::from_der(i)?; + let (i, hash_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(0)) + .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; + // let (i, mask_gen_algorithm) = OptTaggedExplicit::<_, Error, 1>::from_der(i)?; + let (i, mask_gen_algorithm) = OptTaggedParser::new(Class::ContextSpecific, Tag(1)) + .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; + let (i, salt_length) = OptTaggedExplicit::<_, Error, 2>::from_der(i)?; + let (_, trailer_field) = OptTaggedExplicit::<_, Error, 3>::from_der(i)?; + let params = RsaSsaPssParams { + hash_alg, + mask_gen_algorithm, + salt_length: salt_length.map(|t| t.into_inner()), + trailer_field: trailer_field.map(|t| t.into_inner()), + }; + Ok(params) + } +} + +impl CheckDerConstraints for RsaSsaPssParams<'_> { + fn check_constraints(any: &Any) -> asn1_rs::Result<()> { + any.header.assert_constructed()?; + Ok(()) + } +} + +impl DerAutoDerive for RsaSsaPssParams<'_> {} + +#[derive(Debug, PartialEq, Eq)] +pub struct MaskGenAlgorithm<'a, 'b> { + pub mgf: Oid<'a>, + pub hash: Oid<'b>, +} + +impl<'a, 'b> MaskGenAlgorithm<'a, 'b> { + pub const fn new(mgf: Oid<'a>, hash: Oid<'b>) -> Self { + Self { mgf, hash } + } +} + +// RSAAES-OAEP public keys [RFC8017](https://www.rfc-editor.org/rfc/rfc8017.html) + +// RSAES-OAEP-params ::= SEQUENCE { +// hashFunc [0] AlgorithmIdentifier DEFAULT +// sha1Identifier, +// maskGenFunc [1] AlgorithmIdentifier DEFAULT +// mgf1SHA1Identifier, +// pSourceFunc [2] AlgorithmIdentifier DEFAULT +// pSpecifiedEmptyIdentifier } +// +// pSpecifiedEmptyIdentifier AlgorithmIdentifier ::= +// { id-pSpecified, nullOctetString } +// +// nullOctetString OCTET STRING (SIZE (0)) ::= { ''H } +#[derive(Debug, PartialEq)] +pub struct RsaAesOaepParams<'a> { + hash_alg: Option<AlgorithmIdentifier<'a>>, + mask_gen_alg: Option<AlgorithmIdentifier<'a>>, + p_source_alg: Option<AlgorithmIdentifier<'a>>, +} + +impl<'a> RsaAesOaepParams<'a> { + pub const EMPTY: &'static AlgorithmIdentifier<'static> = &AlgorithmIdentifier::new( + oid! {1.2.840.113549.1.1.9}, // id-pSpecified + None, + ); + + /// Get a reference to the rsa aes oaep params's hash algorithm. + 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 { + const SHA1: &Oid = &OID_HASH_SHA1; + self.hash_alg + .as_ref() + .map(|alg| &alg.algorithm) + .unwrap_or(SHA1) + } + + /// Get a reference to the rsa ssa pss params's mask generation algorithm. + 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<MaskGenAlgorithm, X509Error> { + match self.mask_gen_alg.as_ref() { + Some(alg) => { + let (_, hash) = alg + .parameters() + .and_then(|any| Oid::from_der(any.data).ok()) + .ok_or(X509Error::InvalidAlgorithmIdentifier)?; + Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) + } + _ => { + Ok(MaskGenAlgorithm::new( + oid! {1.2.840.113549.1.1.8}, // id-mgf1 + OID_HASH_SHA1, + )) + } + } + } + + /// Return the pSourceFunc algorithm + pub fn p_source_alg(&'a self) -> &'a AlgorithmIdentifier { + self.p_source_alg.as_ref().unwrap_or(Self::EMPTY) + } +} + +impl<'a> TryFrom<Any<'a>> for RsaAesOaepParams<'a> { + type Error = X509Error; + + fn try_from(value: Any<'a>) -> Result<Self, Self::Error> { + Self::try_from(&value) + } +} + +// hashFunc [0] AlgorithmIdentifier DEFAULT +// sha1Identifier, +// maskGenFunc [1] AlgorithmIdentifier DEFAULT +// mgf1SHA1Identifier, +// pSourceFunc [2] AlgorithmIdentifier DEFAULT +// pSpecifiedEmptyIdentifier } +impl<'a, 'b> TryFrom<&'b Any<'a>> for RsaAesOaepParams<'a> { + type Error = X509Error; + + fn try_from(value: &'b Any<'a>) -> Result<Self, Self::Error> { + value.tag().assert_eq(Tag::Sequence)?; + let i = &value.data; + // let (i, hash_alg) = OptTaggedExplicit::<_, X509Error, 0>::from_der(i)?; + let (i, hash_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(0)) + .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; + // let (i, mask_gen_algorithm) = OptTaggedExplicit::<_, Error, 1>::from_der(i)?; + let (i, mask_gen_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(1)) + .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; + let (_, p_source_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(2)) + .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; + let params = RsaAesOaepParams { + hash_alg, + mask_gen_alg, + p_source_alg, + }; + Ok(params) + } +} + +impl CheckDerConstraints for RsaAesOaepParams<'_> { + fn check_constraints(any: &Any) -> asn1_rs::Result<()> { + any.header.assert_constructed()?; + Ok(()) + } +} + +impl DerAutoDerive for RsaAesOaepParams<'_> {} + +// ECC subject public key information [RFC5480](https://datatracker.ietf.org/doc/rfc5480/) + +// ECParameters ::= CHOICE { +// namedCurve OBJECT IDENTIFIER +// -- implicitCurve NULL +// -- specifiedCurve SpecifiedECDomain +// } +// -- implicitCurve and specifiedCurve MUST NOT be used in PKIX. +// -- Details for SpecifiedECDomain can be found in [X9.62]. +// -- Any future additions to this CHOICE should be coordinated +// -- with ANSI X9. diff --git a/rust/vendor/x509-parser/src/signature_value.rs b/rust/vendor/x509-parser/src/signature_value.rs new file mode 100644 index 0000000..df13bd0 --- /dev/null +++ b/rust/vendor/x509-parser/src/signature_value.rs @@ -0,0 +1,11 @@ +use asn1_rs::{DerSequence, Integer}; + +/// ECDSA Signature Value (RFC3279) +// Ecdsa-Sig-Value ::= SEQUENCE { +// r INTEGER, +// s INTEGER } +#[derive(Debug, PartialEq, Eq, DerSequence)] +pub struct EcdsaSigValue<'a> { + pub r: Integer<'a>, + pub s: Integer<'a>, +} diff --git a/rust/vendor/x509-parser/src/time.rs b/rust/vendor/x509-parser/src/time.rs new file mode 100644 index 0000000..7d2b008 --- /dev/null +++ b/rust/vendor/x509-parser/src/time.rs @@ -0,0 +1,179 @@ +use asn1_rs::nom::Err; +use asn1_rs::{Error, FromDer, GeneralizedTime, Header, ParseResult, UtcTime}; +use der_parser::ber::{Tag, MAX_OBJECT_SIZE}; +use std::fmt; +use std::ops::{Add, Sub}; +use time::macros::format_description; +use time::{Duration, OffsetDateTime}; + +use crate::error::{X509Error, X509Result}; + +/// An ASN.1 timestamp. +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct ASN1Time(OffsetDateTime); + +impl ASN1Time { + pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<Option<Self>> { + if i.is_empty() { + return Ok((i, None)); + } + match parse_choice_of_time(i) { + Ok((rem, dt)) => Ok((rem, Some(ASN1Time(dt)))), + Err(Err::Error(Error::InvalidTag)) | Err(Err::Error(Error::UnexpectedTag { .. })) => { + Ok((i, None)) + } + Err(_) => Err(Err::Error(X509Error::InvalidDate)), + } + } + + #[inline] + pub const fn new(dt: OffsetDateTime) -> Self { + Self(dt) + } + + #[inline] + pub const fn to_datetime(&self) -> OffsetDateTime { + self.0 + } + + /// Makes a new `ASN1Time` from the number of non-leap seconds since Epoch + pub fn from_timestamp(secs: i64) -> Result<Self, X509Error> { + let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| X509Error::InvalidDate)?; + Ok(ASN1Time(dt)) + } + + /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). + #[inline] + pub fn timestamp(&self) -> i64 { + self.0.unix_timestamp() + } + + /// Returns a `ASN1Time` which corresponds to the current date. + #[inline] + pub fn now() -> Self { + ASN1Time(OffsetDateTime::now_utc()) + } + + /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + /// + /// Conversion to RFC2822 date can fail if date cannot be represented in this format, + /// for example if year < 1900. + /// + /// For an infallible conversion to string, use `.to_string()`. + #[inline] + pub fn to_rfc2822(self) -> Result<String, String> { + self.0 + .format(&time::format_description::well_known::Rfc2822) + .map_err(|e| e.to_string()) + } +} + +impl<'a> FromDer<'a, X509Error> for ASN1Time { + fn from_der(i: &[u8]) -> X509Result<Self> { + let (rem, dt) = parse_choice_of_time(i).map_err(|_| X509Error::InvalidDate)?; + Ok((rem, ASN1Time(dt))) + } +} + +pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult<OffsetDateTime> { + if let Ok((rem, t)) = UtcTime::from_der(i) { + let dt = t.utc_adjusted_datetime()?; + return Ok((rem, dt)); + } + if let Ok((rem, t)) = GeneralizedTime::from_der(i) { + let dt = t.utc_datetime()?; + return Ok((rem, dt)); + } + parse_malformed_date(i) +} + +// allow relaxed parsing of UTCTime (ex: 370116130016+0000) +fn parse_malformed_date(i: &[u8]) -> ParseResult<OffsetDateTime> { + #[allow(clippy::trivially_copy_pass_by_ref)] + // fn check_char(b: &u8) -> bool { + // (0x20 <= *b && *b <= 0x7f) || (*b == b'+') + // } + let (_rem, hdr) = Header::from_der(i)?; + let len = hdr.length().definite()?; + if len > MAX_OBJECT_SIZE { + return Err(nom::Err::Error(Error::InvalidLength)); + } + match hdr.tag() { + Tag::UtcTime => { + // // if we are in this function, the PrintableString could not be validated. + // // Accept it without validating charset, because some tools do not respect the charset + // // restrictions (for ex. they use '*' while explicingly disallowed) + // let (rem, data) = take(len as usize)(rem)?; + // if !data.iter().all(check_char) { + // return Err(nom::Err::Error(BerError::BerValueError)); + // } + // let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?; + // let content = BerObjectContent::UTCTime(s); + // let obj = DerObject::from_header_and_content(hdr, content); + // Ok((rem, obj)) + Err(nom::Err::Error(Error::BerValueError)) + } + _ => Err(nom::Err::Error(Error::unexpected_tag(None, hdr.tag()))), + } +} + +impl fmt::Display for ASN1Time { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let format = format_description!("[month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none] [offset_hour sign:mandatory]:[offset_minute]"); + let s = self + .0 + .format(format) + .unwrap_or_else(|e| format!("Invalid date: {}", e)); + f.write_str(&s) + } +} + +impl Add<Duration> for ASN1Time { + type Output = Option<ASN1Time>; + + #[inline] + fn add(self, rhs: Duration) -> Option<ASN1Time> { + Some(ASN1Time(self.0 + rhs)) + } +} + +impl Sub<ASN1Time> for ASN1Time { + type Output = Option<Duration>; + + #[inline] + fn sub(self, rhs: ASN1Time) -> Option<Duration> { + if self.0 > rhs.0 { + Some(self.0 - rhs.0) + } else { + None + } + } +} + +impl From<OffsetDateTime> for ASN1Time { + fn from(dt: OffsetDateTime) -> Self { + ASN1Time(dt) + } +} + +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use super::ASN1Time; + + #[test] + fn test_time_to_string() { + let d = datetime!(1 - 1 - 1 12:34:56 UTC); + let t = ASN1Time::from(d); + assert_eq!(t.to_string(), "Jan 1 12:34:56 1 +00:00".to_string()); + } + + #[test] + fn test_nonrfc2822_date() { + // test year < 1900 + let d = datetime!(1 - 1 - 1 00:00:00 UTC); + let t = ASN1Time::from(d); + assert!(t.to_rfc2822().is_err()); + } +} diff --git a/rust/vendor/x509-parser/src/utils.rs b/rust/vendor/x509-parser/src/utils.rs new file mode 100644 index 0000000..72b4d99 --- /dev/null +++ b/rust/vendor/x509-parser/src/utils.rs @@ -0,0 +1,19 @@ +/// 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) + }); + s.pop(); + s +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_serial() { + let b: &[u8] = &[1, 2, 3, 4, 0xff]; + assert_eq!("01:02:03:04:ff", format_serial(b)); + } +} diff --git a/rust/vendor/x509-parser/src/validate/certificate.rs b/rust/vendor/x509-parser/src/validate/certificate.rs new file mode 100644 index 0000000..d375a80 --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/certificate.rs @@ -0,0 +1,17 @@ +use crate::certificate::*; +use crate::validate::*; + +use extensions::X509ExtensionsValidator; + +#[derive(Debug)] +pub struct X509CertificateValidator; + +impl<'a> Validator<'a> for X509CertificateValidator { + type Item = X509Certificate<'a>; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let mut res = true; + res &= X509ExtensionsValidator.validate(&item.extensions(), l); + res + } +} diff --git a/rust/vendor/x509-parser/src/validate/extensions.rs b/rust/vendor/x509-parser/src/validate/extensions.rs new file mode 100644 index 0000000..a838f6e --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/extensions.rs @@ -0,0 +1,115 @@ +use crate::extensions::*; +use crate::validate::*; +use std::collections::HashSet; + +// extra-pedantic checks + +const WARN_SHOULD_BE_CRITICAL: bool = false; + +macro_rules! test_critical { + (MUST $ext:ident, $l:ident, $name:expr) => { + if !$ext.critical { + $l.err(&format!("Extension {} MUST be critical, but is not", $name)); + } + }; + (MUST NOT $ext:ident, $l:ident, $name:expr) => { + if $ext.critical { + $l.err(&format!("Extension {} MUST NOT be critical, but is", $name)); + } + }; + (SHOULD $ext:ident, $l:ident, $name:expr) => { + if WARN_SHOULD_BE_CRITICAL && !$ext.critical { + $l.warn(&format!( + "Extension {} SHOULD be critical, but is not", + $name + )); + } + }; + (SHOULD NOT $ext:ident, $l:ident, $name:expr) => { + if WARN_SHOULD_BE_CRITICAL && $ext.critical { + $l.warn(&format!( + "Extension {} SHOULD NOT be critical, but is", + $name + )); + } + }; +} + +#[derive(Debug)] +pub struct X509ExtensionsValidator; + +impl<'a> Validator<'a> for X509ExtensionsValidator { + type Item = &'a [X509Extension<'a>]; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let mut res = true; + // check for duplicate extensions + { + let mut m = HashSet::new(); + for ext in item.iter() { + if m.contains(&ext.oid) { + l.err(&format!("Duplicate extension {}", ext.oid)); + res = false; + } else { + m.insert(ext.oid.clone()); + } + } + } + + for ext in item.iter() { + // specific extension checks + match ext.parsed_extension() { + ParsedExtension::AuthorityKeyIdentifier(aki) => { + // Conforming CAs MUST mark this extension as non-critical + test_critical!(MUST NOT ext, l, "AKI"); + // issuer or serial is present must be either both present or both absent + if aki.authority_cert_issuer.is_some() ^ aki.authority_cert_serial.is_some() { + l.warn("AKI: only one of Issuer and Serial is present"); + } + } + ParsedExtension::CertificatePolicies(policies) => { + // A certificate policy OID MUST NOT appear more than once in a + // certificate policies extension. + let mut policy_oids = HashSet::new(); + for policy_info in policies { + if policy_oids.contains(&policy_info.policy_id) { + l.err(&format!( + "Certificate Policies: duplicate policy {}", + policy_info.policy_id + )); + res = false; + } else { + policy_oids.insert(policy_info.policy_id.clone()); + } + } + } + ParsedExtension::KeyUsage(ku) => { + // SHOULD be critical + test_critical!(SHOULD ext, l, "KeyUsage"); + // When the keyUsage extension appears in a certificate, at least one of the bits + // MUST be set to 1. + if ku.flags == 0 { + l.err("KeyUsage: all flags are set to 0"); + } + } + ParsedExtension::SubjectAlternativeName(san) => { + // SHOULD be non-critical + test_critical!(SHOULD NOT ext, l, "SubjectAltName"); + for name in &san.general_names { + match name { + 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)); + } + } + _ => (), + } + } + } + _ => (), + } + } + res + } +} diff --git a/rust/vendor/x509-parser/src/validate/loggers.rs b/rust/vendor/x509-parser/src/validate/loggers.rs new file mode 100644 index 0000000..0052f8d --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/loggers.rs @@ -0,0 +1,83 @@ +pub trait Logger { + fn warn(&mut self, message: &str); + + fn err(&mut self, message: &str); +} + +/// Simple Logger for [`Validator`](crate::validate::Validator) trait, storing messages in `Vec` +#[derive(Debug, Default)] +pub struct VecLogger { + warnings: Vec<String>, + errors: Vec<String>, +} + +impl VecLogger { + /// Get stored warnings + pub fn warnings(&self) -> &[String] { + &self.warnings + } + + /// Get stored errors + pub fn errors(&self) -> &[String] { + &self.errors + } +} + +impl Logger for VecLogger { + fn warn(&mut self, message: &str) { + self.warnings.push(message.to_owned()) + } + + fn err(&mut self, message: &str) { + self.errors.push(message.to_owned()) + } +} + +/// Simple Logger for [`Validator`](crate::validate::Validator) trait, printing messages to `stderr` +#[derive(Debug, Default)] +pub struct StderrLogger; + +impl Logger for StderrLogger { + fn warn(&mut self, message: &str) { + eprintln!("[W] {}", message); + } + + fn err(&mut self, message: &str) { + eprintln!("[E] {}", message); + } +} + +/// Simple Logger for [`Validator`](crate::validate::Validator) trait, using closures for `warn`/`err`. +#[derive(Debug, Default)] +pub struct CallbackLogger<W, E> +where + W: FnMut(&str), + E: FnMut(&str), +{ + warn: W, + err: E, +} + +impl<W, E> CallbackLogger<W, E> +where + W: FnMut(&str), + E: FnMut(&str), +{ + pub fn new(warn: W, err: E) -> Self { + CallbackLogger { warn, err } + } +} + +impl<W, E> Logger for CallbackLogger<W, E> +where + W: FnMut(&str), + E: FnMut(&str), +{ + fn warn(&mut self, message: &str) { + (self.warn)(message); + } + + fn err(&mut self, message: &str) { + (self.err)(message); + } +} diff --git a/rust/vendor/x509-parser/src/validate/mod.rs b/rust/vendor/x509-parser/src/validate/mod.rs new file mode 100644 index 0000000..a2cba26 --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/mod.rs @@ -0,0 +1,262 @@ +mod certificate; +mod extensions; +mod loggers; +mod name; +mod structure; +use std::marker::PhantomData; + +pub use certificate::*; +pub use extensions::*; +pub use loggers::*; +pub use name::*; +pub use structure::*; + +/// Trait for validating item (for ex. validate X.509 structure) +/// +/// # Examples +/// +/// Using callbacks: +/// +/// ``` +/// use x509_parser::certificate::X509Certificate; +/// # #[allow(deprecated)] +/// use x509_parser::validate::Validate; +/// # #[allow(deprecated)] +/// #[cfg(feature = "validate")] +/// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { +/// println!(" Subject: {}", x509.subject()); +/// // validate and print warnings and errors to stderr +/// let ok = x509.validate( +/// |msg| { +/// eprintln!(" [W] {}", msg); +/// }, +/// |msg| { +/// eprintln!(" [E] {}", msg); +/// }, +/// ); +/// print!("Structure validation status: "); +/// if ok { +/// println!("Ok"); +/// Ok(()) +/// } else { +/// println!("FAIL"); +/// Err("validation failed") +/// } +/// } +/// ``` +/// +/// Collecting warnings and errors to `Vec`: +/// +/// ``` +/// use x509_parser::certificate::X509Certificate; +/// # #[allow(deprecated)] +/// use x509_parser::validate::Validate; +/// +/// # #[allow(deprecated)] +/// #[cfg(feature = "validate")] +/// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { +/// println!(" Subject: {}", x509.subject()); +/// // validate and print warnings and errors to stderr +/// let (ok, warnings, errors) = x509.validate_to_vec(); +/// print!("Structure validation status: "); +/// if ok { +/// println!("Ok"); +/// } else { +/// println!("FAIL"); +/// } +/// for warning in &warnings { +/// eprintln!(" [W] {}", warning); +/// } +/// for error in &errors { +/// eprintln!(" [E] {}", error); +/// } +/// println!(); +/// if !errors.is_empty() { +/// return Err("validation failed"); +/// } +/// Ok(()) +/// } +/// ``` +#[deprecated(since = "0.13.0", note = "please use `X509StructureValidator` instead")] +pub trait Validate { + /// Attempts to validate current item. + /// + /// Returns `true` if item was validated. + /// + /// Call `warn()` if a non-fatal error was encountered, and `err()` + /// if the error is fatal. These fucntions receive a description of the error. + fn validate<W, E>(&self, warn: W, err: E) -> bool + where + W: FnMut(&str), + E: FnMut(&str); + + /// Attempts to validate current item, storing warning and errors in `Vec`. + /// + /// Returns the validation result (`true` if validated), the list of warnings, + /// and the list of errors. + fn validate_to_vec(&self) -> (bool, Vec<String>, Vec<String>) { + let mut warn_list = Vec::new(); + let mut err_list = Vec::new(); + let res = self.validate( + |s| warn_list.push(s.to_owned()), + |s| err_list.push(s.to_owned()), + ); + (res, warn_list, err_list) + } +} + +/// Trait for build item validators (for ex. validate X.509 structure) +/// +/// See [`X509StructureValidator`] for a default implementation, validating the +/// DER structure of a X.509 Certificate. +/// +/// See implementors of the [`Logger`] trait for methods to collect or handle warnings and errors. +/// +/// # Examples +/// +/// Collecting warnings and errors to `Vec`: +/// +/// ``` +/// use x509_parser::certificate::X509Certificate; +/// use x509_parser::validate::*; +/// +/// # #[allow(deprecated)] +/// #[cfg(feature = "validate")] +/// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { +/// let mut logger = VecLogger::default(); +/// println!(" Subject: {}", x509.subject()); +/// // validate and print warnings and errors to stderr +/// let ok = X509StructureValidator.validate(&x509, &mut logger); +/// print!("Structure validation status: "); +/// if ok { +/// println!("Ok"); +/// } else { +/// println!("FAIL"); +/// } +/// for warning in logger.warnings() { +/// eprintln!(" [W] {}", warning); +/// } +/// for error in logger.errors() { +/// eprintln!(" [E] {}", error); +/// } +/// println!(); +/// if !logger.errors().is_empty() { +/// return Err("validation failed"); +/// } +/// Ok(()) +/// } +/// ``` +pub trait Validator<'a> { + /// The item to validate + type Item; + + /// Attempts to validate current item. + /// + /// Returns `true` if item was validated. + /// + /// Call `l.warn()` if a non-fatal error was encountered, and `l.err()` + /// if the error is fatal. These functions receive a description of the error. + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool; + + fn chain<V2>(self, v2: V2) -> ChainValidator<'a, Self, V2, Self::Item> + where + Self: Sized, + V2: Validator<'a, Item = Self::Item>, + { + ChainValidator { + v1: self, + v2, + _p: PhantomData, + } + } +} + +#[derive(Debug)] +pub struct ChainValidator<'a, A, B, I> +where + A: Validator<'a, Item = I>, + B: Validator<'a, Item = I>, +{ + v1: A, + v2: B, + _p: PhantomData<&'a ()>, +} + +impl<'a, A, B, I> Validator<'a> for ChainValidator<'a, A, B, I> +where + A: Validator<'a, Item = I>, + B: Validator<'a, Item = I>, +{ + type Item = I; + + fn validate<L: Logger>(&'_ self, item: &'a Self::Item, l: &'_ mut L) -> bool { + self.v1.validate(item, l) & self.v2.validate(item, l) + } +} + +#[allow(deprecated)] +#[cfg(test)] +mod tests { + use crate::validate::*; + + struct V1 { + a: u32, + } + + impl Validate for V1 { + fn validate<W, E>(&self, mut warn: W, _err: E) -> bool + where + W: FnMut(&str), + E: FnMut(&str), + { + if self.a > 10 { + warn("a is greater than 10"); + } + true + } + } + + struct V1Validator; + + impl<'a> Validator<'a> for V1Validator { + type Item = V1; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + if item.a > 10 { + l.warn("a is greater than 10"); + } + true + } + } + + #[test] + fn validate_warn() { + let v1 = V1 { a: 1 }; + let (res, warn, err) = v1.validate_to_vec(); + assert!(res); + assert!(warn.is_empty()); + assert!(err.is_empty()); + // same, with one warning + let v20 = V1 { a: 20 }; + let (res, warn, err) = v20.validate_to_vec(); + assert!(res); + assert_eq!(warn, vec!["a is greater than 10".to_string()]); + assert!(err.is_empty()); + } + + #[test] + fn validator_warn() { + let mut logger = VecLogger::default(); + let v1 = V1 { a: 1 }; + let res = V1Validator.validate(&v1, &mut logger); + assert!(res); + assert!(logger.warnings().is_empty()); + assert!(logger.errors().is_empty()); + // same, with one warning + let v20 = V1 { a: 20 }; + let res = V1Validator.validate(&v20, &mut logger); + assert!(res); + assert_eq!(logger.warnings(), &["a is greater than 10".to_string()]); + assert!(logger.errors().is_empty()); + } +} diff --git a/rust/vendor/x509-parser/src/validate/name.rs b/rust/vendor/x509-parser/src/validate/name.rs new file mode 100644 index 0000000..569f157 --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/name.rs @@ -0,0 +1,32 @@ +use crate::validate::*; +use crate::x509::*; +use asn1_rs::Tag; + +#[derive(Debug)] +pub struct X509NameStructureValidator; + +impl<'a> Validator<'a> for X509NameStructureValidator { + type Item = X509Name<'a>; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let res = true; + // subject/issuer: verify charsets + // - wildcards in PrintableString + // - non-IA5 in IA5String + for attr in item.iter_attributes() { + match attr.attr_value().tag() { + Tag::PrintableString | Tag::Ia5String => { + let b = attr.attr_value().as_bytes(); + if !b.iter().all(u8::is_ascii) { + l.warn(&format!( + "Invalid charset in X.509 Name, component {}", + attr.attr_type() + )); + } + } + _ => (), + } + } + res + } +} diff --git a/rust/vendor/x509-parser/src/validate/structure.rs b/rust/vendor/x509-parser/src/validate/structure.rs new file mode 100644 index 0000000..191a3d7 --- /dev/null +++ b/rust/vendor/x509-parser/src/validate/structure.rs @@ -0,0 +1,160 @@ +use super::{Logger, Validator, X509NameStructureValidator}; +use crate::certificate::*; +use crate::extensions::{GeneralName, ParsedExtension}; +use crate::public_key::PublicKey; +use crate::x509::{SubjectPublicKeyInfo, X509Version}; + +/// Default X.509 structure validator for `X509Certificate` +/// +/// This [`Validator`] iterates the X.509 Certificate fields, and verifies the +/// DER encoding and structure: +/// - numbers with wrong encoding/sign (for ex. serial number) +/// - strings with characters not allowed in DER type (for ex. '*' in `PrintableString`) +/// +/// # Examples +/// +/// Validate structure, collect warnings and errors to a `Vec`: +/// +/// ``` +/// use x509_parser::certificate::X509Certificate; +/// use x509_parser::validate::*; +/// +/// # #[allow(deprecated)] +/// #[cfg(feature = "validate")] +/// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { +/// let mut logger = VecLogger::default(); +/// println!(" Subject: {}", x509.subject()); +/// // validate and print warnings and errors to stderr +/// let ok = X509StructureValidator.validate(&x509, &mut logger); +/// print!("Structure validation status: "); +/// if ok { +/// println!("Ok"); +/// } else { +/// println!("FAIL"); +/// } +/// for warning in logger.warnings() { +/// eprintln!(" [W] {}", warning); +/// } +/// for error in logger.errors() { +/// eprintln!(" [E] {}", error); +/// } +/// println!(); +/// if !logger.errors().is_empty() { +/// return Err("validation failed"); +/// } +/// Ok(()) +/// } +/// ``` +#[derive(Debug, Default)] +pub struct X509StructureValidator; + +impl<'a> Validator<'a> for X509StructureValidator { + type Item = X509Certificate<'a>; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let mut res = true; + res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); + res + } +} + +/// Default X.509 structure validator for `TbsCertificate` +#[derive(Debug, Default)] +pub struct TbsCertificateStructureValidator; + +impl<'a> Validator<'a> for TbsCertificateStructureValidator { + type Item = TbsCertificate<'a>; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let mut res = true; + // version must be 0, 1 or 2 + if item.version.0 >= 3 { + l.err("Invalid version"); + res = false; + } + // extensions require v3 + if !item.extensions().is_empty() && item.version != X509Version::V3 { + l.err("Extensions present but version is not 3"); + res = false; + } + let b = item.raw_serial(); + if b.is_empty() { + l.err("Serial is empty"); + res = false; + } else { + // check MSB of serial + if b[0] & 0x80 != 0 { + l.warn("Serial number is negative"); + } + // check leading zeroes in serial + if b.len() > 1 && b[0] == 0 && b[1] & 0x80 == 0 { + l.warn("Leading zeroes in serial number"); + } + } + // subject/issuer: verify charsets + res &= X509NameStructureValidator.validate(&item.subject, l); + res &= X509NameStructureValidator.validate(&item.issuer, l); + // subject public key + res &= X509PublicKeyValidator.validate(&item.subject_pki, l); + // check for parse errors or unsupported extensions + for ext in item.extensions() { + if let ParsedExtension::UnsupportedExtension { .. } = &ext.parsed_extension { + l.warn(&format!("Unsupported extension {}", ext.oid)); + } + if let ParsedExtension::ParseError { error } = &ext.parsed_extension { + l.err(&format!("Parse error in extension {}: {}", ext.oid, error)); + res = false; + } + } + // check extensions + for ext in item.extensions() { + // specific extension checks + // SAN + if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() { + for name in &san.general_names { + match name { + 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)); + } + } + _ => (), + } + } + } + } + res + } +} + +#[derive(Debug, Default)] +pub struct X509PublicKeyValidator; + +impl<'a> Validator<'a> for X509PublicKeyValidator { + type Item = SubjectPublicKeyInfo<'a>; + + fn validate<L: Logger>(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { + let mut res = true; + // res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); + match item.parsed() { + Ok(PublicKey::RSA(rsa)) => { + if rsa.modulus[0] & 0x80 != 0 { + l.warn("Public key: (RSA) modulus is negative"); + } + if rsa.exponent[0] & 0x80 != 0 { + l.warn("Public key: (RSA) exponent is negative"); + } + } + Ok(PublicKey::Unknown(_b)) => { + l.warn("Unknown public key type"); + } + Ok(_) => {} + Err(_) => { + l.err("Invalid public key"); + res = false; + } + } + res + } +} diff --git a/rust/vendor/x509-parser/src/verify.rs b/rust/vendor/x509-parser/src/verify.rs new file mode 100644 index 0000000..c056d77 --- /dev/null +++ b/rust/vendor/x509-parser/src/verify.rs @@ -0,0 +1,78 @@ +use crate::prelude::*; +use asn1_rs::BitString; +use oid_registry::{ + OID_EC_P256, OID_NIST_EC_P384, OID_PKCS1_SHA1WITHRSA, OID_PKCS1_SHA256WITHRSA, + OID_PKCS1_SHA384WITHRSA, OID_PKCS1_SHA512WITHRSA, OID_SHA1_WITH_RSA, OID_SIG_ECDSA_WITH_SHA256, + OID_SIG_ECDSA_WITH_SHA384, OID_SIG_ED25519, +}; + +/// 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. +pub fn verify_signature( + public_key: &SubjectPublicKeyInfo, + signature_algorithm: &AlgorithmIdentifier, + signature_value: &BitString, + raw_data: &[u8], +) -> Result<(), X509Error> { + use ring::signature; + let signature_algorithm = &signature_algorithm.algorithm; + // identify verification algorithm + let verification_alg: &dyn signature::VerificationAlgorithm = if *signature_algorithm + == OID_PKCS1_SHA1WITHRSA + || *signature_algorithm == OID_SHA1_WITH_RSA + { + &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY + } else if *signature_algorithm == OID_PKCS1_SHA256WITHRSA { + &signature::RSA_PKCS1_2048_8192_SHA256 + } else if *signature_algorithm == OID_PKCS1_SHA384WITHRSA { + &signature::RSA_PKCS1_2048_8192_SHA384 + } else if *signature_algorithm == OID_PKCS1_SHA512WITHRSA { + &signature::RSA_PKCS1_2048_8192_SHA512 + } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA256 { + get_ec_curve_sha(&public_key.algorithm, 256) + .ok_or(X509Error::SignatureUnsupportedAlgorithm)? + } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA384 { + get_ec_curve_sha(&public_key.algorithm, 384) + .ok_or(X509Error::SignatureUnsupportedAlgorithm)? + } else if *signature_algorithm == OID_SIG_ED25519 { + &signature::ED25519 + } else { + return Err(X509Error::SignatureUnsupportedAlgorithm); + }; + // get public key + let key = + signature::UnparsedPublicKey::new(verification_alg, &public_key.subject_public_key.data); + // verify signature + key.verify(raw_data, &signature_value.data) + .or(Err(X509Error::SignatureVerificationError)) +} + +/// 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. +fn get_ec_curve_sha( + pubkey_alg: &AlgorithmIdentifier, + sha_len: usize, +) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { + use ring::signature; + 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 { + match sha_len { + 256 => Some(&signature::ECDSA_P256_SHA256_ASN1), + 384 => Some(&signature::ECDSA_P256_SHA384_ASN1), + _ => None, + } + } else if curve_oid == OID_NIST_EC_P384 { + match sha_len { + 256 => Some(&signature::ECDSA_P384_SHA256_ASN1), + 384 => Some(&signature::ECDSA_P384_SHA384_ASN1), + _ => None, + } + } else { + None + } +} diff --git a/rust/vendor/x509-parser/src/x509.rs b/rust/vendor/x509-parser/src/x509.rs new file mode 100644 index 0000000..5052ebd --- /dev/null +++ b/rust/vendor/x509-parser/src/x509.rs @@ -0,0 +1,667 @@ +//! X.509 objects and types +//! +//! Based on RFC5280 +//! + +use crate::error::{X509Error, X509Result}; +use crate::objects::*; +use crate::public_key::*; + +use asn1_rs::{ + Any, BitString, BmpString, DerSequence, FromBer, FromDer, Oid, OptTaggedParser, ParseResult, +}; +use core::convert::TryFrom; +use data_encoding::HEXUPPER; +use der_parser::ber::MAX_OBJECT_SIZE; +use der_parser::der::*; +use der_parser::error::*; +use der_parser::num_bigint::BigUint; +use der_parser::*; +use nom::branch::alt; +use nom::bytes::complete::take; +use nom::combinator::{complete, map}; +use nom::multi::{many0, many1}; +use nom::{Err, Offset}; +use oid_registry::*; +use rusticata_macros::newtype_enum; +use std::fmt; +use std::iter::FromIterator; + +/// The version of the encoded certificate. +/// +/// When extensions are used, as expected in this profile, version MUST be 3 +/// (value is `2`). If no extensions are present, but a UniqueIdentifier +/// is present, the version SHOULD be 2 (value is `1`); however, the +/// version MAY be 3. If only basic fields are present, the version +/// SHOULD be 1 (the value is omitted from the certificate as the default +/// value); however, the version MAY be 2 or 3. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct X509Version(pub u32); + +impl X509Version { + /// Parse [0] EXPLICIT Version DEFAULT v1 + 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)?; + let version = opt_version.unwrap_or(X509Version::V1); + Ok((rem, version)) + } +} + +// Version ::= INTEGER { v1(0), v2(1), v3(2) } +impl<'a> FromDer<'a, X509Error> for X509Version { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + map(<u32>::from_der, X509Version)(i).map_err(|_| Err::Error(X509Error::InvalidVersion)) + } +} + +newtype_enum! { + impl display X509Version { + V1 = 0, + V2 = 1, + V3 = 2, + } +} + +/// A generic attribute type and value +/// +/// These objects are used as [`RelativeDistinguishedName`] components. +#[derive(Clone, Debug, PartialEq)] +pub struct AttributeTypeAndValue<'a> { + attr_type: Oid<'a>, + attr_value: Any<'a>, // ANY -- DEFINED BY AttributeType +} + +impl<'a> AttributeTypeAndValue<'a> { + /// Builds a new `AttributeTypeAndValue` + #[inline] + pub const fn new(attr_type: Oid<'a>, attr_value: Any<'a>) -> Self { + AttributeTypeAndValue { + attr_type, + attr_value, + } + } + + /// Returns the attribute type + #[inline] + pub const fn attr_type(&self) -> &Oid<'a> { + &self.attr_type + } + + /// Returns the attribute value, as `ANY` + #[inline] + pub const fn attr_value(&self) -> &Any<'a> { + &self.attr_value + } + + /// Attempt to get the content as `str`. + /// This can fail if the object does not contain a string type. + /// + /// Note: the [`TryFrom`](core::convert::TryFrom) trait is implemented for `&str`, so this is + /// equivalent to `attr.try_into()`. + /// + /// Only NumericString, PrintableString, UTF8String and IA5String + /// are considered here. Other string types can be read using `as_slice`. + #[inline] + pub fn as_str(&self) -> Result<&'a str, X509Error> { + // TODO: replace this with helper function, when it is added to asn1-rs + match self.attr_value.tag() { + Tag::NumericString | Tag::PrintableString | Tag::Utf8String | Tag::Ia5String => { + let s = core::str::from_utf8(self.attr_value.data) + .map_err(|_| X509Error::InvalidAttributes)?; + Ok(s) + } + t => Err(X509Error::Der(Error::unexpected_tag(None, t))), + } + } + + /// Get the content as a slice. + #[inline] + pub fn as_slice(&self) -> &[u8] { + self.attr_value.as_bytes() + } +} + +impl<'a, 'b> TryFrom<&'a AttributeTypeAndValue<'b>> for &'a str { + type Error = X509Error; + + fn try_from(value: &'a AttributeTypeAndValue<'b>) -> Result<Self, Self::Error> { + value.attr_value.as_str().map_err(|e| e.into()) + } +} + +impl<'a, 'b> From<&'a AttributeTypeAndValue<'b>> for &'a [u8] { + fn from(value: &'a AttributeTypeAndValue<'b>) -> Self { + value.as_slice() + } +} + +// AttributeTypeAndValue ::= SEQUENCE { +// type AttributeType, +// value AttributeValue } +impl<'a> FromDer<'a, X509Error> for AttributeTypeAndValue<'a> { + fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + parse_der_sequence_defined_g(|i, _| { + let (i, attr_type) = Oid::from_der(i).or(Err(X509Error::InvalidX509Name))?; + let (i, attr_value) = parse_attribute_value(i).or(Err(X509Error::InvalidX509Name))?; + let attr = AttributeTypeAndValue::new(attr_type, attr_value); + Ok((i, attr)) + })(i) + } +} + +// AttributeValue ::= ANY -- DEFINED BY AttributeType +#[inline] +fn parse_attribute_value(i: &[u8]) -> ParseResult<Any, Error> { + alt((Any::from_der, parse_malformed_string))(i) +} + +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 { + return Err(nom::Err::Error(Error::InvalidLength)); + } + match hdr.tag() { + Tag::PrintableString => { + // if we are in this function, the PrintableString could not be validated. + // Accept it without validating charset, because some tools do not respect the charset + // restrictions (for ex. they use '*' while explicingly disallowed) + let (rem, data) = take(len)(rem)?; + // check valid encoding + let _ = std::str::from_utf8(data).map_err(|_| Error::BerValueError)?; + let obj = Any::new(hdr, data); + Ok((rem, obj)) + } + t => Err(nom::Err::Error(Error::unexpected_tag( + Some(Tag::PrintableString), + t, + ))), + } +} + +/// A Relative Distinguished Name element. +/// +/// These objects are used as [`X509Name`] components. +#[derive(Clone, Debug, PartialEq)] +pub struct RelativeDistinguishedName<'a> { + set: Vec<AttributeTypeAndValue<'a>>, +} + +impl<'a> RelativeDistinguishedName<'a> { + /// Builds a new `RelativeDistinguishedName` + #[inline] + pub const fn new(set: Vec<AttributeTypeAndValue<'a>>) -> Self { + RelativeDistinguishedName { set } + } + + /// Return an iterator over the components of this object + pub fn iter(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.set.iter() + } +} + +impl<'a> FromIterator<AttributeTypeAndValue<'a>> for RelativeDistinguishedName<'a> { + fn from_iter<T: IntoIterator<Item = AttributeTypeAndValue<'a>>>(iter: T) -> Self { + let set = iter.into_iter().collect(); + RelativeDistinguishedName { set } + } +} + +impl<'a> FromDer<'a, X509Error> for RelativeDistinguishedName<'a> { + fn from_der(i: &'a [u8]) -> X509Result<Self> { + parse_der_set_defined_g(|i, _| { + let (i, set) = many1(complete(AttributeTypeAndValue::from_der))(i)?; + let rdn = RelativeDistinguishedName { set }; + Ok((i, rdn)) + })(i) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SubjectPublicKeyInfo<'a> { + pub algorithm: AlgorithmIdentifier<'a>, + pub subject_public_key: BitString<'a>, + /// A raw unparsed PKIX, ASN.1 DER form (see RFC 5280, Section 4.1). + /// + /// Note: use the [`Self::parsed()`] function to parse this object. + pub raw: &'a [u8], +} + +impl<'a> SubjectPublicKeyInfo<'a> { + /// Attempt to parse the public key, and return the parsed version or an error + pub fn parsed(&self) -> Result<PublicKey, 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)?; + Ok(PublicKey::RSA(key)) + } else if self.algorithm.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY { + let key = ECPoint::from(b.as_ref()); + Ok(PublicKey::EC(key)) + } else if self.algorithm.algorithm == OID_KEY_TYPE_DSA { + let s = parse_der_integer(b) + .and_then(|(_, obj)| obj.as_slice().map_err(Err::Error)) + .or(Err(X509Error::InvalidSPKI))?; + Ok(PublicKey::DSA(s)) + } else if self.algorithm.algorithm == OID_GOST_R3410_2001 { + let (_, s) = <&[u8]>::from_der(b).or(Err(X509Error::InvalidSPKI))?; + Ok(PublicKey::GostR3410(s)) + } else if self.algorithm.algorithm == OID_KEY_TYPE_GOST_R3410_2012_256 + || self.algorithm.algorithm == OID_KEY_TYPE_GOST_R3410_2012_512 + { + let (_, s) = <&[u8]>::from_der(b).or(Err(X509Error::InvalidSPKI))?; + Ok(PublicKey::GostR3410_2012(s)) + } else { + Ok(PublicKey::Unknown(b)) + } + } +} + +impl<'a> FromDer<'a, X509Error> for SubjectPublicKeyInfo<'a> { + /// Parse the SubjectPublicKeyInfo struct portion of a DER-encoded X.509 Certificate + fn from_der(i: &'a [u8]) -> X509Result<Self> { + let start_i = i; + parse_der_sequence_defined_g(move |i, _| { + let (i, algorithm) = AlgorithmIdentifier::from_der(i)?; + let (i, subject_public_key) = BitString::from_der(i).or(Err(X509Error::InvalidSPKI))?; + let len = start_i.offset(i); + let raw = &start_i[..len]; + let spki = SubjectPublicKeyInfo { + algorithm, + subject_public_key, + raw, + }; + Ok((i, spki)) + })(i) + } +} + +/// Algorithm identifier +/// +/// An algorithm identifier is defined by the following ASN.1 structure: +/// +/// <pre> +/// AlgorithmIdentifier ::= SEQUENCE { +/// algorithm OBJECT IDENTIFIER, +/// parameters ANY DEFINED BY algorithm OPTIONAL } +/// </pre> +/// +/// The algorithm identifier is used to identify a cryptographic +/// algorithm. The OBJECT IDENTIFIER component identifies the algorithm +/// (such as DSA with SHA-1). The contents of the optional parameters +/// field will vary according to the algorithm identified. +#[derive(Clone, Debug, PartialEq, DerSequence)] +#[error(X509Error)] +pub struct AlgorithmIdentifier<'a> { + #[map_err(|_| X509Error::InvalidAlgorithmIdentifier)] + pub algorithm: Oid<'a>, + #[optional] + pub parameters: Option<Any<'a>>, +} + +impl<'a> AlgorithmIdentifier<'a> { + /// Create a new `AlgorithmIdentifier` + pub const fn new(algorithm: Oid<'a>, parameters: Option<Any<'a>>) -> Self { + Self { + algorithm, + parameters, + } + } + + /// Get the algorithm OID + pub const fn oid(&'a self) -> &'a Oid { + &self.algorithm + } + + /// Get a reference to the algorithm parameters, if present + pub const fn parameters(&'a self) -> Option<&'a Any> { + self.parameters.as_ref() + } +} + +/// X.509 Name (as used in `Issuer` and `Subject` fields) +/// +/// The Name describes a hierarchical name composed of attributes, such +/// as country name, and corresponding values, such as US. The type of +/// the component AttributeValue is determined by the AttributeType; in +/// general it will be a DirectoryString. +#[derive(Clone, Debug, PartialEq)] +pub struct X509Name<'a> { + pub(crate) rdn_seq: Vec<RelativeDistinguishedName<'a>>, + pub(crate) raw: &'a [u8], +} + +impl<'a> fmt::Display for X509Name<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match x509name_to_string(&self.rdn_seq, oid_registry()) { + Ok(o) => write!(f, "{}", o), + Err(_) => write!(f, "<X509Error: Invalid X.509 name>"), + } + } +} + +impl<'a> X509Name<'a> { + /// Builds a new `X509Name` from the provided elements. + #[inline] + pub const fn new(rdn_seq: Vec<RelativeDistinguishedName<'a>>, raw: &'a [u8]) -> Self { + X509Name { rdn_seq, raw } + } + + /// Attempt to format the current name, using the given registry to convert OIDs to strings. + /// + /// Note: a default registry is provided with this crate, and is returned by the + /// [`oid_registry()`] method. + pub fn to_string_with_registry(&self, oid_registry: &OidRegistry) -> Result<String, X509Error> { + x509name_to_string(&self.rdn_seq, oid_registry) + } + + // Not using the AsRef trait, as that would not give back the full 'a lifetime + pub fn as_raw(&self) -> &'a [u8] { + self.raw + } + + /// Return an iterator over the `RelativeDistinguishedName` components of the name + pub fn iter(&self) -> impl Iterator<Item = &RelativeDistinguishedName<'a>> { + self.rdn_seq.iter() + } + + /// Return an iterator over the `RelativeDistinguishedName` components of the name + pub fn iter_rdn(&self) -> impl Iterator<Item = &RelativeDistinguishedName<'a>> { + self.rdn_seq.iter() + } + + /// Return an iterator over the attribute types and values of the name + pub fn iter_attributes(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.rdn_seq.iter().flat_map(|rdn| rdn.set.iter()) + } + + /// Return an iterator over the components identified by the given OID + /// + /// The type of the component AttributeValue is determined by the AttributeType; in + /// general it will be a DirectoryString. + /// + /// Attributes with same OID may be present multiple times, so the returned object is + /// an iterator. + /// Expected number of objects in this iterator are + /// - 0: not found + /// - 1: present once (common case) + /// - 2 or more: attribute is present multiple times + pub fn iter_by_oid(&self, oid: &Oid<'a>) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + // this is necessary, otherwise rustc complains + // that caller creates a temporary value for reference (for ex. + // `self.iter_by_oid(&OID_X509_LOCALITY_NAME)` + // ) + let oid = oid.clone(); + self.iter_attributes() + .filter(move |obj| obj.attr_type == oid) + } + + /// Return an iterator over the `CommonName` attributes of the X.509 Name. + /// + /// Returned iterator can be empty if there are no `CommonName` attributes. + /// If you expect only one `CommonName` to be present, then using `next()` will + /// get an `Option<&AttributeTypeAndValue>`. + /// + /// A common operation is to extract the `CommonName` as a string. + /// + /// ``` + /// use x509_parser::x509::X509Name; + /// + /// fn get_first_cn_as_str<'a>(name: &'a X509Name<'_>) -> Option<&'a str> { + /// name.iter_common_name() + /// .next() + /// .and_then(|cn| cn.as_str().ok()) + /// } + /// ``` + /// + /// Note that there are multiple reasons for failure or incorrect behavior, for ex. if + /// the attribute is present multiple times, or is not a UTF-8 encoded string (it can be + /// UTF-16, or even an OCTETSTRING according to the standard). + pub fn iter_common_name(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_COMMON_NAME) + } + + /// Return an iterator over the `Country` attributes of the X.509 Name. + pub fn iter_country(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_COUNTRY_NAME) + } + + /// Return an iterator over the `Organization` attributes of the X.509 Name. + pub fn iter_organization(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_ORGANIZATION_NAME) + } + + /// Return an iterator over the `OrganizationalUnit` attributes of the X.509 Name. + pub fn iter_organizational_unit(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_ORGANIZATIONAL_UNIT) + } + + /// Return an iterator over the `StateOrProvinceName` attributes of the X.509 Name. + pub fn iter_state_or_province(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_STATE_OR_PROVINCE_NAME) + } + + /// Return an iterator over the `Locality` attributes of the X.509 Name. + pub fn iter_locality(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_X509_LOCALITY_NAME) + } + + /// Return an iterator over the `EmailAddress` attributes of the X.509 Name. + pub fn iter_email(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> { + self.iter_by_oid(&OID_PKCS9_EMAIL_ADDRESS) + } +} + +impl<'a> FromIterator<RelativeDistinguishedName<'a>> for X509Name<'a> { + fn from_iter<T: IntoIterator<Item = RelativeDistinguishedName<'a>>>(iter: T) -> Self { + let rdn_seq = iter.into_iter().collect(); + X509Name { rdn_seq, raw: &[] } + } +} + +impl<'a> From<X509Name<'a>> for Vec<RelativeDistinguishedName<'a>> { + fn from(name: X509Name<'a>) -> Self { + name.rdn_seq + } +} + +impl<'a> FromDer<'a, X509Error> for X509Name<'a> { + /// Parse the X.501 type Name, used for ex in issuer and subject of a X.509 certificate + fn from_der(i: &'a [u8]) -> X509Result<Self> { + let start_i = i; + parse_der_sequence_defined_g(move |i, _| { + let (i, rdn_seq) = many0(complete(RelativeDistinguishedName::from_der))(i)?; + let len = start_i.offset(i); + let name = X509Name { + rdn_seq, + raw: &start_i[..len], + }; + Ok((i, name)) + })(i) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct ReasonCode(pub u8); + +newtype_enum! { +impl display ReasonCode { + Unspecified = 0, + KeyCompromise = 1, + CACompromise = 2, + AffiliationChanged = 3, + Superseded = 4, + CessationOfOperation = 5, + CertificateHold = 6, + // value 7 is not used + RemoveFromCRL = 8, + PrivilegeWithdrawn = 9, + AACompromise = 10, +} +} + +impl Default for ReasonCode { + fn default() -> Self { + ReasonCode::Unspecified + } +} + +// Attempt to convert attribute to string. If type is not a string, return value is the hex +// encoding of the attribute value +fn attribute_value_to_string(attr: &Any, _attr_type: &Oid) -> Result<String, X509Error> { + // TODO: replace this with helper function, when it is added to asn1-rs + match attr.tag() { + Tag::NumericString + | Tag::VisibleString + | Tag::PrintableString + | Tag::GeneralString + | Tag::ObjectDescriptor + | Tag::GraphicString + | Tag::T61String + | Tag::VideotexString + | Tag::Utf8String + | Tag::Ia5String => { + let s = core::str::from_utf8(attr.data).map_err(|_| X509Error::InvalidAttributes)?; + Ok(s.to_owned()) + } + Tag::BmpString => { + // TODO: remove this when a new release of asn1-rs removes the need to consume attr in try_from + let any = attr.clone(); + let s = BmpString::try_from(any).map_err(|_| X509Error::InvalidAttributes)?; + Ok(s.string()) + } + _ => { + // type is not a string, get slice and convert it to base64 + Ok(HEXUPPER.encode(attr.as_bytes())) + } + } +} + +/// Convert a DER representation of a X.509 name to a human-readable string +/// +/// RDNs are separated with "," +/// Multiple RDNs are separated with "+" +/// +/// Attributes that cannot be represented by a string are hex-encoded +fn x509name_to_string( + rdn_seq: &[RelativeDistinguishedName], + oid_registry: &OidRegistry, +) -> Result<String, X509Error> { + rdn_seq.iter().fold(Ok(String::new()), |acc, rdn| { + acc.and_then(|mut _vec| { + rdn.set + .iter() + .fold(Ok(String::new()), |acc2, attr| { + acc2.and_then(|mut _vec2| { + let val_str = attribute_value_to_string(&attr.attr_value, &attr.attr_type)?; + // look ABBREV, and if not found, use shortname + let abbrev = match oid2abbrev(&attr.attr_type, oid_registry) { + Ok(s) => String::from(s), + _ => format!("{:?}", attr.attr_type), + }; + let rdn = format!("{}={}", abbrev, val_str); + match _vec2.len() { + 0 => Ok(rdn), + _ => Ok(_vec2 + " + " + &rdn), + } + }) + }) + .map(|v| match _vec.len() { + 0 => v, + _ => _vec + ", " + &v, + }) + }) + }) +} + +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)> { + 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, + // so we do not use `as_biguint()` + any.tag() + .assert_eq(Tag::Integer) + .map_err(|_| X509Error::InvalidSerial)?; + let slice = any.data; + let big = BigUint::from_bytes_be(slice); + Ok((rem, (slice, big))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_x509_version() { + // correct version + let data: &[u8] = &[0xa0, 0x03, 0x02, 0x01, 0x00]; + let r = X509Version::from_der_tagged_0(data); + assert!(r.is_ok()); + + // wrong tag + let data: &[u8] = &[0xa1, 0x03, 0x02, 0x01, 0x00]; + let r = X509Version::from_der_tagged_0(data); + assert!(r.is_ok()); + + // short read + let data: &[u8] = &[0xa0, 0x01]; + let r = X509Version::from_der_tagged_0(data); + assert!(r.is_err()); + + // short read with wrong tag + let data: &[u8] = &[0xa1, 0x01]; + let r = X509Version::from_der_tagged_0(data); + assert!(r.is_err()); + } + + #[test] + fn test_x509_name() { + let name = X509Name { + rdn_seq: vec![ + RelativeDistinguishedName { + set: vec![AttributeTypeAndValue { + attr_type: oid! {2.5.4.6}, // countryName + attr_value: Any::from_tag_and_data(Tag::PrintableString, b"FR"), + }], + }, + RelativeDistinguishedName { + set: vec![AttributeTypeAndValue { + attr_type: oid! {2.5.4.8}, // stateOrProvinceName + attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Some-State"), + }], + }, + RelativeDistinguishedName { + set: vec![AttributeTypeAndValue { + attr_type: oid! {2.5.4.10}, // organizationName + attr_value: Any::from_tag_and_data( + Tag::PrintableString, + b"Internet Widgits Pty Ltd", + ), + }], + }, + RelativeDistinguishedName { + set: vec![ + AttributeTypeAndValue { + attr_type: oid! {2.5.4.3}, // CN + attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Test1"), + }, + AttributeTypeAndValue { + attr_type: oid! {2.5.4.3}, // CN + attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Test2"), + }, + ], + }, + ], + raw: &[], // incorrect, but enough for testing + }; + assert_eq!( + name.to_string(), + "C=FR, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Test1 + CN=Test2" + ); + } +} diff --git a/rust/vendor/x509-parser/tests/pem.rs b/rust/vendor/x509-parser/tests/pem.rs new file mode 100644 index 0000000..eb97ff2 --- /dev/null +++ b/rust/vendor/x509-parser/tests/pem.rs @@ -0,0 +1,49 @@ +use std::io::Cursor; +use x509_parser::pem::{parse_x509_pem, Pem}; +use x509_parser::{parse_x509_certificate, x509::X509Version}; + +static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); + +#[test] +fn test_x509_parse_pem() { + let (rem, pem) = parse_x509_pem(IGCA_PEM).expect("PEM parsing failed"); + // println!("{:?}", pem); + assert!(rem.is_empty()); + assert_eq!(pem.label, String::from("CERTIFICATE")); + // + // now check that the content is indeed a certificate + let (rem, crt) = parse_x509_certificate(&pem.contents).expect("X.509 parsing failed"); + // println!("res: {:?}", res); + assert!(rem.is_empty()); + assert_eq!(crt.tbs_certificate.version, X509Version::V3); +} + +#[test] +fn test_pem_read() { + let reader = Cursor::new(IGCA_PEM); + let (pem, bytes_read) = Pem::read(reader).expect("Reading PEM failed"); + // println!("{:?}", pem); + assert_eq!(bytes_read, IGCA_PEM.len()); + assert_eq!(pem.label, String::from("CERTIFICATE")); + // + // now check that the content is indeed a certificate + let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); + assert_eq!(x509.tbs_certificate.version, X509Version::V3); +} + +#[test] +fn test_pem_not_pem() { + let bytes = vec![0x1, 0x2, 0x3, 0x4, 0x5]; + let reader = Cursor::new(bytes); + let res = Pem::read(reader); + assert!(res.is_err()); +} + +static NO_END: &[u8] = include_bytes!("../assets/no_end.pem"); + +#[test] +fn test_pem_no_end() { + let reader = Cursor::new(NO_END); + let res = Pem::read(reader); + assert!(res.is_err()); +} diff --git a/rust/vendor/x509-parser/tests/readcert.rs b/rust/vendor/x509-parser/tests/readcert.rs new file mode 100644 index 0000000..3fa3cd4 --- /dev/null +++ b/rust/vendor/x509-parser/tests/readcert.rs @@ -0,0 +1,360 @@ +use ::time::macros::datetime; +use ::time::OffsetDateTime; +use der_parser::oid; +use nom::Parser; +use oid_registry::*; +use std::collections::HashMap; +use x509_parser::prelude::*; + +static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); +static NO_EXTENSIONS_DER: &[u8] = include_bytes!("../assets/no_extensions.der"); +static V1: &[u8] = include_bytes!("../assets/v1.der"); +static CRL_DER: &[u8] = include_bytes!("../assets/example.crl"); +static EMPTY_CRL_DER: &[u8] = include_bytes!("../assets/empty.crl"); +static MINIMAL_CRL_DER: &[u8] = include_bytes!("../assets/minimal.crl"); +static DUPLICATE_VALUE_IN_AIA: &[u8] = + include_bytes!("../assets/duplicate_value_in_authority_info_access.der"); + +#[test] +fn test_x509_parser() { + let empty = &b""[..]; + //assert_eq!(x509_parser(IGCA_DER), IResult::Done(empty, (1))); + let res = parse_x509_certificate(IGCA_DER); + // println!("res: {:?}", res); + match res { + Ok((e, cert)) => { + assert_eq!(e, empty); + // + let tbs_cert = &cert.tbs_certificate; + assert_eq!(tbs_cert.version, X509Version::V3); + // + let s = tbs_cert.raw_serial_as_string(); + assert_eq!(&s, "39:11:45:10:94"); + // + let expected_subject = "C=FR, ST=France, L=Paris, O=PM/SGDN, OU=DCSSI, CN=IGC/A, Email=igca@sgdn.pm.gouv.fr"; + assert_eq!(format!("{}", tbs_cert.subject), expected_subject); + // + let cn_list: Result<Vec<_>, X509Error> = cert + .subject() + .iter_common_name() + .map(|attr| attr.as_str()) + .collect(); + assert_eq!(cn_list, Ok(vec!["IGC/A"])); + // + let sig = &tbs_cert.signature; + assert_eq!(sig.algorithm, oid! {1.2.840.113549.1.1.5}); + // + let expected_issuer = "C=FR, ST=France, L=Paris, O=PM/SGDN, OU=DCSSI, CN=IGC/A, Email=igca@sgdn.pm.gouv.fr"; + assert_eq!(format!("{}", tbs_cert.issuer), expected_issuer); + let expected_issuer_der = &IGCA_DER[35..171]; + assert_eq!(tbs_cert.issuer.as_raw(), expected_issuer_der); + // + let sig_alg = &cert.signature_algorithm; + assert_eq!(sig_alg.algorithm, OID_PKCS1_SHA1WITHRSA); + // + let not_before = &tbs_cert.validity.not_before; + let not_after = &tbs_cert.validity.not_after; + let nb = not_before.to_datetime(); + let na = not_after.to_datetime(); + assert_eq!(nb.year(), 2002); + assert_eq!(nb.month() as u8, 12); + assert_eq!(nb.day(), 13); + assert_eq!(na.year(), 2020); + assert_eq!(na.month() as u8, 10); + assert_eq!(na.day(), 17); + let policies = vec![PolicyInformation { + policy_id: oid!(1.2.250 .1 .121 .1 .1 .1), + policy_qualifiers: None, + }]; + let expected_extensions = vec![ + X509Extension::new( + oid!(2.5.29 .19), + true, + &[48, 3, 1, 1, 255], + ParsedExtension::BasicConstraints(BasicConstraints { + ca: true, + path_len_constraint: None, + }), + ), + X509Extension::new( + oid!(2.5.29 .15), + false, + &[3, 2, 1, 70], + ParsedExtension::KeyUsage(KeyUsage { flags: 98 }), + ), + X509Extension::new( + oid!(2.5.29 .32), + false, + &[48, 12, 48, 10, 6, 8, 42, 129, 122, 1, 121, 1, 1, 1], + ParsedExtension::CertificatePolicies(policies), + ), + X509Extension::new( + oid!(2.5.29 .14), + false, + &[ + 4, 20, 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, + 168, 48, 49, 54, + ], + ParsedExtension::SubjectKeyIdentifier(KeyIdentifier(&[ + 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, 168, + 48, 49, 54, + ])), + ), + X509Extension::new( + oid!(2.5.29 .35), + false, + &[ + 48, 22, 128, 20, 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, + 255, 142, 78, 168, 48, 49, 54, + ], + ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { + key_identifier: Some(KeyIdentifier(&[ + 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, + 168, 48, 49, 54, + ])), + authority_cert_issuer: None, + authority_cert_serial: None, + }), + ), + ]; + assert_eq!(tbs_cert.extensions(), &expected_extensions as &[_]); + // + assert!(tbs_cert.is_ca()); + // + assert_eq!(tbs_cert.as_ref(), &IGCA_DER[4..(8 + 746)]); + } + _ => panic!("x509 parsing failed: {:?}", res), + } +} + +#[test] +fn test_x509_no_extensions() { + let empty = &b""[..]; + let res = parse_x509_certificate(NO_EXTENSIONS_DER); + match res { + Ok((e, cert)) => { + assert_eq!(e, empty); + + let tbs_cert = cert.tbs_certificate; + assert_eq!(tbs_cert.version, X509Version::V3); + assert_eq!(tbs_cert.extensions().len(), 0); + } + _ => panic!("x509 parsing failed: {:?}", res), + } +} + +#[test] +fn test_parse_subject_public_key_info() { + let res = SubjectPublicKeyInfo::from_der(&IGCA_DER[339..]) + .expect("Parse public key info") + .1; + assert_eq!(res.algorithm.algorithm, OID_PKCS1_RSAENCRYPTION); + let params = res.algorithm.parameters.expect("algorithm parameters"); + assert_eq!(params.tag().0, 5); + let spk = res.subject_public_key; + println!("spk.data.len {}", spk.data.len()); + assert_eq!(spk.data.len(), 270); +} + +#[test] +fn test_version_v1() { + let (rem, cert) = parse_x509_certificate(V1).expect("Could not parse v1 certificate"); + assert!(rem.is_empty()); + assert_eq!(cert.version(), X509Version::V1); + let tbs_cert = cert.tbs_certificate; + assert_eq!(format!("{}", tbs_cert.subject), "CN=marquee"); + assert_eq!(format!("{}", tbs_cert.issuer), "CN=marquee"); +} + +#[test] +fn test_crl_parse() { + match parse_x509_crl(CRL_DER) { + Ok((e, cert)) => { + assert!(e.is_empty()); + + let tbs_cert_list = cert.tbs_cert_list; + assert_eq!(tbs_cert_list.version, Some(X509Version::V2)); + + let sig = &tbs_cert_list.signature; + assert_eq!(sig.algorithm, OID_PKCS1_SHA1WITHRSA); + + let expected_issuer = + "O=Sample Signer Organization, OU=Sample Signer Unit, CN=Sample Signer Cert"; + assert_eq!(format!("{}", tbs_cert_list.issuer), expected_issuer); + + let sig_alg = &cert.signature_algorithm; + assert_eq!(sig_alg.algorithm, OID_PKCS1_SHA1WITHRSA); + + let this_update = tbs_cert_list.this_update; + let next_update = tbs_cert_list.next_update.unwrap(); + let tu = OffsetDateTime::from_unix_timestamp(this_update.timestamp()).unwrap(); + let nu = OffsetDateTime::from_unix_timestamp(next_update.timestamp()).unwrap(); + assert_eq!(tu.year(), 2013); + assert_eq!(tu.month() as u8, 2); + assert_eq!(tu.day(), 18); + assert_eq!(nu.year(), 2013); + assert_eq!(nu.month() as u8, 2); + assert_eq!(nu.day(), 18); + + let revocation_date = datetime! {2013-02-18 10:22:12 UTC}; + let invalidity_date = datetime! {2013-02-18 10:22:00 UTC}; + + let revoked_certs = &tbs_cert_list.revoked_certificates; + let revoked_cert_0 = &revoked_certs[0]; + assert_eq!(*revoked_cert_0.serial(), 0x147947u32.into()); + assert_eq!( + revoked_cert_0.revocation_date.to_datetime(), + revocation_date + ); + let expected_extensions = vec![ + X509Extension::new( + oid!(2.5.29 .21), + false, + &[10, 1, 3], + ParsedExtension::ReasonCode(ReasonCode::AffiliationChanged), + ), + X509Extension::new( + oid!(2.5.29 .24), + false, + &[ + 24, 15, 50, 48, 49, 51, 48, 50, 49, 56, 49, 48, 50, 50, 48, 48, 90, + ], + ParsedExtension::InvalidityDate(invalidity_date.into()), + ), + ]; + assert_eq!(revoked_cert_0.extensions(), &expected_extensions as &[_]); + + assert_eq!(revoked_certs.len(), 5); + assert_eq!(revoked_certs[4].user_certificate, 1_341_771_u32.into()); + + let expected_extensions = vec![ + X509Extension::new( + oid!(2.5.29 .35), + false, + &[ + 48, 22, 128, 20, 190, 18, 1, 204, 170, 234, 17, 128, 218, 46, 173, 178, + 234, 199, 181, 251, 159, 249, 173, 52, + ], + ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { + key_identifier: Some(KeyIdentifier(&[ + 190, 18, 1, 204, 170, 234, 17, 128, 218, 46, 173, 178, 234, 199, 181, + 251, 159, 249, 173, 52, + ])), + authority_cert_issuer: None, + authority_cert_serial: None, + }), + ), + X509Extension::new( + oid!(2.5.29 .20), + false, + &[2, 1, 3], + ParsedExtension::CRLNumber(3u32.into()), + ), + ]; + assert_eq!(tbs_cert_list.extensions(), &expected_extensions as &[_]); + + assert_eq!(tbs_cert_list.as_ref(), &CRL_DER[4..(4 + 4 + 508)]); + } + err => panic!("x509 parsing failed: {:?}", err), + } +} + +#[test] +fn test_crl_parse_empty() { + match parse_x509_crl(EMPTY_CRL_DER) { + Ok((e, cert)) => { + assert!(e.is_empty()); + assert!(cert.tbs_cert_list.revoked_certificates.is_empty()); + + let expected_extensions = vec![ + X509Extension::new( + oid!(2.5.29 .20), + false, + &[2, 1, 2], + ParsedExtension::CRLNumber(2u32.into()), + ), + X509Extension::new( + OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, + false, + &[ + 48, 22, 128, 20, 34, 101, 12, 214, 90, 157, 52, 137, 243, 131, 180, 149, + 82, 191, 80, 27, 57, 39, 6, 172, + ], + ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { + key_identifier: Some(KeyIdentifier(&[ + 34, 101, 12, 214, 90, 157, 52, 137, 243, 131, 180, 149, 82, 191, 80, + 27, 57, 39, 6, 172, + ])), + authority_cert_issuer: None, + authority_cert_serial: None, + }), + ), + ]; + assert_eq!(cert.extensions(), &expected_extensions as &[_]); + assert_eq!( + cert.tbs_cert_list.as_ref(), + &EMPTY_CRL_DER[4..(4 + 3 + 200)] + ); + } + err => panic!("x509 parsing failed: {:?}", err), + } +} + +#[test] +fn test_crl_parse_minimal() { + match parse_x509_crl(MINIMAL_CRL_DER) { + Ok((e, crl)) => { + assert!(e.is_empty()); + let revocation_date = datetime! {1970-01-01 00:00:00 UTC}; + let revoked_certificates = &crl.tbs_cert_list.revoked_certificates; + assert_eq!(revoked_certificates.len(), 1); + let revoked_cert_0 = &revoked_certificates[0]; + assert_eq!(*revoked_cert_0.serial(), 42u32.into()); + assert_eq!( + revoked_cert_0.revocation_date.to_datetime(), + revocation_date + ); + assert!(revoked_cert_0.extensions().is_empty()); + assert!(crl.tbs_cert_list.extensions().is_empty()); + assert_eq!(crl.tbs_cert_list.as_ref(), &MINIMAL_CRL_DER[4..(4 + 79)]); + } + err => panic!("x509 parsing failed: {:?}", err), + } +} + +#[test] +fn test_duplicate_authority_info_access() { + match parse_x509_certificate(DUPLICATE_VALUE_IN_AIA) { + Ok((_, cert)) => { + let extension = cert + .tbs_certificate + .get_extension_unique(&OID_PKIX_AUTHORITY_INFO_ACCESS) + .expect("could not get AIA") + .expect("no AIA found"); + let mut accessdescs = HashMap::new(); + let ca_issuers = vec![ + &GeneralName::URI("http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt"), + &GeneralName::URI("http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt"), + ]; + let ocsp = vec![&GeneralName::URI("http://ocsp.pca.dfn.de/OCSP-Server/OCSP")]; + accessdescs.insert(OID_PKIX_ACCESS_DESCRIPTOR_CA_ISSUERS, ca_issuers); + accessdescs.insert(OID_PKIX_ACCESS_DESCRIPTOR_OCSP, ocsp); + if let ParsedExtension::AuthorityInfoAccess(aia) = extension.parsed_extension() { + let h = aia.as_hashmap(); + assert_eq!(h, accessdescs); + } else { + panic!("Wrong extension type parsed"); + } + } + err => panic!("x509 parsing failed: {:?}", err), + } +} + +#[test] +fn test_x509_parser_no_ext() { + let mut parser = X509CertificateParser::new().with_deep_parse_extensions(false); + let (_, x509) = parser.parse(IGCA_DER).expect("parsing failed"); + for ext in x509.extensions() { + assert_eq!(ext.parsed_extension(), &ParsedExtension::Unparsed); + } +} diff --git a/rust/vendor/x509-parser/tests/readcrl.rs b/rust/vendor/x509-parser/tests/readcrl.rs new file mode 100644 index 0000000..45b63bf --- /dev/null +++ b/rust/vendor/x509-parser/tests/readcrl.rs @@ -0,0 +1,18 @@ +// Currently, this file is only used to test 'verify' features, so we guard it to this feature +// To be removed if other test functions with different features are added +#![cfg(feature = "verify")] + +use x509_parser::prelude::*; + +const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der"); +const CRL_DATA: &[u8] = include_bytes!("../assets/minimal.crl"); + +#[cfg(feature = "verify")] +#[test] +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); + assert!(res.is_ok()); +} diff --git a/rust/vendor/x509-parser/tests/readcsr.rs b/rust/vendor/x509-parser/tests/readcsr.rs new file mode 100644 index 0000000..1124c48 --- /dev/null +++ b/rust/vendor/x509-parser/tests/readcsr.rs @@ -0,0 +1,125 @@ +use asn1_rs::Set; +use oid_registry::{ + OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_SIG_ECDSA_WITH_SHA256, + OID_X509_COMMON_NAME, +}; +use x509_parser::prelude::*; + +const CSR_DATA_EMPTY_ATTRIB: &[u8] = include_bytes!("../assets/csr-empty-attributes.csr"); +const CSR_DATA: &[u8] = include_bytes!("../assets/test.csr"); +const CSR_CHALLENGE_PASSWORD: &[u8] = include_bytes!("../assets/csr-challenge-password.pem"); +#[test] +fn read_csr_empty_attrib() { + let (rem, csr) = + X509CertificationRequest::from_der(CSR_DATA_EMPTY_ATTRIB).expect("could not parse CSR"); + + assert!(rem.is_empty()); + let cri = &csr.certification_request_info; + assert_eq!(cri.version, X509Version(0)); + assert_eq!(cri.attributes().len(), 0); + assert_eq!(csr.signature_algorithm.algorithm, OID_PKCS1_SHA256WITHRSA); +} + +#[test] +fn read_csr_with_san() { + let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; + let (rem, csr) = + X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); + + assert!(rem.is_empty()); + let cri = &csr.certification_request_info; + assert_eq!(cri.version, X509Version(0)); + assert_eq!(cri.attributes().len(), 1); + assert_eq!(csr.signature_algorithm.algorithm, OID_SIG_ECDSA_WITH_SHA256); + + let mut rdns = cri.subject.iter(); + let rdn = rdns.next().unwrap(); + let first = rdn.iter().next().unwrap(); + assert_eq!(first.attr_type(), &OID_X509_COMMON_NAME); + assert_eq!(first.as_str().unwrap(), "test.rusticata.fr"); + + let expected: &[u8] = &[ + 4, 195, 245, 126, 177, 113, 192, 146, 215, 136, 181, 58, 82, 138, 142, 61, 253, 245, 185, + 192, 166, 216, 218, 145, 219, 42, 169, 112, 122, 58, 91, 184, 150, 37, 237, 245, 59, 54, + 44, 210, 44, 207, 218, 167, 148, 189, 210, 159, 207, 103, 233, 1, 187, 134, 137, 24, 240, + 188, 223, 135, 215, 71, 80, 64, 65, + ]; + assert_eq!(cri.subject_pki.subject_public_key.data, expected); + + let mut extensions = csr.requested_extensions().unwrap(); + match extensions.next().unwrap() { + ParsedExtension::SubjectAlternativeName(san) => { + let name = san.general_names.first().unwrap(); + assert!(matches!(name, GeneralName::DNSName("test.rusticata.fr"))); + } + _ => unreachable!(), + } +} + +#[test] +fn read_csr_with_challenge_password() { + let der = pem::parse_x509_pem(CSR_CHALLENGE_PASSWORD).unwrap().1; + let (rem, csr) = X509CertificationRequest::from_der(&der.contents) + .expect("Could not parse CSR with challenge password"); + + assert!(rem.is_empty()); + let cri = &csr.certification_request_info; + assert_eq!(cri.version, X509Version(0)); + assert_eq!(cri.attributes().len(), 2); + + let challenge_password_attr = csr + .certification_request_info + .find_attribute(&OID_PKCS9_CHALLENGE_PASSWORD) + .expect("Challenge password not found in CSR"); + + // 1. Check: Parse value + let (rem, challenge_password_from_value) = + Set::from_der_and_then(challenge_password_attr.value, String::from_der) + .expect("Error parsing challenge password attribute"); + assert_eq!(challenge_password_from_value, "A challenge password"); + assert!(rem.is_empty()); + + // 2. Check: Get value directly from parsed attribute + if let ParsedCriAttribute::ChallengePassword(challenge_password_from_parsed_attribute) = + challenge_password_attr.parsed_attribute() + { + assert_eq!( + challenge_password_from_parsed_attribute.0, + "A challenge password" + ); + } else { + panic!("Parsed attribute is not a challenge password"); + } + + // Make sure we can read requested extensions + let extensions = csr + .requested_extensions() + .expect("Didn't find requested extensions in CSR"); + let mut found_san = false; + for extension in extensions { + if let ParsedExtension::SubjectAlternativeName(san) = extension { + let name = san.general_names.get(2).unwrap(); + assert!(matches!(name, GeneralName::DNSName("localhost"))); + found_san = true; + } + } + assert!(found_san); +} + +#[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"); + csr.verify_signature().unwrap(); + + let mut der = pem::parse_x509_pem(CSR_DATA).unwrap().1; + assert_eq!(&der.contents[28..37], b"rusticata"); + for (i, b) in b"foobarbaz".iter().enumerate() { + der.contents[28 + i] = *b; + } + assert_eq!(&der.contents[28..37], b"foobarbaz"); + + let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); + csr.verify_signature().unwrap_err(); +} diff --git a/rust/vendor/x509-parser/tests/run_all_fuzz_files.rs b/rust/vendor/x509-parser/tests/run_all_fuzz_files.rs new file mode 100644 index 0000000..96041b4 --- /dev/null +++ b/rust/vendor/x509-parser/tests/run_all_fuzz_files.rs @@ -0,0 +1,39 @@ +use std::fs::{self, DirEntry}; +use x509_parser::parse_x509_certificate; + +const ARTIFACTS_DIR: &str = "fuzz/artifacts/fuzzer_script_1"; +const CORPUS_DIR: &str = "fuzz/corpus/fuzzer_script_1"; + +#[test] +fn run_all_fuzz_files() { + parse_dir(ARTIFACTS_DIR); + parse_dir(CORPUS_DIR); +} + +fn parse_dir(name: &str) { + match fs::read_dir(name) { + Ok(dir_entries) => { + dir_entries.for_each(|entry| { + let _ = entry.as_ref().map(parse_file); + }); + } + Err(_) => eprintln!("fuzzer corpus/artifacts not found - ignoring test"), + } +} + +fn parse_file(entry: &DirEntry) -> std::io::Result<()> { + let path = entry.path(); + // println!("{:?}", entry.path()); + let data = fs::read(path).unwrap(); + let _ = parse_x509_certificate(&data); + Ok(()) +} + +#[test] +#[ignore = "placeholder for specific tests"] +fn run_fuzz_candidate() { + const CANDIDATE: &str = "fuzz/corpus/fuzzer_script_1/bd0096a63b9979d64763915a342a59af9dc281fb"; + + let data = fs::read(CANDIDATE).unwrap(); + let _ = parse_x509_certificate(&data); +} diff --git a/rust/vendor/x509-parser/tests/test01.rs b/rust/vendor/x509-parser/tests/test01.rs new file mode 100644 index 0000000..45c0dca --- /dev/null +++ b/rust/vendor/x509-parser/tests/test01.rs @@ -0,0 +1,19 @@ +use nom::bytes::complete::take; + +#[test] +fn test01() { + let data = b"0\x88\xff\xff\xff\xff\xff\xff\xff\xff00\x0f\x02\x000\x00\x00\x00\x00\x00\x0000\x0f\x00\xff\x0a\xbb\xff"; + let _ = x509_parser::parse_x509_certificate(data); +} + +fn parser02(input: &[u8]) -> nom::IResult<&[u8], ()> { + let (_hdr, input) = take(1_usize)(input)?; + let (_data, input) = take(18_446_744_073_709_551_615_usize)(input)?; + Ok((input, ())) +} + +#[test] +fn test02() { + let data = b"0\x88\xff\xff\xff\xff\xff\xff\xff\xff00\x0f\x02\x000\x00\x00\x00\x00\x00\x0000\x0f\x00\xff\x0a\xbb\xff"; + let _ = parser02(data); +} diff --git a/rust/vendor/x509-parser/tests/verify.rs b/rust/vendor/x509-parser/tests/verify.rs new file mode 100644 index 0000000..6cb3835 --- /dev/null +++ b/rust/vendor/x509-parser/tests/verify.rs @@ -0,0 +1,35 @@ +#![cfg(feature = "verify")] + +use x509_parser::parse_x509_certificate; + +static CA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); +static CA_LETSENCRYPT_X3: &[u8] = include_bytes!("../assets/lets-encrypt-x3-cross-signed.der"); +static CERT_DER: &[u8] = include_bytes!("../assets/certificate.der"); + +#[test] +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); + assert!(res.is_ok()); + + // for a standard certificate, first load the authority, then the certificate, and verify it + let (_, x509_ca) = + 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); + assert!(res.is_ok()); +} + +static ED25519_DER: &[u8] = include_bytes!("../assets/ed25519.der"); + +#[test] +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); + assert!(res.is_ok()); +} |