aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Galenson <jgalenson@google.com>2021-08-11 15:11:47 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-08-11 15:11:47 +0000
commit5f154e9e5a5c7ef0c93f2749d9d493c61492fa31 (patch)
treed33b9b22a50b92ae03e6b95a75adf29be963f2cc
parent14d30916c9bb5e814dacb535fda32d33290e4654 (diff)
parent49e29dd4d5f104cb7d70437eea9d77ea02c78ca9 (diff)
downloadx509-parser-5f154e9e5a5c7ef0c93f2749d9d493c61492fa31.tar.gz
Merge "Upgrade rust/crates/x509-parser to 0.10.0" am: eba6f1a1a3 am: 72c1637339 am: 1c7a30a9a1 am: 49e29dd4d5
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/x509-parser/+/1791044 Change-Id: If2653c57e2826d2532aa05f780c50a4494c050cf
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp147
-rw-r--r--CHANGELOG.md48
-rw-r--r--Cargo.toml8
-rw-r--r--Cargo.toml.orig6
-rw-r--r--METADATA9
-rw-r--r--README.md25
-rw-r--r--assets/csr-empty-attributes.csrbin670 -> 0 bytes
-rw-r--r--assets/ed25519.derbin0 -> 268 bytes
-rw-r--r--assets/empty.crlbin293 -> 0 bytes
-rw-r--r--assets/example.crlbin792 -> 0 bytes
-rw-r--r--assets/gen_minimal_crl.py40
-rw-r--r--assets/minimal.crlbin359 -> 0 bytes
-rw-r--r--assets/test.csr8
-rw-r--r--cargo2android.json6
-rw-r--r--examples/print-cert.rs95
-rw-r--r--examples/print-crl.rs12
-rw-r--r--patches/data-encoding_dep.patch4
-rw-r--r--src/certificate.rs603
-rw-r--r--src/certification_request.rs148
-rw-r--r--src/cri_attributes.rs56
-rw-r--r--src/error.rs2
-rw-r--r--src/extensions.rs1128
-rw-r--r--src/lib.rs36
-rw-r--r--src/objects.rs25
-rw-r--r--src/pem.rs2
-rw-r--r--src/prelude.rs3
-rw-r--r--src/revocation_list.rs272
-rw-r--r--src/time.rs13
-rw-r--r--src/traits.rs36
-rw-r--r--src/validate.rs126
-rw-r--r--src/x509.rs272
-rw-r--r--tests/readcert.rs81
-rw-r--r--tests/readcsr.rs10
-rw-r--r--tests/verify.rs11
35 files changed, 2258 insertions, 976 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 50631bc..a19dcef 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
{
"git": {
- "sha1": "2685e2f7f2015774fb1d812f37d527b9c0f6bc36"
+ "sha1": "8c8e7a47c7728b3ff6e6bf91bcc54d6acd9eeca5"
}
}
diff --git a/Android.bp b/Android.bp
index 790a309..91bde0f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -56,151 +56,4 @@ 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 2ca97ef..dfcd92a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,54 @@
### 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
diff --git a/Cargo.toml b/Cargo.toml
index d81f889..36bb744 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@
[package]
edition = "2018"
name = "x509-parser"
-version = "0.9.2"
+version = "0.10.0"
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.0"
+version = "5.1"
features = ["bigint"]
[dependencies.lazy_static]
@@ -58,12 +58,10 @@ 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 30698c6..0833677 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "x509-parser"
-version = "0.9.2"
+version = "0.10.0"
description = "Parser for the X.509 v3 format (RFC 5280 certificates)"
license = "MIT/Apache-2.0"
keywords = ["X509","Certificate","parser","nom"]
@@ -32,6 +32,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = []
verify = ["ring"]
+validate = []
[dependencies]
base64 = "0.13"
@@ -42,6 +43,5 @@ nom = "6.0"
oid-registry = { version="0.1.1", features=["crypto", "x509"] }
rusticata-macros = "3.0"
ring = { version="0.16", optional=true }
-rustversion = "1.0"
-der-parser = { version = "5.0", features=["bigint"] }
+der-parser = { version = "5.1", features=["bigint"] }
thiserror = "1.0"
diff --git a/METADATA b/METADATA
index dd9ebef..4d6197c 100644
--- a/METADATA
+++ b/METADATA
@@ -7,14 +7,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/x509-parser/x509-parser-0.9.2.crate"
+ value: "https://static.crates.io/crates/x509-parser/x509-parser-0.10.0.crate"
}
- version: "0.9.2"
- # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ version: "0.10.0"
license_type: NOTICE
last_upgrade_date {
year: 2021
- month: 7
- day: 26
+ month: 8
+ day: 9
}
}
diff --git a/README.md b/README.md
index 3989131..b6ffbeb 100644
--- a/README.md
+++ b/README.md
@@ -25,12 +25,17 @@ 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
-[`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.
+[`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).
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/x509/struct.X509Certificate.html#method.issuer) returns the
+example [`X509Certificate::issuer()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.issuer) returns the
same as accessing `<object>.tbs_certificate.issuer`.
For PEM-encoded certificates, use the [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module.
@@ -44,12 +49,12 @@ use x509_parser::prelude::*;
static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der");
-let res = parse_x509_certificate(IGCA_DER);
+let res = X509Certificate::from_der(IGCA_DER);
match res {
Ok((rem, cert)) => {
assert!(rem.is_empty());
//
- assert_eq!(cert.tbs_certificate.version, X509Version::V3);
+ assert_eq!(cert.version(), X509Version::V3);
},
_ => panic!("x509 parsing failed: {:?}", res),
}
@@ -60,7 +65,7 @@ To parse a CRL and print information about revoked certificates:
```rust
#
#
-let res = parse_x509_crl(DER);
+let res = CertificateRevocationList::from_der(DER);
match res {
Ok((_rem, crl)) => {
for revoked in crl.iter_revoked_certificates() {
@@ -78,20 +83,24 @@ 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/x509/struct.X509Certificate.html#method.verify_signature)
+ [`X509Certificate::verify_signature()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.verify_signature)
to `X509Certificate`.
```rust
/// Cryptographic signature verification: returns true if certificate was signed by issuer
#[cfg(feature = "verify")]
pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool {
- let issuer_public_key = &issuer.tbs_certificate.subject_pki;
+ let issuer_public_key = issuer.public_key();
cert
.verify_signature(Some(issuer_public_key))
.is_ok()
}
```
+- The `validate` features add methods to run more validation functions on the certificate structure
+ and values using the [`Validate`](https://docs.rs/x509-parser/latest/x509_parser/validate/trait.Validate.html) trait.
+ It does not validate any cryptographic parameter (see `verify` above).
+
## Rust version requirements
`x509-parser` requires **Rustc version 1.45 or greater**, based on nom 6
diff --git a/assets/csr-empty-attributes.csr b/assets/csr-empty-attributes.csr
deleted file mode 100644
index bfb84c8..0000000
--- a/assets/csr-empty-attributes.csr
+++ /dev/null
Binary files differ
diff --git a/assets/ed25519.der b/assets/ed25519.der
new file mode 100644
index 0000000..cae76a1
--- /dev/null
+++ b/assets/ed25519.der
Binary files differ
diff --git a/assets/empty.crl b/assets/empty.crl
deleted file mode 100644
index fc3f6b3..0000000
--- a/assets/empty.crl
+++ /dev/null
Binary files differ
diff --git a/assets/example.crl b/assets/example.crl
deleted file mode 100644
index 3df0b6f..0000000
--- a/assets/example.crl
+++ /dev/null
Binary files differ
diff --git a/assets/gen_minimal_crl.py b/assets/gen_minimal_crl.py
deleted file mode 100644
index b3f9e65..0000000
--- a/assets/gen_minimal_crl.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""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
deleted file mode 100644
index 32cd969..0000000
--- a/assets/minimal.crl
+++ /dev/null
Binary files differ
diff --git a/assets/test.csr b/assets/test.csr
deleted file mode 100644
index 729df67..0000000
--- a/assets/test.csr
+++ /dev/null
@@ -1,8 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIBBjCBrQIBADAcMRowGAYDVQQDDBF0ZXN0LnJ1c3RpY2F0YS5mcjBZMBMGByqG
-SM49AgEGCCqGSM49AwEHA0IABMP1frFxwJLXiLU6UoqOPf31ucCm2NqR2yqpcHo6
-W7iWJe31OzYs0izP2qeUvdKfz2fpAbuGiRjwvN+H10dQQEGgLzAtBgkqhkiG9w0B
-CQ4xIDAeMBwGA1UdEQQVMBOCEXRlc3QucnVzdGljYXRhLmZyMAoGCCqGSM49BAMC
-A0gAMEUCIGqQHPHgpeyZa5YMLP2X5IwfmrvpIcg5fQ2xkXotGAa0AiEAydeBwr4r
-Iu7XDe015h8uz8xZs2QUEgRdr73lJXTX+Ck=
------END CERTIFICATE REQUEST-----
diff --git a/cargo2android.json b/cargo2android.json
index 64cedd9..5f001a6 100644
--- a/cargo2android.json
+++ b/cargo2android.json
@@ -3,10 +3,6 @@
"data_encoding"
],
"device": true,
- "run": true,
- "test-data": [
- "src/lib.rs=assets/certificate.pem"
- ],
- "tests": true
+ "run": true
}
diff --git a/examples/print-cert.rs b/examples/print-cert.rs
index 50a5c6d..73a9149 100644
--- a/examples/print-cert.rs
+++ b/examples/print-cert.rs
@@ -4,6 +4,12 @@ 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);
@@ -14,12 +20,26 @@ fn print_hex_dump(bytes: &[u8], max_len: usize) {
}
fn format_oid(oid: &Oid) -> String {
- match oid2sn(oid) {
+ match oid2sn(oid, oid_registry()) {
Ok(s) => s.to_owned(),
_ => format!("{}", oid),
}
}
+fn generalname_to_string(gn: &GeneralName) -> String {
+ match gn {
+ GeneralName::DNSName(name) => format!("DNSName:{}", name),
+ GeneralName::DirectoryName(n) => format!("DirName:{}", n),
+ GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{:?}", obj),
+ GeneralName::IPAddress(n) => format!("IPAddress:{:?}", n),
+ GeneralName::OtherName(oid, n) => format!("OtherName:{}, {:?}", oid, n),
+ GeneralName::RFC822Name(n) => format!("RFC822Name:{}", n),
+ GeneralName::RegisteredID(oid) => format!("RegisteredID:{}", oid),
+ GeneralName::URI(n) => format!("URI:{}", n),
+ GeneralName::X400Address(obj) => format!("X400Address:{:?}", obj),
+ }
+}
+
fn print_x509_extension(oid: &Oid, ext: &X509Extension) {
print!(" {}: ", format_oid(oid));
print!(" Critical={}", ext.critical);
@@ -29,6 +49,25 @@ 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);
}
@@ -75,7 +114,7 @@ fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) {
}
}
-fn print_x509_info(x509: &X509Certificate) {
+fn print_x509_info(x509: &X509Certificate) -> io::Result<()> {
println!(" Subject: {}", x509.subject());
println!(" Signature Algorithm:");
print_x509_digest_algorithm(&x509.signature_algorithm, 4);
@@ -86,28 +125,66 @@ fn print_x509_info(x509: &X509Certificate) {
println!(" NotAfter: {}", x509.validity().not_after.to_rfc2822());
println!(" is_valid: {}", x509.validity().is_valid());
println!(" Extensions:");
- for (oid, ext) in x509.extensions() {
- print_x509_extension(oid, ext);
+ for ext in x509.extensions() {
+ print_x509_extension(&ext.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 (data[0], data[1]) == (0x30, 0x82) {
+ if matches!((data[0], data[1]), (0x30, 0x81..=0x83)) {
// probably DER
- let (_, x509) = parse_x509_certificate(&data).expect("Could not decode DER data");
- print_x509_info(&x509);
+ handle_certificate(&file_name, &data)?;
} 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);
- print_x509_info(&x509);
+ handle_certificate(&file_name, data)?;
}
}
}
diff --git a/examples/print-crl.rs b/examples/print-crl.rs
index de6c0f3..a9af731 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) {
+ match oid2sn(oid, oid_registry()) {
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 (oid, ext) in revoked.extensions() {
- print_x509_extension(oid, ext, level + 4);
+ for ext in revoked.extensions() {
+ print_x509_extension(&ext.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 (oid, ext) in crl.extensions() {
- print_x509_extension(oid, ext, 4);
+ for ext in crl.extensions() {
+ print_x509_extension(&ext.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 f5d9396..f4d04c0 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::{BitStringObject, MAX_OBJECT_SIZE};
+ use der_parser::ber::{parse_ber_integer, 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 f29bc4e..1d937b1 100644
--- a/src/certificate.rs
+++ b/src/certificate.rs
@@ -3,6 +3,9 @@
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,
@@ -14,9 +17,11 @@ use der_parser::error::*;
use der_parser::num_bigint::BigUint;
use der_parser::oid::Oid;
use der_parser::*;
-use nom::Offset;
+use nom::{Offset, Parser};
use oid_registry::*;
use std::collections::HashMap;
+#[cfg(feature = "validate")]
+use std::collections::HashSet;
/// An X.509 v3 Certificate.
///
@@ -31,21 +36,21 @@ use std::collections::HashMap;
/// 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.tbs_certificate.subject;
-/// let issuer = &x509.tbs_certificate.issuer;
+/// let subject = x509.subject();
+/// let issuer = x509.issuer();
/// println!("X.509 Subject: {}", subject);
/// println!("X.509 Issuer: {}", issuer);
/// println!("X.509 serial: {}", x509.tbs_certificate.raw_serial_as_string());
/// }
/// #
/// # fn main() {
-/// # let res = parse_x509_certificate(DER);
+/// # let res = X509Certificate::from_der(DER);
/// # match res {
/// # Ok((_rem, x509)) => {
/// # display_x509_info(&x509);
@@ -54,7 +59,7 @@ use std::collections::HashMap;
/// # }
/// # }
/// ```
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct X509Certificate<'a> {
pub tbs_certificate: TbsCertificate<'a>,
pub signature_algorithm: AlgorithmIdentifier<'a>,
@@ -62,56 +67,6 @@ 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
@@ -135,10 +90,16 @@ 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) -> &HashMap<Oid, X509Extension> {
- self.tbs_certificate.extensions()
+ pub fn extensions(&self) -> &[X509Extension] {
+ &self.tbs_certificate.extensions
}
/// Verify the cryptographic signature of this certificate
@@ -150,12 +111,13 @@ 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(&self.tbs_certificate.subject_pki);
+ let spki = public_key.unwrap_or_else(|| self.public_key());
let signature_alg = &self.signature_algorithm.algorithm;
// identify verification algorithm
let verification_alg: &dyn signature::VerificationAlgorithm =
@@ -171,6 +133,8 @@ 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);
};
@@ -183,7 +147,141 @@ impl<'a> X509Certificate<'a> {
}
}
-/// The sequence TBSCertificate contains information associated with the
+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
/// subject of the certificate and the CA that issued it.
///
/// RFC5280 definition:
@@ -205,7 +303,7 @@ impl<'a> X509Certificate<'a> {
/// -- If present, version MUST be v3
/// }
/// </pre>
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct TbsCertificate<'a> {
pub version: X509Version,
pub serial: BigUint,
@@ -216,12 +314,143 @@ pub struct TbsCertificate<'a> {
pub subject_pki: SubjectPublicKeyInfo<'a>,
pub issuer_uid: Option<UniqueIdentifier<'a>>,
pub subject_uid: Option<UniqueIdentifier<'a>>,
- pub extensions: HashMap<Oid<'a>, X509Extension<'a>>,
+ extensions: Vec<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>
@@ -240,7 +469,7 @@ impl<'a> TbsCertificate<'a> {
/// extensions [3] Extensions OPTIONAL
/// -- If present, version MUST be v3 -- }
/// </pre>
- pub fn from_der(i: &'a [u8]) -> X509Result<TbsCertificate<'a>> {
+ 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)?;
@@ -272,126 +501,164 @@ impl<'a> TbsCertificate<'a> {
Ok((i, tbs))
})(i)
}
+}
- /// Get a reference to the map of extensions.
- pub fn extensions(&self) -> &HashMap<Oid, X509Extension> {
- &self.extensions
- }
+/// `TbsCertificate` parser builder
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct TbsCertificateParser {
+ deep_parse_extensions: bool,
+}
- 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,
+impl TbsCertificateParser {
+ #[inline]
+ pub const fn new() -> Self {
+ TbsCertificateParser {
+ deep_parse_extensions: true,
}
}
- 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,
+ #[inline]
+ pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self {
+ TbsCertificateParser {
+ deep_parse_extensions,
}
}
+}
- 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,
- }
- }
+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,
- 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,
- }
+ raw: &start_i[..len],
+ raw_serial: serial.0,
+ };
+ Ok((i, tbs))
+ })(input)
}
+}
- 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,
+#[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;
}
- }
-
- 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,
+ // extensions require v3
+ if !self.extensions().is_empty() && self.version != X509Version::V3 {
+ err("Extensions present but version is not 3");
+ 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,
+ 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 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,
+ // 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()
+ ));
+ }
+ }
+ _ => (),
+ }
}
- }
-
- /// 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> {
- fn as_ref(&self) -> &[u8] {
- &self.raw
+ // 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;
+ }
+ }
+ // 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));
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+ }
+ res
}
}
-#[derive(Debug, PartialEq)]
+#[derive(Clone, 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
@@ -410,7 +677,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
@@ -420,7 +687,21 @@ impl Validity {
}
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct UniqueIdentifier<'a>(pub BitStringObject<'a>);
impl<'a> UniqueIdentifier<'a> {
diff --git a/src/certification_request.rs b/src/certification_request.rs
index ec8a9f0..12d301f 100644
--- a/src/certification_request.rs
+++ b/src/certification_request.rs
@@ -1,8 +1,7 @@
use crate::cri_attributes::*;
-#[cfg(feature = "verify")]
-use crate::error::X509Error;
-use crate::error::X509Result;
+use crate::error::{X509Error, X509Result};
use crate::extensions::*;
+use crate::traits::FromDer;
use crate::x509::{
parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version,
};
@@ -16,6 +15,7 @@ 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,45 +24,12 @@ pub struct X509CertificationRequest<'a> {
}
impl<'a> X509CertificationRequest<'a> {
- /// 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>>> {
+ pub fn requested_extensions(&self) -> Option<impl Iterator<Item = &ParsedExtension>> {
self.certification_request_info
- .attributes
- .values()
+ .iter_attributes()
.find_map(|attr| {
if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute {
- Some(
- requested
- .extensions
- .values()
- .map(|ext| &ext.parsed_extension),
- )
+ Some(requested.extensions.iter().map(|ext| &ext.parsed_extension))
} else {
None
}
@@ -92,6 +59,8 @@ 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);
};
@@ -104,34 +73,101 @@ 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>,
- pub attributes: HashMap<Oid<'a>, X509CriAttribute<'a>>,
+ attributes: Vec<X509CriAttribute<'a>>,
pub raw: &'a [u8],
}
impl<'a> X509CertificationRequestInfo<'a> {
- /// Parse a certification request info structure
- ///
- /// Certification request information is defined by the following ASN.1 structure:
+ /// 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`.
///
- /// <pre>
- /// CertificationRequestInfo ::= SEQUENCE {
- /// version INTEGER { v1(0) } (v1,...),
- /// subject Name,
- /// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
- /// attributes [0] Attributes{{ CRIAttributes }}
- /// }
- /// </pre>
+ /// 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.
///
- /// 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> {
+ /// 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> {
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 410bf45..cfb7192 100644
--- a/src/cri_attributes.rs
+++ b/src/cri_attributes.rs
@@ -1,6 +1,7 @@
use crate::{
error::{X509Error, X509Result},
extensions::X509Extension,
+ traits::FromDer,
};
use der_parser::der::{
@@ -14,15 +15,15 @@ use oid_registry::*;
use std::collections::HashMap;
/// Attributes for Certification Request
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct X509CriAttribute<'a> {
pub oid: Oid<'a>,
pub value: &'a [u8],
pub(crate) parsed_attribute: ParsedCriAttribute<'a>,
}
-impl<'a> X509CriAttribute<'a> {
- pub fn from_der(i: &'a [u8]) -> X509Result<X509CriAttribute> {
+impl<'a> FromDer<'a> for X509CriAttribute<'a> {
+ 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;
@@ -44,13 +45,19 @@ impl<'a> X509CriAttribute<'a> {
}
/// Section 3.1 of rfc 5272
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct ExtensionRequest<'a> {
- pub extensions: HashMap<Oid<'a>, X509Extension<'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)
+ }
}
/// Attributes for Certification Request
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub enum ParsedCriAttribute<'a> {
ExtensionRequest(ExtensionRequest<'a>),
UnsupportedAttribute,
@@ -61,6 +68,7 @@ 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>;
@@ -74,7 +82,7 @@ pub(crate) mod parser {
}
let mut m = HashMap::new();
- add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request);
+ add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_ext);
m
};
}
@@ -92,39 +100,24 @@ pub(crate) mod parser {
}
}
- fn parse_extension_request(i: &[u8]) -> IResult<&[u8], ParsedCriAttribute, BerError> {
+ pub(super) fn parse_extension_request(i: &[u8]) -> IResult<&[u8], ExtensionRequest, BerError> {
crate::extensions::parse_extension_sequence(i)
- .and_then(|(i, extensions)| {
- crate::extensions::extensions_sequence_to_map(i, extensions)
- })
- .map(|(i, extensions)| {
- (
- i,
- ParsedCriAttribute::ExtensionRequest(ExtensionRequest { extensions }),
- )
- })
+ .map(|(i, extensions)| (i, ExtensionRequest { extensions }))
.map_err(|_| Err::Error(BerError::BerTypeError))
}
-}
-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));
- }
+ fn parse_extension_request_ext(i: &[u8]) -> IResult<&[u8], ParsedCriAttribute, BerError> {
+ map(
+ parse_extension_request,
+ ParsedCriAttribute::ExtensionRequest,
+ )(i)
}
- Ok((i, attributes))
}
-pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<HashMap<Oid, X509CriAttribute>> {
+pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<Vec<X509CriAttribute>> {
let (i, hdr) = der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidAttributes)))?;
if i.is_empty() {
- return Ok((i, HashMap::new()));
+ return Ok((i, Vec::new()));
}
(0..hdr.structured)
.into_iter()
@@ -133,5 +126,4 @@ pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<HashMap<Oid, X509CriA
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 b2da105..65b0503 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(Debug, PartialEq, thiserror::Error)]
+#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub enum X509Error {
#[error("generic error")]
Generic,
diff --git a/src/extensions.rs b/src/extensions.rs
index 9932169..6172d47 100644
--- a/src/extensions.rs
+++ b/src/extensions.rs
@@ -2,20 +2,72 @@
use crate::error::{X509Error, X509Result};
use crate::time::{der_to_utctime, ASN1Time};
-use crate::x509::{ReasonCode, X509Name};
+use crate::traits::FromDer;
+use crate::x509::{ReasonCode, RelativeDistinguishedName, X509Name};
+use der_parser::ber::parse_ber_bool;
use der_parser::der::*;
-use der_parser::error::BerResult;
+use der_parser::error::{BerError, BerResult};
use der_parser::num_bigint::BigUint;
use der_parser::oid::Oid;
-use nom::combinator::{all_consuming, complete, map_opt, map_res, opt};
+use nom::combinator::{all_consuming, complete, map, map_opt, map_res, opt};
use nom::multi::{many0, many1};
-use nom::{exact, Err};
+use nom::{Err, IResult, Parser};
use oid_registry::*;
use std::collections::HashMap;
use std::fmt;
-#[derive(Debug, PartialEq)]
+/// 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)]
pub struct X509Extension<'a> {
/// OID describing the extension content
pub oid: Oid<'a>,
@@ -29,78 +81,9 @@ pub struct X509Extension<'a> {
}
impl<'a> X509Extension<'a> {
- /// 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(
+ /// Creates a new extension with the provided values.
+ #[inline]
+ pub const fn new(
oid: Oid<'a>,
critical: bool,
value: &'a [u8],
@@ -115,16 +98,78 @@ 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
}
}
-#[derive(Debug, PartialEq)]
+/// <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)]
pub enum ParsedExtension<'a> {
/// Crate parser does not support this extension (yet)
- UnsupportedExtension,
- ParseError,
+ UnsupportedExtension {
+ oid: Oid<'a>,
+ },
+ ParseError {
+ error: Err<BerError>,
+ },
/// Section 4.2.1.1 of rfc 5280
AuthorityKeyIdentifier(AuthorityKeyIdentifier<'a>),
/// Section 4.2.1.2 of rfc 5280
@@ -145,6 +190,8 @@ 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
@@ -157,31 +204,81 @@ 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(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct AuthorityKeyIdentifier<'a> {
pub key_identifier: Option<KeyIdentifier<'a>>,
pub authority_cert_issuer: Option<Vec<GeneralName<'a>>>,
pub authority_cert_serial: Option<&'a [u8]>,
}
-#[derive(Debug, PartialEq)]
-pub struct CertificatePolicies<'a> {
- pub policies: HashMap<Oid<'a>, &'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],
}
/// Identifies whether the subject of the certificate is a CA, and the max validation depth.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct BasicConstraints {
pub ca: bool,
pub path_len_constraint: Option<u32>,
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct KeyIdentifier<'a>(pub &'a [u8]);
-#[derive(Debug, PartialEq)]
+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)]
pub struct KeyUsage {
pub flags: u16,
}
@@ -247,7 +344,13 @@ impl fmt::Display for KeyUsage {
}
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct ExtendedKeyUsage<'a> {
pub any: bool,
pub server_auth: bool,
@@ -255,11 +358,17 @@ pub struct ExtendedKeyUsage<'a> {
pub code_signing: bool,
pub email_protection: bool,
pub time_stamping: bool,
- pub ocscp_signing: bool,
+ pub ocsp_signing: bool,
pub other: Vec<Oid<'a>>,
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct NSCertType(u8);
// The value is a bit-string, where the individual bit positions are defined as:
@@ -323,33 +432,188 @@ impl fmt::Display for NSCertType {
}
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct AuthorityInfoAccess<'a> {
- pub accessdescs: HashMap<Oid<'a>, Vec<GeneralName<'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,
+ }
+ }
}
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct InhibitAnyPolicy {
pub skip_certs: u32,
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct PolicyMappings<'a> {
- pub mappings: HashMap<Oid<'a>, Vec<Oid<'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,
+ }
+ }
}
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct PolicyConstraints {
pub require_explicit_policy: Option<u32>,
pub inhibit_policy_mapping: Option<u32>,
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct SubjectAlternativeName<'a> {
pub general_names: Vec<GeneralName<'a>>,
}
-#[derive(Debug, PartialEq)]
+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)]
/// Represents a GeneralName as defined in RFC5280. There
/// is no support X.400 addresses and EDIPartyName.
///
@@ -360,11 +624,13 @@ pub enum GeneralName<'a> {
RFC822Name(&'a str),
/// A hostname, format is not checked.
DNSName(&'a str),
- // X400Address,
+ /// X400Address,
+ X400Address(UnparsedObject<'a>),
/// RFC5280 defines several string types, we always try to parse as utf-8
/// which is more or less a superset of the string types.
DirectoryName(X509Name<'a>),
- // EDIPartyName { name_assigner: Option<&'a str>, party_name: &'a str },
+ /// EDIPartyName
+ EDIPartyName(UnparsedObject<'a>),
/// An uniform resource identifier. The format is not checked.
URI(&'a str),
/// An ip address, provided as encoded.
@@ -372,13 +638,31 @@ pub enum GeneralName<'a> {
RegisteredID(Oid<'a>),
}
-#[derive(Debug, PartialEq)]
+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)]
pub struct NameConstraints<'a> {
pub permitted_subtrees: Option<Vec<GeneralSubtree<'a>>>,
pub excluded_subtrees: Option<Vec<GeneralSubtree<'a>>>,
}
-#[derive(Debug, PartialEq)]
+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)]
/// 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> {
@@ -387,11 +671,94 @@ 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};
@@ -406,35 +773,60 @@ pub(crate) mod parser {
}
let mut m = HashMap::new();
- 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_KEY_IDENTIFIER,
+ parse_keyidentifier_ext
+ );
+ add!(m, OID_X509_EXT_KEY_USAGE, parse_keyusage_ext);
add!(
m,
OID_X509_EXT_SUBJECT_ALT_NAME,
- parse_subjectalternativename
+ parse_subjectalternativename_ext
+ );
+ add!(
+ m,
+ OID_X509_EXT_BASIC_CONSTRAINTS,
+ parse_basicconstraints_ext
);
- add!(m, OID_X509_EXT_BASIC_CONSTRAINTS, parse_basicconstraints);
- add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints);
+ add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints_ext);
add!(
m,
OID_X509_EXT_CERTIFICATE_POLICIES,
- parse_certificatepolicies
+ parse_certificatepolicies_ext
+ );
+ add!(m, OID_X509_EXT_POLICY_MAPPINGS, parse_policymappings_ext);
+ add!(
+ m,
+ OID_X509_EXT_POLICY_CONSTRAINTS,
+ parse_policyconstraints_ext
+ );
+ add!(
+ m,
+ OID_X509_EXT_EXTENDED_KEY_USAGE,
+ parse_extendedkeyusage_ext
+ );
+ add!(
+ m,
+ OID_X509_EXT_CRL_DISTRIBUTION_POINTS,
+ parse_crldistributionpoints_ext
);
- add!(m, OID_X509_EXT_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
+ parse_inhibitanypolicy_ext
+ );
+ add!(
+ m,
+ OID_PKIX_AUTHORITY_INFO_ACCESS,
+ parse_authorityinfoaccess_ext
);
- add!(m, OID_PKIX_AUTHORITY_INFO_ACCESS, parse_authorityinfoaccess);
add!(
m,
OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER,
- parse_authoritykeyidentifier
+ parse_authoritykeyidentifier_ext
);
- add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype);
+ add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype_ext);
add!(m, OID_X509_EXT_CRL_NUMBER, parse_crl_number);
add!(m, OID_X509_EXT_REASON_CODE, parse_reason_code);
add!(m, OID_X509_EXT_INVALIDITY_DATE, parse_invalidity_date);
@@ -450,10 +842,17 @@ pub(crate) mod parser {
oid: &Oid,
) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> {
if let Some(parser) = EXTENSION_PARSERS.get(oid) {
- let (_, ext) = parser(i)?;
- Ok((orig_i, ext))
+ match parser(i) {
+ Ok((_, ext)) => Ok((orig_i, ext)),
+ Err(error) => Ok((orig_i, ParsedExtension::ParseError { error })),
+ }
} else {
- Ok((orig_i, ParsedExtension::UnsupportedExtension))
+ Ok((
+ orig_i,
+ ParsedExtension::UnsupportedExtension {
+ oid: oid.to_owned(),
+ },
+ ))
}
}
@@ -462,11 +861,7 @@ pub(crate) mod parser {
i: &'a [u8],
oid: &Oid,
) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> {
- let r = parse_extension0(orig_i, i, oid);
- if let Err(nom::Err::Incomplete(_)) = r {
- return Ok((orig_i, ParsedExtension::UnsupportedExtension));
- }
- r
+ parse_extension0(orig_i, i, oid)
}
/// Parse a "Basic Constraints" extension
@@ -480,7 +875,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.
- fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], BasicConstraints, BerError> {
let (rem, obj) = parse_der_sequence(i)?;
if let Ok(seq) = obj.as_sequence() {
let (ca, path_len_constraint) = match seq.len() {
@@ -507,17 +902,23 @@ pub(crate) mod parser {
};
Ok((
rem,
- ParsedExtension::BasicConstraints(BasicConstraints {
+ BasicConstraints {
ca,
path_len_constraint,
- }),
+ },
))
} else {
Err(nom::Err::Error(BerError::InvalidLength))
}
}
- fn parse_nameconstraints<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> {
+ 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_subtree<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralSubtree, BerError> {
parse_der_sequence_defined_g(|input, _| {
map(parse_generalname, |base| GeneralSubtree { base })(input)
@@ -543,17 +944,23 @@ pub(crate) mod parser {
Ok((rem, named_constraints))
})(i)?;
- Ok((ret, ParsedExtension::NameConstraints(named_constraints)))
+ Ok((ret, named_constraints))
}
- fn parse_generalname<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralName, BerError> {
+ 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> {
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>> {
- der_read_element_content_as(i, DerTag::Ia5String, hdr.len, hdr.is_constructed(), 0)?
+ // 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)?
.1
.as_slice()
.and_then(|s| std::str::from_utf8(s).map_err(|_| BerError::BerValueError))
@@ -568,7 +975,12 @@ pub(crate) mod parser {
}
1 => GeneralName::RFC822Name(ia5str(rest, hdr)?),
2 => GeneralName::DNSName(ia5str(rest, hdr)?),
- 3 => return Err(Err::Failure(BerError::Unsupported)), // x400Address
+ 3 => {
+ // XXX Not yet implemented
+ let (_, data) = take(len)(rest)?;
+ let obj = UnparsedObject { header: hdr, data };
+ GeneralName::X400Address(obj)
+ }
4 => {
// directoryName, name
let (_, name) = all_consuming(X509Name::from_der)(&rest[..len])
@@ -576,7 +988,12 @@ pub(crate) mod parser {
?;
GeneralName::DirectoryName(name)
}
- 5 => return Err(Err::Failure(BerError::Unsupported)), // ediPartyName
+ 5 => {
+ // XXX Not yet implemented
+ let (_, data) = take(len)(rest)?;
+ let obj = UnparsedObject { header: hdr, data };
+ GeneralName::EDIPartyName(obj)
+ }
6 => GeneralName::URI(ia5str(rest, hdr)?),
7 => {
// IPAddress, OctetString
@@ -610,7 +1027,7 @@ pub(crate) mod parser {
Ok((&rest[len..], name))
}
- fn parse_subjectalternativename<'a>(
+ pub(super) fn parse_subjectalternativename_ext<'a>(
i: &'a [u8],
) -> IResult<&'a [u8], ParsedExtension, BerError> {
parse_der_sequence_defined_g(|input, _| {
@@ -622,7 +1039,7 @@ pub(crate) mod parser {
})(i)
}
- fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], PolicyConstraints, BerError> {
parse_der_sequence_defined_g(|input, _| {
let (i, require_explicit_policy) = opt(complete(map_res(
parse_der_tagged_implicit(0, parse_der_content(DerTag::Integer)),
@@ -636,50 +1053,54 @@ pub(crate) mod parser {
require_explicit_policy,
inhibit_policy_mapping,
};
- Ok((i, ParsedExtension::PolicyConstraints(policy_constraint)))
+ Ok((i, 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 }
- fn parse_policymappings(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, 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: HashMap<Oid, Vec<Oid>> = HashMap::new();
+ let mut mappings = Vec::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)?;
- 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()]);
+ // XXX this should go to Validate
+ // if left.bytes() == oid!(raw 2.5.29.32.0) || right.bytes() == oid!(raw 2.5.29.32.0) {
+ // // mapping to or from anyPolicy is not allowed
+ // return Err(Err::Failure(BerError::InvalidTag));
+ // }
+ mappings.push(PolicyMapping::new(left, right));
}
- Ok((
- ret,
- ParsedExtension::PolicyMappings(PolicyMappings { mappings }),
- ))
+ Ok((ret, PolicyMappings { mappings }))
}
- fn parse_inhibitanypolicy(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
- let (ret, skip_certs) = map_res(parse_der_integer, |x: DerObject| x.as_u32())(i)?;
+ fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ map(parse_policymappings, ParsedExtension::PolicyMappings)(i)
+ }
+
+ fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ let (ret, skip_certs) = parse_der_u32(i)?;
Ok((
ret,
ParsedExtension::InhibitAnyPolicy(InhibitAnyPolicy { skip_certs }),
))
}
- fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> {
let (ret, seq) = parse_der_sequence_of(parse_der_oid)(i)?;
let mut seen = std::collections::HashSet::new();
let mut eku = ExtendedKeyUsage {
@@ -689,7 +1110,7 @@ pub(crate) mod parser {
code_signing: false,
email_protection: false,
time_stamping: false,
- ocscp_signing: false,
+ ocsp_signing: false,
other: Vec::new(),
};
for oid in seq.as_sequence().map_err(nom::Err::Failure)?.iter() {
@@ -711,12 +1132,101 @@ 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.ocscp_signing = true;
+ eku.ocsp_signing = true;
} else {
eku.other.push(oid);
}
}
- Ok((ret, ParsedExtension::ExtendedKeyUsage(eku)))
+ 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)
}
// AuthorityInfoAccessSyntax ::=
@@ -725,30 +1235,27 @@ pub(crate) mod parser {
// AccessDescription ::= SEQUENCE {
// accessMethod OBJECT IDENTIFIER,
// accessLocation GeneralName }
- fn parse_authorityinfoaccess(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
- fn parse_aia(i: &[u8]) -> IResult<&[u8], (Oid, GeneralName), BerError> {
+ pub(super) fn parse_authorityinfoaccess(
+ i: &[u8],
+ ) -> IResult<&[u8], AuthorityInfoAccess, BerError> {
+ fn parse_aia(i: &[u8]) -> IResult<&[u8], AccessDescription, BerError> {
parse_der_sequence_defined_g(|content, _| {
// Read first element, an oid.
let (gn, oid) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?;
// Parse second element
let (rest, gn) = parse_generalname(gn)?;
- Ok((rest, (oid, gn)))
+ Ok((rest, AccessDescription::new(oid, gn)))
})(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 }),
- ))
+ let (ret, accessdescs) = parse_der_sequence_of_v(parse_aia)(i)?;
+ Ok((ret, AuthorityInfoAccess { accessdescs }))
+ }
+
+ fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ map(
+ parse_authorityinfoaccess,
+ ParsedExtension::AuthorityInfoAccess,
+ )(i)
}
fn parse_aki_content<'a>(
@@ -776,40 +1283,37 @@ pub(crate) mod parser {
}
// RFC 5280 section 4.2.1.1: Authority Key Identifier
- fn parse_authoritykeyidentifier(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_authoritykeyidentifier(
+ i: &[u8],
+ ) -> IResult<&[u8], AuthorityKeyIdentifier, BerError> {
let (rem, aki) = parse_der_sequence_defined_g(parse_aki_content)(i)?;
- Ok((rem, ParsedExtension::AuthorityKeyIdentifier(aki)))
+ Ok((rem, aki))
}
- #[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
- }
-
- #[rustversion::since(1.37)]
- #[inline]
- fn reverse_bits(n: u8) -> u8 {
- n.reverse_bits()
+ fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ map(
+ parse_authoritykeyidentifier,
+ ParsedExtension::AuthorityKeyIdentifier,
+ )(i)
}
- fn parse_keyidentifier<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> {
+ pub(super) fn parse_keyidentifier<'a>(
+ i: &'a [u8],
+ ) -> IResult<&'a [u8], KeyIdentifier, BerError> {
let (rest, obj) = parse_der_octetstring(i)?;
let id = obj
.content
.as_slice()
.or(Err(Err::Error(BerError::BerTypeError)))?;
let ki = KeyIdentifier(id);
- let ret = ParsedExtension::SubjectKeyIdentifier(ki);
- Ok((rest, ret))
+ Ok((rest, ki))
}
- fn parse_keyusage(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ fn parse_keyidentifier_ext<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> {
+ map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i)
+ }
+
+ pub(super) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> {
let (rest, obj) = parse_der_bitstring(i)?;
let bitstring = obj
.content
@@ -819,11 +1323,15 @@ pub(crate) mod parser {
.data
.iter()
.rev()
- .fold(0, |acc, x| acc << 8 | (reverse_bits(*x) as u16));
- Ok((rest, ParsedExtension::KeyUsage(KeyUsage { flags })))
+ .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)
}
- fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ pub(super) fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], NSCertType, BerError> {
let (rest, obj) = parse_der_bitstring(i)?;
let bitstring = obj
.content
@@ -833,8 +1341,12 @@ pub(crate) mod parser {
if bitstring.data.len() != 1 {
return Err(Err::Error(BerError::BerValueError));
}
- let flags = reverse_bits(bitstring.data[0]);
- Ok((rest, ParsedExtension::NSCertType(NSCertType(flags))))
+ 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)
}
// CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
@@ -854,27 +1366,43 @@ pub(crate) mod parser {
// -- augment the following definition for PolicyQualifierId
//
// PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
- fn parse_certificatepolicies(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
- fn parse_policy_information(i: &[u8]) -> IResult<&[u8], (Oid, &[u8]), BerError> {
+ pub(super) fn parse_certificatepolicies(
+ i: &[u8],
+ ) -> IResult<&[u8], Vec<PolicyInformation>, BerError> {
+ fn parse_policy_qualifier_info(i: &[u8]) -> IResult<&[u8], PolicyQualifierInfo, BerError> {
parse_der_sequence_defined_g(|content, _| {
- let (qualifier_set, oid) =
+ let (rem, policy_qualifier_id) =
map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?;
- Ok((&[], (oid, qualifier_set)))
+ let info = PolicyQualifierInfo {
+ policy_qualifier_id,
+ qualifier: rem,
+ };
+ Ok((&[], 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));
- }
+ 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)
}
- Ok((
- ret,
- ParsedExtension::CertificatePolicies(CertificatePolicies { policies }),
- ))
+ parse_der_sequence_of_v(parse_policy_information)(i)
+ }
+
+ fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
+ map(
+ parse_certificatepolicies,
+ ParsedExtension::CertificatePolicies,
+ )(i)
}
// CRLReason ::= ENUMERATED { ...
@@ -912,26 +1440,35 @@ pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result<Vec<X509Extension
)
}
-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));
+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)
}
+ Err(_) => Err(X509Error::InvalidExtensions.into()),
}
- Ok((i, extensions))
}
-pub(crate) fn parse_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(
i: &[u8],
explicit_tag: DerTag,
-) -> X509Result<HashMap<Oid, X509Extension>> {
+) -> X509Result<Vec<X509Extension>> {
if i.is_empty() {
- return Ok((i, HashMap::new()));
+ return Ok((i, Vec::new()));
}
match der_read_element_header(i) {
@@ -939,16 +1476,16 @@ pub(crate) fn parse_extensions(
if hdr.tag != explicit_tag {
return Err(Err::Error(X509Error::InvalidExtensions));
}
- let (rem, list) = exact!(rem, parse_extension_sequence)?;
- extensions_sequence_to_map(rem, list)
+ all_consuming(parse_extension_envelope_sequence)(rem)
}
Err(_) => Err(X509Error::InvalidExtensions.into()),
}
}
fn der_read_critical(i: &[u8]) -> BerResult<bool> {
- // parse_der_optional!(i, parse_der_bool)
- let (rem, obj) = opt(parse_der_bool)(i)?;
+ // Some certificates do not respect the DER BOOLEAN constraint (true must be encoded as 0xff)
+ // so we attempt to parse as BER
+ let (rem, obj) = opt(parse_ber_bool)(i)?;
let value = obj
.map(|o| o.as_bool().unwrap_or_default()) // unwrap cannot fail, we just read a bool
.unwrap_or(false) // default critical value
@@ -1008,7 +1545,7 @@ mod tests {
assert!(eku.code_signing);
assert!(!eku.email_protection);
assert!(eku.time_stamping);
- assert!(!eku.ocscp_signing);
+ assert!(!eku.ocsp_signing);
assert_eq!(eku.other, vec![oid!(1.2.3 .4 .0 .42)]);
}
assert_eq!(
@@ -1080,12 +1617,135 @@ mod tests {
}
);
{
- let pm = tbs.policy_mappings().unwrap().1;
+ let pm = tbs.policy_mappings().unwrap().1.clone().into_hashmap();
let mut pm_ref = HashMap::new();
pm_ref.insert(oid!(2.34.23), vec![oid!(2.2)]);
pm_ref.insert(oid!(1.1), vec![oid!(0.0.4)]);
pm_ref.insert(oid!(2.2), vec![oid!(2.2.1), oid!(2.2.3)]);
- assert_eq!(pm.mappings, pm_ref);
+ 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")
+ }
+ }
+ }
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 9cb4edb..7939c82 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,12 +23,17 @@
//! [`pem`](pem/index.html) module for more documentation.
//!
//! To decode a DER-encoded certificate, the main parsing method is
-//! [`parse_x509_certificate`](fn.parse_x509_certificate.html), which builds a
-//! [`X509Certificate`](x509/struct.X509Certificate.html) object.
+//! [`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).
//!
//! 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()](x509/struct.X509Certificate.html#method.issuer) returns the
+//! example [`X509Certificate::issuer()`](certificate/struct.X509Certificate.html#method.issuer) returns the
//! same as accessing `<object>.tbs_certificate.issuer`.
//!
//! For PEM-encoded certificates, use the [`pem`](pem/index.html) module.
@@ -43,12 +48,12 @@
//! static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der");
//!
//! # fn main() {
-//! let res = parse_x509_certificate(IGCA_DER);
+//! let res = X509Certificate::from_der(IGCA_DER);
//! match res {
//! Ok((rem, cert)) => {
//! assert!(rem.is_empty());
//! //
-//! assert_eq!(cert.tbs_certificate.version, X509Version::V3);
+//! assert_eq!(cert.version(), X509Version::V3);
//! },
//! _ => panic!("x509 parsing failed: {:?}", res),
//! }
@@ -58,12 +63,12 @@
//! To parse a CRL and print information about revoked certificates:
//!
//! ```rust
-//! # use x509_parser::parse_x509_crl;
+//! # use x509_parser::prelude::*;
//! #
//! # static DER: &[u8] = include_bytes!("../assets/example.crl");
//! #
//! # fn main() {
-//! let res = parse_x509_crl(DER);
+//! let res = CertificateRevocationList::from_der(DER);
//! match res {
//! Ok((_rem, crl)) => {
//! for revoked in crl.iter_revoked_certificates() {
@@ -82,7 +87,7 @@
//!
//! - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`.
//! It adds the
-//! [X509Certificate::verify_signature()](x509/struct.X509Certificate.html#method.verify_signature)
+//! [`X509Certificate::verify_signature()`](certificate/struct.X509Certificate.html#method.verify_signature)
//! to `X509Certificate`.
//!
//! ```rust
@@ -91,13 +96,17 @@
//! /// 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.tbs_certificate.subject_pki;
+//! let issuer_public_key = issuer.public_key();
//! cert
//! .verify_signature(Some(issuer_public_key))
//! .is_ok()
//! }
//! ```
//!
+//! - The `validate` features add methods to run more validation functions on the certificate structure
+//! and values using the [`Validate`](validate/trait.Validate.html) trait.
+//! It does not validate any cryptographic parameter (see `verify` above).
+//!
//! ## Rust version requirements
//!
//! `x509-parser` requires **Rustc version 1.45 or greater**, based on nom 6
@@ -132,6 +141,10 @@ 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
@@ -143,12 +156,13 @@ 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](x509/struct.X509Certificate.html#method.from_der). See this function
+/// This function is an alias to [X509Certificate::from_der](certificate::X509Certificate::from_der). See this function
/// for more information.
///
/// For PEM-encoded certificates, use the [`pem`](pem/index.html) module.
@@ -160,7 +174,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](x509/struct.CertificateRevocationList.html#method.from_der). See this function
+/// This function is an alias to [CertificateRevocationList::from_der](revocation_list::CertificateRevocationList::from_der). See this function
/// for more information.
#[inline]
pub fn parse_x509_crl(i: &[u8]) -> X509Result<CertificateRevocationList> {
diff --git a/src/objects.rs b/src/objects.rs
index 7729c30..9bcbe72 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::oid2sn;
+//! use x509_parser::objects::*;
//! use x509_parser::oid_registry::*;
//!
//! let oid = &OID_X509_COMMON_NAME;
-//! let sn = oid2sn(oid);
+//! let sn = oid2sn(oid, oid_registry());
//! assert_eq!(sn, Ok("commonName"));
//! ```
@@ -45,19 +45,26 @@ 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) -> Result<&'a str, NidError> {
+pub fn oid2abbrev<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> {
if let Some(abbrev) = ABBREV_MAP.get(oid) {
return Ok(abbrev);
}
- OID_REGISTRY
- .get(oid)
- .map(|entry| entry.sn())
- .ok_or(NidError)
+ 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) -> Result<&'a str, NidError> {
- OID_REGISTRY.get(oid).map(|ref o| o.sn()).ok_or(NidError)
+pub fn oid2sn<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> {
+ registry.get(oid).map(|o| o.sn()).ok_or(NidError)
+}
+
+/// Returns the description corresponding to the OID
+pub fn oid2description<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> {
+ registry.get(oid).map(|o| o.description()).ok_or(NidError)
+}
+
+/// Return a reference to the default registry of known OIDs
+pub fn oid_registry() -> &'static OidRegistry<'static> {
+ &OID_REGISTRY
}
#[cfg(test)]
diff --git a/src/pem.rs b/src/pem.rs
index 95a943f..0bd8153 100644
--- a/src/pem.rs
+++ b/src/pem.rs
@@ -64,7 +64,7 @@ use nom::{Err, IResult};
use std::io::{BufRead, Cursor, Seek, SeekFrom};
/// Representation of PEM data
-#[derive(PartialEq, Debug)]
+#[derive(Clone, PartialEq, Debug)]
pub struct Pem {
/// The PEM label
pub label: String,
diff --git a/src/prelude.rs b/src/prelude.rs
index 45be9ca..5e6d780 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -9,5 +9,8 @@ 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 1c9a0c3..6cf9d55 100644
--- a/src/revocation_list.rs
+++ b/src/revocation_list.rs
@@ -1,6 +1,7 @@
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,
};
@@ -18,7 +19,31 @@ 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).
-#[derive(Debug)]
+///
+/// # 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)]
pub struct CertificateRevocationList<'a> {
pub tbs_cert_list: TbsCertList<'a>,
pub signature_algorithm: AlgorithmIdentifier<'a>,
@@ -26,54 +51,6 @@ 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
@@ -102,9 +79,9 @@ impl<'a> CertificateRevocationList<'a> {
self.tbs_cert_list.revoked_certificates.iter()
}
- /// Get the certificate extensions.
+ /// Get the CRL extensions.
#[inline]
- pub fn extensions(&self) -> &HashMap<Oid, X509Extension> {
+ pub fn extensions(&self) -> &[X509Extension] {
&self.tbs_cert_list.extensions
}
@@ -117,11 +94,35 @@ impl<'a> CertificateRevocationList<'a> {
/// MUST NOT use CRLNumber values longer than 20 octets.
/// </pre>
pub fn crl_number(&self) -> Option<&BigUint> {
- let ext = self.extensions().get(&OID_X509_EXT_CRL_NUMBER)?;
- match ext.parsed_extension {
- ParsedExtension::CRLNumber(ref num) => Some(num),
- _ => None,
- }
+ 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)
}
}
@@ -148,7 +149,7 @@ impl<'a> CertificateRevocationList<'a> {
/// -- if present, version MUST be v2
/// }
/// </pre>
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct TbsCertList<'a> {
pub version: Option<X509Version>,
pub signature: AlgorithmIdentifier<'a>,
@@ -156,11 +157,53 @@ pub struct TbsCertList<'a> {
pub this_update: ASN1Time,
pub next_update: Option<ASN1Time>,
pub revoked_certificates: Vec<RevokedCertificate<'a>>,
- pub extensions: HashMap<Oid<'a>, X509Extension<'a>>,
+ extensions: Vec<X509Extension<'a>>,
pub(crate) raw: &'a [u8],
}
impl<'a> TbsCertList<'a> {
+ /// Returns the certificate extensions
+ #[inline]
+ pub fn extensions(&self) -> &[X509Extension] {
+ &self.extensions
+ }
+
+ /// Returns an iterator over the certificate extensions
+ #[inline]
+ pub fn iter_extensions(&self) -> impl Iterator<Item = &X509Extension> {
+ self.extensions.iter()
+ }
+
+ /// Searches for an extension with the given `Oid`.
+ ///
+ /// Note: if there are several extensions with the same `Oid`, the first one is returned.
+ pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension> {
+ self.extensions.iter().find(|&ext| ext.oid == *oid)
+ }
+
+ /// Builds and returns a map of extensions.
+ ///
+ /// If an extension is present twice, this will fail and return `DuplicateExtensions`.
+ pub fn extensions_map(&self) -> Result<HashMap<Oid, &X509Extension>, X509Error> {
+ self.extensions
+ .iter()
+ .try_fold(HashMap::new(), |mut m, ext| {
+ if m.contains_key(&ext.oid) {
+ return Err(X509Error::DuplicateExtensions);
+ }
+ m.insert(ext.oid.clone(), ext);
+ Ok(m)
+ })
+ }
+}
+
+impl<'a> AsRef<[u8]> for TbsCertList<'a> {
+ fn as_ref(&self) -> &[u8] {
+ self.raw
+ }
+}
+
+impl<'a> FromDer<'a> for TbsCertList<'a> {
fn from_der(i: &'a [u8]) -> X509Result<Self> {
let start_i = i;
parse_der_sequence_defined_g(move |i, _| {
@@ -188,53 +231,57 @@ impl<'a> TbsCertList<'a> {
}
}
-impl<'a> AsRef<[u8]> for TbsCertList<'a> {
- fn as_ref(&self) -> &[u8] {
- &self.raw
- }
-}
-
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct RevokedCertificate<'a> {
/// The Serial number of the revoked certificate
pub user_certificate: BigUint,
/// The date on which the revocation occurred is specified.
pub revocation_date: ASN1Time,
/// Additional information about revocation
- pub extensions: HashMap<Oid<'a>, X509Extension<'a>>,
+ extensions: Vec<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
@@ -254,11 +301,11 @@ impl<'a> RevokedCertificate<'a> {
/// Get the code identifying the reason for the revocation, if present
pub fn reason_code(&self) -> Option<(bool, ReasonCode)> {
- let ext = self.extensions.get(&OID_X509_EXT_REASON_CODE)?;
- match ext.parsed_extension {
- ParsedExtension::ReasonCode(code) => Some((ext.critical, code)),
- _ => None,
- }
+ self.find_extension(&OID_X509_EXT_REASON_CODE)
+ .and_then(|ext| match ext.parsed_extension {
+ ParsedExtension::ReasonCode(code) => Some((ext.critical, code)),
+ _ => None,
+ })
}
/// Get the invalidity date, if present
@@ -266,17 +313,34 @@ 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)> {
- let ext = self.extensions.get(&OID_X509_EXT_INVALIDITY_DATE)?;
- match ext.parsed_extension {
- ParsedExtension::InvalidityDate(date) => Some((ext.critical, date)),
- _ => None,
- }
+ self.find_extension(&OID_X509_EXT_INVALIDITY_DATE)
+ .and_then(|ext| match ext.parsed_extension {
+ ParsedExtension::InvalidityDate(date) => Some((ext.critical, date)),
+ _ => None,
+ })
}
+}
- /// Get the certificate extensions.
- #[inline]
- pub fn extensions(&self) -> &HashMap<Oid, X509Extension> {
- &self.extensions
+// 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)
}
}
diff --git a/src/time.rs b/src/time.rs
index 1e72b87..ca37217 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -12,16 +12,13 @@ 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())
@@ -51,11 +48,17 @@ 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
new file mode 100644
index 0000000..bd4d070
--- /dev/null
+++ b/src/traits.rs
@@ -0,0 +1,36 @@
+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
new file mode 100644
index 0000000..6a495af
--- /dev/null
+++ b/src/validate.rs
@@ -0,0 +1,126 @@
+/// 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 9590fe6..ecee084 100644
--- a/src/x509.rs
+++ b/src/x509.rs
@@ -5,8 +5,9 @@
use crate::error::{X509Error, X509Result};
use crate::objects::*;
+use crate::traits::FromDer;
-use der_parser::ber::{BitStringObject, MAX_OBJECT_SIZE};
+use der_parser::ber::{parse_ber_integer, BitStringObject, MAX_OBJECT_SIZE};
use der_parser::der::*;
use der_parser::error::*;
use der_parser::num_bigint::BigUint;
@@ -19,32 +20,44 @@ 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 {
- // Parse [0] EXPLICIT Version DEFAULT v1
- pub(crate) fn from_der(i: &[u8]) -> X509Result<X509Version> {
+ 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)),
}
}
+}
- pub(crate) fn from_der_required(i: &[u8]) -> X509Result<X509Version> {
+// Parse [0] EXPLICIT Version DEFAULT v1
+impl<'a> FromDer<'a> for X509Version {
+ fn from_der(i: &'a [u8]) -> X509Result<'a, Self> {
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)),
}
}
}
@@ -57,34 +70,45 @@ newtype_enum! {
}
}
-#[derive(Debug, PartialEq)]
+/// A generic attribute type and value
+///
+/// These objects are used as [`RelativeDistinguishedName`] components.
+#[derive(Clone, Debug, PartialEq)]
pub struct AttributeTypeAndValue<'a> {
- pub attr_type: Oid<'a>,
- pub attr_value: DerObject<'a>, // ANY -- DEFINED BY AttributeType
+ attr_type: Oid<'a>,
+ attr_value: DerObject<'a>, // ANY -- DEFINED BY AttributeType
}
impl<'a> AttributeTypeAndValue<'a> {
- // 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)
+ /// 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
}
/// 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())
}
@@ -92,11 +116,45 @@ 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 {
@@ -124,12 +182,35 @@ fn parse_malformed_string(i: &[u8]) -> DerResult {
}
}
-#[derive(Debug, PartialEq)]
+/// A Relative Distinguished Name element.
+///
+/// These objects are used as [`X509Name`] components.
+#[derive(Clone, Debug, PartialEq)]
pub struct RelativeDistinguishedName<'a> {
- pub set: Vec<AttributeTypeAndValue<'a>>,
+ set: Vec<AttributeTypeAndValue<'a>>,
}
impl<'a> RelativeDistinguishedName<'a> {
+ /// Builds a new `RelativeDistinguishedName`
+ #[inline]
+ pub const fn new(set: Vec<AttributeTypeAndValue<'a>>) -> Self {
+ RelativeDistinguishedName { set }
+ }
+
+ /// Return an iterator over the components of this object
+ pub fn iter(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
+ self.set.iter()
+ }
+}
+
+impl<'a> FromIterator<AttributeTypeAndValue<'a>> for RelativeDistinguishedName<'a> {
+ fn from_iter<T: IntoIterator<Item = AttributeTypeAndValue<'a>>>(iter: T) -> Self {
+ let set = iter.into_iter().collect();
+ RelativeDistinguishedName { set }
+ }
+}
+
+impl<'a> FromDer<'a> 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)?;
@@ -139,15 +220,15 @@ impl<'a> RelativeDistinguishedName<'a> {
}
}
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct SubjectPublicKeyInfo<'a> {
pub algorithm: AlgorithmIdentifier<'a>,
pub subject_public_key: BitStringObject<'a>,
}
-impl<'a> SubjectPublicKeyInfo<'a> {
+impl<'a> FromDer<'a> for SubjectPublicKeyInfo<'a> {
/// Parse the SubjectPublicKeyInfo struct portion of a DER-encoded X.509 Certificate
- pub fn from_der(i: &'a [u8]) -> X509Result<Self> {
+ 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>| {
@@ -166,31 +247,29 @@ impl<'a> SubjectPublicKeyInfo<'a> {
}
}
-#[derive(Debug, PartialEq)]
+/// 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)]
pub struct AlgorithmIdentifier<'a> {
pub algorithm: Oid<'a>,
pub parameters: Option<DerObject<'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
+impl<'a> FromDer<'a> for AlgorithmIdentifier<'a> {
#[allow(clippy::needless_lifetimes)]
- pub fn from_der(i: &[u8]) -> X509Result<AlgorithmIdentifier> {
+ 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))?;
@@ -206,15 +285,21 @@ impl<'a> AlgorithmIdentifier<'a> {
}
}
-#[derive(Debug, PartialEq)]
+/// X.509 Name (as used in `Issuer` and `Subject` fields)
+///
+/// The Name describes a hierarchical name composed of attributes, such
+/// as country name, and corresponding values, such as US. The type of
+/// the component AttributeValue is determined by the AttributeType; in
+/// general it will be a DirectoryString.
+#[derive(Clone, Debug, PartialEq)]
pub struct X509Name<'a> {
- pub rdn_seq: Vec<RelativeDistinguishedName<'a>>,
+ pub(crate) rdn_seq: Vec<RelativeDistinguishedName<'a>>,
pub(crate) raw: &'a [u8],
}
impl<'a> fmt::Display for X509Name<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match x509name_to_string(&self.rdn_seq) {
+ match x509name_to_string(&self.rdn_seq, oid_registry()) {
Ok(o) => write!(f, "{}", o),
Err(_) => write!(f, "<X509Error: Invalid X.509 name>"),
}
@@ -222,18 +307,18 @@ impl<'a> fmt::Display for X509Name<'a> {
}
impl<'a> X509Name<'a> {
- /// 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)
+ /// Builds a new `X509Name` from the provided elements.
+ #[inline]
+ pub const fn new(rdn_seq: Vec<RelativeDistinguishedName<'a>>, raw: &'a [u8]) -> Self {
+ X509Name { rdn_seq, raw }
+ }
+
+ /// Attempt to format the current name, using the given registry to convert OIDs to strings.
+ ///
+ /// Note: a default registry is provided with this crate, and is returned by the
+ /// [`oid_registry()`] method.
+ pub fn to_string_with_registry(&self, oid_registry: &OidRegistry) -> Result<String, X509Error> {
+ x509name_to_string(&self.rdn_seq, oid_registry)
}
// Not using the AsRef trait, as that would not give back the full 'a lifetime
@@ -242,6 +327,11 @@ 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()
}
@@ -293,41 +383,70 @@ 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> {
+ pub fn iter_common_name(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_COMMON_NAME)
}
/// Return an iterator over the `Country` attributes of the X.509 Name.
- pub fn iter_country(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_country(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_COUNTRY_NAME)
}
/// Return an iterator over the `Organization` attributes of the X.509 Name.
- pub fn iter_organization(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_organization(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_ORGANIZATION_NAME)
}
/// Return an iterator over the `OrganizationalUnit` attributes of the X.509 Name.
- pub fn iter_organizational_unit(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_organizational_unit(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_ORGANIZATIONAL_UNIT)
}
/// Return an iterator over the `StateOrProvinceName` attributes of the X.509 Name.
- pub fn iter_state_or_province(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_state_or_province(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_STATE_OR_PROVINCE_NAME)
}
/// Return an iterator over the `Locality` attributes of the X.509 Name.
- pub fn iter_locality(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_locality(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_X509_LOCALITY_NAME)
}
/// Return an iterator over the `EmailAddress` attributes of the X.509 Name.
- pub fn iter_email(&self) -> impl Iterator<Item = &AttributeTypeAndValue> {
+ pub fn iter_email(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
self.iter_by_oid(&OID_PKCS9_EMAIL_ADDRESS)
}
}
+impl<'a> FromIterator<RelativeDistinguishedName<'a>> for X509Name<'a> {
+ fn from_iter<T: IntoIterator<Item = RelativeDistinguishedName<'a>>>(iter: T) -> Self {
+ let rdn_seq = iter.into_iter().collect();
+ X509Name { rdn_seq, raw: &[] }
+ }
+}
+
+impl<'a> From<X509Name<'a>> for Vec<RelativeDistinguishedName<'a>> {
+ fn from(name: X509Name<'a>) -> Self {
+ name.rdn_seq
+ }
+}
+
+impl<'a> FromDer<'a> 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);
@@ -376,7 +495,10 @@ 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]) -> Result<String, X509Error> {
+fn x509name_to_string(
+ rdn_seq: &[RelativeDistinguishedName],
+ oid_registry: &OidRegistry,
+) -> Result<String, X509Error> {
rdn_seq.iter().fold(Ok(String::new()), |acc, rdn| {
acc.and_then(|mut _vec| {
rdn.set
@@ -385,7 +507,7 @@ fn x509name_to_string(rdn_seq: &[RelativeDistinguishedName]) -> Result<String, X
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) {
+ let abbrev = match oid2abbrev(&attr.attr_type, oid_registry) {
Ok(s) => String::from(s),
_ => format!("{:?}", attr.attr_type),
};
@@ -415,12 +537,16 @@ pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result<BitStringObject> {
}
pub(crate) fn parse_serial(i: &[u8]) -> X509Result<(&[u8], BigUint)> {
- map_opt(parse_der_integer, get_serial_info)(i).map_err(|_| X509Error::InvalidSerial.into())
+ // 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())
}
fn get_serial_info(o: DerObject) -> Option<(&[u8], BigUint)> {
- let big = o.as_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 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 064664b..6c5f4ed 100644
--- a/tests/readcert.rs
+++ b/tests/readcert.rs
@@ -1,6 +1,7 @@
use chrono::offset::{TimeZone, Utc};
use chrono::Datelike;
-use der_parser::{oid, oid::Oid};
+use der_parser::oid;
+use nom::Parser;
use oid_registry::*;
use std::collections::HashMap;
use x509_parser::prelude::*;
@@ -61,10 +62,11 @@ fn test_x509_parser() {
assert_eq!(na.year(), 2020);
assert_eq!(na.month(), 10);
assert_eq!(na.day(), 17);
- let policies = vec![(oid!(1.2.250 .1 .121 .1 .1 .1), [].as_ref())]
- .into_iter()
- .collect();
- let expected_extensions_list = vec![
+ let policies = vec![PolicyInformation {
+ policy_id: oid!(1.2.250 .1 .121 .1 .1 .1),
+ policy_qualifiers: None,
+ }];
+ let expected_extensions = vec![
X509Extension::new(
oid!(2.5.29 .19),
true,
@@ -84,7 +86,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(CertificatePolicies { policies }),
+ ParsedExtension::CertificatePolicies(policies),
),
X509Extension::new(
oid!(2.5.29 .14),
@@ -115,11 +117,7 @@ fn test_x509_parser() {
}),
),
];
- 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_eq!(tbs_cert.extensions(), &expected_extensions as &[_]);
//
assert!(tbs_cert.is_ca());
//
@@ -139,7 +137,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),
}
@@ -207,18 +205,13 @@ 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 mut extensions_map = HashMap::new();
- extensions_map.insert(
- oid!(2.5.29 .21),
+ let expected_extensions = vec![
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,
@@ -227,8 +220,8 @@ fn test_crl_parse() {
],
ParsedExtension::InvalidityDate(invalidity_date),
),
- );
- assert_eq!(revoked_cert_0.extensions, extensions_map);
+ ];
+ assert_eq!(revoked_cert_0.extensions(), &expected_extensions as &[_]);
assert_eq!(revoked_certs.len(), 5);
assert_eq!(revoked_certs[4].user_certificate, 1_341_771_u32.into());
@@ -257,12 +250,7 @@ fn test_crl_parse() {
ParsedExtension::CRLNumber(3u32.into()),
),
];
-
- 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.extensions(), &expected_extensions as &[_]);
assert_eq!(tbs_cert_list.as_ref(), &CRL_DER[4..(4 + 4 + 508)]);
}
@@ -301,15 +289,7 @@ fn test_crl_parse_empty() {
}),
),
];
- // 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.extensions(), &expected_extensions as &[_]);
assert_eq!(
cert.tbs_cert_list.as_ref(),
&EMPTY_CRL_DER[4..(4 + 3 + 200)]
@@ -331,8 +311,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_eq!(revoked_cert_0.extensions, HashMap::new());
- assert!(crl.tbs_cert_list.extensions.is_empty());
+ assert!(revoked_cert_0.extensions().is_empty());
+ assert!(crl.tbs_cert_list.extensions().is_empty());
assert_eq!(crl.tbs_cert_list.as_ref(), &MINIMAL_CRL_DER[4..(4 + 79)]);
}
err => panic!("x509 parsing failed: {:?}", err),
@@ -345,21 +325,32 @@ fn test_duplicate_authority_info_access() {
Ok((_, cert)) => {
let extension = cert
.tbs_certificate
- .extensions
- .get(&OID_PKIX_AUTHORITY_INFO_ACCESS)
+ .find_extension(&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);
- let expected_aia =
- ParsedExtension::AuthorityInfoAccess(AuthorityInfoAccess { accessdescs });
- assert_eq!(*extension.parsed_extension(), expected_aia);
+ if let ParsedExtension::AuthorityInfoAccess(aia) = extension.parsed_extension() {
+ let h = aia.as_hashmap();
+ assert_eq!(h, accessdescs);
+ } else {
+ panic!("Wrong extension type parsed");
+ }
}
err => panic!("x509 parsing failed: {:?}", err),
}
}
+
+#[test]
+fn test_x509_parser_no_ext() {
+ let mut parser = X509CertificateParser::new().with_deep_parse_extensions(false);
+ let (_, x509) = parser.parse(IGCA_DER).expect("parsing failed");
+ for ext in x509.extensions() {
+ assert_eq!(ext.parsed_extension(), &ParsedExtension::Unparsed);
+ }
+}
diff --git a/tests/readcsr.rs b/tests/readcsr.rs
index 5b0944c..77dafec 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.rdn_seq.iter();
+ let mut rdns = cri.subject.iter();
let rdn = rdns.next().unwrap();
- let first = rdn.set.first().unwrap();
- assert_eq!(first.attr_type, OID_X509_COMMON_NAME);
+ let first = rdn.iter().next().unwrap();
+ assert_eq!(first.attr_type(), &OID_X509_COMMON_NAME);
assert_eq!(first.as_str().unwrap(), "test.rusticata.fr");
let expected: &[u8] = &[
diff --git a/tests/verify.rs b/tests/verify.rs
index 4cdd2e2..6cb3835 100644
--- a/tests/verify.rs
+++ b/tests/verify.rs
@@ -22,3 +22,14 @@ 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());
+}