From e08ec4797d26c305e1620aeef37604ab8ab98403 Mon Sep 17 00:00:00 2001 From: Lukas Velikov Date: Tue, 7 May 2024 23:16:53 -0400 Subject: [PATCH 1/3] Update pki-types to v1.7.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e33a198..a9ebe1f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,9 +436,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" diff --git a/Cargo.toml b/Cargo.toml index 104e089c..9becbdf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ std = ["alloc", "pki-types/std"] [dependencies] aws-lc-rs = { version = "1", optional = true, default-features = false, features = ["aws-lc-sys"] } -pki-types = { package = "rustls-pki-types", version = "1.2", default-features = false } +pki-types = { package = "rustls-pki-types", version = "1.7", default-features = false } ring = { version = "0.17", default-features = false, optional = true } untrusted = "0.9" From dfbb58b7af4178bf0f8500ee460933633550a56f Mon Sep 17 00:00:00 2001 From: Lukas Velikov Date: Mon, 6 May 2024 21:55:14 -0400 Subject: [PATCH 2/3] Add der::asn1_wrap function lifted from rustls::x509 --- src/der.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/der.rs b/src/der.rs index aebc5133..0536cc92 100644 --- a/src/der.rs +++ b/src/der.rs @@ -12,6 +12,8 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::marker::PhantomData; use crate::{error::DerTypeId, Error}; @@ -212,6 +214,47 @@ pub(crate) fn read_tag_and_get_value_limited<'a>( Ok((tag, inner)) } +/// Prepend `bytes` with the given ASN.1 [`Tag`] and appropriately encoded length byte(s). +/// Useful for "adding back" ASN.1 bytes to parsed content. +#[cfg(feature = "alloc")] +#[allow(clippy::as_conversions)] +pub(crate) fn asn1_wrap(tag: Tag, bytes: &[u8]) -> Vec { + let len = bytes.len(); + // The length is encoded differently depending on how many bytes there are + if len < SHORT_FORM_LEN_MAX.into() { + // Short form: the length is encoded using a single byte + // Contents: Tag byte, single length byte, and passed bytes + let mut ret = Vec::with_capacity(2 + len); + ret.push(tag.into()); // Tag byte + ret.push(len as u8); // Single length byte + ret.extend_from_slice(bytes); // Passed bytes + ret + } else { + // Long form: The length is encoded using multiple bytes + // Contents: Tag byte, number-of-length-bytes byte, length bytes, and passed bytes + // The first byte indicates how many more bytes will be used to encode the length + // First, get a big-endian representation of the byte slice's length + let size = len.to_be_bytes(); + // Find the number of leading empty bytes in that representation + // This will determine the smallest number of bytes we need to encode the length + let leading_zero_bytes = size + .iter() + .position(|&byte| byte != 0) + .unwrap_or(size.len()); + assert!(leading_zero_bytes < size.len()); + // Number of bytes used - number of not needed bytes = smallest number needed + let encoded_bytes = size.len() - leading_zero_bytes; + let mut ret = Vec::with_capacity(2 + encoded_bytes + len); + // Indicate this is a number-of-length-bytes byte by setting the high order bit + let number_of_length_bytes_byte = SHORT_FORM_LEN_MAX + encoded_bytes as u8; + ret.push(tag.into()); // Tag byte + ret.push(number_of_length_bytes_byte); // Number-of-length-bytes byte + ret.extend_from_slice(&size[leading_zero_bytes..]); // Length bytes + ret.extend_from_slice(bytes); // Passed bytes + ret + } +} + // Long-form DER encoded lengths of two bytes can express lengths up to the following limit. // // The upstream ring::io::der::read_tag_and_get_value() function limits itself to up to two byte @@ -425,6 +468,63 @@ mod tests { use super::DerTypeId; use std::prelude::v1::*; + #[cfg(feature = "alloc")] + #[test] + fn test_asn1_wrap() { + // Prepend stuff to `bytes` to put it in a DER SEQUENCE. + let wrap_in_sequence = |bytes: &[u8]| super::asn1_wrap(super::Tag::Sequence, bytes); + + // Empty slice + assert_eq!(vec![0x30, 0x00], wrap_in_sequence(&[])); + + // Small size + assert_eq!( + vec![0x30, 0x04, 0x00, 0x11, 0x22, 0x33], + wrap_in_sequence(&[0x00, 0x11, 0x22, 0x33]) + ); + + // Medium size + let mut val = Vec::new(); + val.resize(255, 0x12); + assert_eq!( + vec![0x30, 0x81, 0xff, 0x12, 0x12, 0x12], + wrap_in_sequence(&val)[..6] + ); + + // Large size + let mut val = Vec::new(); + val.resize(4660, 0x12); + wrap_in_sequence(&val); + assert_eq!( + vec![0x30, 0x82, 0x12, 0x34, 0x12, 0x12], + wrap_in_sequence(&val)[..6] + ); + + // Huge size + let mut val = Vec::new(); + val.resize(0xffff, 0x12); + let result = wrap_in_sequence(&val); + assert_eq!(vec![0x30, 0x82, 0xff, 0xff, 0x12, 0x12], result[..6]); + assert_eq!(result.len(), 0xffff + 4); + + // Gigantic size + let mut val = Vec::new(); + val.resize(0x100000, 0x12); + let result = wrap_in_sequence(&val); + assert_eq!(vec![0x30, 0x83, 0x10, 0x00, 0x00, 0x12, 0x12], result[..7]); + assert_eq!(result.len(), 0x100000 + 5); + + // Ludicrous size + let mut val = Vec::new(); + val.resize(0x1000000, 0x12); + let result = wrap_in_sequence(&val); + assert_eq!( + vec![0x30, 0x84, 0x01, 0x00, 0x00, 0x00, 0x12, 0x12], + result[..8] + ); + assert_eq!(result.len(), 0x1000000 + 6); + } + #[test] fn test_optional_boolean() { use super::{Error, FromDer}; From a1768abfdee0da92cb6aec89148906530d18bda5 Mon Sep 17 00:00:00 2001 From: Lukas Velikov Date: Mon, 6 May 2024 21:56:24 -0400 Subject: [PATCH 3/3] Add public and crate-private SPKI methods on cert::Cert --- src/cert.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/cert.rs b/src/cert.rs index 4e280b13..d60a7911 100644 --- a/src/cert.rs +++ b/src/cert.rs @@ -12,6 +12,8 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +#[cfg(feature = "alloc")] +use pki_types::SubjectPublicKeyInfoDer; use pki_types::{CertificateDer, DnsName}; use crate::der::{self, DerIterator, FromDer, Tag, CONSTRUCTED, CONTEXT_SPECIFIC}; @@ -173,6 +175,17 @@ impl<'a> Cert<'a> { self.subject.as_slice_less_safe() } + /// Get the RFC 5280-compliant [`SubjectPublicKeyInfoDer`] (SPKI) of this [`Cert`]. + #[cfg(feature = "alloc")] + pub fn subject_public_key_info(&self) -> SubjectPublicKeyInfoDer { + // Our SPKI representation contains only the content of the RFC 5280 SEQUENCE + // So we wrap the SPKI contents back into a properly-encoded ASN.1 SEQUENCE + SubjectPublicKeyInfoDer::from(der::asn1_wrap( + Tag::Sequence, + self.spki.as_slice_less_safe(), + )) + } + /// Returns an iterator over the certificate's cRLDistributionPoints extension values, if any. pub(crate) fn crl_distribution_points( &self, @@ -365,6 +378,24 @@ mod tests { ) } + #[cfg(feature = "alloc")] + #[test] + fn test_spki_read() { + let ee = include_bytes!("../tests/ed25519/ee.der"); + let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate"); + // How did I get this lovely string of hex bytes? + // openssl x509 -in tests/ed25519/ee.der -pubkey -noout > pubkey.pem + // openssl ec -pubin -in pubkey.pem -outform DER -out pubkey.der + // xxd -plain -cols 1 pubkey.der + let expected_spki = [ + 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xfe, 0x5a, + 0x1e, 0x36, 0x6c, 0x17, 0x27, 0x5b, 0xf1, 0x58, 0x1e, 0x3a, 0x0e, 0xe6, 0x56, 0x29, + 0x8d, 0x9e, 0x1b, 0x3f, 0xd3, 0x3f, 0x96, 0x46, 0xef, 0xbf, 0x04, 0x6b, 0xc7, 0x3d, + 0x47, 0x5c, + ]; + assert_eq!(expected_spki, *cert.subject_public_key_info()) + } + #[test] #[cfg(feature = "alloc")] fn test_crl_distribution_point_netflix() {