diff options
author | Joel Galenson <jgalenson@google.com> | 2021-08-11 14:44:14 +0000 |
---|---|---|
committer | Joel Galenson <jgalenson@google.com> | 2021-08-11 14:44:14 +0000 |
commit | c5051d11821e3e739e35ce22bc1c7b9a9decb9b4 (patch) | |
tree | 81ab7d86c6f58cc7c9faa2cbc6b9d188d91a71a7 | |
parent | 856d48fea3c60de2ec21cddbde112a5a9025af7f (diff) | |
download | x509-parser-c5051d11821e3e739e35ce22bc1c7b9a9decb9b4.tar.gz |
Revert "Upgrade rust/crates/x509-parser to 0.10.0"
Revert submission 1791044-der-parser-5.1.2-xml-parser-0.10.0
Reason for revert: Build breakage due to aosp/1791850
Reverted Changes:
Ib0a1d7bde:Upgrade rust/crates/x509-parser to 0.10.0
I9eead6d6d:Upgrade rust/crates/der-parser to 5.1.2
Change-Id: I89f48dfb50b4504ce7021cfc8f868a3e68baf9dd
35 files changed, 976 insertions, 2258 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index a19dcef..50631bc 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,5 +1,5 @@ { "git": { - "sha1": "8c8e7a47c7728b3ff6e6bf91bcc54d6acd9eeca5" + "sha1": "2685e2f7f2015774fb1d812f37d527b9c0f6bc36" } } @@ -20,4 +20,151 @@ rust_library { "librusticata_macros", "libthiserror", ], + proc_macros: ["librustversion"], +} + +rust_defaults { + name: "x509-parser_test_defaults", + crate_name: "x509_parser", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + edition: "2018", + features: ["default"], + rustlibs: [ + "libbase64_rust", + "libchrono", + "libder_parser", + "liblazy_static", + "libnom", + "liboid_registry", + "librusticata_macros", + "libthiserror", + ], + proc_macros: ["librustversion"], + data: ["assets/certificate.pem"], +} + +rust_test_host { + name: "x509-parser_host_test_src_lib", + defaults: ["x509-parser_test_defaults"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_src_lib", + defaults: ["x509-parser_test_defaults"], +} + +rust_defaults { + name: "x509-parser_test_defaults_x509_parser", + crate_name: "x509_parser", + test_suites: ["general-tests"], + auto_gen_config: true, + edition: "2018", + features: ["default"], + rustlibs: [ + "libbase64_rust", + "libchrono", + "libder_parser", + "liblazy_static", + "libnom", + "liboid_registry", + "librusticata_macros", + "libthiserror", + "libx509_parser", + ], + proc_macros: ["librustversion"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_pem", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/pem.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_pem", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/pem.rs"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_readcert", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/readcert.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_readcert", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/readcert.rs"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_readcsr", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/readcsr.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_readcsr", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/readcsr.rs"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_run_all_fuzz_files", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/run_all_fuzz_files.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_run_all_fuzz_files", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/run_all_fuzz_files.rs"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_test01", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/test01.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_test01", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/test01.rs"], +} + +rust_test_host { + name: "x509-parser_host_test_tests_verify", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/verify.rs"], + test_options: { + unit_test: true, + }, +} + +rust_test { + name: "x509-parser_device_test_tests_verify", + defaults: ["x509-parser_test_defaults_x509_parser"], + srcs: ["tests/verify.rs"], } diff --git a/CHANGELOG.md b/CHANGELOG.md index dfcd92a..2ca97ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,54 +6,6 @@ ### Thanks -## 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 @@ -13,7 +13,7 @@ [package] edition = "2018" name = "x509-parser" -version = "0.10.0" +version = "0.9.2" authors = ["Pierre Chifflier <chifflier@wzdftpd.net>"] include = ["CHANGELOG.md", "LICENSE-*", "README.md", ".gitignore", ".travis.yml", "Cargo.toml", "src/*.rs", "tests/*.rs", "assets/*.der", "assets/*.pem", "examples/*.rs"] description = "Parser for the X.509 v3 format (RFC 5280 certificates)" @@ -38,7 +38,7 @@ default-features = false version = "2.2.1" [dependencies.der-parser] -version = "5.1" +version = "5.0" features = ["bigint"] [dependencies.lazy_static] @@ -58,10 +58,12 @@ optional = true [dependencies.rusticata-macros] version = "3.0" +[dependencies.rustversion] +version = "1.0" + [dependencies.thiserror] version = "1.0" [features] default = [] -validate = [] verify = ["ring"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 0833677..30698c6 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "x509-parser" -version = "0.10.0" +version = "0.9.2" description = "Parser for the X.509 v3 format (RFC 5280 certificates)" license = "MIT/Apache-2.0" keywords = ["X509","Certificate","parser","nom"] @@ -32,7 +32,6 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] verify = ["ring"] -validate = [] [dependencies] base64 = "0.13" @@ -43,5 +42,6 @@ nom = "6.0" oid-registry = { version="0.1.1", features=["crypto", "x509"] } rusticata-macros = "3.0" ring = { version="0.16", optional=true } -der-parser = { version = "5.1", features=["bigint"] } +rustversion = "1.0" +der-parser = { version = "5.0", features=["bigint"] } thiserror = "1.0" @@ -7,13 +7,14 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/x509-parser/x509-parser-0.10.0.crate" + value: "https://static.crates.io/crates/x509-parser/x509-parser-0.9.2.crate" } - version: "0.10.0" + version: "0.9.2" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. license_type: NOTICE last_upgrade_date { year: 2021 - month: 8 - day: 9 + month: 7 + day: 26 } } @@ -25,17 +25,12 @@ 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/traits/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). +[`parse_x509_certificate`](https://docs.rs/x509-parser/latest/x509_parser/fn.parse_x509_certificate.html), which builds a +[`X509Certificate`](https://docs.rs/x509-parser/latest/x509_parser/x509/struct.X509Certificate.html) object. 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 +example [X509Certificate::issuer()](https://docs.rs/x509-parser/latest/x509_parser/x509/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. @@ -49,12 +44,12 @@ use x509_parser::prelude::*; static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); -let res = X509Certificate::from_der(IGCA_DER); +let res = parse_x509_certificate(IGCA_DER); match res { Ok((rem, cert)) => { assert!(rem.is_empty()); // - assert_eq!(cert.version(), X509Version::V3); + assert_eq!(cert.tbs_certificate.version, X509Version::V3); }, _ => panic!("x509 parsing failed: {:?}", res), } @@ -65,7 +60,7 @@ To parse a CRL and print information about revoked certificates: ```rust # # -let res = CertificateRevocationList::from_der(DER); +let res = parse_x509_crl(DER); match res { Ok((_rem, crl)) => { for revoked in crl.iter_revoked_certificates() { @@ -83,24 +78,20 @@ See also `examples/print-cert.rs`. - 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) + [X509Certificate::verify_signature()](https://docs.rs/x509-parser/latest/x509_parser/x509/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(); + let issuer_public_key = &issuer.tbs_certificate.subject_pki; 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.45 or greater**, based on nom 6 diff --git a/assets/csr-empty-attributes.csr b/assets/csr-empty-attributes.csr Binary files differnew file mode 100644 index 0000000..bfb84c8 --- /dev/null +++ b/assets/csr-empty-attributes.csr diff --git a/assets/ed25519.der b/assets/ed25519.der Binary files differdeleted file mode 100644 index cae76a1..0000000 --- a/assets/ed25519.der +++ /dev/null diff --git a/assets/empty.crl b/assets/empty.crl Binary files differnew file mode 100644 index 0000000..fc3f6b3 --- /dev/null +++ b/assets/empty.crl diff --git a/assets/example.crl b/assets/example.crl Binary files differnew file mode 100644 index 0000000..3df0b6f --- /dev/null +++ b/assets/example.crl diff --git a/assets/gen_minimal_crl.py b/assets/gen_minimal_crl.py new file mode 100644 index 0000000..b3f9e65 --- /dev/null +++ b/assets/gen_minimal_crl.py @@ -0,0 +1,40 @@ +"""Generates a DER encoded CRL with a single revoked serial and no extensions. +This exercises the optional-parsing functionalitites of `parse_crl_der`. +""" + +import os.path as osp + +from OpenSSL import crypto + + +def main(): + pkey = crypto.PKey() + pkey.generate_key(crypto.TYPE_RSA, 2048) + + ca = crypto.X509() + ca.set_version(2) + ca.set_serial_number(1) + ca.get_subject().CN = 'snakeoil' + ca.set_notBefore(b'19700101000000Z') + ca.set_notAfter(b'20991231235959Z') + ca.set_issuer(ca.get_subject()) + ca.set_pubkey(pkey) + ca.sign(pkey, 'sha256') + + revoked = crypto.Revoked() + revoked.set_serial(b'2a') + revoked.set_rev_date(b'19700101000000Z') + revoked.set_reason(None) + + crl = crypto.CRL() + crl.set_lastUpdate(b'19700101000000Z') + crl.set_nextUpdate(b'20990101000000Z') + crl.add_revoked(revoked) + crl.sign(issuer_cert=ca, issuer_key=pkey, digest=b'sha256') + + with open(osp.join(osp.dirname(__file__), 'minimal.crl'), 'wb') as f_crl: + f_crl.write(crypto.dump_crl(crypto.FILETYPE_ASN1, crl)) + + +if __name__ == '__main__': + main() diff --git a/assets/minimal.crl b/assets/minimal.crl Binary files differnew file mode 100644 index 0000000..32cd969 --- /dev/null +++ b/assets/minimal.crl diff --git a/assets/test.csr b/assets/test.csr new file mode 100644 index 0000000..729df67 --- /dev/null +++ b/assets/test.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST-----
+MIIBBjCBrQIBADAcMRowGAYDVQQDDBF0ZXN0LnJ1c3RpY2F0YS5mcjBZMBMGByqG
+SM49AgEGCCqGSM49AwEHA0IABMP1frFxwJLXiLU6UoqOPf31ucCm2NqR2yqpcHo6
+W7iWJe31OzYs0izP2qeUvdKfz2fpAbuGiRjwvN+H10dQQEGgLzAtBgkqhkiG9w0B
+CQ4xIDAeMBwGA1UdEQQVMBOCEXRlc3QucnVzdGljYXRhLmZyMAoGCCqGSM49BAMC
+A0gAMEUCIGqQHPHgpeyZa5YMLP2X5IwfmrvpIcg5fQ2xkXotGAa0AiEAydeBwr4r
+Iu7XDe015h8uz8xZs2QUEgRdr73lJXTX+Ck=
+-----END CERTIFICATE REQUEST-----
diff --git a/cargo2android.json b/cargo2android.json index 5f001a6..64cedd9 100644 --- a/cargo2android.json +++ b/cargo2android.json @@ -3,6 +3,10 @@ "data_encoding" ], "device": true, - "run": true + "run": true, + "test-data": [ + "src/lib.rs=assets/certificate.pem" + ], + "tests": true } diff --git a/examples/print-cert.rs b/examples/print-cert.rs index 73a9149..50a5c6d 100644 --- a/examples/print-cert.rs +++ b/examples/print-cert.rs @@ -4,12 +4,6 @@ use std::cmp::min; use std::env; use std::io; use x509_parser::prelude::*; -#[cfg(feature = "validate")] -use x509_parser::validate::Validate; - -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); @@ -20,26 +14,12 @@ fn print_hex_dump(bytes: &[u8], max_len: usize) { } fn format_oid(oid: &Oid) -> String { - match oid2sn(oid, oid_registry()) { + match oid2sn(oid) { 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) { print!(" {}: ", format_oid(oid)); print!(" Critical={}", ext.critical); @@ -49,25 +29,6 @@ fn print_x509_extension(oid: &Oid, ext: &X509Extension) { ParsedExtension::BasicConstraints(bc) => { println!(" X509v3 CA: {}", bc.ca); } - ParsedExtension::CRLDistributionPoints(points) => { - println!(" X509v3 CRL Distribution Points:"); - for point in points { - 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); } @@ -114,7 +75,7 @@ fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { } } -fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { +fn print_x509_info(x509: &X509Certificate) { println!(" Subject: {}", x509.subject()); println!(" Signature Algorithm:"); print_x509_digest_algorithm(&x509.signature_algorithm, 4); @@ -125,66 +86,28 @@ fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { println!(" NotAfter: {}", x509.validity().not_after.to_rfc2822()); println!(" is_valid: {}", x509.validity().is_valid()); println!(" Extensions:"); - for ext in x509.extensions() { - print_x509_extension(&ext.oid, ext); + for (oid, ext) in x509.extensions() { + print_x509_extension(oid, ext); } println!(); - #[cfg(feature = "validate")] - { - // structure validation status - let (ok, warnings, errors) = x509.validate_to_vec(); - print!("Structure validation status: "); - if ok { - println!("Ok"); - } else { - println!("FAIL"); - } - for warning in &warnings { - println!(" [W] {}", warning); - } - for error in &errors { - println!(" [E] {}", error); - } - println!(); - if VALIDATE_ERRORS_FATAL && !errors.is_empty() { - return Err(io::Error::new(io::ErrorKind::Other, "validation failed")); - } - } - Ok(()) -} - -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)) { + if (data[0], data[1]) == (0x30, 0x82) { // probably DER - handle_certificate(&file_name, &data)?; + let (_, x509) = parse_x509_certificate(&data).expect("Could not decode DER data"); + print_x509_info(&x509); } else { // try as PEM for (n, pem) in Pem::iter_from_buffer(&data).enumerate() { let pem = pem.expect("Could not decode the PEM file"); let data = &pem.contents; + let (_, x509) = parse_x509_certificate(&data).expect("Could not decode DER data"); println!("Certificate [{}]", n); - handle_certificate(&file_name, data)?; + print_x509_info(&x509); } } } diff --git a/examples/print-crl.rs b/examples/print-crl.rs index a9af731..de6c0f3 100644 --- a/examples/print-crl.rs +++ b/examples/print-crl.rs @@ -14,7 +14,7 @@ fn print_hex_dump(bytes: &[u8], max_len: usize) { } fn format_oid(oid: &Oid) -> String { - match oid2sn(oid, oid_registry()) { + match oid2sn(oid) { Ok(s) => s.to_owned(), _ => format!("{}", oid), } @@ -119,8 +119,8 @@ fn print_revoked_certificate(revoked: &RevokedCertificate, level: usize) { indent = level + 2 ); println!("{:indent$}CRL Extensions:", "", indent = level + 2); - for ext in revoked.extensions() { - print_x509_extension(&ext.oid, ext, level + 4); + for (oid, ext) in revoked.extensions() { + print_x509_extension(oid, ext, level + 4); } } @@ -138,8 +138,8 @@ fn print_crl_info(crl: &CertificateRevocationList) { .map_or("NONE".to_owned(), |d| d.to_rfc2822()) ); println!("{:indent$}CRL Extensions:", "", indent = 2); - for ext in crl.extensions() { - print_x509_extension(&ext.oid, ext, 4); + for (oid, ext) in crl.extensions() { + print_x509_extension(oid, ext, 4); } println!(" Revoked certificates:"); for revoked in crl.iter_revoked_certificates() { @@ -164,7 +164,7 @@ pub fn main() -> io::Result<()> { tmpdata = data; &tmpdata.contents }; - let (_, crl) = parse_x509_crl(der_data).expect("Could not decode DER data"); + let (_, crl) = parse_x509_crl(&der_data).expect("Could not decode DER data"); print_crl_info(&crl); } Ok(()) diff --git a/patches/data-encoding_dep.patch b/patches/data-encoding_dep.patch index f4d04c0..f5d9396 100644 --- a/patches/data-encoding_dep.patch +++ b/patches/data-encoding_dep.patch @@ -3,11 +3,11 @@ index 0f8c0fd..9590fe6 100644 --- a/src/x509.rs +++ b/src/x509.rs @@ -6,7 +6,6 @@ + use crate::error::{X509Error, X509Result}; use crate::objects::*; - use crate::traits::FromDer; -use data_encoding::HEXUPPER; - use der_parser::ber::{parse_ber_integer, BitStringObject, MAX_OBJECT_SIZE}; + use der_parser::ber::{BitStringObject, MAX_OBJECT_SIZE}; use der_parser::der::*; use der_parser::error::*; @@ -365,7 +364,7 @@ fn attribute_value_to_string(attr: &DerObject, _attr_type: &Oid) -> Result<Strin diff --git a/src/certificate.rs b/src/certificate.rs index 1d937b1..f29bc4e 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -3,9 +3,6 @@ use crate::error::{X509Error, X509Result}; use crate::extensions::*; use crate::time::ASN1Time; -use crate::traits::FromDer; -#[cfg(feature = "validate")] -use crate::validate::Validate; use crate::x509::{ parse_serial, parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, @@ -17,11 +14,9 @@ use der_parser::error::*; use der_parser::num_bigint::BigUint; use der_parser::oid::Oid; use der_parser::*; -use nom::{Offset, Parser}; +use nom::Offset; use oid_registry::*; use std::collections::HashMap; -#[cfg(feature = "validate")] -use std::collections::HashSet; /// An X.509 v3 Certificate. /// @@ -36,21 +31,21 @@ use std::collections::HashSet; /// buffer containing the binary representation. /// /// ```rust +/// # use x509_parser::parse_x509_certificate; /// # use x509_parser::certificate::X509Certificate; -/// # use x509_parser::traits::FromDer; /// # /// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); /// # /// fn display_x509_info(x509: &X509Certificate<'_>) { -/// let subject = x509.subject(); -/// let issuer = x509.issuer(); +/// let subject = &x509.tbs_certificate.subject; +/// let issuer = &x509.tbs_certificate.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); +/// # let res = parse_x509_certificate(DER); /// # match res { /// # Ok((_rem, x509)) => { /// # display_x509_info(&x509); @@ -59,7 +54,7 @@ use std::collections::HashSet; /// # } /// # } /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct X509Certificate<'a> { pub tbs_certificate: TbsCertificate<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, @@ -67,6 +62,56 @@ pub struct X509Certificate<'a> { } impl<'a> 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.tbs_certificate.subject; + /// let issuer = &x509.tbs_certificate.issuer; + /// println!("X.509 Subject: {}", subject); + /// println!("X.509 Issuer: {}", issuer); + /// }, + /// _ => panic!("x509 parsing failed: {:?}", res), + /// } + /// # } + /// ``` + pub fn from_der(i: &'a [u8]) -> X509Result<Self> { + parse_der_sequence_defined_g(|i, _| { + let (i, tbs_certificate) = TbsCertificate::from_der(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)) + })(i) + } + /// Get the version of the encoded certificate pub fn version(&self) -> X509Version { self.tbs_certificate.version @@ -90,16 +135,10 @@ impl<'a> X509Certificate<'a> { &self.tbs_certificate.validity } - /// Get the certificate public key information. - #[inline] - pub fn public_key(&self) -> &SubjectPublicKeyInfo { - &self.tbs_certificate.subject_pki - } - /// Get the certificate extensions. #[inline] - pub fn extensions(&self) -> &[X509Extension] { - &self.tbs_certificate.extensions + pub fn extensions(&self) -> &HashMap<Oid, X509Extension> { + self.tbs_certificate.extensions() } /// Verify the cryptographic signature of this certificate @@ -111,13 +150,12 @@ impl<'a> X509Certificate<'a> { /// For a leaf certificate, this is the public key of the certificate that signed it. /// It is usually an intermediate authority. #[cfg(feature = "verify")] - #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] pub fn verify_signature( &self, public_key: Option<&SubjectPublicKeyInfo>, ) -> Result<(), X509Error> { use ring::signature; - let spki = public_key.unwrap_or_else(|| self.public_key()); + let spki = public_key.unwrap_or(&self.tbs_certificate.subject_pki); let signature_alg = &self.signature_algorithm.algorithm; // identify verification algorithm let verification_alg: &dyn signature::VerificationAlgorithm = @@ -133,8 +171,6 @@ impl<'a> X509Certificate<'a> { &signature::ECDSA_P256_SHA256_ASN1 } else if *signature_alg == OID_SIG_ECDSA_WITH_SHA384 { &signature::ECDSA_P384_SHA384_ASN1 - } else if *signature_alg == OID_SIG_ED25519 { - &signature::ED25519 } else { return Err(X509Error::SignatureUnsupportedAlgorithm); }; @@ -147,141 +183,7 @@ impl<'a> X509Certificate<'a> { } } -impl<'a> FromDer<'a> 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, PartialEq)] -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) - } -} - -#[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), - { - let mut res = true; - res |= self.tbs_certificate.validate(warn, err); - res - } -} - -/// The sequence `TBSCertificate` contains information associated with the +/// The sequence TBSCertificate contains information associated with the /// subject of the certificate and the CA that issued it. /// /// RFC5280 definition: @@ -303,7 +205,7 @@ impl Validate for X509Certificate<'_> { /// -- If present, version MUST be v3 /// } /// </pre> -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct TbsCertificate<'a> { pub version: X509Version, pub serial: BigUint, @@ -314,143 +216,12 @@ pub struct TbsCertificate<'a> { pub subject_pki: SubjectPublicKeyInfo<'a>, pub issuer_uid: Option<UniqueIdentifier<'a>>, pub subject_uid: Option<UniqueIdentifier<'a>>, - extensions: Vec<X509Extension<'a>>, + pub extensions: HashMap<Oid<'a>, X509Extension<'a>>, pub(crate) raw: &'a [u8], pub(crate) raw_serial: &'a [u8], } impl<'a> TbsCertificate<'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) - }) - } - - pub fn basic_constraints(&self) -> Option<(bool, &BasicConstraints)> { - self.find_extension(&OID_X509_EXT_BASIC_CONSTRAINTS) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::BasicConstraints(ref bc) => Some((ext.critical, bc)), - _ => None, - }) - } - - pub fn key_usage(&self) -> Option<(bool, &KeyUsage)> { - self.find_extension(&OID_X509_EXT_KEY_USAGE) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::KeyUsage(ref ku) => Some((ext.critical, ku)), - _ => None, - }) - } - - pub fn extended_key_usage(&self) -> Option<(bool, &ExtendedKeyUsage)> { - self.find_extension(&OID_X509_EXT_EXTENDED_KEY_USAGE) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::ExtendedKeyUsage(ref eku) => Some((ext.critical, eku)), - _ => None, - }) - } - - pub fn policy_constraints(&self) -> Option<(bool, &PolicyConstraints)> { - self.find_extension(&OID_X509_EXT_POLICY_CONSTRAINTS) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::PolicyConstraints(ref pc) => Some((ext.critical, pc)), - _ => None, - }) - } - - pub fn inhibit_anypolicy(&self) -> Option<(bool, &InhibitAnyPolicy)> { - self.find_extension(&OID_X509_EXT_INHIBITANT_ANY_POLICY) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::InhibitAnyPolicy(ref iap) => Some((ext.critical, iap)), - _ => None, - }) - } - - pub fn policy_mappings(&self) -> Option<(bool, &PolicyMappings)> { - self.find_extension(&OID_X509_EXT_POLICY_MAPPINGS) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::PolicyMappings(ref pm) => Some((ext.critical, pm)), - _ => None, - }) - } - - pub fn subject_alternative_name(&self) -> Option<(bool, &SubjectAlternativeName)> { - self.find_extension(&OID_X509_EXT_SUBJECT_ALT_NAME) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::SubjectAlternativeName(ref san) => Some((ext.critical, san)), - _ => None, - }) - } - - pub fn name_constraints(&self) -> Option<(bool, &NameConstraints)> { - self.find_extension(&OID_X509_EXT_NAME_CONSTRAINTS) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::NameConstraints(ref nc) => Some((ext.critical, nc)), - _ => None, - }) - } - - /// Returns true if certificate has `basicConstraints CA:true` - pub fn is_ca(&self) -> bool { - self.basic_constraints() - .map(|(_, bc)| bc.ca) - .unwrap_or(false) - } - - /// 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 { - let mut s = self - .raw_serial - .iter() - .fold(String::with_capacity(3 * self.raw_serial.len()), |a, b| { - a + &format!("{:02x}:", b) - }); - s.pop(); - s - } -} - -impl<'a> AsRef<[u8]> for TbsCertificate<'a> { - #[inline] - fn as_ref(&self) -> &[u8] { - self.raw - } -} - -impl<'a> FromDer<'a> for TbsCertificate<'a> { /// Parse a DER-encoded TbsCertificate object /// /// <pre> @@ -469,7 +240,7 @@ impl<'a> FromDer<'a> for TbsCertificate<'a> { /// extensions [3] Extensions OPTIONAL /// -- If present, version MUST be v3 -- } /// </pre> - fn from_der(i: &'a [u8]) -> X509Result<TbsCertificate<'a>> { + pub 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(i)?; @@ -501,164 +272,126 @@ impl<'a> FromDer<'a> for TbsCertificate<'a> { Ok((i, tbs)) })(i) } -} -/// `TbsCertificate` parser builder -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct TbsCertificateParser { - deep_parse_extensions: bool, -} + /// Get a reference to the map of extensions. + pub fn extensions(&self) -> &HashMap<Oid, X509Extension> { + &self.extensions + } -impl TbsCertificateParser { - #[inline] - pub const fn new() -> Self { - TbsCertificateParser { - deep_parse_extensions: true, + pub fn basic_constraints(&self) -> Option<(bool, &BasicConstraints)> { + let ext = self.extensions.get(&OID_X509_EXT_BASIC_CONSTRAINTS)?; + match ext.parsed_extension { + ParsedExtension::BasicConstraints(ref bc) => Some((ext.critical, bc)), + _ => None, } } - #[inline] - pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { - TbsCertificateParser { - deep_parse_extensions, + pub fn key_usage(&self) -> Option<(bool, &KeyUsage)> { + let ext = self.extensions.get(&OID_X509_EXT_KEY_USAGE)?; + match ext.parsed_extension { + ParsedExtension::KeyUsage(ref ku) => Some((ext.critical, ku)), + _ => None, } } -} - -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(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, BerTag(3))? - } else { - parse_extensions_envelope(i, BerTag(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) + pub fn extended_key_usage(&self) -> Option<(bool, &ExtendedKeyUsage)> { + let ext = self.extensions.get(&OID_X509_EXT_EXTENDED_KEY_USAGE)?; + match ext.parsed_extension { + ParsedExtension::ExtendedKeyUsage(ref eku) => Some((ext.critical, eku)), + _ => None, + } } -} -#[cfg(feature = "validate")] -#[cfg_attr(docsrs, doc(cfg(feature = "validate")))] -impl Validate for TbsCertificate<'_> { - fn validate<W, E>(&self, mut warn: W, mut err: E) -> bool - where - W: FnMut(&str), - E: FnMut(&str), - { - let mut res = true; - // version must be 0, 1 or 2 - if self.version.0 >= 3 { - err("Invalid version"); - res = false; - } - // extensions require v3 - if !self.extensions().is_empty() && self.version != X509Version::V3 { - err("Extensions present but version is not 3"); - res = false; + pub fn policy_constraints(&self) -> Option<(bool, &PolicyConstraints)> { + let ext = self.extensions.get(&OID_X509_EXT_POLICY_CONSTRAINTS)?; + match ext.parsed_extension { + ParsedExtension::PolicyConstraints(ref pc) => Some((ext.critical, pc)), + _ => None, } - let b = self.raw_serial(); - if b.is_empty() { - err("Serial is empty"); - res = false; - } else { - // check MSB of serial - if b[0] & 0x80 != 0 { - warn("Serial number is negative"); - } - // check leading zeroes in serial - if b.len() > 1 && b[0] == 0 && b[1] & 0x80 == 0 { - warn("Leading zeroes in serial number"); - } + } + + pub fn inhibit_anypolicy(&self) -> Option<(bool, &InhibitAnyPolicy)> { + let ext = self.extensions.get(&OID_X509_EXT_INHIBITANT_ANY_POLICY)?; + match ext.parsed_extension { + ParsedExtension::InhibitAnyPolicy(ref iap) => Some((ext.critical, iap)), + _ => None, } - // subject/issuer: verify charsets - // - wildcards in PrintableString - // - non-IA5 in IA5String - for attr in self.subject.iter_attributes() { - match attr.attr_value().content { - DerObjectContent::PrintableString(s) | DerObjectContent::IA5String(s) => { - if !s.as_bytes().iter().all(u8::is_ascii) { - warn(&format!( - "Invalid charset in 'Subject', component {}", - attr.attr_type() - )); - } - } - _ => (), - } + } + + pub fn policy_mappings(&self) -> Option<(bool, &PolicyMappings)> { + let ext = self.extensions.get(&OID_X509_EXT_POLICY_MAPPINGS)?; + match ext.parsed_extension { + ParsedExtension::PolicyMappings(ref pm) => Some((ext.critical, pm)), + _ => None, } - // check for parse errors or unsupported extensions - for ext in self.extensions() { - if let ParsedExtension::UnsupportedExtension { .. } = &ext.parsed_extension { - warn(&format!("Unsupported extension {}", ext.oid)); - } - if let ParsedExtension::ParseError { error } = &ext.parsed_extension { - err(&format!("Parse error in extension {}: {}", ext.oid, error)); - res = false; - } + } + + pub fn subject_alternative_name(&self) -> Option<(bool, &SubjectAlternativeName)> { + let ext = self.extensions.get(&OID_X509_EXT_SUBJECT_ALT_NAME)?; + match ext.parsed_extension { + ParsedExtension::SubjectAlternativeName(ref san) => Some((ext.critical, san)), + _ => None, } - // check for duplicate extensions - let mut m = HashSet::new(); - for ext in self.extensions() { - if m.contains(&ext.oid) { - err(&format!("Duplicate extension {}", ext.oid)); - res = false; - } else { - m.insert(ext.oid.clone()); - } - // 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) { - warn(&format!("Invalid charset in 'SAN' entry '{}'", s)); - } - } - _ => (), - } - } - } + } + + pub fn name_constraints(&self) -> Option<(bool, &NameConstraints)> { + let ext = self.extensions.get(&OID_X509_EXT_NAME_CONSTRAINTS)?; + match ext.parsed_extension { + ParsedExtension::NameConstraints(ref nc) => Some((ext.critical, nc)), + _ => None, } - res + } + + /// Returns true if certificate has `basicConstraints CA:true` + pub fn is_ca(&self) -> bool { + self.basic_constraints() + .map(|(_, bc)| bc.ca) + .unwrap_or(false) + } + + /// 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 { + let mut s = self + .raw_serial + .iter() + .fold(String::with_capacity(3 * self.raw_serial.len()), |a, b| { + a + &format!("{:02x}:", b) + }); + s.pop(); + s } } -#[derive(Clone, Debug, PartialEq)] +impl<'a> AsRef<[u8]> for TbsCertificate<'a> { + fn as_ref(&self) -> &[u8] { + &self.raw + } +} + +#[derive(Debug, PartialEq)] pub struct Validity { pub not_before: ASN1Time, pub not_after: ASN1Time, } impl 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) + } + /// The time left before the certificate expires. /// /// If the certificate is not currently valid, then `None` is @@ -677,7 +410,7 @@ impl Validity { /// 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 + time >= self.not_before && time < self.not_after } /// Check the certificate time validity @@ -687,21 +420,7 @@ impl Validity { } } -impl<'a> FromDer<'a> 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)] +#[derive(Debug, PartialEq)] pub struct UniqueIdentifier<'a>(pub BitStringObject<'a>); impl<'a> UniqueIdentifier<'a> { diff --git a/src/certification_request.rs b/src/certification_request.rs index 12d301f..ec8a9f0 100644 --- a/src/certification_request.rs +++ b/src/certification_request.rs @@ -1,7 +1,8 @@ use crate::cri_attributes::*; -use crate::error::{X509Error, X509Result}; +#[cfg(feature = "verify")] +use crate::error::X509Error; +use crate::error::X509Result; use crate::extensions::*; -use crate::traits::FromDer; use crate::x509::{ parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, }; @@ -15,7 +16,6 @@ use nom::Offset; use oid_registry::*; use std::collections::HashMap; -/// Certification Signing Request (CSR) #[derive(Debug, PartialEq)] pub struct X509CertificationRequest<'a> { pub certification_request_info: X509CertificationRequestInfo<'a>, @@ -24,12 +24,45 @@ pub struct X509CertificationRequest<'a> { } impl<'a> X509CertificationRequest<'a> { - pub fn requested_extensions(&self) -> Option<impl Iterator<Item = &ParsedExtension>> { + /// Parse a certification signing request (CSR) + /// + /// <pre> + /// CertificationRequest ::= SEQUENCE { + /// certificationRequestInfo CertificationRequestInfo, + /// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + /// signature BIT STRING + /// } + /// </pre> + /// + /// certificateRequestInfo is the "Certification request information", it is the value being + /// signed; signatureAlgorithm identifies the signature algorithm; and signature is the result + /// of signing the certification request information with the subject's private key. + pub fn from_der(i: &'a [u8]) -> X509Result<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) + } + + pub fn requested_extensions(&self) -> Option<impl Iterator<Item = &ParsedExtension<'a>>> { self.certification_request_info - .iter_attributes() + .attributes + .values() .find_map(|attr| { if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute { - Some(requested.extensions.iter().map(|ext| &ext.parsed_extension)) + Some( + requested + .extensions + .values() + .map(|ext| &ext.parsed_extension), + ) } else { None } @@ -59,8 +92,6 @@ impl<'a> X509CertificationRequest<'a> { &signature::ECDSA_P256_SHA256_ASN1 } else if *signature_alg == OID_SIG_ECDSA_WITH_SHA384 { &signature::ECDSA_P384_SHA384_ASN1 - } else if *signature_alg == OID_SIG_ED25519 { - &signature::ED25519 } else { return Err(X509Error::SignatureUnsupportedAlgorithm); }; @@ -73,101 +104,34 @@ impl<'a> X509CertificationRequest<'a> { } } -/// <pre> -/// CertificationRequest ::= SEQUENCE { -/// certificationRequestInfo CertificationRequestInfo, -/// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, -/// signature BIT STRING -/// } -/// </pre> -impl<'a> FromDer<'a> 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 attributes: HashMap<Oid<'a>, 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`. + /// Parse a certification request info structure /// - /// 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. + /// Certification request information is defined by the following ASN.1 structure: /// - /// 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> for X509CertificationRequestInfo<'a> { - fn from_der(i: &'a [u8]) -> X509Result<Self> { + /// <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. + pub 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_required(i)?; diff --git a/src/cri_attributes.rs b/src/cri_attributes.rs index cfb7192..410bf45 100644 --- a/src/cri_attributes.rs +++ b/src/cri_attributes.rs @@ -1,7 +1,6 @@ use crate::{ error::{X509Error, X509Result}, extensions::X509Extension, - traits::FromDer, }; use der_parser::der::{ @@ -15,15 +14,15 @@ use oid_registry::*; use std::collections::HashMap; /// Attributes for Certification Request -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct X509CriAttribute<'a> { pub oid: Oid<'a>, pub value: &'a [u8], pub(crate) parsed_attribute: ParsedCriAttribute<'a>, } -impl<'a> FromDer<'a> for X509CriAttribute<'a> { - fn from_der(i: &'a [u8]) -> X509Result<X509CriAttribute> { +impl<'a> X509CriAttribute<'a> { + pub fn from_der(i: &'a [u8]) -> X509Result<X509CriAttribute> { parse_der_sequence_defined_g(|i, _| { let (i, oid) = map_res(parse_der_oid, |x| x.as_oid_val())(i)?; let value_start = i; @@ -45,19 +44,13 @@ impl<'a> FromDer<'a> for X509CriAttribute<'a> { } /// Section 3.1 of rfc 5272 -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct ExtensionRequest<'a> { - pub extensions: Vec<X509Extension<'a>>, -} - -impl<'a> FromDer<'a> for ExtensionRequest<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_extension_request(i).map_err(Err::convert) - } + pub extensions: HashMap<Oid<'a>, X509Extension<'a>>, } /// Attributes for Certification Request -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub enum ParsedCriAttribute<'a> { ExtensionRequest(ExtensionRequest<'a>), UnsupportedAttribute, @@ -68,7 +61,6 @@ pub(crate) mod parser { use der_parser::error::BerError; use der_parser::{oid::Oid, *}; use lazy_static::lazy_static; - use nom::combinator::map; use nom::{Err, IResult}; type AttrParser = fn(&[u8]) -> IResult<&[u8], ParsedCriAttribute, BerError>; @@ -82,7 +74,7 @@ pub(crate) mod parser { } let mut m = HashMap::new(); - add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_ext); + add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request); m }; } @@ -100,24 +92,39 @@ pub(crate) mod parser { } } - pub(super) fn parse_extension_request(i: &[u8]) -> IResult<&[u8], ExtensionRequest, BerError> { + fn parse_extension_request(i: &[u8]) -> IResult<&[u8], ParsedCriAttribute, BerError> { crate::extensions::parse_extension_sequence(i) - .map(|(i, extensions)| (i, ExtensionRequest { extensions })) + .and_then(|(i, extensions)| { + crate::extensions::extensions_sequence_to_map(i, extensions) + }) + .map(|(i, extensions)| { + ( + i, + ParsedCriAttribute::ExtensionRequest(ExtensionRequest { extensions }), + ) + }) .map_err(|_| Err::Error(BerError::BerTypeError)) } +} - fn parse_extension_request_ext(i: &[u8]) -> IResult<&[u8], ParsedCriAttribute, BerError> { - map( - parse_extension_request, - ParsedCriAttribute::ExtensionRequest, - )(i) +fn attributes_sequence_to_map<'a>( + i: &'a [u8], + v: Vec<X509CriAttribute<'a>>, +) -> X509Result<'a, HashMap<Oid<'a>, X509CriAttribute<'a>>> { + let mut attributes = HashMap::new(); + for attr in v.into_iter() { + if attributes.insert(attr.oid.clone(), attr).is_some() { + // duplicate attributes are not allowed + return Err(Err::Failure(X509Error::DuplicateAttributes)); + } } + Ok((i, attributes)) } -pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<Vec<X509CriAttribute>> { +pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<HashMap<Oid, X509CriAttribute>> { let (i, hdr) = der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidAttributes)))?; if i.is_empty() { - return Ok((i, Vec::new())); + return Ok((i, HashMap::new())); } (0..hdr.structured) .into_iter() @@ -126,4 +133,5 @@ pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<Vec<X509CriAttribute> attrs.push(attr); Ok((rem, attrs)) }) + .and_then(|(i, attrs)| attributes_sequence_to_map(i, attrs)) } diff --git a/src/error.rs b/src/error.rs index 65b0503..b2da105 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,7 +14,7 @@ pub struct NidError; 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)] +#[derive(Debug, PartialEq, thiserror::Error)] pub enum X509Error { #[error("generic error")] Generic, diff --git a/src/extensions.rs b/src/extensions.rs index 6172d47..9932169 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -2,72 +2,20 @@ use crate::error::{X509Error, X509Result}; use crate::time::{der_to_utctime, ASN1Time}; -use crate::traits::FromDer; -use crate::x509::{ReasonCode, RelativeDistinguishedName, X509Name}; +use crate::x509::{ReasonCode, X509Name}; -use der_parser::ber::parse_ber_bool; use der_parser::der::*; -use der_parser::error::{BerError, BerResult}; +use der_parser::error::BerResult; use der_parser::num_bigint::BigUint; use der_parser::oid::Oid; -use nom::combinator::{all_consuming, complete, map, map_opt, map_res, opt}; +use nom::combinator::{all_consuming, complete, map_opt, map_res, opt}; use nom::multi::{many0, many1}; -use nom::{Err, IResult, Parser}; +use nom::{exact, Err}; use oid_registry::*; use std::collections::HashMap; use std::fmt; -/// 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::extensions::{X509Extension, ParsedExtension}; -/// use x509_parser::traits::FromDer; -/// -/// 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)] +#[derive(Debug, PartialEq)] pub struct X509Extension<'a> { /// OID describing the extension content pub oid: Oid<'a>, @@ -81,9 +29,78 @@ pub struct X509Extension<'a> { } impl<'a> X509Extension<'a> { - /// Creates a new extension with the provided values. - #[inline] - pub const fn new( + /// Parse a DER-encoded X.509 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. + /// + /// This function parses the global structure (described above), and will return the object if it + /// succeeds. During this step, it also attempts to parse the content of the extension, if known. + /// The returned object has a + /// [parsed_extension](x509/struct.X509Extension.html#method.parsed_extension) method. The returned + /// enum is either a known extension, or the special value `ParsedExtension::UnsupportedExtension`. + /// + /// <pre> + /// Extension ::= SEQUENCE { + /// extnID OBJECT IDENTIFIER, + /// critical BOOLEAN DEFAULT FALSE, + /// extnValue OCTET STRING } + /// </pre> + /// + /// # Example + /// + /// ```rust + /// # 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 != ParsedExtension::UnsupportedExtension); + /// 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), + /// } + /// # } + /// ``` + pub fn from_der(i: &'a [u8]) -> X509Result<Self> { + parse_der_sequence_defined_g(|i, _| { + let (i, oid) = map_res(parse_der_oid, |x| x.as_oid_val())(i)?; + let (i, critical) = der_read_critical(i)?; + let (i, value) = map_res(parse_der_octetstring, |x| x.as_slice())(i)?; + let (i, parsed_extension) = crate::extensions::parser::parse_extension(i, value, &oid)?; + let ext = X509Extension { + oid, + critical, + value, + parsed_extension, + }; + Ok((i, ext)) + })(i) + .map_err(|_| X509Error::InvalidExtensions.into()) + } + + pub fn new( oid: Oid<'a>, critical: bool, value: &'a [u8], @@ -98,78 +115,16 @@ impl<'a> X509Extension<'a> { } /// 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> for X509Extension<'a> { - fn from_der(i: &'a [u8]) -> X509Result<Self> { - X509ExtensionParser::new().parse(i) - } -} - -/// `X509Extension` parser builder -#[derive(Clone, Copy, Debug, PartialEq)] -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) = map_res(parse_der_oid, |x| x.as_oid_val())(i)?; - let (i, critical) = der_read_critical(i)?; - let (i, value) = map_res(parse_der_octetstring, |x| x.as_slice())(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)] +#[derive(Debug, PartialEq)] pub enum ParsedExtension<'a> { /// Crate parser does not support this extension (yet) - UnsupportedExtension { - oid: Oid<'a>, - }, - ParseError { - error: Err<BerError>, - }, + UnsupportedExtension, + ParseError, /// Section 4.2.1.1 of rfc 5280 AuthorityKeyIdentifier(AuthorityKeyIdentifier<'a>), /// Section 4.2.1.2 of rfc 5280 @@ -190,8 +145,6 @@ pub enum ParsedExtension<'a> { 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 @@ -204,81 +157,31 @@ pub enum ParsedExtension<'a> { ReasonCode(ReasonCode), /// Section 5.3.3 of rfc 5280 InvalidityDate(ASN1Time), - /// 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)] +#[derive(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> 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)] -pub struct PolicyInformation<'a> { - pub policy_id: Oid<'a>, - pub policy_qualifiers: Option<Vec<PolicyQualifierInfo<'a>>>, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct PolicyQualifierInfo<'a> { - pub policy_qualifier_id: Oid<'a>, - pub qualifier: &'a [u8], +#[derive(Debug, PartialEq)] +pub struct CertificatePolicies<'a> { + pub policies: HashMap<Oid<'a>, &'a [u8]>, } /// Identifies whether the subject of the certificate is a CA, and the max validation depth. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct BasicConstraints { pub ca: bool, pub path_len_constraint: Option<u32>, } -impl<'a> FromDer<'a> for BasicConstraints { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_basicconstraints(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct KeyIdentifier<'a>(pub &'a [u8]); -impl<'a> FromDer<'a> for KeyIdentifier<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_keyidentifier(i).map_err(Err::convert) - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct KeyUsage { pub flags: u16, } @@ -344,13 +247,7 @@ impl fmt::Display for KeyUsage { } } -impl<'a> FromDer<'a> for KeyUsage { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_keyusage(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct ExtendedKeyUsage<'a> { pub any: bool, pub server_auth: bool, @@ -358,17 +255,11 @@ pub struct ExtendedKeyUsage<'a> { pub code_signing: bool, pub email_protection: bool, pub time_stamping: bool, - pub ocsp_signing: bool, + pub ocscp_signing: bool, pub other: Vec<Oid<'a>>, } -impl<'a> FromDer<'a> for ExtendedKeyUsage<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_extendedkeyusage(i).map_err(Err::convert) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq)] pub struct NSCertType(u8); // The value is a bit-string, where the individual bit positions are defined as: @@ -432,188 +323,33 @@ impl fmt::Display for NSCertType { } } -impl<'a> FromDer<'a> for NSCertType { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_nscerttype(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct AuthorityInfoAccess<'a> { - pub accessdescs: Vec<AccessDescription<'a>>, -} - -impl<'a> AuthorityInfoAccess<'a> { - /// 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> 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, - } - } + pub accessdescs: HashMap<Oid<'a>, Vec<GeneralName<'a>>>, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct InhibitAnyPolicy { pub skip_certs: u32, } -impl<'a> FromDer<'a> 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)] +#[derive(Debug, PartialEq)] pub struct PolicyMappings<'a> { - pub mappings: Vec<PolicyMapping<'a>>, -} - -impl<'a> FromDer<'a> for PolicyMappings<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::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)] -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, - } - } + pub mappings: HashMap<Oid<'a>, Vec<Oid<'a>>>, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct PolicyConstraints { pub require_explicit_policy: Option<u32>, pub inhibit_policy_mapping: Option<u32>, } -impl<'a> FromDer<'a> for PolicyConstraints { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_policyconstraints(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct SubjectAlternativeName<'a> { pub general_names: Vec<GeneralName<'a>>, } -impl<'a> FromDer<'a> 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(GeneralName::from_der)))(input)?; - Ok((i, SubjectAlternativeName { general_names })) - })(i) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] /// Represents a GeneralName as defined in RFC5280. There /// is no support X.400 addresses and EDIPartyName. /// @@ -624,13 +360,11 @@ pub enum GeneralName<'a> { RFC822Name(&'a str), /// A hostname, format is not checked. DNSName(&'a str), - /// X400Address, - X400Address(UnparsedObject<'a>), + // X400Address, /// 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(UnparsedObject<'a>), + // EDIPartyName { name_assigner: Option<&'a str>, party_name: &'a str }, /// An uniform resource identifier. The format is not checked. URI(&'a str), /// An ip address, provided as encoded. @@ -638,31 +372,13 @@ pub enum GeneralName<'a> { RegisteredID(Oid<'a>), } -impl<'a> FromDer<'a> for GeneralName<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_generalname(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct UnparsedObject<'a> { - pub header: DerObjectHeader<'a>, - pub data: &'a [u8], -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct NameConstraints<'a> { pub permitted_subtrees: Option<Vec<GeneralSubtree<'a>>>, pub excluded_subtrees: Option<Vec<GeneralSubtree<'a>>>, } -impl<'a> FromDer<'a> for NameConstraints<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parser::parse_nameconstraints(i).map_err(Err::convert) - } -} - -#[derive(Clone, Debug, PartialEq)] +#[derive(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> { @@ -671,94 +387,11 @@ pub struct GeneralSubtree<'a> { // maximum: Option<u32>, } -pub type CRLDistributionPoints<'a> = Vec<CRLDistributionPoint<'a>>; - -impl<'a> FromDer<'a> 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)] -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::traits::FromDer; use der_parser::error::BerError; use der_parser::{oid::Oid, *}; use lazy_static::lazy_static; - use nom::bytes::streaming::take; use nom::combinator::{map, verify}; use nom::{Err, IResult}; @@ -773,60 +406,35 @@ pub(crate) mod parser { } 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_KEY_IDENTIFIER, parse_keyidentifier); + add!(m, OID_X509_EXT_KEY_USAGE, parse_keyusage); add!( m, OID_X509_EXT_SUBJECT_ALT_NAME, - parse_subjectalternativename_ext - ); - add!( - m, - OID_X509_EXT_BASIC_CONSTRAINTS, - parse_basicconstraints_ext + parse_subjectalternativename ); - add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints_ext); + add!(m, OID_X509_EXT_BASIC_CONSTRAINTS, parse_basicconstraints); + add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints); 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 + parse_certificatepolicies ); + add!(m, OID_X509_EXT_POLICY_MAPPINGS, parse_policymappings); + add!(m, OID_X509_EXT_POLICY_CONSTRAINTS, parse_policyconstraints); + add!(m, OID_X509_EXT_EXTENDED_KEY_USAGE, parse_extendedkeyusage); add!( m, OID_X509_EXT_INHIBITANT_ANY_POLICY, - parse_inhibitanypolicy_ext - ); - add!( - m, - OID_PKIX_AUTHORITY_INFO_ACCESS, - parse_authorityinfoaccess_ext + parse_inhibitanypolicy ); + add!(m, OID_PKIX_AUTHORITY_INFO_ACCESS, parse_authorityinfoaccess); add!( m, OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, - parse_authoritykeyidentifier_ext + parse_authoritykeyidentifier ); - add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype_ext); + add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype); 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); @@ -842,17 +450,10 @@ pub(crate) mod parser { 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 })), - } + let (_, ext) = parser(i)?; + Ok((orig_i, ext)) } else { - Ok(( - orig_i, - ParsedExtension::UnsupportedExtension { - oid: oid.to_owned(), - }, - )) + Ok((orig_i, ParsedExtension::UnsupportedExtension)) } } @@ -861,7 +462,11 @@ pub(crate) mod parser { i: &'a [u8], oid: &Oid, ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { - parse_extension0(orig_i, i, oid) + let r = parse_extension0(orig_i, i, oid); + if let Err(nom::Err::Incomplete(_)) = r { + return Ok((orig_i, ParsedExtension::UnsupportedExtension)); + } + r } /// Parse a "Basic Constraints" extension @@ -875,7 +480,7 @@ pub(crate) mod parser { /// /// 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> { + fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rem, obj) = parse_der_sequence(i)?; if let Ok(seq) = obj.as_sequence() { let (ca, path_len_constraint) = match seq.len() { @@ -902,23 +507,17 @@ pub(crate) mod parser { }; Ok(( rem, - BasicConstraints { + ParsedExtension::BasicConstraints(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) - } - - pub(super) fn parse_nameconstraints<'a>( - i: &'a [u8], - ) -> IResult<&'a [u8], NameConstraints, BerError> { + fn parse_nameconstraints<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { fn parse_subtree<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralSubtree, BerError> { parse_der_sequence_defined_g(|input, _| { map(parse_generalname, |base| GeneralSubtree { base })(input) @@ -944,23 +543,17 @@ pub(crate) mod parser { Ok((rem, named_constraints)) })(i)?; - Ok((ret, named_constraints)) + Ok((ret, ParsedExtension::NameConstraints(named_constraints))) } - fn parse_nameconstraints_ext<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { - map(parse_nameconstraints, ParsedExtension::NameConstraints)(i) - } - - pub(super) fn parse_generalname<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralName, BerError> { + fn parse_generalname<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralName, BerError> { let (rest, hdr) = verify(der_read_element_header, |hdr| hdr.is_contextspecific())(i)?; let len = hdr.len.primitive()?; if len > rest.len() { return Err(nom::Err::Failure(BerError::ObjectTooShort)); } fn ia5str<'a>(i: &'a [u8], hdr: DerObjectHeader) -> Result<&'a str, Err<BerError>> { - // Relax constraints from RFC here: we are expecting an IA5String, but many certificates - // are using unicode characters - der_read_element_content_as(i, DerTag::Utf8String, hdr.len, hdr.is_constructed(), 0)? + der_read_element_content_as(i, DerTag::Ia5String, hdr.len, hdr.is_constructed(), 0)? .1 .as_slice() .and_then(|s| std::str::from_utf8(s).map_err(|_| BerError::BerValueError)) @@ -975,12 +568,7 @@ pub(crate) mod parser { } 1 => GeneralName::RFC822Name(ia5str(rest, hdr)?), 2 => GeneralName::DNSName(ia5str(rest, hdr)?), - 3 => { - // XXX Not yet implemented - let (_, data) = take(len)(rest)?; - let obj = UnparsedObject { header: hdr, data }; - GeneralName::X400Address(obj) - } + 3 => return Err(Err::Failure(BerError::Unsupported)), // x400Address 4 => { // directoryName, name let (_, name) = all_consuming(X509Name::from_der)(&rest[..len]) @@ -988,12 +576,7 @@ pub(crate) mod parser { ?; GeneralName::DirectoryName(name) } - 5 => { - // XXX Not yet implemented - let (_, data) = take(len)(rest)?; - let obj = UnparsedObject { header: hdr, data }; - GeneralName::EDIPartyName(obj) - } + 5 => return Err(Err::Failure(BerError::Unsupported)), // ediPartyName 6 => GeneralName::URI(ia5str(rest, hdr)?), 7 => { // IPAddress, OctetString @@ -1027,7 +610,7 @@ pub(crate) mod parser { Ok((&rest[len..], name)) } - pub(super) fn parse_subjectalternativename_ext<'a>( + fn parse_subjectalternativename<'a>( i: &'a [u8], ) -> IResult<&'a [u8], ParsedExtension, BerError> { parse_der_sequence_defined_g(|input, _| { @@ -1039,7 +622,7 @@ pub(crate) mod parser { })(i) } - pub(super) fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], PolicyConstraints, BerError> { + fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, require_explicit_policy) = opt(complete(map_res( parse_der_tagged_implicit(0, parse_der_content(DerTag::Integer)), @@ -1053,54 +636,50 @@ pub(crate) mod parser { require_explicit_policy, inhibit_policy_mapping, }; - Ok((i, policy_constraint)) + Ok((i, ParsedExtension::PolicyConstraints(policy_constraint))) })(i) } - fn parse_policyconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - map(parse_policyconstraints, ParsedExtension::PolicyConstraints)(i) - } - // PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { // issuerDomainPolicy CertPolicyId, // subjectDomainPolicy CertPolicyId } - pub(super) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, BerError> { + fn parse_policymappings(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { fn parse_oid_pair(i: &[u8]) -> IResult<&[u8], Vec<DerObject<'_>>, BerError> { // read 2 OID as a SEQUENCE OF OID - length will be checked later parse_der_sequence_of_v(parse_der_oid)(i) } let (ret, pairs) = parse_der_sequence_of_v(parse_oid_pair)(i)?; - let mut mappings = Vec::new(); - // let mut mappings: HashMap<Oid, Vec<Oid>> = HashMap::new(); + let mut mappings: HashMap<Oid, Vec<Oid>> = HashMap::new(); for pair in pairs.iter() { if pair.len() != 2 { return Err(Err::Failure(BerError::BerValueError)); } let left = pair[0].as_oid_val().map_err(nom::Err::Failure)?; let right = pair[1].as_oid_val().map_err(nom::Err::Failure)?; - // 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)); + 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 + .entry(left) + .and_modify(|v| v.push(right.clone())) + .or_insert_with(|| vec![right.clone()]); } - Ok((ret, PolicyMappings { mappings })) - } - - fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - map(parse_policymappings, ParsedExtension::PolicyMappings)(i) + Ok(( + ret, + ParsedExtension::PolicyMappings(PolicyMappings { mappings }), + )) } - fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - let (ret, skip_certs) = parse_der_u32(i)?; + fn parse_inhibitanypolicy(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + let (ret, skip_certs) = map_res(parse_der_integer, |x: DerObject| x.as_u32())(i)?; Ok(( ret, ParsedExtension::InhibitAnyPolicy(InhibitAnyPolicy { skip_certs }), )) } - pub(super) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> { + fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (ret, seq) = parse_der_sequence_of(parse_der_oid)(i)?; let mut seen = std::collections::HashSet::new(); let mut eku = ExtendedKeyUsage { @@ -1110,7 +689,7 @@ pub(crate) mod parser { code_signing: false, email_protection: false, time_stamping: false, - ocsp_signing: false, + ocscp_signing: false, other: Vec::new(), }; for oid in seq.as_sequence().map_err(nom::Err::Failure)?.iter() { @@ -1132,101 +711,12 @@ pub(crate) mod parser { } 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; + eku.ocscp_signing = true; } else { eku.other.push(oid); } } - Ok((ret, eku)) - } - - 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(DerTag::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> { - parse_der_sequence_of_v(parse_crldistributionpoint)(i) - } - - fn parse_crldistributionpoints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - map( - parse_crldistributionpoints, - ParsedExtension::CRLDistributionPoints, - )(i) + Ok((ret, ParsedExtension::ExtendedKeyUsage(eku))) } // AuthorityInfoAccessSyntax ::= @@ -1235,27 +725,30 @@ pub(crate) mod parser { // 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> { + fn parse_authorityinfoaccess(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_aia(i: &[u8]) -> IResult<&[u8], (Oid, GeneralName), BerError> { parse_der_sequence_defined_g(|content, _| { // Read first element, an oid. let (gn, oid) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?; // Parse second element let (rest, gn) = parse_generalname(gn)?; - Ok((rest, AccessDescription::new(oid, gn))) + Ok((rest, (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) + let (ret, mut aia_list) = parse_der_sequence_of_v(parse_aia)(i)?; + // create the hashmap and merge entries with same OID + let mut accessdescs: HashMap<Oid, Vec<GeneralName>> = HashMap::new(); + for (oid, gn) in aia_list.drain(..) { + if let Some(general_names) = accessdescs.get_mut(&oid) { + general_names.push(gn); + } else { + accessdescs.insert(oid, vec![gn]); + } + } + Ok(( + ret, + ParsedExtension::AuthorityInfoAccess(AuthorityInfoAccess { accessdescs }), + )) } fn parse_aki_content<'a>( @@ -1283,37 +776,40 @@ pub(crate) mod parser { } // RFC 5280 section 4.2.1.1: Authority Key Identifier - pub(super) fn parse_authoritykeyidentifier( - i: &[u8], - ) -> IResult<&[u8], AuthorityKeyIdentifier, BerError> { + fn parse_authoritykeyidentifier(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rem, aki) = parse_der_sequence_defined_g(parse_aki_content)(i)?; - Ok((rem, aki)) + Ok((rem, ParsedExtension::AuthorityKeyIdentifier(aki))) } - fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - map( - parse_authoritykeyidentifier, - ParsedExtension::AuthorityKeyIdentifier, - )(i) + #[rustversion::not(since(1.37))] + fn reverse_bits(n: u8) -> u8 { + let mut out = 0; + for i in 0..=7 { + if n & (1 << i) != 0 { + out |= 1 << (7 - i); + } + } + out } - pub(super) fn parse_keyidentifier<'a>( - i: &'a [u8], - ) -> IResult<&'a [u8], KeyIdentifier, BerError> { + #[rustversion::since(1.37)] + #[inline] + fn reverse_bits(n: u8) -> u8 { + n.reverse_bits() + } + + fn parse_keyidentifier<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { let (rest, obj) = parse_der_octetstring(i)?; let id = obj .content .as_slice() .or(Err(Err::Error(BerError::BerTypeError)))?; let ki = KeyIdentifier(id); - Ok((rest, ki)) - } - - fn parse_keyidentifier_ext<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { - map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i) + let ret = ParsedExtension::SubjectKeyIdentifier(ki); + Ok((rest, ret)) } - pub(super) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> { + fn parse_keyusage(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content @@ -1323,15 +819,11 @@ pub(crate) mod parser { .data .iter() .rev() - .fold(0, |acc, x| acc << 8 | (x.reverse_bits() as u16)); - Ok((rest, KeyUsage { flags })) - } - - fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { - map(parse_keyusage, ParsedExtension::KeyUsage)(i) + .fold(0, |acc, x| acc << 8 | (reverse_bits(*x) as u16)); + Ok((rest, ParsedExtension::KeyUsage(KeyUsage { flags }))) } - pub(super) fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], NSCertType, BerError> { + fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content @@ -1341,12 +833,8 @@ pub(crate) mod parser { 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) + let flags = reverse_bits(bitstring.data[0]); + Ok((rest, ParsedExtension::NSCertType(NSCertType(flags)))) } // CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation @@ -1366,43 +854,27 @@ pub(crate) mod parser { // -- 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> { + fn parse_certificatepolicies(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { + fn parse_policy_information(i: &[u8]) -> IResult<&[u8], (Oid, &[u8]), BerError> { parse_der_sequence_defined_g(|content, _| { - let (rem, policy_qualifier_id) = + let (qualifier_set, oid) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?; - let info = PolicyQualifierInfo { - policy_qualifier_id, - qualifier: rem, - }; - Ok((&[], info)) + Ok((&[], (oid, qualifier_set))) })(i) } - fn parse_policy_information(i: &[u8]) -> IResult<&[u8], PolicyInformation, BerError> { - parse_der_sequence_defined_g(|content, _| { - let (rem, policy_id) = - map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(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) + let (ret, mut policy_list) = parse_der_sequence_of_v(parse_policy_information)(i)?; + // create the policy hashmap + let mut policies = HashMap::new(); + for (oid, qualifier_set) in policy_list.drain(..) { + if policies.insert(oid, qualifier_set).is_some() { + // duplicate policies are not allowed + return Err(Err::Failure(BerError::InvalidTag)); + } } - 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) + Ok(( + ret, + ParsedExtension::CertificatePolicies(CertificatePolicies { policies }), + )) } // CRLReason ::= ENUMERATED { ... @@ -1440,35 +912,26 @@ pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result<Vec<X509Extension ) } -pub(crate) fn parse_extensions(i: &[u8], explicit_tag: DerTag) -> 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) +pub(crate) fn extensions_sequence_to_map<'a>( + i: &'a [u8], + v: Vec<X509Extension<'a>>, +) -> X509Result<'a, HashMap<Oid<'a>, X509Extension<'a>>> { + let mut extensions = HashMap::new(); + for ext in v.into_iter() { + if extensions.insert(ext.oid.clone(), ext).is_some() { + // duplicate extensions are not allowed + return Err(Err::Failure(X509Error::DuplicateExtensions)); } - Err(_) => Err(X509Error::InvalidExtensions.into()), } + Ok((i, extensions)) } -/// 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( +pub(crate) fn parse_extensions( i: &[u8], explicit_tag: DerTag, -) -> X509Result<Vec<X509Extension>> { +) -> X509Result<HashMap<Oid, X509Extension>> { if i.is_empty() { - return Ok((i, Vec::new())); + return Ok((i, HashMap::new())); } match der_read_element_header(i) { @@ -1476,16 +939,16 @@ pub(crate) fn parse_extensions_envelope( if hdr.tag != explicit_tag { return Err(Err::Error(X509Error::InvalidExtensions)); } - all_consuming(parse_extension_envelope_sequence)(rem) + let (rem, list) = exact!(rem, parse_extension_sequence)?; + extensions_sequence_to_map(rem, list) } 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)?; + // parse_der_optional!(i, parse_der_bool) + let (rem, obj) = opt(parse_der_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 @@ -1545,7 +1008,7 @@ mod tests { assert!(eku.code_signing); assert!(!eku.email_protection); assert!(eku.time_stamping); - assert!(!eku.ocsp_signing); + assert!(!eku.ocscp_signing); assert_eq!(eku.other, vec![oid!(1.2.3 .4 .0 .42)]); } assert_eq!( @@ -1617,135 +1080,12 @@ mod tests { } ); { - let pm = tbs.policy_mappings().unwrap().1.clone().into_hashmap(); + let pm = tbs.policy_mappings().unwrap().1; 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") - } - } - } + assert_eq!(pm.mappings, pm_ref); } } @@ -23,17 +23,12 @@ //! [`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`](traits/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). +//! [`parse_x509_certificate`](fn.parse_x509_certificate.html), which builds a +//! [`X509Certificate`](x509/struct.X509Certificate.html) object. //! //! 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 +//! example [X509Certificate::issuer()](x509/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. @@ -48,12 +43,12 @@ //! static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); //! //! # fn main() { -//! let res = X509Certificate::from_der(IGCA_DER); +//! let res = parse_x509_certificate(IGCA_DER); //! match res { //! Ok((rem, cert)) => { //! assert!(rem.is_empty()); //! // -//! assert_eq!(cert.version(), X509Version::V3); +//! assert_eq!(cert.tbs_certificate.version, X509Version::V3); //! }, //! _ => panic!("x509 parsing failed: {:?}", res), //! } @@ -63,12 +58,12 @@ //! To parse a CRL and print information about revoked certificates: //! //! ```rust -//! # use x509_parser::prelude::*; +//! # use x509_parser::parse_x509_crl; //! # //! # static DER: &[u8] = include_bytes!("../assets/example.crl"); //! # //! # fn main() { -//! let res = CertificateRevocationList::from_der(DER); +//! let res = parse_x509_crl(DER); //! match res { //! Ok((_rem, crl)) => { //! for revoked in crl.iter_revoked_certificates() { @@ -87,7 +82,7 @@ //! //! - 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) +//! [X509Certificate::verify_signature()](x509/struct.X509Certificate.html#method.verify_signature) //! to `X509Certificate`. //! //! ```rust @@ -96,17 +91,13 @@ //! /// 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(); +//! let issuer_public_key = &issuer.tbs_certificate.subject_pki; //! 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.45 or greater**, based on nom 6 @@ -141,10 +132,6 @@ pub mod pem; pub mod prelude; pub mod revocation_list; pub mod time; -pub mod traits; -#[cfg(feature = "validate")] -#[cfg_attr(docsrs, doc(cfg(feature = "validate")))] -pub mod validate; pub mod x509; // reexports @@ -156,13 +143,12 @@ pub use oid_registry; use certificate::X509Certificate; use error::X509Result; use revocation_list::CertificateRevocationList; -use traits::FromDer; /// 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 +/// This function is an alias to [X509Certificate::from_der](x509/struct.X509Certificate.html#method.from_der). See this function /// for more information. /// /// For PEM-encoded certificates, use the [`pem`](pem/index.html) module. @@ -174,7 +160,7 @@ pub fn parse_x509_certificate<'a>(i: &'a [u8]) -> X509Result<X509Certificate<'a> /// 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 +/// This function is an alias to [CertificateRevocationList::from_der](x509/struct.CertificateRevocationList.html#method.from_der). See this function /// for more information. #[inline] pub fn parse_x509_crl(i: &[u8]) -> X509Result<CertificateRevocationList> { diff --git a/src/objects.rs b/src/objects.rs index 9bcbe72..7729c30 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -10,11 +10,11 @@ //! To get the short name for a given OID: //! //! ```rust -//! use x509_parser::objects::*; +//! use x509_parser::objects::oid2sn; //! use x509_parser::oid_registry::*; //! //! let oid = &OID_X509_COMMON_NAME; -//! let sn = oid2sn(oid, oid_registry()); +//! let sn = oid2sn(oid); //! assert_eq!(sn, Ok("commonName")); //! ``` @@ -45,26 +45,19 @@ lazy_static! { } /// 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> { +pub fn oid2abbrev<'a>(oid: &'a Oid) -> 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) + OID_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 +pub fn oid2sn<'a>(oid: &'a Oid) -> Result<&'a str, NidError> { + OID_REGISTRY.get(oid).map(|ref o| o.sn()).ok_or(NidError) } #[cfg(test)] @@ -64,7 +64,7 @@ use nom::{Err, IResult}; use std::io::{BufRead, Cursor, Seek, SeekFrom}; /// Representation of PEM data -#[derive(Clone, PartialEq, Debug)] +#[derive(PartialEq, Debug)] pub struct Pem { /// The PEM label pub label: String, diff --git a/src/prelude.rs b/src/prelude.rs index 5e6d780..45be9ca 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,8 +9,5 @@ pub use crate::objects::*; pub use crate::pem::*; pub use crate::revocation_list::*; pub use crate::time::*; -#[cfg(feature = "validate")] -pub use crate::validate::*; pub use crate::x509::*; pub use crate::*; -pub use traits::*; diff --git a/src/revocation_list.rs b/src/revocation_list.rs index 6cf9d55..1c9a0c3 100644 --- a/src/revocation_list.rs +++ b/src/revocation_list.rs @@ -1,7 +1,6 @@ use crate::error::{X509Error, X509Result}; use crate::extensions::*; use crate::time::ASN1Time; -use crate::traits::FromDer; use crate::x509::{ parse_serial, parse_signature_value, AlgorithmIdentifier, ReasonCode, X509Name, X509Version, }; @@ -19,31 +18,7 @@ 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::revocation_list::CertificateRevocationList; -/// use x509_parser::traits::FromDer; -/// -/// # 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)] +#[derive(Debug)] pub struct CertificateRevocationList<'a> { pub tbs_cert_list: TbsCertList<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, @@ -51,6 +26,54 @@ pub struct CertificateRevocationList<'a> { } impl<'a> CertificateRevocationList<'a> { + /// Parse a DER-encoded X.509 v2 CRL, 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. + /// + /// <pre> + /// CertificateList ::= SEQUENCE { + /// tbsCertList TBSCertList, + /// signatureAlgorithm AlgorithmIdentifier, + /// signatureValue BIT STRING } + /// </pre> + /// + /// # Example + /// + /// To parse a CRL and print information about revoked certificates: + /// + /// ```rust + /// # use x509_parser::parse_x509_crl; + /// # + /// # static DER: &'static [u8] = include_bytes!("../assets/example.crl"); + /// # + /// # fn main() { + /// let res = parse_x509_crl(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), + /// } + /// # } + /// ``` + pub 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) + } + /// Get the version of the encoded certificate pub fn version(&self) -> Option<X509Version> { self.tbs_cert_list.version @@ -79,9 +102,9 @@ impl<'a> CertificateRevocationList<'a> { self.tbs_cert_list.revoked_certificates.iter() } - /// Get the CRL extensions. + /// Get the certificate extensions. #[inline] - pub fn extensions(&self) -> &[X509Extension] { + pub fn extensions(&self) -> &HashMap<Oid, X509Extension> { &self.tbs_cert_list.extensions } @@ -94,35 +117,11 @@ impl<'a> CertificateRevocationList<'a> { /// 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_BASIC_CONSTRAINTS) - .and_then(|ext| match ext.parsed_extension { - ParsedExtension::CRLNumber(ref num) => Some(num), - _ => None, - }) - } -} - -/// <pre> -/// CertificateList ::= SEQUENCE { -/// tbsCertList TBSCertList, -/// signatureAlgorithm AlgorithmIdentifier, -/// signatureValue BIT STRING } -/// </pre> -impl<'a> FromDer<'a> 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) + let ext = self.extensions().get(&OID_X509_EXT_CRL_NUMBER)?; + match ext.parsed_extension { + ParsedExtension::CRLNumber(ref num) => Some(num), + _ => None, + } } } @@ -149,7 +148,7 @@ impl<'a> FromDer<'a> for CertificateRevocationList<'a> { /// -- if present, version MUST be v2 /// } /// </pre> -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct TbsCertList<'a> { pub version: Option<X509Version>, pub signature: AlgorithmIdentifier<'a>, @@ -157,53 +156,11 @@ pub struct TbsCertList<'a> { pub this_update: ASN1Time, pub next_update: Option<ASN1Time>, pub revoked_certificates: Vec<RevokedCertificate<'a>>, - extensions: Vec<X509Extension<'a>>, + pub extensions: HashMap<Oid<'a>, 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> for TbsCertList<'a> { fn from_der(i: &'a [u8]) -> X509Result<Self> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { @@ -231,57 +188,53 @@ impl<'a> FromDer<'a> for TbsCertList<'a> { } } -#[derive(Clone, Debug, PartialEq)] +impl<'a> AsRef<[u8]> for TbsCertList<'a> { + fn as_ref(&self) -> &[u8] { + &self.raw + } +} + +#[derive(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 extensions: HashMap<Oid<'a>, X509Extension<'a>>, pub(crate) raw_serial: &'a [u8], } impl<'a> RevokedCertificate<'a> { + // revokedCertificates SEQUENCE OF SEQUENCE { + // userCertificate CertificateSerialNumber, + // revocationDate Time, + // crlEntryExtensions Extensions OPTIONAL + // -- if present, MUST be v2 + // } OPTIONAL, + pub(crate) 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(|i| { + let (rem, v) = parse_extension_sequence(i)?; + extensions_sequence_to_map(rem, v) + }))(i)?; + let revoked = RevokedCertificate { + user_certificate, + revocation_date, + extensions: extensions.unwrap_or_default(), + raw_serial, + }; + Ok((i, revoked)) + })(i) + } + /// 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 @@ -301,11 +254,11 @@ impl<'a> RevokedCertificate<'a> { /// 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, - }) + let ext = self.extensions.get(&OID_X509_EXT_REASON_CODE)?; + match ext.parsed_extension { + ParsedExtension::ReasonCode(code) => Some((ext.critical, code)), + _ => None, + } } /// Get the invalidity date, if present @@ -313,34 +266,17 @@ impl<'a> RevokedCertificate<'a> { /// 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, - }) + let ext = self.extensions.get(&OID_X509_EXT_INVALIDITY_DATE)?; + 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> 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) + /// Get the certificate extensions. + #[inline] + pub fn extensions(&self) -> &HashMap<Oid, X509Extension> { + &self.extensions } } diff --git a/src/time.rs b/src/time.rs index ca37217..1e72b87 100644 --- a/src/time.rs +++ b/src/time.rs @@ -12,13 +12,16 @@ use nom::bytes::complete::take; use nom::combinator::{complete, map_res, opt}; use crate::error::{X509Error, X509Result}; -use crate::traits::FromDer; /// An ASN.1 timestamp. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ASN1Time(DateTime<Utc>); impl ASN1Time { + pub(crate) fn from_der(i: &[u8]) -> X509Result<Self> { + map_res(parse_choice_of_time, der_to_utctime)(i).map_err(|_| X509Error::InvalidDate.into()) + } + pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<Option<Self>> { opt(map_res(parse_choice_of_time, der_to_utctime))(i) .map_err(|_| X509Error::InvalidDate.into()) @@ -48,17 +51,11 @@ impl ASN1Time { /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. #[inline] - pub fn to_rfc2822(self) -> String { + pub fn to_rfc2822(&self) -> String { self.0.to_rfc2822() } } -impl<'a> FromDer<'a> for ASN1Time { - fn from_der(i: &[u8]) -> X509Result<Self> { - map_res(parse_choice_of_time, der_to_utctime)(i).map_err(|_| X509Error::InvalidDate.into()) - } -} - fn parse_choice_of_time(i: &[u8]) -> DerResult { alt(( complete(parse_der_utctime), diff --git a/src/traits.rs b/src/traits.rs deleted file mode 100644 index bd4d070..0000000 --- a/src/traits.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::error::X509Result; - -/// Parse a DER-encoded object, 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 (see the [`Validate`](crate::validate::Validate) trait). -/// -/// # Example -/// -/// To parse a certificate and print the subject and issuer: -/// -/// ```rust -/// # use x509_parser::prelude::*; -/// # -/// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); -/// # -/// # fn main() { -/// let res = X509Certificate::from_der(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), -/// } -/// # } -/// ``` - -pub trait FromDer<'a>: Sized { - /// Attempt to parse input bytes into a DER object - fn from_der(bytes: &'a [u8]) -> X509Result<'a, Self>; -} diff --git a/src/validate.rs b/src/validate.rs deleted file mode 100644 index 6a495af..0000000 --- a/src/validate.rs +++ /dev/null @@ -1,126 +0,0 @@ -/// Trait for validating item (for ex. validate X.509 structure) -/// -/// # Examples -/// -/// Using callbacks: -/// -/// ``` -/// use x509_parser::certificate::X509Certificate; -/// use x509_parser::validate::Validate; -/// -/// #[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; -/// use x509_parser::validate::Validate; -/// -/// #[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(()) -/// } -/// ``` -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) - } -} - -#[cfg(test)] -mod tests { - use super::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 - } - } - - #[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()); - } -} diff --git a/src/x509.rs b/src/x509.rs index ecee084..9590fe6 100644 --- a/src/x509.rs +++ b/src/x509.rs @@ -5,9 +5,8 @@ use crate::error::{X509Error, X509Result}; use crate::objects::*; -use crate::traits::FromDer; -use der_parser::ber::{parse_ber_integer, BitStringObject, MAX_OBJECT_SIZE}; +use der_parser::ber::{BitStringObject, MAX_OBJECT_SIZE}; use der_parser::der::*; use der_parser::error::*; use der_parser::num_bigint::BigUint; @@ -20,44 +19,32 @@ use nom::multi::{many0, many1}; use nom::{Err, Offset}; use oid_registry::*; use rusticata_macros::newtype_enum; -use std::convert::TryFrom; 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 { - pub(crate) fn from_der_required(i: &[u8]) -> X509Result<X509Version> { + // Parse [0] EXPLICIT Version DEFAULT v1 + pub(crate) fn from_der(i: &[u8]) -> X509Result<X509Version> { let (rem, hdr) = der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidVersion)))?; match hdr.tag.0 { 0 => { map(parse_der_u32, X509Version)(rem).or(Err(Err::Error(X509Error::InvalidVersion))) } - _ => Ok((&rem[1..], X509Version::V1)), + _ => Ok((i, X509Version::V1)), } } -} -// Parse [0] EXPLICIT Version DEFAULT v1 -impl<'a> FromDer<'a> for X509Version { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { + pub(crate) fn from_der_required(i: &[u8]) -> X509Result<X509Version> { let (rem, hdr) = der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidVersion)))?; match hdr.tag.0 { 0 => { map(parse_der_u32, X509Version)(rem).or(Err(Err::Error(X509Error::InvalidVersion))) } - _ => Ok((i, X509Version::V1)), + _ => Ok((&rem[1..], X509Version::V1)), } } } @@ -70,45 +57,34 @@ newtype_enum! { } } -/// A generic attribute type and value -/// -/// These objects are used as [`RelativeDistinguishedName`] components. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct AttributeTypeAndValue<'a> { - attr_type: Oid<'a>, - attr_value: DerObject<'a>, // ANY -- DEFINED BY AttributeType + pub attr_type: Oid<'a>, + pub attr_value: DerObject<'a>, // ANY -- DEFINED BY AttributeType } impl<'a> AttributeTypeAndValue<'a> { - /// Builds a new `AttributeTypeAndValue` - #[inline] - pub const fn new(attr_type: Oid<'a>, attr_value: DerObject<'a>) -> Self { - AttributeTypeAndValue { - attr_type, - attr_value, - } - } - - /// Returns the attribute type - #[inline] - pub const fn attr_type(&self) -> &Oid { - &self.attr_type - } - - /// Returns the attribute value, as raw `DerObject` - #[inline] - pub const fn attr_value(&self) -> &DerObject { - &self.attr_value + // AttributeTypeAndValue ::= SEQUENCE { + // type AttributeType, + // value AttributeValue } + fn from_der(i: &'a [u8]) -> X509Result<Self> { + parse_der_sequence_defined_g(|i, _| { + let (i, attr_type) = map_res(parse_der_oid, |x: DerObject<'a>| x.as_oid_val())(i) + .or(Err(X509Error::InvalidX509Name))?; + let (i, attr_value) = parse_attribute_value(i).or(Err(X509Error::InvalidX509Name))?; + let attr = AttributeTypeAndValue { + attr_type, + attr_value, + }; + Ok((i, attr)) + })(i) } /// Attempt to get the content as `str`. /// This can fail if the object does not contain a string type. /// - /// Note: the [`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> { self.attr_value.as_str().map_err(|e| e.into()) } @@ -116,45 +92,11 @@ impl<'a> AttributeTypeAndValue<'a> { /// Attempt to get the content as a slice. /// This can fail if the object does not contain a type directly equivalent to a slice (e.g a /// sequence). - /// - /// Note: the [`TryFrom`] trait is implemented for `&[u8]`, so this is equivalent to `attr.try_into()`. - #[inline] pub fn as_slice(&self) -> Result<&'a [u8], X509Error> { self.attr_value.as_slice().map_err(|e| e.into()) } } -impl<'a> TryFrom<AttributeTypeAndValue<'a>> for &'a str { - type Error = X509Error; - - fn try_from(value: AttributeTypeAndValue<'a>) -> Result<Self, Self::Error> { - value.attr_value.as_str().map_err(|e| e.into()) - } -} - -impl<'a> TryFrom<AttributeTypeAndValue<'a>> for &'a [u8] { - type Error = X509Error; - - fn try_from(value: AttributeTypeAndValue<'a>) -> Result<Self, Self::Error> { - value.attr_value.as_slice().map_err(|e| e.into()) - } -} - -// AttributeTypeAndValue ::= SEQUENCE { -// type AttributeType, -// value AttributeValue } -impl<'a> FromDer<'a> for AttributeTypeAndValue<'a> { - fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { - parse_der_sequence_defined_g(|i, _| { - let (i, attr_type) = map_res(parse_der_oid, |x: DerObject<'a>| x.as_oid_val())(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]) -> DerResult { @@ -182,35 +124,12 @@ fn parse_malformed_string(i: &[u8]) -> DerResult { } } -/// A Relative Distinguished Name element. -/// -/// These objects are used as [`X509Name`] components. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct RelativeDistinguishedName<'a> { - set: Vec<AttributeTypeAndValue<'a>>, + pub 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> 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)?; @@ -220,15 +139,15 @@ impl<'a> FromDer<'a> for RelativeDistinguishedName<'a> { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, PartialEq)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, pub subject_public_key: BitStringObject<'a>, } -impl<'a> FromDer<'a> for SubjectPublicKeyInfo<'a> { +impl<'a> SubjectPublicKeyInfo<'a> { /// Parse the SubjectPublicKeyInfo struct portion of a DER-encoded X.509 Certificate - fn from_der(i: &'a [u8]) -> X509Result<Self> { + pub fn from_der(i: &'a [u8]) -> X509Result<Self> { parse_der_sequence_defined_g(|i, _| { let (i, algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, subject_public_key) = map_res(parse_der_bitstring, |x: DerObject<'a>| { @@ -247,29 +166,31 @@ impl<'a> FromDer<'a> for SubjectPublicKeyInfo<'a> { } } -/// 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)] +#[derive(Debug, PartialEq)] pub struct AlgorithmIdentifier<'a> { pub algorithm: Oid<'a>, pub parameters: Option<DerObject<'a>>, } -impl<'a> FromDer<'a> for AlgorithmIdentifier<'a> { +impl<'a> AlgorithmIdentifier<'a> { + /// Parse an 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. + // lifetime is *not* useless, it is required to tell the compiler the content of the temporary + // DerObject has the same lifetime as the input #[allow(clippy::needless_lifetimes)] - fn from_der(i: &[u8]) -> X509Result<AlgorithmIdentifier> { + pub fn from_der(i: &[u8]) -> X509Result<AlgorithmIdentifier> { parse_der_sequence_defined_g(|i, _| { let (i, algorithm) = map_res(parse_der_oid, |x| x.as_oid_val())(i) .or(Err(X509Error::InvalidAlgorithmIdentifier))?; @@ -285,21 +206,15 @@ impl<'a> FromDer<'a> for AlgorithmIdentifier<'a> { } } -/// 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)] +#[derive(Debug, PartialEq)] pub struct X509Name<'a> { - pub(crate) rdn_seq: Vec<RelativeDistinguishedName<'a>>, + pub 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()) { + match x509name_to_string(&self.rdn_seq) { Ok(o) => write!(f, "{}", o), Err(_) => write!(f, "<X509Error: Invalid X.509 name>"), } @@ -307,18 +222,18 @@ impl<'a> fmt::Display for X509Name<'a> { } 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) + /// Parse the X.501 type Name, used for ex in issuer and subject of a X.509 certificate + pub 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) } // Not using the AsRef trait, as that would not give back the full 'a lifetime @@ -327,11 +242,6 @@ impl<'a> X509Name<'a> { } /// 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() } @@ -383,70 +293,41 @@ impl<'a> X509Name<'a> { /// 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>> { + pub fn iter_common_name(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_country(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_organization(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_organizational_unit(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_state_or_province(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_locality(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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>> { + pub fn iter_email(&self) -> impl Iterator<Item = &AttributeTypeAndValue> { 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> 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); @@ -495,10 +376,7 @@ fn attribute_value_to_string(attr: &DerObject, _attr_type: &Oid) -> Result<Strin /// 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> { +fn x509name_to_string(rdn_seq: &[RelativeDistinguishedName]) -> Result<String, X509Error> { rdn_seq.iter().fold(Ok(String::new()), |acc, rdn| { acc.and_then(|mut _vec| { rdn.set @@ -507,7 +385,7 @@ fn x509name_to_string( 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) { + let abbrev = match oid2abbrev(&attr.attr_type) { Ok(s) => String::from(s), _ => format!("{:?}", attr.attr_type), }; @@ -537,16 +415,12 @@ pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result<BitStringObject> { } pub(crate) fn parse_serial(i: &[u8]) -> X509Result<(&[u8], BigUint)> { - // This should be parse_der_integer, but some certificates encode leading zeroes - map_opt(parse_ber_integer, get_serial_info)(i).map_err(|_| X509Error::InvalidSerial.into()) + map_opt(parse_der_integer, get_serial_info)(i).map_err(|_| X509Error::InvalidSerial.into()) } fn get_serial_info(o: DerObject) -> Option<(&[u8], BigUint)> { - // 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()` + let big = o.as_biguint()?; let slice = o.as_slice().ok()?; - let big = BigUint::from_bytes_be(slice); Some((slice, big)) } diff --git a/tests/readcert.rs b/tests/readcert.rs index 6c5f4ed..064664b 100644 --- a/tests/readcert.rs +++ b/tests/readcert.rs @@ -1,7 +1,6 @@ use chrono::offset::{TimeZone, Utc}; use chrono::Datelike; -use der_parser::oid; -use nom::Parser; +use der_parser::{oid, oid::Oid}; use oid_registry::*; use std::collections::HashMap; use x509_parser::prelude::*; @@ -62,11 +61,10 @@ fn test_x509_parser() { assert_eq!(na.year(), 2020); assert_eq!(na.month(), 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![ + let policies = vec![(oid!(1.2.250 .1 .121 .1 .1 .1), [].as_ref())] + .into_iter() + .collect(); + let expected_extensions_list = vec![ X509Extension::new( oid!(2.5.29 .19), true, @@ -86,7 +84,7 @@ fn test_x509_parser() { oid!(2.5.29 .32), false, &[48, 12, 48, 10, 6, 8, 42, 129, 122, 1, 121, 1, 1, 1], - ParsedExtension::CertificatePolicies(policies), + ParsedExtension::CertificatePolicies(CertificatePolicies { policies }), ), X509Extension::new( oid!(2.5.29 .14), @@ -117,7 +115,11 @@ fn test_x509_parser() { }), ), ]; - assert_eq!(tbs_cert.extensions(), &expected_extensions as &[_]); + let expected_extensions: HashMap<Oid, X509Extension> = expected_extensions_list + .into_iter() + .map(|e| (e.oid.clone(), e)) + .collect(); + assert_eq!(tbs_cert.extensions(), &expected_extensions); // assert!(tbs_cert.is_ca()); // @@ -137,7 +139,7 @@ fn test_x509_no_extensions() { let tbs_cert = cert.tbs_certificate; assert_eq!(tbs_cert.version, X509Version::V3); - assert_eq!(tbs_cert.extensions().len(), 0); + assert_eq!(tbs_cert.extensions.len(), 0); } _ => panic!("x509 parsing failed: {:?}", res), } @@ -205,13 +207,18 @@ fn test_crl_parse() { let revoked_cert_0 = &revoked_certs[0]; assert_eq!(*revoked_cert_0.serial(), 0x147947u32.into()); assert_eq!(revoked_cert_0.revocation_date, revocation_date); - let expected_extensions = vec![ + let mut extensions_map = HashMap::new(); + extensions_map.insert( + oid!(2.5.29 .21), X509Extension::new( oid!(2.5.29 .21), false, &[10, 1, 3], ParsedExtension::ReasonCode(ReasonCode::AffiliationChanged), ), + ); + extensions_map.insert( + oid!(2.5.29 .24), X509Extension::new( oid!(2.5.29 .24), false, @@ -220,8 +227,8 @@ fn test_crl_parse() { ], ParsedExtension::InvalidityDate(invalidity_date), ), - ]; - assert_eq!(revoked_cert_0.extensions(), &expected_extensions as &[_]); + ); + assert_eq!(revoked_cert_0.extensions, extensions_map); assert_eq!(revoked_certs.len(), 5); assert_eq!(revoked_certs[4].user_certificate, 1_341_771_u32.into()); @@ -250,7 +257,12 @@ fn test_crl_parse() { ParsedExtension::CRLNumber(3u32.into()), ), ]; - assert_eq!(tbs_cert_list.extensions(), &expected_extensions as &[_]); + + let expected_extensions: HashMap<Oid, X509Extension> = expected_extensions + .into_iter() + .map(|e| (e.oid.clone(), e)) + .collect(); + assert_eq!(tbs_cert_list.extensions, expected_extensions); assert_eq!(tbs_cert_list.as_ref(), &CRL_DER[4..(4 + 4 + 508)]); } @@ -289,7 +301,15 @@ fn test_crl_parse_empty() { }), ), ]; - assert_eq!(cert.extensions(), &expected_extensions as &[_]); + // do not compare iterators because of random order in hashmap + assert_eq!( + cert.tbs_cert_list.extensions.len(), + expected_extensions.len() + ); + let parsed_extension_values: Vec<_> = cert.tbs_cert_list.extensions.values().collect(); + for extension in &expected_extensions { + assert!(parsed_extension_values.contains(&extension)); + } assert_eq!( cert.tbs_cert_list.as_ref(), &EMPTY_CRL_DER[4..(4 + 3 + 200)] @@ -311,8 +331,8 @@ fn test_crl_parse_minimal() { let revoked_cert_0 = &revoked_certificates[0]; assert_eq!(*revoked_cert_0.serial(), 42u32.into()); assert_eq!(revoked_cert_0.revocation_date, revocation_date); - assert!(revoked_cert_0.extensions().is_empty()); - assert!(crl.tbs_cert_list.extensions().is_empty()); + assert_eq!(revoked_cert_0.extensions, HashMap::new()); + 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), @@ -325,32 +345,21 @@ fn test_duplicate_authority_info_access() { Ok((_, cert)) => { let extension = cert .tbs_certificate - .find_extension(&OID_PKIX_AUTHORITY_INFO_ACCESS) + .extensions + .get(&OID_PKIX_AUTHORITY_INFO_ACCESS) .unwrap(); 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"), + 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")]; + 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"); - } + let expected_aia = + ParsedExtension::AuthorityInfoAccess(AuthorityInfoAccess { accessdescs }); + assert_eq!(*extension.parsed_extension(), expected_aia); } 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/tests/readcsr.rs b/tests/readcsr.rs index 77dafec..5b0944c 100644 --- a/tests/readcsr.rs +++ b/tests/readcsr.rs @@ -12,7 +12,7 @@ fn read_csr_empty_attrib() { assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); - assert_eq!(cri.attributes().len(), 0); + assert_eq!(cri.attributes.len(), 0); assert_eq!(csr.signature_algorithm.algorithm, OID_PKCS1_SHA256WITHRSA); } @@ -25,13 +25,13 @@ fn read_csr_with_san() { assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); - assert_eq!(cri.attributes().len(), 1); + assert_eq!(cri.attributes.len(), 1); assert_eq!(csr.signature_algorithm.algorithm, OID_SIG_ECDSA_WITH_SHA256); - let mut rdns = cri.subject.iter(); + let mut rdns = cri.subject.rdn_seq.iter(); let rdn = rdns.next().unwrap(); - let first = rdn.iter().next().unwrap(); - assert_eq!(first.attr_type(), &OID_X509_COMMON_NAME); + let first = rdn.set.first().unwrap(); + assert_eq!(first.attr_type, OID_X509_COMMON_NAME); assert_eq!(first.as_str().unwrap(), "test.rusticata.fr"); let expected: &[u8] = &[ diff --git a/tests/verify.rs b/tests/verify.rs index 6cb3835..4cdd2e2 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -22,14 +22,3 @@ fn test_signature_verification() { 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()); -} |