From 94a003f76df52344024b4e357d87a75857ff4688 Mon Sep 17 00:00:00 2001
From: jean-airoldie <25088801+jean-airoldie@users.noreply.github.com>
Date: Sun, 4 May 2025 08:55:34 -0400
Subject: [PATCH] Add value field to ParsedExtension::UnsupportedExtension
* This is a breaking change.
* Allows a user to manually parse the value of a custom extension.
* Add tests.
* Fix clippy & run rustfmt
---
.gitignore | 1 +
assets/csr-custom-extension.pem | 7 +++++
src/extensions/mod.rs | 49 +++++++++++++++++++--------------
tests/readcsr.rs | 48 ++++++++++++++++++++++++++++++--
4 files changed, 81 insertions(+), 24 deletions(-)
create mode 100644 assets/csr-custom-extension.pem
diff --git a/.gitignore b/.gitignore
index a1d9281..7baa0e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.*.swp
target
/.idea
+tags
diff --git a/assets/csr-custom-extension.pem b/assets/csr-custom-extension.pem
new file mode 100644
index 0000000..fceb6c7
--- /dev/null
+++ b/assets/csr-custom-extension.pem
@@ -0,0 +1,7 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIHJMH0CAQAwLjEbMBkGA1UEAwwSY2xpZW50IGNlcnRpZmljYXRlMQ8wDQYDVQQK
+DAZteSBvcmcwKjAFBgMrZXADIQC3Gw0vJiIXML8uYH86NEMtmiMuAdWn2WPEZVKu
+mbsOTaAcMBoGCSqGSIb3DQEJDjENMAswCQYCKgMEAwECAzAFBgMrZXADQQDPcUvh
+yrGGTBlgv4W3//5/h5AkEG2UVa1Bvfbi/UL4tkFYhkc5sAFZ37Igr2tgDNhofu7r
+JVo5qjVH6T9QJ8gD
+-----END CERTIFICATE REQUEST-----
diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs
index da74142..ff730c4 100644
--- a/src/extensions/mod.rs
+++ b/src/extensions/mod.rs
@@ -175,7 +175,7 @@ impl<'a> DerParser<'a> for X509Extension<'a> {
let (rem, (_, value)) = <&[u8]>::parse_der_as_input(rem)
.map_err(|_| Err::Error(X509Error::InvalidExtensions))?;
- let (_, parsed_extension) = parser::parse_extension(value.clone(), &oid)?;
+ let parsed_extension = parser::parse_extension(value.clone(), &oid, critical);
let ext = X509Extension {
oid,
critical,
@@ -226,10 +226,11 @@ impl<'i> Parser> for X509ExtensionParser {
let (rem, (_, value)) = <&[u8]>::parse_der_as_input(rem)
.map_err(|_| Err::Error(X509Error::InvalidExtensions))?;
- let (_, parsed_extension) = if self.deep_parse_extensions {
- parser::parse_extension(value.clone(), &oid)?
+ let parsed_extension = if self.deep_parse_extensions {
+ parser::parse_extension(value.clone(), &oid, critical)
} else {
- (rem.take(rem.input_len()), ParsedExtension::Unparsed)
+ rem.take(rem.input_len());
+ ParsedExtension::Unparsed
};
let ext = X509Extension {
@@ -258,12 +259,21 @@ impl<'i> Parser> for X509ExtensionParser {
}
}
+/// A unsupported extension.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnsupportedExtension<'a> {
+ /// The Object ID of the extension.
+ pub oid: Oid<'a>,
+ /// The unparsed value.
+ pub value: &'a [u8],
+ /// Whether the extension is critical.
+ pub critical: bool,
+}
+
#[derive(Clone, Debug, PartialEq)]
pub enum ParsedExtension<'a> {
/// Crate parser does not support this extension (yet)
- UnsupportedExtension {
- oid: Oid<'a>,
- },
+ UnsupportedExtension(UnsupportedExtension<'a>),
ParseError {
error: Err,
},
@@ -422,30 +432,27 @@ pub(crate) mod parser {
// look into the parser map if the extension is known, and parse it
// otherwise, leave it as UnsupportedExtension
- fn parse_extension0<'i>(
- input: Input<'i>,
- oid: &Oid,
- ) -> IResult, ParsedExtension<'i>, X509Error> {
+ fn parse_extension0<'i>(input: Input<'i>, oid: &Oid, critical: bool) -> ParsedExtension<'i> {
if let Some(parser) = EXTENSION_PARSERS.get(oid) {
match parser(input.clone()) {
- Ok((rem, ext)) => Ok((rem, ext)),
- Err(error) => Ok((input, ParsedExtension::ParseError { error })),
+ Ok((_, ext)) => ext,
+ Err(error) => ParsedExtension::ParseError { error },
}
} else {
- Ok((
- input,
- ParsedExtension::UnsupportedExtension {
- oid: oid.to_owned(),
- },
- ))
+ ParsedExtension::UnsupportedExtension(UnsupportedExtension {
+ oid: oid.to_owned(),
+ value: input.as_bytes2(),
+ critical,
+ })
}
}
pub(crate) fn parse_extension<'i>(
input: Input<'i>,
oid: &Oid,
- ) -> IResult, ParsedExtension<'i>, X509Error> {
- parse_extension0(input, oid)
+ critical: bool,
+ ) -> ParsedExtension<'i> {
+ parse_extension0(input, oid, critical)
}
fn parse_basicconstraints_ext(input: Input) -> IResult {
diff --git a/tests/readcsr.rs b/tests/readcsr.rs
index cc5a73a..998b0e9 100644
--- a/tests/readcsr.rs
+++ b/tests/readcsr.rs
@@ -1,13 +1,16 @@
-use asn1_rs::Set;
+use asn1_rs::{oid, Oid, Set};
use oid_registry::{
- OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_SIG_ECDSA_WITH_SHA256,
- OID_X509_COMMON_NAME,
+ OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_PKCS9_EXTENSION_REQUEST,
+ OID_SIG_ECDSA_WITH_SHA256, OID_X509_COMMON_NAME,
};
use x509_parser::prelude::*;
const CSR_DATA_EMPTY_ATTRIB: &[u8] = include_bytes!("../assets/csr-empty-attributes.csr");
const CSR_DATA: &[u8] = include_bytes!("../assets/test.csr");
const CSR_CHALLENGE_PASSWORD: &[u8] = include_bytes!("../assets/csr-challenge-password.pem");
+const CSR_CUSTOM_EXTENSION: &[u8] = include_bytes!("../assets/csr-custom-extension.pem");
+const OID_CUSTOM_EXTENSION: Oid<'static> = oid!(1.2.3);
+const VALUE_CUSTOM_EXTENSION: &[u8] = &[1, 2, 3];
#[test]
fn read_csr_empty_attrib() {
let (rem, csr) =
@@ -129,3 +132,42 @@ fn read_csr_verify() {
let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR");
csr.verify_signature().unwrap_err();
}
+
+#[test]
+fn read_csr_with_custom_extension() {
+ let der = pem::parse_x509_pem(CSR_CUSTOM_EXTENSION).unwrap().1;
+ let (rem, csr) = X509CertificationRequest::from_der(&der.contents)
+ .expect("Could not parse CSR with custom extension");
+
+ assert!(rem.is_empty());
+ dbg!(csr.certification_request_info.attributes());
+ let cri = &csr.certification_request_info;
+ assert_eq!(cri.version, X509Version(0));
+ assert_eq!(cri.attributes().len(), 1);
+
+ let custom_attr = csr
+ .certification_request_info
+ .find_attribute(&OID_PKCS9_EXTENSION_REQUEST)
+ .expect("Custom extension not found in CSR");
+ for attr in custom_attr.parsed_attributes() {
+ match attr {
+ ParsedCriAttribute::ExtensionRequest(req) => {
+ assert_eq!(req.extensions.len(), 1);
+ let extension = req.extensions.first().unwrap();
+ assert_eq!(extension.oid, OID_CUSTOM_EXTENSION);
+ assert_eq!(extension.critical, false);
+ assert_eq!(extension.value.as_bytes2(), VALUE_CUSTOM_EXTENSION);
+ }
+ _ => unreachable!(),
+ }
+ }
+
+ let extensions = csr.requested_extensions();
+ for extension in extensions {
+ if let ParsedExtension::UnsupportedExtension(ext) = extension {
+ assert_eq!(ext.oid, OID_CUSTOM_EXTENSION);
+ assert_eq!(ext.value, VALUE_CUSTOM_EXTENSION);
+ assert_eq!(ext.critical, false);
+ }
+ }
+}