617 lines
22 KiB
Rust
617 lines
22 KiB
Rust
//! X.509 objects and types
|
|
//!
|
|
//! Based on RFC5280
|
|
//!
|
|
|
|
use crate::error::{X509Error, X509Result};
|
|
use crate::objects::*;
|
|
use crate::traits::FromDer;
|
|
|
|
use der_parser::ber::{parse_ber_integer, BitStringObject, MAX_OBJECT_SIZE};
|
|
use der_parser::der::*;
|
|
use der_parser::error::*;
|
|
use der_parser::num_bigint::BigUint;
|
|
use der_parser::oid::Oid;
|
|
use der_parser::*;
|
|
use nom::branch::alt;
|
|
use nom::bytes::complete::take;
|
|
use nom::combinator::{complete, map, map_opt, map_res, opt};
|
|
use nom::multi::{many0, many1};
|
|
use nom::{Err, Offset};
|
|
use oid_registry::*;
|
|
use rusticata_macros::newtype_enum;
|
|
use std::convert::TryFrom;
|
|
use std::fmt;
|
|
use std::iter::FromIterator;
|
|
|
|
/// The version of the encoded certificate.
|
|
///
|
|
/// When extensions are used, as expected in this profile, version MUST be 3
|
|
/// (value is `2`). If no extensions are present, but a UniqueIdentifier
|
|
/// is present, the version SHOULD be 2 (value is `1`); however, the
|
|
/// version MAY be 3. If only basic fields are present, the version
|
|
/// SHOULD be 1 (the value is omitted from the certificate as the default
|
|
/// value); however, the version MAY be 2 or 3.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub struct X509Version(pub u32);
|
|
|
|
impl X509Version {
|
|
pub(crate) fn from_der_required(i: &[u8]) -> X509Result<X509Version> {
|
|
let (rem, hdr) =
|
|
der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidVersion)))?;
|
|
match hdr.tag.0 {
|
|
0 => {
|
|
map(parse_der_u32, X509Version)(rem).or(Err(Err::Error(X509Error::InvalidVersion)))
|
|
}
|
|
_ => Ok((&rem[1..], X509Version::V1)),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse [0] EXPLICIT Version DEFAULT v1
|
|
impl<'a> FromDer<'a> for X509Version {
|
|
fn from_der(i: &'a [u8]) -> X509Result<'a, Self> {
|
|
let (rem, hdr) =
|
|
der_read_element_header(i).or(Err(Err::Error(X509Error::InvalidVersion)))?;
|
|
match hdr.tag.0 {
|
|
0 => {
|
|
map(parse_der_u32, X509Version)(rem).or(Err(Err::Error(X509Error::InvalidVersion)))
|
|
}
|
|
_ => Ok((i, X509Version::V1)),
|
|
}
|
|
}
|
|
}
|
|
|
|
newtype_enum! {
|
|
impl display X509Version {
|
|
V1 = 0,
|
|
V2 = 1,
|
|
V3 = 2,
|
|
}
|
|
}
|
|
|
|
/// A generic attribute type and value
|
|
///
|
|
/// These objects are used as [`RelativeDistinguishedName`] components.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct AttributeTypeAndValue<'a> {
|
|
attr_type: Oid<'a>,
|
|
attr_value: DerObject<'a>, // ANY -- DEFINED BY AttributeType
|
|
}
|
|
|
|
impl<'a> AttributeTypeAndValue<'a> {
|
|
/// Builds a new `AttributeTypeAndValue`
|
|
#[inline]
|
|
pub const fn new(attr_type: Oid<'a>, attr_value: DerObject<'a>) -> Self {
|
|
AttributeTypeAndValue {
|
|
attr_type,
|
|
attr_value,
|
|
}
|
|
}
|
|
|
|
/// Returns the attribute type
|
|
#[inline]
|
|
pub const fn attr_type(&self) -> &Oid {
|
|
&self.attr_type
|
|
}
|
|
|
|
/// Returns the attribute value, as raw `DerObject`
|
|
#[inline]
|
|
pub const fn attr_value(&self) -> &DerObject {
|
|
&self.attr_value
|
|
}
|
|
|
|
/// Attempt to get the content as `str`.
|
|
/// This can fail if the object does not contain a string type.
|
|
///
|
|
/// Note: the [`TryFrom`] trait is implemented for `&str`, so this is equivalent to `attr.try_into()`.
|
|
///
|
|
/// Only NumericString, PrintableString, UTF8String and IA5String
|
|
/// are considered here. Other string types can be read using `as_slice`.
|
|
#[inline]
|
|
pub fn as_str(&self) -> Result<&'a str, X509Error> {
|
|
self.attr_value.as_str().map_err(|e| e.into())
|
|
}
|
|
|
|
/// Attempt to get the content as a slice.
|
|
/// This can fail if the object does not contain a type directly equivalent to a slice (e.g a
|
|
/// sequence).
|
|
///
|
|
/// Note: the [`TryFrom`] trait is implemented for `&[u8]`, so this is equivalent to `attr.try_into()`.
|
|
#[inline]
|
|
pub fn as_slice(&self) -> Result<&'a [u8], X509Error> {
|
|
self.attr_value.as_slice().map_err(|e| e.into())
|
|
}
|
|
}
|
|
|
|
impl<'a> TryFrom<AttributeTypeAndValue<'a>> for &'a str {
|
|
type Error = X509Error;
|
|
|
|
fn try_from(value: AttributeTypeAndValue<'a>) -> Result<Self, Self::Error> {
|
|
value.attr_value.as_str().map_err(|e| e.into())
|
|
}
|
|
}
|
|
|
|
impl<'a> TryFrom<AttributeTypeAndValue<'a>> for &'a [u8] {
|
|
type Error = X509Error;
|
|
|
|
fn try_from(value: AttributeTypeAndValue<'a>) -> Result<Self, Self::Error> {
|
|
value.attr_value.as_slice().map_err(|e| e.into())
|
|
}
|
|
}
|
|
|
|
// AttributeTypeAndValue ::= SEQUENCE {
|
|
// type AttributeType,
|
|
// value AttributeValue }
|
|
impl<'a> FromDer<'a> for AttributeTypeAndValue<'a> {
|
|
fn from_der(i: &'a [u8]) -> X509Result<'a, Self> {
|
|
parse_der_sequence_defined_g(|i, _| {
|
|
let (i, attr_type) = map_res(parse_der_oid, |x: DerObject<'a>| x.as_oid_val())(i)
|
|
.or(Err(X509Error::InvalidX509Name))?;
|
|
let (i, attr_value) = parse_attribute_value(i).or(Err(X509Error::InvalidX509Name))?;
|
|
let attr = AttributeTypeAndValue::new(attr_type, attr_value);
|
|
Ok((i, attr))
|
|
})(i)
|
|
}
|
|
}
|
|
|
|
// AttributeValue ::= ANY -- DEFINED BY AttributeType
|
|
#[inline]
|
|
fn parse_attribute_value(i: &[u8]) -> DerResult {
|
|
alt((parse_der, parse_malformed_string))(i)
|
|
}
|
|
|
|
fn parse_malformed_string(i: &[u8]) -> DerResult {
|
|
let (rem, hdr) = der_read_element_header(i)?;
|
|
let len = hdr.len.primitive()?;
|
|
if len > MAX_OBJECT_SIZE {
|
|
return Err(nom::Err::Error(BerError::InvalidLength));
|
|
}
|
|
match hdr.tag {
|
|
DerTag::PrintableString => {
|
|
// if we are in this function, the PrintableString could not be validated.
|
|
// Accept it without validating charset, because some tools do not respect the charset
|
|
// restrictions (for ex. they use '*' while explicingly disallowed)
|
|
let (rem, data) = take(len as usize)(rem)?;
|
|
let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?;
|
|
let content = DerObjectContent::PrintableString(s);
|
|
let obj = DerObject::from_header_and_content(hdr, content);
|
|
Ok((rem, obj))
|
|
}
|
|
_ => Err(nom::Err::Error(BerError::InvalidTag)),
|
|
}
|
|
}
|
|
|
|
/// A Relative Distinguished Name element.
|
|
///
|
|
/// These objects are used as [`X509Name`] components.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct RelativeDistinguishedName<'a> {
|
|
set: Vec<AttributeTypeAndValue<'a>>,
|
|
}
|
|
|
|
impl<'a> RelativeDistinguishedName<'a> {
|
|
/// Builds a new `RelativeDistinguishedName`
|
|
#[inline]
|
|
pub const fn new(set: Vec<AttributeTypeAndValue<'a>>) -> Self {
|
|
RelativeDistinguishedName { set }
|
|
}
|
|
|
|
/// Return an iterator over the components of this object
|
|
pub fn iter(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.set.iter()
|
|
}
|
|
}
|
|
|
|
impl<'a> FromIterator<AttributeTypeAndValue<'a>> for RelativeDistinguishedName<'a> {
|
|
fn from_iter<T: IntoIterator<Item = AttributeTypeAndValue<'a>>>(iter: T) -> Self {
|
|
let set = iter.into_iter().collect();
|
|
RelativeDistinguishedName { set }
|
|
}
|
|
}
|
|
|
|
impl<'a> FromDer<'a> for RelativeDistinguishedName<'a> {
|
|
fn from_der(i: &'a [u8]) -> X509Result<Self> {
|
|
parse_der_set_defined_g(|i, _| {
|
|
let (i, set) = many1(complete(AttributeTypeAndValue::from_der))(i)?;
|
|
let rdn = RelativeDistinguishedName { set };
|
|
Ok((i, rdn))
|
|
})(i)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SubjectPublicKeyInfo<'a> {
|
|
pub algorithm: AlgorithmIdentifier<'a>,
|
|
pub subject_public_key: BitStringObject<'a>,
|
|
/// A raw unparsed PKIX, ASN.1 DER form (see RFC 5280, Section 4.1).
|
|
pub raw: &'a [u8],
|
|
}
|
|
|
|
impl<'a> FromDer<'a> for SubjectPublicKeyInfo<'a> {
|
|
/// Parse the SubjectPublicKeyInfo struct portion of a DER-encoded X.509 Certificate
|
|
fn from_der(i: &'a [u8]) -> X509Result<Self> {
|
|
let start_i = i;
|
|
parse_der_sequence_defined_g(move |i, _| {
|
|
let (i, algorithm) = AlgorithmIdentifier::from_der(i)?;
|
|
let (i, subject_public_key) = map_res(parse_der_bitstring, |x: DerObject<'a>| {
|
|
match x.content {
|
|
DerObjectContent::BitString(_, ref b) => Ok(b.to_owned()), // XXX padding ignored
|
|
_ => Err(BerError::BerTypeError),
|
|
}
|
|
})(i)
|
|
.or(Err(X509Error::InvalidSPKI))?;
|
|
let len = start_i.offset(i);
|
|
let raw = &start_i[..len];
|
|
let spki = SubjectPublicKeyInfo {
|
|
algorithm,
|
|
subject_public_key,
|
|
raw,
|
|
};
|
|
Ok((i, spki))
|
|
})(i)
|
|
}
|
|
}
|
|
|
|
/// Algorithm identifier
|
|
///
|
|
/// An algorithm identifier is defined by the following ASN.1 structure:
|
|
///
|
|
/// <pre>
|
|
/// AlgorithmIdentifier ::= SEQUENCE {
|
|
/// algorithm OBJECT IDENTIFIER,
|
|
/// parameters ANY DEFINED BY algorithm OPTIONAL }
|
|
/// </pre>
|
|
///
|
|
/// The algorithm identifier is used to identify a cryptographic
|
|
/// algorithm. The OBJECT IDENTIFIER component identifies the algorithm
|
|
/// (such as DSA with SHA-1). The contents of the optional parameters
|
|
/// field will vary according to the algorithm identified.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct AlgorithmIdentifier<'a> {
|
|
pub algorithm: Oid<'a>,
|
|
pub parameters: Option<DerObject<'a>>,
|
|
}
|
|
|
|
impl<'a> FromDer<'a> for AlgorithmIdentifier<'a> {
|
|
#[allow(clippy::needless_lifetimes)]
|
|
fn from_der(i: &[u8]) -> X509Result<AlgorithmIdentifier> {
|
|
parse_der_sequence_defined_g(|i, _| {
|
|
let (i, algorithm) = map_res(parse_der_oid, |x| x.as_oid_val())(i)
|
|
.or(Err(X509Error::InvalidAlgorithmIdentifier))?;
|
|
let (i, parameters) =
|
|
opt(complete(parse_der))(i).or(Err(X509Error::InvalidAlgorithmIdentifier))?;
|
|
|
|
let alg = AlgorithmIdentifier {
|
|
algorithm,
|
|
parameters,
|
|
};
|
|
Ok((i, alg))
|
|
})(i)
|
|
}
|
|
}
|
|
|
|
/// X.509 Name (as used in `Issuer` and `Subject` fields)
|
|
///
|
|
/// The Name describes a hierarchical name composed of attributes, such
|
|
/// as country name, and corresponding values, such as US. The type of
|
|
/// the component AttributeValue is determined by the AttributeType; in
|
|
/// general it will be a DirectoryString.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct X509Name<'a> {
|
|
pub(crate) rdn_seq: Vec<RelativeDistinguishedName<'a>>,
|
|
pub(crate) raw: &'a [u8],
|
|
}
|
|
|
|
impl<'a> fmt::Display for X509Name<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match x509name_to_string(&self.rdn_seq, oid_registry()) {
|
|
Ok(o) => write!(f, "{}", o),
|
|
Err(_) => write!(f, "<X509Error: Invalid X.509 name>"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> X509Name<'a> {
|
|
/// Builds a new `X509Name` from the provided elements.
|
|
#[inline]
|
|
pub const fn new(rdn_seq: Vec<RelativeDistinguishedName<'a>>, raw: &'a [u8]) -> Self {
|
|
X509Name { rdn_seq, raw }
|
|
}
|
|
|
|
/// Attempt to format the current name, using the given registry to convert OIDs to strings.
|
|
///
|
|
/// Note: a default registry is provided with this crate, and is returned by the
|
|
/// [`oid_registry()`] method.
|
|
pub fn to_string_with_registry(&self, oid_registry: &OidRegistry) -> Result<String, X509Error> {
|
|
x509name_to_string(&self.rdn_seq, oid_registry)
|
|
}
|
|
|
|
// Not using the AsRef trait, as that would not give back the full 'a lifetime
|
|
pub fn as_raw(&self) -> &'a [u8] {
|
|
self.raw
|
|
}
|
|
|
|
/// Return an iterator over the `RelativeDistinguishedName` components of the name
|
|
pub fn iter(&self) -> impl Iterator<Item = &RelativeDistinguishedName<'a>> {
|
|
self.rdn_seq.iter()
|
|
}
|
|
|
|
/// Return an iterator over the `RelativeDistinguishedName` components of the name
|
|
pub fn iter_rdn(&self) -> impl Iterator<Item = &RelativeDistinguishedName<'a>> {
|
|
self.rdn_seq.iter()
|
|
}
|
|
|
|
/// Return an iterator over the attribute types and values of the name
|
|
pub fn iter_attributes(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.rdn_seq.iter().map(|rdn| rdn.set.iter()).flatten()
|
|
}
|
|
|
|
/// Return an iterator over the components identified by the given OID
|
|
///
|
|
/// The type of the component AttributeValue is determined by the AttributeType; in
|
|
/// general it will be a DirectoryString.
|
|
///
|
|
/// Attributes with same OID may be present multiple times, so the returned object is
|
|
/// an iterator.
|
|
/// Expected number of objects in this iterator are
|
|
/// - 0: not found
|
|
/// - 1: present once (common case)
|
|
/// - 2 or more: attribute is present multiple times
|
|
pub fn iter_by_oid(&self, oid: &Oid<'a>) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
// this is necessary, otherwise rustc complains
|
|
// that caller creates a temporary value for reference (for ex.
|
|
// `self.iter_by_oid(&OID_X509_LOCALITY_NAME)`
|
|
// )
|
|
let oid = oid.clone();
|
|
self.iter_attributes()
|
|
.filter(move |obj| obj.attr_type == oid)
|
|
}
|
|
|
|
/// Return an iterator over the `CommonName` attributes of the X.509 Name.
|
|
///
|
|
/// Returned iterator can be empty if there are no `CommonName` attributes.
|
|
/// If you expect only one `CommonName` to be present, then using `next()` will
|
|
/// get an `Option<&AttributeTypeAndValue>`.
|
|
///
|
|
/// A common operation is to extract the `CommonName` as a string.
|
|
///
|
|
/// ```
|
|
/// use x509_parser::x509::X509Name;
|
|
///
|
|
/// fn get_first_cn_as_str<'a>(name: &'a X509Name<'_>) -> Option<&'a str> {
|
|
/// name.iter_common_name()
|
|
/// .next()
|
|
/// .and_then(|cn| cn.as_str().ok())
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Note that there are multiple reasons for failure or incorrect behavior, for ex. if
|
|
/// the attribute is present multiple times, or is not a UTF-8 encoded string (it can be
|
|
/// UTF-16, or even an OCTETSTRING according to the standard).
|
|
pub fn iter_common_name(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_COMMON_NAME)
|
|
}
|
|
|
|
/// Return an iterator over the `Country` attributes of the X.509 Name.
|
|
pub fn iter_country(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_COUNTRY_NAME)
|
|
}
|
|
|
|
/// Return an iterator over the `Organization` attributes of the X.509 Name.
|
|
pub fn iter_organization(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_ORGANIZATION_NAME)
|
|
}
|
|
|
|
/// Return an iterator over the `OrganizationalUnit` attributes of the X.509 Name.
|
|
pub fn iter_organizational_unit(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_ORGANIZATIONAL_UNIT)
|
|
}
|
|
|
|
/// Return an iterator over the `StateOrProvinceName` attributes of the X.509 Name.
|
|
pub fn iter_state_or_province(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_STATE_OR_PROVINCE_NAME)
|
|
}
|
|
|
|
/// Return an iterator over the `Locality` attributes of the X.509 Name.
|
|
pub fn iter_locality(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_X509_LOCALITY_NAME)
|
|
}
|
|
|
|
/// Return an iterator over the `EmailAddress` attributes of the X.509 Name.
|
|
pub fn iter_email(&self) -> impl Iterator<Item = &AttributeTypeAndValue<'a>> {
|
|
self.iter_by_oid(&OID_PKCS9_EMAIL_ADDRESS)
|
|
}
|
|
}
|
|
|
|
impl<'a> FromIterator<RelativeDistinguishedName<'a>> for X509Name<'a> {
|
|
fn from_iter<T: IntoIterator<Item = RelativeDistinguishedName<'a>>>(iter: T) -> Self {
|
|
let rdn_seq = iter.into_iter().collect();
|
|
X509Name { rdn_seq, raw: &[] }
|
|
}
|
|
}
|
|
|
|
impl<'a> From<X509Name<'a>> for Vec<RelativeDistinguishedName<'a>> {
|
|
fn from(name: X509Name<'a>) -> Self {
|
|
name.rdn_seq
|
|
}
|
|
}
|
|
|
|
impl<'a> FromDer<'a> for X509Name<'a> {
|
|
/// Parse the X.501 type Name, used for ex in issuer and subject of a X.509 certificate
|
|
fn from_der(i: &'a [u8]) -> X509Result<Self> {
|
|
let start_i = i;
|
|
parse_der_sequence_defined_g(move |i, _| {
|
|
let (i, rdn_seq) = many0(complete(RelativeDistinguishedName::from_der))(i)?;
|
|
let len = start_i.offset(i);
|
|
let name = X509Name {
|
|
rdn_seq,
|
|
raw: &start_i[..len],
|
|
};
|
|
Ok((i, name))
|
|
})(i)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub struct ReasonCode(pub u8);
|
|
|
|
newtype_enum! {
|
|
impl display ReasonCode {
|
|
Unspecified = 0,
|
|
KeyCompromise = 1,
|
|
CACompromise = 2,
|
|
AffiliationChanged = 3,
|
|
Superseded = 4,
|
|
CessationOfOperation = 5,
|
|
CertificateHold = 6,
|
|
// value 7 is not used
|
|
RemoveFromCRL = 8,
|
|
PrivilegeWithdrawn = 9,
|
|
AACompromise = 10,
|
|
}
|
|
}
|
|
|
|
impl Default for ReasonCode {
|
|
fn default() -> Self {
|
|
ReasonCode::Unspecified
|
|
}
|
|
}
|
|
|
|
// Attempt to convert attribute to string. If type is not a string, return value is the hex
|
|
// encoding of the attribute value
|
|
fn attribute_value_to_string(attr: &DerObject, _attr_type: &Oid) -> Result<String, X509Error> {
|
|
match attr.content {
|
|
DerObjectContent::NumericString(s)
|
|
| DerObjectContent::PrintableString(s)
|
|
| DerObjectContent::UTF8String(s)
|
|
| DerObjectContent::IA5String(s) => Ok(s.to_owned()),
|
|
_ => {
|
|
// type is not a string, get slice and convert it to base64
|
|
attr.as_slice()
|
|
.map(|s| base64::encode(s))
|
|
.or(Err(X509Error::InvalidX509Name))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Convert a DER representation of a X.509 name to a human-readable string
|
|
///
|
|
/// RDNs are separated with ","
|
|
/// Multiple RDNs are separated with "+"
|
|
///
|
|
/// Attributes that cannot be represented by a string are hex-encoded
|
|
fn x509name_to_string(
|
|
rdn_seq: &[RelativeDistinguishedName],
|
|
oid_registry: &OidRegistry,
|
|
) -> Result<String, X509Error> {
|
|
rdn_seq.iter().fold(Ok(String::new()), |acc, rdn| {
|
|
acc.and_then(|mut _vec| {
|
|
rdn.set
|
|
.iter()
|
|
.fold(Ok(String::new()), |acc2, attr| {
|
|
acc2.and_then(|mut _vec2| {
|
|
let val_str = attribute_value_to_string(&attr.attr_value, &attr.attr_type)?;
|
|
// look ABBREV, and if not found, use shortname
|
|
let abbrev = match oid2abbrev(&attr.attr_type, oid_registry) {
|
|
Ok(s) => String::from(s),
|
|
_ => format!("{:?}", attr.attr_type),
|
|
};
|
|
let rdn = format!("{}={}", abbrev, val_str);
|
|
match _vec2.len() {
|
|
0 => Ok(rdn),
|
|
_ => Ok(_vec2 + " + " + &rdn),
|
|
}
|
|
})
|
|
})
|
|
.map(|v| match _vec.len() {
|
|
0 => v,
|
|
_ => _vec + ", " + &v,
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result<BitStringObject> {
|
|
map_res(parse_der_bitstring, |x: DerObject| {
|
|
match x.content {
|
|
DerObjectContent::BitString(_, ref b) => Ok(b.to_owned()), // XXX padding ignored
|
|
_ => Err(BerError::BerTypeError),
|
|
}
|
|
})(i)
|
|
.or(Err(Err::Error(X509Error::InvalidSignatureValue)))
|
|
}
|
|
|
|
pub(crate) fn parse_serial(i: &[u8]) -> X509Result<(&[u8], BigUint)> {
|
|
// This should be parse_der_integer, but some certificates encode leading zeroes
|
|
map_opt(parse_ber_integer, get_serial_info)(i).map_err(|_| X509Error::InvalidSerial.into())
|
|
}
|
|
|
|
fn get_serial_info(o: DerObject) -> Option<(&[u8], BigUint)> {
|
|
// RFC 5280 4.1.2.2: "The serial number MUST be a positive integer"
|
|
// however, many CAs do not respect this and send integers with MSB set,
|
|
// so we do not use `as_biguint()`
|
|
let slice = o.as_slice().ok()?;
|
|
let big = BigUint::from_bytes_be(slice);
|
|
|
|
Some((slice, big))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use der_parser::ber::BerObjectContent;
|
|
use der_parser::oid;
|
|
|
|
#[test]
|
|
fn test_x509_name() {
|
|
let name = X509Name {
|
|
rdn_seq: vec![
|
|
RelativeDistinguishedName {
|
|
set: vec![AttributeTypeAndValue {
|
|
attr_type: oid!(2.5.4 .6), // countryName
|
|
attr_value: DerObject::from_obj(BerObjectContent::PrintableString("FR")),
|
|
}],
|
|
},
|
|
RelativeDistinguishedName {
|
|
set: vec![AttributeTypeAndValue {
|
|
attr_type: oid!(2.5.4 .8), // stateOrProvinceName
|
|
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
|
|
"Some-State",
|
|
)),
|
|
}],
|
|
},
|
|
RelativeDistinguishedName {
|
|
set: vec![AttributeTypeAndValue {
|
|
attr_type: oid!(2.5.4 .10), // organizationName
|
|
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
|
|
"Internet Widgits Pty Ltd",
|
|
)),
|
|
}],
|
|
},
|
|
RelativeDistinguishedName {
|
|
set: vec![
|
|
AttributeTypeAndValue {
|
|
attr_type: oid!(2.5.4 .3), // CN
|
|
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
|
|
"Test1",
|
|
)),
|
|
},
|
|
AttributeTypeAndValue {
|
|
attr_type: oid!(2.5.4 .3), // CN
|
|
attr_value: DerObject::from_obj(BerObjectContent::PrintableString(
|
|
"Test2",
|
|
)),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
raw: &[], // incorrect, but enough for testing
|
|
};
|
|
assert_eq!(
|
|
name.to_string(),
|
|
"C=FR, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Test1 + CN=Test2"
|
|
);
|
|
}
|
|
}
|