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

Add KeyUsage support to CSR generation #287

Merged
merged 5 commits into from
Aug 23, 2024
Merged
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
97 changes: 30 additions & 67 deletions rcgen/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,38 +324,9 @@ impl CertificateParams {
.key_usage()
.or(Err(Error::CouldNotParseCertificate))?
.map(|ext| ext.value);

let mut key_usages = Vec::new();
if let Some(key_usage) = key_usage {
if key_usage.digital_signature() {
key_usages.push(KeyUsagePurpose::DigitalSignature);
}
if key_usage.non_repudiation() {
key_usages.push(KeyUsagePurpose::ContentCommitment);
}
if key_usage.key_encipherment() {
key_usages.push(KeyUsagePurpose::KeyEncipherment);
}
if key_usage.data_encipherment() {
key_usages.push(KeyUsagePurpose::DataEncipherment);
}
if key_usage.key_agreement() {
key_usages.push(KeyUsagePurpose::KeyAgreement);
}
if key_usage.key_cert_sign() {
key_usages.push(KeyUsagePurpose::KeyCertSign);
}
if key_usage.crl_sign() {
key_usages.push(KeyUsagePurpose::CrlSign);
}
if key_usage.encipher_only() {
key_usages.push(KeyUsagePurpose::EncipherOnly);
}
if key_usage.decipher_only() {
key_usages.push(KeyUsagePurpose::DecipherOnly);
}
}
Ok(key_usages)
// This x509 parser stores flags in reversed bit BIT STRING order
let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits();
Ok(KeyUsagePurpose::from_u16(flags))
}
#[cfg(feature = "x509-parser")]
fn convert_x509_extended_key_usages(
Expand Down Expand Up @@ -455,6 +426,25 @@ impl CertificateParams {
Ok(result)
}

/// Write a certificate's KeyUsage as defined in RFC 5280.
fn write_key_usage(&self, writer: DERWriter) {
// RFC 5280 defines 9 key usages, which we detail in our key usage enum
// We could use std::mem::variant_count here, but it's experimental
const KEY_USAGE_BITS: usize = 9;
if self.key_usages.is_empty() {
return;
}

// "When present, conforming CAs SHOULD mark this extension as critical."
write_x509_extension(writer, oid::KEY_USAGE, true, |writer| {
// u16 is large enough to encode the largest possible key usage (two-bytes)
let bit_string = self.key_usages.iter().fold(0u16, |bit_string, key_usage| {
bit_string | key_usage.to_u16()
});
writer.write_bitvec_bytes(&bit_string.to_be_bytes(), KEY_USAGE_BITS);
});
}

fn write_extended_key_usage(&self, writer: DERWriter) {
if !self.extended_key_usages.is_empty() {
write_x509_extension(writer, oid::EXT_KEY_USAGE, false, |writer| {
Expand Down Expand Up @@ -544,7 +534,6 @@ impl CertificateParams {
);
if serial_number.is_some()
|| *is_ca != IsCa::NoCa
|| !key_usages.is_empty()
|| name_constraints.is_some()
|| !crl_distribution_points.is_empty()
|| *use_authority_key_identifier_extension
Expand All @@ -562,12 +551,17 @@ impl CertificateParams {
// Write extensions
// According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag
writer.next().write_tagged(Tag::context(0), |writer| {
if !subject_alt_names.is_empty() || !custom_extensions.is_empty() {
if !key_usages.is_empty()
|| !subject_alt_names.is_empty()
|| !custom_extensions.is_empty()
{
writer.write_sequence(|writer| {
let oid = ObjectIdentifier::from_slice(oid::PKCS_9_AT_EXTENSION_REQUEST);
writer.next().write_oid(&oid);
writer.next().write_set(|writer| {
writer.next().write_sequence(|writer| {
// Write key_usage
self.write_key_usage(writer.next());
// Write subject_alt_names
self.write_subject_alt_names(writer.next());
self.write_extended_key_usage(writer.next());
Expand All @@ -594,6 +588,7 @@ impl CertificateParams {
der: CertificateSigningRequestDer::from(der),
})
}

pub(crate) fn serialize_der_with_signer<K: PublicKeyData>(
&self,
pub_key: &K,
Expand Down Expand Up @@ -671,39 +666,7 @@ impl CertificateParams {
}

// Write standard key usage
if !self.key_usages.is_empty() {
write_x509_extension(writer.next(), oid::KEY_USAGE, true, |writer| {
let mut bits: u16 = 0;

for entry in self.key_usages.iter() {
// Map the index to a value
let index = match entry {
KeyUsagePurpose::DigitalSignature => 0,
KeyUsagePurpose::ContentCommitment => 1,
KeyUsagePurpose::KeyEncipherment => 2,
KeyUsagePurpose::DataEncipherment => 3,
KeyUsagePurpose::KeyAgreement => 4,
KeyUsagePurpose::KeyCertSign => 5,
KeyUsagePurpose::CrlSign => 6,
KeyUsagePurpose::EncipherOnly => 7,
KeyUsagePurpose::DecipherOnly => 8,
};

bits |= 1 << index;
}

// Compute the 1-based most significant bit
let msb = 16 - bits.leading_zeros();
let nb = if msb <= 8 { 1 } else { 2 };

let bits = bits.reverse_bits().to_be_bytes();

// Finally take only the bytes != 0
let bits = &bits[..nb];

writer.write_bitvec_bytes(bits, msb as usize)
});
}
self.write_key_usage(writer.next());

// Write extended key usage
if !self.extended_key_usages.is_empty() {
Expand Down
7 changes: 7 additions & 0 deletions rcgen/src/csr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ 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> {
use crate::KeyUsagePurpose;
use x509_parser::prelude::FromDer;

let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr)
.map_err(|_| Error::CouldNotParseCertificationRequest)?
.1;
Expand All @@ -117,6 +119,11 @@ impl CertificateSigningRequestParams {
if let Some(extensions) = csr.requested_extensions() {
for ext in extensions {
match ext {
x509_parser::extensions::ParsedExtension::KeyUsage(key_usage) => {
// This x509 parser stores flags in reversed bit BIT STRING order
params.key_usages =
KeyUsagePurpose::from_u16(key_usage.flags.reverse_bits());
},
x509_parser::extensions::ParsedExtension::SubjectAlternativeName(san) => {
for name in &san.general_names {
params
Expand Down
44 changes: 43 additions & 1 deletion rcgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ impl<'a> Iterator for DistinguishedNameIterator<'a> {
}

/// One of the purposes contained in the [key usage](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3) extension
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum KeyUsagePurpose {
/// digitalSignature
DigitalSignature,
Expand All @@ -435,6 +435,48 @@ pub enum KeyUsagePurpose {
DecipherOnly,
}

impl KeyUsagePurpose {
/// Encode a key usage as the value of a BIT STRING as defined by RFC 5280.
/// [`u16`] is sufficient to encode the largest possible key usage value (two bytes).
fn to_u16(&self) -> u16 {
const FLAG: u16 = 0b1000_0000_0000_0000;
FLAG >> match self {
KeyUsagePurpose::DigitalSignature => 0,
KeyUsagePurpose::ContentCommitment => 1,
KeyUsagePurpose::KeyEncipherment => 2,
KeyUsagePurpose::DataEncipherment => 3,
KeyUsagePurpose::KeyAgreement => 4,
KeyUsagePurpose::KeyCertSign => 5,
KeyUsagePurpose::CrlSign => 6,
KeyUsagePurpose::EncipherOnly => 7,
KeyUsagePurpose::DecipherOnly => 8,
}
}

/// Parse a collection of key usages from a [`u16`] representing the value
/// of a KeyUsage BIT STRING as defined by RFC 5280.
#[cfg(feature = "x509-parser")]
fn from_u16(value: u16) -> Vec<Self> {
[
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::ContentCommitment,
KeyUsagePurpose::KeyEncipherment,
KeyUsagePurpose::DataEncipherment,
KeyUsagePurpose::KeyAgreement,
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign,
KeyUsagePurpose::EncipherOnly,
KeyUsagePurpose::DecipherOnly,
]
.iter()
.filter_map(|key_usage| {
let present = key_usage.to_u16() & value != 0;
present.then_some(*key_usage)
})
.collect()
}
}

/// Method to generate key identifiers from public keys.
///
/// Key identifiers should be derived from the public key data. [RFC 7093] defines
Expand Down
30 changes: 29 additions & 1 deletion rcgen/tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ mod test_parse_other_name_alt_name {

#[cfg(feature = "x509-parser")]
mod test_csr {
use rcgen::{CertificateParams, CertificateSigningRequestParams, KeyPair};
use rcgen::{CertificateParams, CertificateSigningRequestParams, KeyPair, KeyUsagePurpose};

#[test]
fn test_csr_roundtrip() {
Expand All @@ -375,4 +375,32 @@ mod test_csr {
// Ensure algorithms match.
assert_eq!(key_pair.algorithm(), csrp.public_key.algorithm());
}

#[test]
fn test_nontrivial_csr_roundtrip() {
let key_pair = KeyPair::generate().unwrap();

// We should be able to serialize a CSR, and then parse the CSR.
let mut params = CertificateParams::default();
params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::ContentCommitment,
KeyUsagePurpose::KeyEncipherment,
KeyUsagePurpose::DataEncipherment,
KeyUsagePurpose::KeyAgreement,
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::CrlSign,
// It doesn't make sense to have both encipher and decipher only
// So we'll take this opportunity to test omitting a key usage
// KeyUsagePurpose::EncipherOnly,
KeyUsagePurpose::DecipherOnly,
];
let csr = params.serialize_request(&key_pair).unwrap();
let csrp = CertificateSigningRequestParams::from_der(csr.der()).unwrap();

// Ensure algorithms match.
assert_eq!(key_pair.algorithm(), csrp.public_key.algorithm());
// Ensure key usages match.
assert_eq!(csrp.params.key_usages, params.key_usages);
}
}
5 changes: 5 additions & 0 deletions rcgen/tests/webpki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,11 +412,16 @@ fn test_webpki_separate_ca_name_constraints() {
fn test_webpki_imported_ca() {
let (mut params, ca_key) = util::default_params();
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
params.key_usages.push(KeyUsagePurpose::KeyCertSign);
let ca_cert = params.self_signed(&ca_key).unwrap();

let ca_cert_der = ca_cert.der();

let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap();
assert_eq!(
imported_ca_cert_params.key_usages,
vec![KeyUsagePurpose::KeyCertSign]
);
let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap();

let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap();
Expand Down