diff --git a/src/cri_attributes.rs b/src/cri_attributes.rs index 76c4223..1092200 100644 --- a/src/cri_attributes.rs +++ b/src/cri_attributes.rs @@ -30,17 +30,25 @@ impl<'a> FromDer<'a, X509Error> for X509CriAttribute<'a> { let (i, parsed_attribute) = crate::cri_attributes::parser::parse_attribute(i, &oid) .map_err(|_| Err::Error(Error::BerValueError))?; - let ext = X509CriAttribute { + let attribute = X509CriAttribute { oid, value: &value_start[..value_start.len() - i.len()], parsed_attribute, }; - Ok((i, ext)) + Ok((i, attribute)) }) .map_err(|_| X509Error::InvalidAttributes.into()) } } +impl<'a> X509CriAttribute<'a> { + /// Return the attribute type or `UnsupportedAttribute` if the attribute is unknown. + #[inline] + pub fn parsed_attribute(&self) -> &ParsedCriAttribute<'a> { + &self.parsed_attribute + } +} + /// Section 3.1 of rfc 5272 #[derive(Clone, Debug, PartialEq)] pub struct ExtensionRequest<'a> { @@ -53,16 +61,25 @@ impl<'a> FromDer<'a, X509Error> for ExtensionRequest<'a> { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ChallengePassword(pub String); + /// Attributes for Certification Request #[derive(Clone, Debug, PartialEq)] pub enum ParsedCriAttribute<'a> { + ChallengePassword(ChallengePassword), ExtensionRequest(ExtensionRequest<'a>), UnsupportedAttribute, } pub(crate) mod parser { use crate::cri_attributes::*; + use der_parser::der::{ + parse_der_bmpstring, parse_der_printablestring, parse_der_t61string, + parse_der_universalstring, parse_der_utf8string, + }; use lazy_static::lazy_static; + use nom::branch::alt; use nom::combinator::map; type AttrParser = fn(&[u8]) -> X509Result; @@ -76,7 +93,12 @@ pub(crate) mod parser { } let mut m = HashMap::new(); - add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_ext); + add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_attr); + add!( + m, + OID_PKCS9_CHALLENGE_PASSWORD, + parse_challenge_password_attr + ); m }; } @@ -99,12 +121,52 @@ pub(crate) mod parser { .map(|(i, extensions)| (i, ExtensionRequest { extensions })) } - fn parse_extension_request_ext(i: &[u8]) -> X509Result { + fn parse_extension_request_attr(i: &[u8]) -> X509Result { map( parse_extension_request, ParsedCriAttribute::ExtensionRequest, )(i) } + + // RFC 2985, 5.4.1 Challenge password + // challengePassword ATTRIBUTE ::= { + // WITH SYNTAX DirectoryString {pkcs-9-ub-challengePassword} + // EQUALITY MATCHING RULE caseExactMatch + // SINGLE VALUE TRUE + // ID pkcs-9-at-challengePassword + // } + // RFC 5280, 4.1.2.4. Issuer + // DirectoryString ::= CHOICE { + // teletexString TeletexString (SIZE (1..MAX)), + // printableString PrintableString (SIZE (1..MAX)), + // universalString UniversalString (SIZE (1..MAX)), + // utf8String UTF8String (SIZE (1..MAX)), + // bmpString BMPString (SIZE (1..MAX)) + // } + pub(super) fn parse_challenge_password(i: &[u8]) -> X509Result { + let (rem, obj) = match alt(( + parse_der_utf8string, + parse_der_printablestring, + parse_der_universalstring, + parse_der_bmpstring, + parse_der_t61string, // == teletexString + ))(i) + { + Ok((rem, obj)) => (rem, obj), + Err(_) => return Err(Err::Error(X509Error::InvalidAttributes)), + }; + match obj.content.as_str() { + Ok(s) => Ok((rem, ChallengePassword(s.to_string()))), + Err(_) => Err(Err::Error(X509Error::InvalidAttributes)), + } + } + + fn parse_challenge_password_attr(i: &[u8]) -> X509Result { + map( + parse_challenge_password, + ParsedCriAttribute::ChallengePassword, + )(i) + } } pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result> { diff --git a/tests/readcsr.rs b/tests/readcsr.rs index a1efe5d..4235726 100644 --- a/tests/readcsr.rs +++ b/tests/readcsr.rs @@ -1,10 +1,13 @@ -use oid_registry::{OID_PKCS1_SHA256WITHRSA, OID_SIG_ECDSA_WITH_SHA256, OID_X509_COMMON_NAME}; +use asn1_rs::Set; +use oid_registry::{ + OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_SIG_ECDSA_WITH_SHA256, + OID_X509_COMMON_NAME, +}; use x509_parser::prelude::*; const CSR_DATA_EMPTY_ATTRIB: &[u8] = include_bytes!("../assets/csr-empty-attributes.csr"); const CSR_DATA: &[u8] = include_bytes!("../assets/test.csr"); const CSR_CHALLENGE_PASSWORD: &[u8] = include_bytes!("../assets/csr-challenge-password.pem"); - #[test] fn read_csr_empty_attrib() { let (rem, csr) = @@ -62,9 +65,32 @@ fn read_csr_with_challenge_password() { assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); - // CSR contains 2 attributes: challenge password and extensions assert_eq!(cri.attributes().len(), 2); + let challenge_password_attr = csr + .certification_request_info + .find_attribute(&OID_PKCS9_CHALLENGE_PASSWORD) + .expect("Challenge password not found in CSR"); + + // 1. Check: Parse value + let (rem, challenge_password_from_value) = + Set::from_der_and_then(challenge_password_attr.value, |i| String::from_der(i)) + .expect("Error parsing challenge password attribute"); + assert_eq!(challenge_password_from_value, "A challenge password"); + assert!(rem.is_empty()); + + // 2. Check: Get value directly from parsed attribute + if let ParsedCriAttribute::ChallengePassword(challenge_password_from_parsed_attribute) = + challenge_password_attr.parsed_attribute() + { + assert_eq!( + challenge_password_from_parsed_attribute.0, + "A challenge password" + ); + } else { + panic!("Parsed attribute is not a challenge password"); + } + // Make sure we can read requested extensions let extensions = csr .requested_extensions()