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 SubjectPublicKeyInfo methods for cert::Cert #253

Merged
merged 3 commits into from
May 15, 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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
31 changes: 31 additions & 0 deletions src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down
100 changes: 100 additions & 0 deletions src/der.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
cpu marked this conversation as resolved.
Show resolved Hide resolved
use alloc::vec::Vec;
cpu marked this conversation as resolved.
Show resolved Hide resolved
use core::marker::PhantomData;

use crate::{error::DerTypeId, Error};
Expand Down Expand Up @@ -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<u8> {
cpu marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -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};
Expand Down
Loading