Skip to content
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

wip: refactoring extension handling #164

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e5bb524
error: alpha-sort Error variants
cpu Dec 10, 2023
e8f2721
ext: introduce module for X.509 extension handling
cpu Sep 10, 2023
1948f6a
ext: implement authority key identifier.
cpu Dec 10, 2023
6a3359d
ext: implement subject alternative name.
cpu Dec 10, 2023
063482e
ext: implement key usage
cpu Dec 10, 2023
fecc1ed
wip: extended key usage (and some CSR fixes)
cpu Dec 10, 2023
33c1977
wip: non x509-feature fixes
cpu Dec 10, 2023
559dc09
ext: implement name constraints
cpu Dec 10, 2023
56e3c3c
wip: fixup with name constraints
cpu Dec 10, 2023
e5bcd11
wip: and again...
cpu Dec 10, 2023
5ab3d94
ext: implement CRL distribution points
cpu Dec 10, 2023
2841368
wip: fixup with crldps
cpu Dec 10, 2023
6b9e715
wip: add a TODO
cpu Dec 10, 2023
a447dc6
ext: implement subject key ID, specifying SKI
cpu Dec 10, 2023
d310765
ext: implement basic constraints
cpu Dec 10, 2023
9e9caf6
ext: implement custom extensions
cpu Dec 10, 2023
0369073
ext: use Extensions to write DER as needed
cpu Dec 10, 2023
2d0b890
wip: fixup with last commit
cpu Dec 10, 2023
dbc3d36
ext: implement crl number extension
cpu Dec 10, 2023
f0548fa
ext: implement issuing distribution point extension
cpu Dec 10, 2023
0c96153
crl: unconditionally emit AKI
cpu Dec 10, 2023
a50d976
wip: fixup with CRL IDP ext
cpu Dec 10, 2023
5d95594
crl: write DER with Extensions
cpu Dec 10, 2023
199d604
ext: implement reason code extension
cpu Dec 10, 2023
0287f54
ext: implement invalidity date extension
cpu Dec 10, 2023
006bf28
crl: use Extensions to write DER
cpu Dec 10, 2023
98c020f
wip: tidy up write_request
cpu Dec 10, 2023
973271c
ext: fix type leak in custom extension
cpu Dec 10, 2023
0225a26
ext: mark new constructor as test-only
cpu Dec 10, 2023
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
106 changes: 48 additions & 58 deletions rcgen/src/crl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@
use yasna::DERWriter;
use yasna::Tag;

use crate::oid::*;
use crate::ext::Extensions;
#[cfg(feature = "pem")]
use crate::ENCODE_CONFIG;
use crate::{
write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier,
write_x509_extension,
};
use crate::{ext, write_distinguished_name, write_dt_utc_or_generalized};
use crate::{Certificate, Error, KeyIdMethod, KeyUsagePurpose, SerialNumber, SignatureAlgorithm};

/// A certificate revocation list (CRL)
Expand Down Expand Up @@ -245,41 +242,42 @@
// RFC 5280 §5.1.2.7:
// This field may only appear if the version is 2 (Section 5.1.2.1). If
// present, this field is a sequence of one or more CRL extensions.
// RFC 5280 §5.2:
// Conforming CRL issuers are REQUIRED to include the authority key
// identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
// extensions in all CRLs issued.
writer.next().write_tagged(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
// Write authority key identifier.
write_x509_authority_key_identifier(writer.next(), ca);

// Write CRL number.
write_x509_extension(writer.next(), OID_CRL_NUMBER, false, |writer| {
writer.write_bigint_bytes(self.crl_number.as_ref(), true);
});

// Write issuing distribution point (if present).
if let Some(issuing_distribution_point) = &self.issuing_distribution_point {
write_x509_extension(
writer.next(),
OID_CRL_ISSUING_DISTRIBUTION_POINT,
true,
|writer| {
issuing_distribution_point.write_der(writer);
},
);
}
});
});
self.extensions(ca).write_crl_der(writer.next());

Ok(())
})
}
/// Returns the X.509 extensions that the [CertificateRevocationListParams] describe.
///
/// If an issuer [Certificate] is provided, additional extensions specific to the issuer will
/// be included (e.g. the authority key identifier).
fn extensions(&self, issuer: &Certificate) -> Extensions {
let mut exts = Extensions::default();

// RFC 5280 §5.2:
// Conforming CRL issuers are REQUIRED to include the authority key
// identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
// extensions in all CRLs issued.
// Safety: `exts` is empty at this point - there can be no duplicate AKI ext OID.
exts.add_extension(Box::new(ext::AuthorityKeyIdentifier::from(issuer)))
.unwrap();

// Safety: there can be no duplicate CRL number ext OID by this point.
exts.add_extension(Box::new(ext::CrlNumber::from_params(&self)))
.unwrap();

if let Some(idp_ext) = ext::IssuingDistributionPoint::from_params(&self) {
// Safety: there can be no duplicate IDP ext OID by this point.
exts.add_extension(Box::new(idp_ext)).unwrap();
}

exts
}
}

/// A certificate revocation list (CRL) issuing distribution point, to be included in a CRL's
/// [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CrlIssuingDistributionPoint {
/// The CRL's distribution point, containing a sequence of URIs the CRL can be retrieved from.
pub distribution_point: CrlDistributionPoint,
Expand All @@ -289,7 +287,7 @@
}

impl CrlIssuingDistributionPoint {
fn write_der(&self, writer: DERWriter) {
pub(crate) fn write_der(&self, writer: DERWriter) {
// IssuingDistributionPoint SEQUENCE
writer.write_sequence(|writer| {
// distributionPoint [0] DistributionPointName OPTIONAL
Expand Down Expand Up @@ -359,31 +357,23 @@
// optional for conforming CRL issuers and applications. However, CRL
// issuers SHOULD include reason codes (Section 5.3.1) and invalidity
// dates (Section 5.3.2) whenever this information is available.
let has_reason_code =
matches!(self.reason_code, Some(reason) if reason != RevocationReason::Unspecified);
let has_invalidity_date = self.invalidity_date.is_some();
if has_reason_code || has_invalidity_date {
writer.next().write_sequence(|writer| {
// Write reason code if present.
self.reason_code.map(|reason_code| {
write_x509_extension(writer.next(), OID_CRL_REASONS, false, |writer| {
writer.write_enum(reason_code as i64);
});
});

// Write invalidity date if present.
self.invalidity_date.map(|invalidity_date| {
write_x509_extension(
writer.next(),
OID_CRL_INVALIDITY_DATE,
false,
|writer| {
write_dt_utc_or_generalized(writer, invalidity_date);
},
)
});
});
}
self.extensions().write_der(writer.next());
})
}
/// Returns the X.509 extensions that the [RevokedCertParams] describe.
fn extensions(&self) -> Extensions {
let mut exts = Extensions::default();

if let Some(reason_code_ext) = ext::ReasonCode::from_params(&self) {
// Safety: there can be no duplicate reason code ext OID by this point.
exts.add_extension(Box::new(reason_code_ext)).unwrap();
}

if let Some(invalidity_date_ext) = ext::InvalidityDate::from_params(&self) {
// Safety: there can be no duplicate invalidity date ext OID by this point.
exts.add_extension(Box::new(invalidity_date_ext)).unwrap();

Check warning on line 374 in rcgen/src/crl.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/crl.rs#L373-L374

Added lines #L373 - L374 were not covered by tests
}

exts
}
}
70 changes: 52 additions & 18 deletions rcgen/src/csr.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(feature = "x509-parser")]
use crate::{DistinguishedName, SanType};
use crate::{ext, DistinguishedName};
#[cfg(feature = "pem")]
use pem::Pem;
use std::hash::Hash;
Expand Down Expand Up @@ -45,6 +45,7 @@
///
/// Currently, this only supports the `Subject Alternative Name` extension.
/// On encountering other extensions, this function will return an error.
// TODO(@cpu): update this doc comment.
#[cfg(feature = "x509-parser")]
pub fn from_der(csr: &[u8]) -> Result<Self, Error> {
use x509_parser::prelude::FromDer;
Expand All @@ -66,27 +67,60 @@
params.distinguished_name = DistinguishedName::from_name(&info.subject)?;
let raw = info.subject_pki.subject_public_key.data.to_vec();

if let Some(extensions) = csr.requested_extensions() {
for ext in extensions {
match ext {
x509_parser::extensions::ParsedExtension::SubjectAlternativeName(san) => {
for name in &san.general_names {
params
.subject_alt_names
.push(SanType::try_from_general(name)?);
}
},
_ => return Err(Error::UnsupportedExtension),
// Pull out the extension requests attributes from the CSR.
// Note: we avoid using csr.requested_extensions() here because it maps to the parsed
// extension value and we want the raw extension value to handle unknown extensions
// ourselves.
let requested_exts = csr
.certification_request_info
.iter_attributes()
.filter_map(|attr| {
if let x509_parser::prelude::ParsedCriAttribute::ExtensionRequest(requested) =
&attr.parsed_attribute()
{
Some(requested.extensions.iter().collect::<Vec<_>>())
} else {
None

Check warning on line 83 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L83

Added line #L83 was not covered by tests
}
})
.flatten()
.collect::<Vec<_>>();

for ext in requested_exts {
use x509_parser::extensions::ParsedExtension;

let supported = match ext.parsed_extension() {
ext @ ParsedExtension::SubjectAlternativeName(_) => {
ext::SubjectAlternativeName::from_parsed(&mut params, ext)?
},
ext @ ParsedExtension::KeyUsage(_) => ext::KeyUsage::from_parsed(&mut params, ext)?,
ext @ ParsedExtension::ExtendedKeyUsage(_) => {
ext::ExtendedKeyUsage::from_parsed(&mut params, ext)?

Check warning on line 98 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L96-L98

Added lines #L96 - L98 were not covered by tests
},
ext @ ParsedExtension::NameConstraints(_) => {
ext::NameConstraints::from_parsed(&mut params, ext)?

Check warning on line 101 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L100-L101

Added lines #L100 - L101 were not covered by tests
},
ext @ ParsedExtension::CRLDistributionPoints(_) => {
ext::CrlDistributionPoints::from_parsed(&mut params, ext)?

Check warning on line 104 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L103-L104

Added lines #L103 - L104 were not covered by tests
},
ext @ ParsedExtension::SubjectKeyIdentifier(_) => {
ext::SubjectKeyIdentifier::from_parsed(&mut params, ext)?
},
ext @ ParsedExtension::BasicConstraints(_) => {
ext::BasicConstraints::from_parsed(&mut params, ext)?

Check warning on line 110 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L109-L110

Added lines #L109 - L110 were not covered by tests
},
ParsedExtension::AuthorityKeyIdentifier(_) => {
true // We always handle emitting this ourselves - don't copy it as a custom extension.

Check warning on line 113 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L113

Added line #L113 was not covered by tests
},
_ => false,

Check warning on line 115 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L115

Added line #L115 was not covered by tests
};
if !supported {
params
.custom_extensions
.push(ext::CustomExtension::from_parsed(ext)?);

Check warning on line 120 in rcgen/src/csr.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/csr.rs#L118-L120

Added lines #L118 - L120 were not covered by tests
}
}

// Not yet handled:
// * is_ca
// * extended_key_usages
// * name_constraints
// and any other extensions.

Ok(Self {
params,
public_key: PublicKey { alg, raw },
Expand Down
69 changes: 49 additions & 20 deletions rcgen/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,58 @@
#[non_exhaustive]
/// The error type of the rcgen crate
pub enum Error {
/// The provided certificate's signature algorithm
/// is incompatible with the given key pair
CertificateKeyPairMismatch,
/// The given certificate couldn't be parsed
CouldNotParseCertificate,
/// The given certificate signing request couldn't be parsed
CouldNotParseCertificationRequest,
/// The given key pair couldn't be parsed
CouldNotParseKeyPair,
/// Duplicate extension OID
DuplicateExtension(String),
/// Invalid ACME identifier extension digest length.
InvalidAcmeIdentifierLength,
/// Invalid certificate revocation list (CRL) next update.
InvalidCrlNextUpdate,
/// An IP address was provided as a byte array, but the byte array was an invalid length.
InvalidIpAddressOctetLength(usize),
#[cfg(feature = "x509-parser")]
/// Invalid subject alternative name type
InvalidNameType,
/// An IP address was provided as a byte array, but the byte array was an invalid length.
InvalidIpAddressOctetLength(usize),
/// CRL issuer specifies Key Usages that don't include cRLSign.
IssuerNotCrlSigner,
/// There is no support for generating
/// keys for the given algorithm
KeyGenerationUnavailable,
#[cfg(feature = "x509-parser")]
/// Unsupported extension requested in CSR
UnsupportedExtension,
/// The requested signature algorithm is not supported
UnsupportedSignatureAlgorithm,
/// Unspecified `ring` error
RingUnspecified,
/// The `ring` library rejected the key upon loading
RingKeyRejected(String),
/// The provided certificate's signature algorithm
/// is incompatible with the given key pair
CertificateKeyPairMismatch,
/// Time conversion related errors
Time,
#[cfg(feature = "pem")]
/// Error from the pem crate
PemError(String),
/// Error generated by a remote key operation
RemoteKeyError,
/// The `ring` library rejected the key upon loading
RingKeyRejected(String),
/// Unspecified `ring` error
RingUnspecified,
/// Time conversion related errors
Time,
/// Unsupported basic constraints extension path length in CSR
#[cfg(feature = "x509-parser")]
UnsupportedBasicConstraintsPathLen,
/// Unsupported CRL distribution point extension in CSR
#[cfg(feature = "x509-parser")]
UnsupportedCrlDistributionPoint,
#[cfg(feature = "x509-parser")]
/// Unsupported extension requested in CSR
UnsupportedExtension,
/// Unsupported general name type in CSR
#[cfg(feature = "x509-parser")]
UnsupportedGeneralName,
/// Unsupported field when generating a CSR
UnsupportedInCsr,
/// Invalid certificate revocation list (CRL) next update.
InvalidCrlNextUpdate,
/// CRL issuer specifies Key Usages that don't include cRLSign.
IssuerNotCrlSigner,
/// The requested signature algorithm is not supported
UnsupportedSignatureAlgorithm,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -91,6 +104,22 @@
f,
"CRL issuer must specify no key usage, or key usage including cRLSign"
)?,
DuplicateExtension(oid) => {
write!(f, "Extension with OID {oid} present multiple times")?

Check warning on line 108 in rcgen/src/error.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/error.rs#L107-L108

Added lines #L107 - L108 were not covered by tests
},
#[cfg(feature = "x509-parser")]
UnsupportedGeneralName => write!(f, "Unsupported general name in CSR",)?,

Check warning on line 111 in rcgen/src/error.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/error.rs#L111

Added line #L111 was not covered by tests
#[cfg(feature = "x509-parser")]
UnsupportedCrlDistributionPoint => write!(f, "Unsupported CRL distribution point in CSR",)?,

Check warning on line 113 in rcgen/src/error.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/error.rs#L113

Added line #L113 was not covered by tests
#[cfg(feature = "x509-parser")]
UnsupportedBasicConstraintsPathLen => write!(
f,
"Unsupported basic constraints extension path length constraint in CSR"
)?,
InvalidAcmeIdentifierLength => write!(
f,
"Invalid ACME identifier extension digest length. Must be 32 bytes.",
)?,

Check warning on line 122 in rcgen/src/error.rs

View check run for this annotation

Codecov / codecov/patch

rcgen/src/error.rs#L115-L122

Added lines #L115 - L122 were not covered by tests
};
Ok(())
}
Expand Down
Loading