pub mod asn1;
mod signing;
mod time_stamp_protocol;
pub use {
bcder::Oid,
bytes::Bytes,
signing::{SignedDataBuilder, SignerBuilder},
time_stamp_protocol::{
time_stamp_message_http, time_stamp_request_http, TimeStampError, TimeStampResponse,
},
};
use {
crate::asn1::{
rfc3161::OID_TIME_STAMP_TOKEN,
rfc5652::{
CertificateChoices, SignerIdentifier, Time, OID_CONTENT_TYPE, OID_MESSAGE_DIGEST,
OID_SIGNING_TIME,
},
},
bcder::{Integer, OctetString},
pem::PemError,
ring::{digest::Digest, signature::UnparsedPublicKey},
std::{
collections::HashSet,
fmt::{Debug, Display, Formatter},
ops::Deref,
},
x509_certificate::{
certificate::certificate_is_subset_of, rfc3280::Name, CapturedX509Certificate,
DigestAlgorithm, SignatureAlgorithm, X509Certificate, X509CertificateError,
},
};
#[derive(Debug)]
pub enum CmsError {
DecodeErr(bcder::decode::DecodeError<std::convert::Infallible>),
MissingSignedAttributeContentType,
MalformedSignedAttributeContentType,
MissingSignedAttributeMessageDigest,
MalformedSignedAttributeMessageDigest,
MalformedSignedAttributeSigningTime,
MalformedUnsignedAttributeTimeStampToken,
SubjectKeyIdentifierUnsupported,
Io(std::io::Error),
UnknownKeyAlgorithm(Oid),
UnknownDigestAlgorithm(Oid),
UnknownSignatureAlgorithm(Oid),
UnknownCertificateFormat,
CertificateNotFound,
SignatureVerificationError,
NoSignedAttributes,
DigestNotEqual,
Pem(PemError),
SignatureCreation(signature::Error),
CertificateMissingData,
DistinguishedNameParseError,
TimeStampProtocol(TimeStampError),
X509Certificate(X509CertificateError),
}
impl std::error::Error for CmsError {}
impl Display for CmsError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::DecodeErr(e) => std::fmt::Display::fmt(e, f),
Self::MissingSignedAttributeContentType => {
f.write_str("content-type attribute missing from SignedAttributes")
}
Self::MalformedSignedAttributeContentType => {
f.write_str("content-type attribute in SignedAttributes is malformed")
}
Self::MissingSignedAttributeMessageDigest => {
f.write_str("message-digest attribute missing from SignedAttributes")
}
Self::MalformedSignedAttributeMessageDigest => {
f.write_str("message-digest attribute in SignedAttributes is malformed")
}
Self::MalformedSignedAttributeSigningTime => {
f.write_str("signing-time attribute in SignedAttributes is malformed")
}
Self::MalformedUnsignedAttributeTimeStampToken => {
f.write_str("time-stamp token attribute in UnsignedAttributes is malformed")
}
Self::SubjectKeyIdentifierUnsupported => {
f.write_str("signer info using subject key identifier is not supported")
}
Self::Io(e) => std::fmt::Display::fmt(e, f),
Self::UnknownKeyAlgorithm(oid) => {
f.write_fmt(format_args!("unknown signing key algorithm: {}", oid))
}
Self::UnknownDigestAlgorithm(oid) => {
f.write_fmt(format_args!("unknown digest algorithm: {}", oid))
}
Self::UnknownSignatureAlgorithm(oid) => {
f.write_fmt(format_args!("unknown signature algorithm: {}", oid))
}
Self::UnknownCertificateFormat => f.write_str("unknown certificate format"),
Self::CertificateNotFound => f.write_str("certificate not found"),
Self::SignatureVerificationError => f.write_str("signature verification failed"),
Self::NoSignedAttributes => f.write_str("SignedAttributes structure is missing"),
Self::DigestNotEqual => f.write_str("digests not equivalent"),
Self::Pem(e) => f.write_fmt(format_args!("PEM error: {}", e)),
Self::SignatureCreation(e) => {
f.write_fmt(format_args!("error during signature creation: {}", e))
}
Self::CertificateMissingData => f.write_str("certificate data not available"),
Self::DistinguishedNameParseError => {
f.write_str("could not parse distinguished name data")
}
Self::TimeStampProtocol(e) => {
f.write_fmt(format_args!("Time-Stamp Protocol error: {}", e))
}
Self::X509Certificate(e) => {
f.write_fmt(format_args!("X.509 certificate error: {:?}", e))
}
}
}
}
impl From<bcder::decode::DecodeError<std::convert::Infallible>> for CmsError {
fn from(e: bcder::decode::DecodeError<std::convert::Infallible>) -> Self {
Self::DecodeErr(e)
}
}
impl From<std::io::Error> for CmsError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<PemError> for CmsError {
fn from(e: PemError) -> Self {
Self::Pem(e)
}
}
impl From<TimeStampError> for CmsError {
fn from(e: TimeStampError) -> Self {
Self::TimeStampProtocol(e)
}
}
impl From<signature::Error> for CmsError {
fn from(e: signature::Error) -> Self {
Self::SignatureCreation(e)
}
}
impl From<X509CertificateError> for CmsError {
fn from(e: X509CertificateError) -> Self {
Self::X509Certificate(e)
}
}
#[derive(Clone)]
pub struct SignedData {
digest_algorithms: HashSet<DigestAlgorithm>,
signed_content: Option<Vec<u8>>,
certificates: Option<Vec<CapturedX509Certificate>>,
signers: Vec<SignerInfo>,
}
impl Debug for SignedData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("SignedData");
s.field("digest_algorithms", &self.digest_algorithms);
s.field(
"signed_content",
&format_args!("{:?}", self.signed_content.as_ref().map(hex::encode)),
);
s.field("certificates", &self.certificates);
s.field("signers", &self.signers);
s.finish()
}
}
impl SignedData {
pub fn parse_ber(data: &[u8]) -> Result<Self, CmsError> {
Self::try_from(&crate::asn1::rfc5652::SignedData::decode_ber(data)?)
}
pub fn message_digest_with_algorithm(&self, alg: DigestAlgorithm) -> Digest {
let mut hasher = alg.digester();
if let Some(content) = &self.signed_content {
hasher.update(content);
}
hasher.finish()
}
pub fn signed_content(&self) -> Option<&[u8]> {
if let Some(content) = &self.signed_content {
Some(content)
} else {
None
}
}
pub fn certificates(&self) -> Box<dyn Iterator<Item = &CapturedX509Certificate> + '_> {
match self.certificates.as_ref() {
Some(certs) => Box::new(certs.iter()),
None => Box::new(std::iter::empty()),
}
}
pub fn signers(&self) -> impl Iterator<Item = &SignerInfo> {
self.signers.iter()
}
}
impl TryFrom<&crate::asn1::rfc5652::SignedData> for SignedData {
type Error = CmsError;
fn try_from(raw: &crate::asn1::rfc5652::SignedData) -> Result<Self, Self::Error> {
let digest_algorithms = raw
.digest_algorithms
.iter()
.map(DigestAlgorithm::try_from)
.collect::<Result<HashSet<_>, _>>()?;
let signed_content = raw
.content_info
.content
.as_ref()
.map(|content| content.to_bytes().to_vec());
let certificates = if let Some(certs) = &raw.certificates {
Some(
certs
.iter()
.map(|choice| match choice {
CertificateChoices::Certificate(cert) => {
let cert = X509Certificate::from(cert.deref().clone());
let cert_ber = cert.encode_ber()?;
Ok(CapturedX509Certificate::from_ber(cert_ber)?)
}
_ => Err(CmsError::UnknownCertificateFormat),
})
.collect::<Result<Vec<_>, CmsError>>()?,
)
} else {
None
};
let signers = raw
.signer_infos
.iter()
.map(SignerInfo::try_from)
.collect::<Result<Vec<_>, CmsError>>()?;
Ok(Self {
digest_algorithms,
signed_content,
certificates,
signers,
})
}
}
#[derive(Clone)]
pub struct SignerInfo {
issuer: Name,
serial_number: Integer,
digest_algorithm: DigestAlgorithm,
signature_algorithm: SignatureAlgorithm,
signature: Vec<u8>,
signed_attributes: Option<SignedAttributes>,
digested_signed_attributes_data: Option<Vec<u8>>,
unsigned_attributes: Option<UnsignedAttributes>,
}
impl Debug for SignerInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("SignerInfo");
s.field("issuer", &self.issuer);
s.field("serial_number", &self.serial_number);
s.field("digest_algorithm", &self.digest_algorithm);
s.field("signature_algorithm", &self.signature_algorithm);
s.field(
"signature",
&format_args!("{}", hex::encode(&self.signature)),
);
s.field("signed_attributes", &self.signed_attributes);
s.field(
"digested_signed_attributes_data",
&format_args!(
"{:?}",
self.digested_signed_attributes_data
.as_ref()
.map(hex::encode)
),
);
s.field("unsigned_attributes", &self.unsigned_attributes);
s.finish()
}
}
impl SignerInfo {
pub fn certificate_issuer_and_serial(&self) -> Option<(&Name, &Integer)> {
Some((&self.issuer, &self.serial_number))
}
pub fn digest_algorithm(&self) -> DigestAlgorithm {
self.digest_algorithm
}
pub fn signature_algorithm(&self) -> SignatureAlgorithm {
self.signature_algorithm
}
pub fn signature(&self) -> &[u8] {
&self.signature
}
pub fn signed_attributes(&self) -> Option<&SignedAttributes> {
self.signed_attributes.as_ref()
}
pub fn unsigned_attributes(&self) -> Option<&UnsignedAttributes> {
self.unsigned_attributes.as_ref()
}
pub fn verify_signature_with_signed_data(
&self,
signed_data: &SignedData,
) -> Result<(), CmsError> {
let signed_content = self.signed_content_with_signed_data(signed_data);
self.verify_signature_with_signed_data_and_content(signed_data, &signed_content)
}
pub fn verify_signature_with_signed_data_and_content(
&self,
signed_data: &SignedData,
signed_content: &[u8],
) -> Result<(), CmsError> {
let verifier = self.signature_verifier(signed_data.certificates())?;
let signature = self.signature();
verifier
.verify(signed_content, signature)
.map_err(|_| CmsError::SignatureVerificationError)
}
pub fn verify_message_digest_with_signed_data(
&self,
signed_data: &SignedData,
) -> Result<(), CmsError> {
let signed_attributes = self
.signed_attributes()
.ok_or(CmsError::NoSignedAttributes)?;
let wanted_digest: &[u8] = signed_attributes.message_digest.as_ref();
let got_digest = self.compute_digest_with_signed_data(signed_data);
if wanted_digest == got_digest.as_ref() {
Ok(())
} else {
Err(CmsError::DigestNotEqual)
}
}
pub fn verify_message_digest_with_content(&self, data: &[u8]) -> Result<(), CmsError> {
let signed_attributes = self
.signed_attributes()
.ok_or(CmsError::NoSignedAttributes)?;
let wanted_digest: &[u8] = signed_attributes.message_digest.as_ref();
let got_digest = self.compute_digest(Some(data));
if wanted_digest == got_digest.as_ref() {
Ok(())
} else {
Err(CmsError::DigestNotEqual)
}
}
pub fn signature_verifier<'a, C>(
&self,
mut certs: C,
) -> Result<UnparsedPublicKey<Vec<u8>>, CmsError>
where
C: Iterator<Item = &'a CapturedX509Certificate>,
{
let signing_cert = certs
.find(|cert| {
certificate_is_subset_of(
&self.serial_number,
&self.issuer,
cert.serial_number_asn1(),
cert.issuer_name(),
)
})
.ok_or(CmsError::CertificateNotFound)?;
let key_algorithm = signing_cert.key_algorithm().ok_or_else(|| {
CmsError::UnknownKeyAlgorithm(signing_cert.key_algorithm_oid().clone())
})?;
let verification_algorithm = self
.signature_algorithm
.resolve_verification_algorithm(key_algorithm)?;
let public_key = UnparsedPublicKey::new(
verification_algorithm,
signing_cert.public_key_data().to_vec(),
);
Ok(public_key)
}
pub fn time_stamp_token_signed_data(&self) -> Result<Option<SignedData>, CmsError> {
if let Some(attrs) = self.unsigned_attributes() {
if let Some(signed_data) = &attrs.time_stamp_token {
Ok(Some(SignedData::try_from(signed_data)?))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub fn verify_time_stamp_token(&self) -> Result<Option<()>, CmsError> {
let signed_data = if let Some(v) = self.time_stamp_token_signed_data()? {
v
} else {
return Ok(None);
};
if signed_data.signers.is_empty() {
return Ok(None);
}
for signer in signed_data.signers() {
signer.verify_signature_with_signed_data(&signed_data)?;
signer.verify_message_digest_with_signed_data(&signed_data)?;
}
Ok(Some(()))
}
pub fn signed_content_with_signed_data(&self, signed_data: &SignedData) -> Vec<u8> {
self.signed_content(signed_data.signed_content())
}
pub fn signed_content(&self, content: Option<&[u8]>) -> Vec<u8> {
if let Some(signed_attributes_data) = &self.digested_signed_attributes_data {
signed_attributes_data.clone()
} else if let Some(content) = content {
content.to_vec()
} else {
vec![]
}
}
pub fn signed_attributes_data(&self) -> Option<&[u8]> {
self.digested_signed_attributes_data
.as_ref()
.map(|x| x.as_ref())
}
pub fn compute_digest_with_signed_data(&self, signed_data: &SignedData) -> Digest {
self.compute_digest(signed_data.signed_content())
}
pub fn compute_digest(&self, content: Option<&[u8]>) -> Digest {
self.compute_digest_with_algorithm(content, self.digest_algorithm)
}
pub fn compute_digest_with_algorithm(
&self,
content: Option<&[u8]>,
alg: DigestAlgorithm,
) -> Digest {
let mut hasher = alg.digester();
if let Some(content) = content {
hasher.update(content);
}
hasher.finish()
}
}
impl TryFrom<&crate::asn1::rfc5652::SignerInfo> for SignerInfo {
type Error = CmsError;
fn try_from(signer_info: &crate::asn1::rfc5652::SignerInfo) -> Result<Self, Self::Error> {
let (issuer, serial_number) = match &signer_info.sid {
SignerIdentifier::IssuerAndSerialNumber(issuer) => {
(issuer.issuer.clone(), issuer.serial_number.clone())
}
SignerIdentifier::SubjectKeyIdentifier(_) => {
return Err(CmsError::SubjectKeyIdentifierUnsupported);
}
};
let digest_algorithm = DigestAlgorithm::try_from(&signer_info.digest_algorithm)?;
let signature_algorithm = SignatureAlgorithm::from_oid_and_digest_algorithm(
&signer_info.signature_algorithm.algorithm,
digest_algorithm,
)?;
let signature = signer_info.signature.to_bytes().to_vec();
let signed_attributes = if let Some(attributes) = &signer_info.signed_attributes {
let content_type = attributes
.iter()
.find(|attr| attr.typ == OID_CONTENT_TYPE)
.ok_or(CmsError::MissingSignedAttributeContentType)?;
if content_type.values.len() != 1 {
return Err(CmsError::MalformedSignedAttributeContentType);
}
let content_type = content_type
.values
.get(0)
.unwrap()
.deref()
.clone()
.decode(Oid::take_from)
.map_err(|_| CmsError::MalformedSignedAttributeContentType)?;
let message_digest = attributes
.iter()
.find(|attr| attr.typ == OID_MESSAGE_DIGEST)
.ok_or(CmsError::MissingSignedAttributeMessageDigest)?;
if message_digest.values.len() != 1 {
return Err(CmsError::MalformedSignedAttributeMessageDigest);
}
let message_digest = message_digest
.values
.get(0)
.unwrap()
.deref()
.clone()
.decode(OctetString::take_from)
.map_err(|_| CmsError::MalformedSignedAttributeMessageDigest)?
.to_bytes()
.to_vec();
let signing_time = attributes
.iter()
.find(|attr| attr.typ == OID_SIGNING_TIME)
.map(|attr| {
if attr.values.len() != 1 {
Err(CmsError::MalformedSignedAttributeSigningTime)
} else {
let time = attr
.values
.get(0)
.unwrap()
.deref()
.clone()
.decode(Time::take_from)?;
let time = chrono::DateTime::from(time);
Ok(time)
}
})
.transpose()?;
Some(SignedAttributes {
content_type,
message_digest,
signing_time,
raw: attributes.clone(),
})
} else {
None
};
let digested_signed_attributes_data = signer_info.signed_attributes_digested_content()?;
let unsigned_attributes =
if let Some(attributes) = &signer_info.unsigned_attributes {
let time_stamp_token =
attributes
.iter()
.find(|attr| attr.typ == OID_TIME_STAMP_TOKEN)
.map(|attr| {
if attr.values.len() != 1 {
Err(CmsError::MalformedUnsignedAttributeTimeStampToken)
} else {
Ok(attr.values.get(0).unwrap().deref().clone().decode(|cons| {
crate::asn1::rfc5652::SignedData::decode(cons)
})?)
}
})
.transpose()?;
Some(UnsignedAttributes { time_stamp_token })
} else {
None
};
Ok(SignerInfo {
issuer,
serial_number,
digest_algorithm,
signature_algorithm,
signature,
signed_attributes,
digested_signed_attributes_data,
unsigned_attributes,
})
}
}
#[derive(Clone)]
pub struct SignedAttributes {
content_type: Oid,
message_digest: Vec<u8>,
signing_time: Option<chrono::DateTime<chrono::Utc>>,
raw: crate::asn1::rfc5652::SignedAttributes,
}
impl SignedAttributes {
pub fn content_type(&self) -> &Oid {
&self.content_type
}
pub fn message_digest(&self) -> &[u8] {
&self.message_digest
}
pub fn signing_time(&self) -> Option<&chrono::DateTime<chrono::Utc>> {
self.signing_time.as_ref()
}
pub fn attributes(&self) -> &crate::asn1::rfc5652::SignedAttributes {
&self.raw
}
}
impl Debug for SignedAttributes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_struct("SignedAttributes");
s.field("content_type", &format_args!("{}", self.content_type));
s.field(
"message_digest",
&format_args!("{}", hex::encode(&self.message_digest)),
);
s.field("signing_time", &self.signing_time);
s.finish()
}
}
#[derive(Clone, Debug)]
pub struct UnsignedAttributes {
time_stamp_token: Option<crate::asn1::rfc5652::SignedData>,
}
#[cfg(test)]
mod tests {
use {
super::*,
bcder::{encode::Values, Mode},
};
const FIREFOX_SIGNATURE: &[u8] = include_bytes!("testdata/firefox.ber");
const FIREFOX_CODE_DIRECTORY: &[u8] = include_bytes!("testdata/firefox-code-directory");
#[test]
fn parse_firefox() {
let raw = crate::asn1::rfc5652::SignedData::decode_ber(FIREFOX_SIGNATURE).unwrap();
let mut buffer = Vec::new();
raw.encode_ref()
.write_encoded(Mode::Ber, &mut buffer)
.unwrap();
let raw2 = crate::asn1::rfc5652::SignedData::decode_ber(&buffer).unwrap();
assert_eq!(raw, raw2, "BER round tripping is identical");
}
#[test]
fn verify_firefox() {
let signed_data = SignedData::parse_ber(FIREFOX_SIGNATURE).unwrap();
for signer in signed_data.signers.iter() {
signer
.verify_signature_with_signed_data(&signed_data)
.unwrap();
signer
.verify_message_digest_with_signed_data(&signed_data)
.unwrap_err();
signer
.verify_message_digest_with_content(FIREFOX_CODE_DIRECTORY)
.unwrap();
let tst_signed_data = signer.time_stamp_token_signed_data().unwrap().unwrap();
for signer in tst_signed_data.signers() {
signer
.verify_message_digest_with_signed_data(&tst_signed_data)
.unwrap();
signer
.verify_signature_with_signed_data(&tst_signed_data)
.unwrap();
}
}
}
#[test]
fn parse_no_certificate_version() {
let signed = SignedData::parse_ber(include_bytes!("testdata/no-cert-version.ber")).unwrap();
let cert_orig = signed.certificates().collect::<Vec<_>>()[0].clone();
let cert = CapturedX509Certificate::from_der(cert_orig.encode_ber().unwrap()).unwrap();
assert_eq!(
hex::encode(cert.sha256_fingerprint().unwrap()),
"b7c2eefd8dac7806af67dfcd92eb18126bc08312a7f2d6f3862e46013c7a6135"
);
}
const IZZYSOFT_SIGNED_DATA: &[u8] = include_bytes!("testdata/izzysoft-signeddata");
const IZZYSOFT_DATA: &[u8] = include_bytes!("testdata/izzysoft-data");
#[test]
fn verify_izzysoft() {
let signed = SignedData::parse_ber(IZZYSOFT_SIGNED_DATA).unwrap();
let cert = signed.certificates().next().unwrap();
for signer in signed.signers() {
assert!(matches!(
signer.verify_signature_with_signed_data(&signed),
Err(CmsError::SignatureVerificationError)
));
assert!(matches!(
signer.verify_message_digest_with_signed_data(&signed),
Err(CmsError::NoSignedAttributes)
));
assert!(matches!(
signer.verify_message_digest_with_signed_data(&signed),
Err(CmsError::NoSignedAttributes)
));
assert!(matches!(
cert.verify_signed_data(IZZYSOFT_DATA, signer.signature()),
Err(X509CertificateError::CertificateSignatureVerificationFailed)
));
cert.verify_signed_data_with_algorithm(
IZZYSOFT_DATA,
signer.signature(),
&ring::signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
)
.unwrap();
signer
.verify_signature_with_signed_data_and_content(&signed, IZZYSOFT_DATA)
.unwrap();
let verifier = signer.signature_verifier(signed.certificates()).unwrap();
verifier.verify(IZZYSOFT_DATA, signer.signature()).unwrap();
}
}
}