345 lines
12 KiB
Rust
345 lines
12 KiB
Rust
// Signed attributes, signing-time, and algorithm parsing helpers.
|
|
|
|
fn parse_signed_attrs_implicit(
|
|
input: &[u8],
|
|
) -> Result<SignedAttrsProfiled, SignedObjectValidateError> {
|
|
let mut content_type: Option<String> = None;
|
|
let mut message_digest: Option<Vec<u8>> = None;
|
|
let mut signing_time: Option<Asn1TimeUtc> = None;
|
|
|
|
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
|
|
let mut n = 0usize;
|
|
while !r.is_empty() {
|
|
r.skip_any()?;
|
|
n += 1;
|
|
}
|
|
Ok(n)
|
|
}
|
|
|
|
let mut remaining = DerReader::new(input);
|
|
while !remaining.is_empty() {
|
|
let mut attr = remaining
|
|
.take_sequence()
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
|
|
|
let oid_bytes = attr
|
|
.take_tag(0x06)
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
|
let oid = oid_value_bytes_to_string(oid_bytes);
|
|
|
|
let values_bytes = attr
|
|
.take_tag(0x31)
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
|
if !attr.is_empty() {
|
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
|
"Attribute must be SEQUENCE of 2".into(),
|
|
));
|
|
}
|
|
|
|
let mut values = DerReader::new(values_bytes);
|
|
let count = if values.is_empty() {
|
|
0
|
|
} else {
|
|
values
|
|
.skip_any()
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
|
if values.is_empty() {
|
|
1
|
|
} else {
|
|
1 + count_elements(values)
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
|
|
}
|
|
};
|
|
if count != 1 {
|
|
return Err(
|
|
SignedObjectValidateError::InvalidSignedAttributeValuesCount { oid, count },
|
|
);
|
|
}
|
|
|
|
// Re-parse the sole value.
|
|
let mut values = DerReader::new(values_bytes);
|
|
let (val_tag, val_bytes) = values
|
|
.take_any()
|
|
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
|
|
|
|
match oid.as_str() {
|
|
OID_CMS_ATTR_CONTENT_TYPE => {
|
|
if content_type.is_some() {
|
|
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
|
}
|
|
if val_tag != 0x06 {
|
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
|
"content-type attr value must be OBJECT IDENTIFIER".into(),
|
|
));
|
|
}
|
|
content_type = Some(oid_value_bytes_to_string(val_bytes));
|
|
}
|
|
OID_CMS_ATTR_MESSAGE_DIGEST => {
|
|
if message_digest.is_some() {
|
|
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
|
}
|
|
if val_tag != 0x04 {
|
|
return Err(SignedObjectValidateError::SignedAttrsParse(
|
|
"message-digest attr value must be OCTET STRING".into(),
|
|
));
|
|
}
|
|
message_digest = Some(val_bytes.to_vec());
|
|
}
|
|
OID_CMS_ATTR_SIGNING_TIME => {
|
|
if signing_time.is_some() {
|
|
return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid));
|
|
}
|
|
signing_time = Some(parse_signing_time_value_tlv(val_tag, val_bytes)?);
|
|
}
|
|
_ => {
|
|
return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(SignedAttrsProfiled {
|
|
content_type: content_type
|
|
.ok_or(SignedObjectValidateError::SignedAttrsContentTypeMissing)?,
|
|
message_digest: message_digest
|
|
.ok_or(SignedObjectValidateError::SignedAttrsMessageDigestMissing)?,
|
|
signing_time: signing_time
|
|
.ok_or(SignedObjectValidateError::SignedAttrsSigningTimeMissing)?,
|
|
other_attrs_present: false,
|
|
})
|
|
}
|
|
|
|
fn parse_signing_time_value_tlv(
|
|
tag: u8,
|
|
value: &[u8],
|
|
) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
|
|
match tag {
|
|
0x17 => Ok(Asn1TimeUtc {
|
|
utc: parse_utctime(value)?,
|
|
encoding: Asn1TimeEncoding::UtcTime,
|
|
}),
|
|
0x18 => Ok(Asn1TimeUtc {
|
|
utc: parse_generalized_time(value)?,
|
|
encoding: Asn1TimeEncoding::GeneralizedTime,
|
|
}),
|
|
_ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
|
|
}
|
|
}
|
|
|
|
fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> Result<Vec<u8>, SignedObjectParseError> {
|
|
// We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input.
|
|
// The SignedAttributes field in SignerInfo is `[0] IMPLICIT`, so the on-wire bytes start with
|
|
// a context-specific constructed tag (0xA0 for tag 0). For signature verification, this tag
|
|
// is replaced with the universal SET tag (0x31), leaving length+content unchanged.
|
|
//
|
|
let mut cs_der = full_tlv.to_vec();
|
|
if cs_der.is_empty() {
|
|
return Err(SignedObjectParseError::Parse(
|
|
"signedAttrs encoding is empty".into(),
|
|
));
|
|
}
|
|
// The first byte should be the context-specific tag (0xA0) for [0] constructed.
|
|
// Replace it with universal SET (0x31) for signature input.
|
|
cs_der[0] = 0x31;
|
|
Ok(cs_der)
|
|
}
|
|
|
|
fn take_oid_string(seq: &mut CmsReader<'_>) -> Result<String, SignedObjectParseError> {
|
|
let oid = seq
|
|
.take_tag(0x06)
|
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
Ok(oid_value_bytes_to_string(oid))
|
|
}
|
|
|
|
fn oid_value_bytes_to_string(oid_value: &[u8]) -> String {
|
|
if oid_value == OID_SHA256_RAW {
|
|
return OID_SHA256.to_string();
|
|
}
|
|
if oid_value == OID_SIGNED_DATA_RAW {
|
|
return OID_SIGNED_DATA.to_string();
|
|
}
|
|
if oid_value == OID_CMS_ATTR_CONTENT_TYPE_RAW {
|
|
return OID_CMS_ATTR_CONTENT_TYPE.to_string();
|
|
}
|
|
if oid_value == OID_CMS_ATTR_MESSAGE_DIGEST_RAW {
|
|
return OID_CMS_ATTR_MESSAGE_DIGEST.to_string();
|
|
}
|
|
if oid_value == OID_CMS_ATTR_SIGNING_TIME_RAW {
|
|
return OID_CMS_ATTR_SIGNING_TIME.to_string();
|
|
}
|
|
if oid_value == OID_RSA_ENCRYPTION_RAW {
|
|
return OID_RSA_ENCRYPTION.to_string();
|
|
}
|
|
if oid_value == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
|
|
return OID_SHA256_WITH_RSA_ENCRYPTION.to_string();
|
|
}
|
|
if oid_value == OID_CT_RPKI_MANIFEST_RAW {
|
|
return OID_CT_RPKI_MANIFEST.to_string();
|
|
}
|
|
if oid_value == OID_CT_ROUTE_ORIGIN_AUTHZ_RAW {
|
|
return OID_CT_ROUTE_ORIGIN_AUTHZ.to_string();
|
|
}
|
|
if oid_value == OID_CT_ASPA_RAW {
|
|
return OID_CT_ASPA.to_string();
|
|
}
|
|
decode_oid_to_dotted_string(oid_value)
|
|
}
|
|
|
|
fn decode_oid_to_dotted_string(value: &[u8]) -> String {
|
|
if value.is_empty() {
|
|
return "<empty-oid>".into();
|
|
}
|
|
let first = value[0];
|
|
let a = (first / 40) as u32;
|
|
let b = (first % 40) as u32;
|
|
let mut out = String::new();
|
|
out.push_str(&a.to_string());
|
|
out.push('.');
|
|
out.push_str(&b.to_string());
|
|
|
|
let mut idx = 1usize;
|
|
while idx < value.len() {
|
|
let mut v: u32 = 0;
|
|
loop {
|
|
if idx >= value.len() {
|
|
out.push_str(".<truncated>");
|
|
return out;
|
|
}
|
|
let byte = value[idx];
|
|
idx += 1;
|
|
v = (v << 7) | (byte as u32 & 0x7F);
|
|
if (byte & 0x80) == 0 {
|
|
break;
|
|
}
|
|
}
|
|
out.push('.');
|
|
out.push_str(&v.to_string());
|
|
}
|
|
out
|
|
}
|
|
|
|
fn parse_algorithm_identifier_cursor(
|
|
mut seq: CmsReader<'_>,
|
|
) -> Result<(String, bool), SignedObjectParseError> {
|
|
if seq.is_empty() {
|
|
return Err(SignedObjectParseError::Parse(
|
|
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
|
));
|
|
}
|
|
let oid = take_oid_string(&mut seq)?;
|
|
let params_ok = if seq.is_empty() {
|
|
true
|
|
} else {
|
|
let (tag, value) = seq
|
|
.take_any()
|
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
|
tag == 0x05 && value.is_empty()
|
|
};
|
|
if !seq.is_empty() {
|
|
return Err(SignedObjectParseError::Parse(
|
|
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
|
|
));
|
|
}
|
|
Ok((oid, params_ok))
|
|
}
|
|
|
|
fn parse_utctime(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
|
|
let s = std::str::from_utf8(value)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
if !s.ends_with('Z') {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
let digits = &s[..s.len() - 1];
|
|
if digits.len() != 10 && digits.len() != 12 {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
let yy: i32 = digits[0..2]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let year = if yy <= 49 { 2000 + yy } else { 1900 + yy };
|
|
let mon: u8 = digits[2..4]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let day: u8 = digits[4..6]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let hour: u8 = digits[6..8]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let min: u8 = digits[8..10]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let sec: u8 = if digits.len() == 12 {
|
|
digits[10..12]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
|
|
} else {
|
|
0
|
|
};
|
|
let month = time::Month::try_from(mon)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let date = time::Date::from_calendar_date(year, month, day)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let time = time::Time::from_hms(hour, min, sec)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
Ok(time::OffsetDateTime::new_utc(date, time))
|
|
}
|
|
|
|
fn parse_generalized_time(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
|
|
let s = std::str::from_utf8(value)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
if !s.ends_with('Z') {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
let digits = &s[..s.len() - 1];
|
|
if digits.len() != 12 && digits.len() != 14 {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
|
|
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
|
|
}
|
|
let year: i32 = digits[0..4]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let mon: u8 = digits[4..6]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let day: u8 = digits[6..8]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let hour: u8 = digits[8..10]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let min: u8 = digits[10..12]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let sec: u8 = if digits.len() == 14 {
|
|
digits[12..14]
|
|
.parse()
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
|
|
} else {
|
|
0
|
|
};
|
|
let month = time::Month::try_from(mon)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let date = time::Date::from_calendar_date(year, month, day)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
let time = time::Time::from_hms(hour, min, sec)
|
|
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
|
|
Ok(time::OffsetDateTime::new_utc(date, time))
|
|
}
|
|
|
|
fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
|
|
let mut idx = 0;
|
|
while idx < bytes.len() && bytes[idx] == 0 {
|
|
idx += 1;
|
|
}
|
|
if idx == bytes.len() {
|
|
&bytes[bytes.len() - 1..]
|
|
} else {
|
|
&bytes[idx..]
|
|
}
|
|
}
|