Skip to content

Add support custom CSR extensions when parsing #337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions rcgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ ring = { workspace = true, optional = true }
pem = { workspace = true, optional = true }
pki-types = { workspace = true }
time = { version = "0.3.6", default-features = false }
x509-parser = { workspace = true, features = ["verify"], optional = true }
#x509-parser = { workspace = true, features = ["verify"], optional = true }
x509-parser = { features = ["verify"], optional = true, git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" }
zeroize = { version = "1.2", optional = true }

[features]
Expand All @@ -51,7 +52,7 @@ allowed_external_types = [
[dev-dependencies]
openssl = "0.10"
pki-types = { package = "rustls-pki-types", version = "1" }
x509-parser = { workspace = true, features = ["verify"] }
x509-parser = { features = ["verify"], git = "https://github.com/jean-airoldie/x509-parser", branch = "custom-extension" }
rustls-webpki = { version = "0.103", features = ["ring", "std"] }
botan = { version = "0.11", features = ["vendored"] }
ring = { workspace = true }
69 changes: 67 additions & 2 deletions rcgen/src/csr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey,
};
#[cfg(feature = "x509-parser")]
use crate::{DistinguishedName, SanType};
use crate::{CustomExtension, DistinguishedName, SanType};

/// A public key, extracted from a CSR
#[derive(Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -65,6 +65,18 @@ impl From<CertificateSigningRequest> for CertificateSigningRequestDer<'static> {
}
}

/// A unsupported extension.
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg(feature = "x509-parser")]
pub struct UnsupportedExtension<'a> {
/// The Object ID of the extension.
pub oid: Vec<u64>,
/// The unparsed value.
pub value: &'a [u8],
/// Whether the extension is critical.
pub critical: bool,
}

/// Parameters for a certificate signing request
#[derive(Debug)]
pub struct CertificateSigningRequestParams {
Expand All @@ -84,6 +96,23 @@ impl CertificateSigningRequestParams {
Self::from_der(&csr.contents().into())
}

/// Parse a certificate signing request from the ASCII PEM format
/// using the provided validator function to handle unknown extension
/// types.
///
/// The validator function must return an error if the attribute OID or value
/// is incorrect.
///
/// See [`from_der`](Self::from_der) for more details.
#[cfg(all(feature = "pem", feature = "x509-parser"))]
pub fn from_pem_custom_validator<F>(pem_str: &str, valid_fn: F) -> Result<Self, Error>
where
F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>,
{
let csr = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificationRequest))?;
Self::from_der_custom_validator(&csr.contents().into(), valid_fn)
}

/// Parse a certificate signing request from DER-encoded bytes
///
/// Currently, this only supports the `Subject Alternative Name` extension.
Expand All @@ -96,6 +125,24 @@ impl CertificateSigningRequestParams {
/// [`rustls_pemfile::csr()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html
#[cfg(feature = "x509-parser")]
pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result<Self, Error> {
Self::from_der_custom_validator(csr, |_| Ok(()))
}

/// Parse a certificate signing request from DER-encoded bytes using the provided
/// validator function to handle unknown extension types.
///
/// The validator function must return an error if the attribute OID or value
/// is incorrect.
///
/// See [`from_der`](Self::from_der) for more details.
#[cfg(feature = "x509-parser")]
pub fn from_der_custom_validator<F>(
csr: &CertificateSigningRequestDer<'_>,
mut valid_fn: F,
) -> Result<Self, Error>
where
F: FnMut(&UnsupportedExtension<'_>) -> Result<(), Error>,
{
use crate::KeyUsagePurpose;
use x509_parser::prelude::FromDer;

Expand Down Expand Up @@ -171,7 +218,25 @@ impl CertificateSigningRequestParams {
return Err(Error::UnsupportedExtension);
}
},
_ => return Err(Error::UnsupportedExtension),
x509_parser::extensions::ParsedExtension::UnsupportedExtension(val) => {
let oid: Vec<u64> = match val.oid.iter() {
Some(iter) => iter.collect(),
None => return Err(Error::UnsupportedExtension),
};
let ext = UnsupportedExtension {
oid,
value: val.value,
critical: val.critical,
};
valid_fn(&ext)?;
let mut ext =
CustomExtension::from_oid_content(&ext.oid, val.value.to_vec());
ext.set_criticality(val.critical);
params.custom_extensions.push(ext);
},
_ => {
return Err(Error::UnsupportedExtension);
},
}
}
}
Expand Down
20 changes: 16 additions & 4 deletions rcgen/tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,11 @@ mod test_x509_custom_ext {
fn custom_ext() {
// Create an imaginary critical custom extension for testing.
let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap();
let oid_vec = test_oid.iter().unwrap().collect::<Vec<u64>>();
let test_ext = yasna::construct_der(|writer| {
writer.write_utf8_string("🦀 greetz to ferris 🦀");
});
let mut custom_ext = CustomExtension::from_oid_content(
test_oid.iter().unwrap().collect::<Vec<u64>>().as_slice(),
test_ext.clone(),
);
let mut custom_ext = CustomExtension::from_oid_content(&oid_vec, test_ext.clone());
custom_ext.set_criticality(true);

// Generate a certificate with the custom extension, parse it with x509-parser.
Expand Down Expand Up @@ -132,6 +130,20 @@ mod test_x509_custom_ext {
.expect("missing requested custom extension");
assert!(custom_ext.critical);
assert_eq!(custom_ext.value, test_ext);

let csr_params = rcgen::CertificateSigningRequestParams::from_der_custom_validator(
test_cert_csr.der(),
|ext| {
if ext.oid != oid_vec || ext.value != test_ext || !ext.critical {
Err(rcgen::Error::UnsupportedExtension)
} else {
Ok(())
}
},
)
.unwrap();

assert_eq!(csr_params.params, params);
}
}

Expand Down
Loading