use cms::{
cert::{
CertificateChoices,
x509::{
Certificate, der,
der::{
EncodePem, asn1::SetOfVec, oid as const_oid, pem::LineEnding,
referenced::OwnedToRef,
},
ext::pkix::{AuthorityKeyIdentifier, SubjectKeyIdentifier},
spki::{AlgorithmIdentifierOwned, ObjectIdentifier},
},
},
content_info::{CmsVersion, ContentInfo},
signed_data::{
EncapsulatedContentInfo, SignatureValue, SignedData, SignerIdentifier, SignerInfo,
SignerInfos,
},
};
use pkcs1::{RsaPssParams, der::oid::AssociatedOid};
use ruma::OwnedDeviceId;
use vodozemac::base64_encode;
#[cfg(doc)]
use crate::x509::{RawX509Signer, RawX509Verifier};
use crate::{
types::X509Signature,
x509::errors::{IntoX509SignatureError, OidMismatch, RawX509SignatureParseError},
};
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[derive(Debug, Clone)]
pub struct RawX509Signature {
pub signature_bytes: Vec<u8>,
pub certificate_chain: String,
pub signature_scheme: X509SignatureScheme,
}
impl RawX509Signature {
pub fn into_x509_signature(
self,
) -> Result<(OwnedDeviceId, X509Signature), IntoX509SignatureError> {
let cert_chain = Certificate::load_pem_chain(self.certificate_chain.as_bytes())
.map_err(IntoX509SignatureError::CertificateChainParseError)?;
let (first_cert, last_cert) = match cert_chain.as_slice() {
[] => return Err(IntoX509SignatureError::EmptyCertChain),
[single] => (single, single),
[first, .., last] => (first, last),
};
let leaf_ski = first_cert
.tbs_certificate
.get::<SubjectKeyIdentifier>()
.map_err(IntoX509SignatureError::LeafCertificateExtensionParseError)?
.ok_or(IntoX509SignatureError::LeafCertificateMissingSubjectKeyIdentifier)?
.1;
let authority_key_identifier = last_cert
.tbs_certificate
.get::<AuthorityKeyIdentifier>()
.map_err(IntoX509SignatureError::LastCertificateExtensionParseError)?
.ok_or(IntoX509SignatureError::LastCertificateMissingAuthorityKeyIdentifier)?
.1;
let authority_key_identifier_bytes = authority_key_identifier.key_identifier.ok_or(
IntoX509SignatureError::LastCertificateMissingKeyIdentifierInAuthorityKeyIdentifier,
)?;
let signer_info = SignerInfo {
version: CmsVersion::V3,
sid: SignerIdentifier::SubjectKeyIdentifier(leaf_ski),
digest_alg: self.signature_scheme.get_digest_algorithm(),
signed_attrs: None, signature_algorithm: self.signature_scheme.get_signature_algorithm(),
signature: SignatureValue::new(self.signature_bytes).expect(
"Unable to encode signature bytes as SignatureValue",
),
unsigned_attrs: None,
};
let signed_data = SignedData {
version: CmsVersion::V3,
digest_algorithms: vec![signer_info.digest_alg.clone()].into_set_of_vec(),
encap_content_info: EncapsulatedContentInfo {
econtent_type: const_oid::db::rfc5911::ID_DATA,
econtent: None,
},
certificates: Some(
cert_chain
.into_iter()
.map(CertificateChoices::Certificate)
.collect::<Vec<_>>()
.into_set_of_vec()
.into(),
),
crls: None,
signer_infos: SignerInfos(vec![signer_info].into_set_of_vec()),
};
let signature = X509Signature::new(ContentInfo {
content_type: const_oid::db::rfc5911::ID_SIGNED_DATA,
content: der::Any::encode_from(&signed_data)
.expect("Unable to encode SignedData as DER"),
});
let device_id = OwnedDeviceId::from(base64_encode(authority_key_identifier_bytes));
Ok((device_id, signature))
}
}
trait IntoSetOfVec<T: der::DerOrd> {
fn into_set_of_vec(self) -> SetOfVec<T>;
}
impl<T: der::DerOrd> IntoSetOfVec<T> for Vec<T> {
fn into_set_of_vec(self) -> SetOfVec<T> {
self.try_into().expect("Unable to construct SetOfVec")
}
}
#[derive(Debug, Clone)]
pub(crate) struct RawX509SignatureAndFirstCertificate {
pub raw_x509signature: RawX509Signature,
pub leaf_cert: Certificate,
}
impl TryFrom<&X509Signature> for RawX509SignatureAndFirstCertificate {
type Error = RawX509SignatureParseError;
fn try_from(value: &X509Signature) -> Result<Self, Self::Error> {
let content_info = value.get_signature();
check_oid_matches(&content_info.content_type, &const_oid::db::rfc5911::ID_SIGNED_DATA)
.map_err(RawX509SignatureParseError::UnexpectedContentInfoContentType)?;
let data: SignedData = content_info
.content
.decode_as()
.map_err(RawX509SignatureParseError::ContentInfoParseError)?;
data.try_into()
}
}
impl TryFrom<SignedData> for RawX509SignatureAndFirstCertificate {
type Error = RawX509SignatureParseError;
fn try_from(data: SignedData) -> Result<Self, Self::Error> {
let encapsulated_content_info = &data.encap_content_info;
check_encapsulated_content_info(encapsulated_content_info)?;
let certificates: Vec<_> = data
.certificates
.ok_or(RawX509SignatureParseError::NoCertificateChainInSignedData)?
.0
.into_vec()
.into_iter()
.map(|cert| match cert {
CertificateChoices::Certificate(c) => Ok(c),
CertificateChoices::Other(_) => {
Err(RawX509SignatureParseError::NonX509CertificateInCertificateChain)
}
})
.collect::<Result<_, _>>()?;
let cert_pems = certificates
.iter()
.map(|cert| {
cert.to_pem(LineEnding::CRLF)
.map_err(RawX509SignatureParseError::CertificatePemEncodingFailed)
})
.collect::<Result<Vec<_>, _>>()?
.join("");
let leaf_cert = certificates
.into_iter()
.next()
.ok_or(RawX509SignatureParseError::EmptyCertificateChainInSignedData)?;
let leaf_ski = leaf_cert
.tbs_certificate
.get::<SubjectKeyIdentifier>()
.map_err(RawX509SignatureParseError::LeafCertificateExtensionParseError)?
.ok_or(RawX509SignatureParseError::LeafCertificateMissingSubjectKeyIdentifier)?
.1;
if data.crls.is_some() {
return Err(RawX509SignatureParseError::SignedDataContainsCrls);
}
let mut signer_infos = data.signer_infos.0.into_vec();
let signer_info = signer_infos.pop().ok_or(RawX509SignatureParseError::EmptySignerInfos)?;
if !signer_infos.is_empty() {
return Err(RawX509SignatureParseError::MultipleSignerInfos);
}
let (signature_scheme, signature_bytes) = parse_signer_info(signer_info, &leaf_ski)?;
let raw_x509signature =
RawX509Signature { signature_bytes, certificate_chain: cert_pems, signature_scheme };
Ok(RawX509SignatureAndFirstCertificate { raw_x509signature, leaf_cert })
}
}
fn check_encapsulated_content_info(
encapsulated_content_info: &EncapsulatedContentInfo,
) -> Result<(), RawX509SignatureParseError> {
check_oid_matches(&encapsulated_content_info.econtent_type, &const_oid::db::rfc5911::ID_DATA)
.map_err(RawX509SignatureParseError::UnexpectedEncapsulatedContentInfoContentType)?;
if encapsulated_content_info.econtent.is_some() {
return Err(RawX509SignatureParseError::EncapsulatedContentNotNull);
}
Ok(())
}
fn parse_signer_info(
signer_info: SignerInfo,
expected_ski: &SubjectKeyIdentifier,
) -> Result<(X509SignatureScheme, Vec<u8>), RawX509SignatureParseError> {
match &signer_info.sid {
SignerIdentifier::IssuerAndSerialNumber(_) => {
return Err(RawX509SignatureParseError::SignerIdentifierNotSubjectKeyIdentifier);
}
SignerIdentifier::SubjectKeyIdentifier(ski) => {
if *ski != *expected_ski {
return Err(RawX509SignatureParseError::SignerIdentifierMismatch);
}
}
}
if signer_info.signed_attrs.is_some() {
return Err(RawX509SignatureParseError::SignerInfoContainsSignedAttrs);
}
let signature_scheme = map_signer_info_algorithms_to_signature_scheme(
&signer_info.digest_alg,
&signer_info.signature_algorithm,
)?;
let signature_bytes = signer_info.signature.into_bytes();
Ok((signature_scheme, signature_bytes))
}
fn map_signer_info_algorithms_to_signature_scheme(
digest_alg: &AlgorithmIdentifierOwned,
signature_algorithm: &AlgorithmIdentifierOwned,
) -> Result<X509SignatureScheme, RawX509SignatureParseError> {
check_oid_matches(&signature_algorithm.oid, &const_oid::db::rfc5912::ID_RSASSA_PSS)
.map_err(RawX509SignatureParseError::UnsupportedSignatureAlgorithm)?;
check_oid_matches(&digest_alg.oid, &const_oid::db::rfc5912::ID_SHA_512)
.map_err(RawX509SignatureParseError::UnsupportedDigestAlgorithm)?;
check_digest_alg_params_null_or_absent(&digest_alg.parameters.owned_to_ref())?;
let signature_algorithm_params: RsaPssParams<'_> = signature_algorithm
.parameters
.as_ref()
.ok_or(RawX509SignatureParseError::SignatureAlgorithmParametersNotSet)?
.decode_as()
.map_err(RawX509SignatureParseError::SignatureAlgorithmParametersParseError)?;
check_oid_matches(&signature_algorithm_params.hash.oid, &const_oid::db::rfc5912::ID_SHA_512)
.map_err(RawX509SignatureParseError::UnsupportedSignatureAlgorithmHash)?;
check_digest_alg_params_null_or_absent(&signature_algorithm_params.hash.parameters)?;
check_oid_matches(&signature_algorithm_params.mask_gen.oid, &const_oid::db::rfc5912::ID_MGF_1)
.map_err(RawX509SignatureParseError::UnsupportedSignatureAlgorithmMaskGen)?;
let mask_gen_params = signature_algorithm_params
.mask_gen
.parameters
.ok_or(RawX509SignatureParseError::SignatureAlgorithmMaskGenParametersNotSet)?;
check_oid_matches(&mask_gen_params.oid, &const_oid::db::rfc5912::ID_SHA_512)
.map_err(RawX509SignatureParseError::UnsupportedSignatureAlgorithmMaskGenHash)?;
check_digest_alg_params_null_or_absent(&mask_gen_params.parameters)?;
if signature_algorithm_params.salt_len != 64 {
return Err(RawX509SignatureParseError::UnsupportedSignatureAlgorithmSaltLen(
signature_algorithm_params.salt_len,
));
}
Ok(X509SignatureScheme::RsaPssSha512)
}
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum X509SignatureScheme {
RsaPssSha512,
}
impl X509SignatureScheme {
pub fn get_digest_algorithm(&self) -> AlgorithmIdentifierOwned {
match self {
X509SignatureScheme::RsaPssSha512 => AlgorithmIdentifierOwned {
oid: sha2_0_10::Sha512::OID,
parameters: Some(der::Any::null()),
},
}
}
pub fn get_signature_algorithm(&self) -> AlgorithmIdentifierOwned {
match self {
X509SignatureScheme::RsaPssSha512 => {
AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5912::ID_RSASSA_PSS,
parameters: Some(
der::Any::encode_from(&RsaPssParams::new::<sha2_0_10::Sha512>(64))
.expect("Unable to encode RSA-PSS parameters"),
),
}
}
}
}
}
fn check_oid_matches(
actual: &ObjectIdentifier,
expected: &ObjectIdentifier,
) -> Result<(), OidMismatch> {
if *actual == *expected {
Ok(())
} else {
Err(OidMismatch { actual: *actual, expected: *expected })
}
}
fn check_digest_alg_params_null_or_absent(
digest_alg_params: &Option<der::AnyRef<'_>>,
) -> Result<(), RawX509SignatureParseError> {
match digest_alg_params {
None => Ok(()),
Some(x) if x.is_null() => Ok(()),
Some(_) => Err(RawX509SignatureParseError::DigestAlgorithmParametersNotNull),
}
}
#[cfg(test)]
mod test {
use insta::assert_debug_snapshot;
use super::*;
#[test]
fn test_sig_to_raw() {
const SIG: &str = include_str!("test_cms.pem");
let x509signature = X509Signature::from_cms_pem(SIG).unwrap();
let raw_signature: RawX509SignatureAndFirstCertificate =
(&x509signature).try_into().unwrap();
assert_debug_snapshot!(raw_signature);
}
#[test]
fn test_roundtrip_sig_to_raw_and_back() {
const SIG: &str = include_str!("test_cms.pem");
let x509signature = X509Signature::from_cms_pem(SIG).unwrap();
let raw_and_certs: RawX509SignatureAndFirstCertificate =
(&x509signature).try_into().unwrap();
let (authority_key_identifier, roundtripped) =
raw_and_certs.raw_x509signature.into_x509_signature().unwrap();
assert_eq!(roundtripped.to_cms_pem(), SIG);
assert_eq!(authority_key_identifier, "2F6Rmhfww1sT23VCfSE3mt8+lhE");
}
}