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

Added support for parsing challenge password attribute in CSR's #129

Merged
merged 15 commits into from
Mar 8, 2023
Merged
70 changes: 66 additions & 4 deletions src/cri_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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<ParsedCriAttribute>;
Expand All @@ -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
};
}
Expand All @@ -99,12 +121,52 @@ pub(crate) mod parser {
.map(|(i, extensions)| (i, ExtensionRequest { extensions }))
}

fn parse_extension_request_ext(i: &[u8]) -> X509Result<ParsedCriAttribute> {
fn parse_extension_request_attr(i: &[u8]) -> X509Result<ParsedCriAttribute> {
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<ChallengePassword> {
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<ParsedCriAttribute> {
map(
parse_challenge_password,
ParsedCriAttribute::ChallengePassword,
)(i)
}
}

pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result<Vec<X509CriAttribute>> {
Expand Down
32 changes: 29 additions & 3 deletions tests/readcsr.rs
Original file line number Diff line number Diff line change
@@ -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) =
Expand Down Expand Up @@ -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()
Expand Down