use ::cms::cert::CertificateChoices;
use ::cms::content_info::{CmsVersion, ContentInfo};
use ::cms::signed_data::{
CertificateSet, EncapsulatedContentInfo, SignedData, SignerInfos,
};
use const_oid::db::rfc5911::{ID_DATA, ID_SIGNED_DATA};
use der::asn1::SetOfVec;
use der::{Decode, Encode};
use x509_cert::Certificate;
use crate::error::{Error, Result};
pub fn encode_degenerate(certs_der: &[Vec<u8>]) -> Result<Vec<u8>> {
if certs_der.is_empty() {
return Err(Error::Cms("encode_degenerate: empty cert list".into()));
}
let mut choices: Vec<CertificateChoices> = Vec::with_capacity(certs_der.len());
for bytes in certs_der {
let cert = Certificate::from_der(bytes)
.map_err(|e| Error::Cms(format!("parse cert before wrap: {e}")))?;
choices.push(CertificateChoices::Certificate(cert));
}
let set = SetOfVec::try_from(choices)
.map_err(|e| Error::Cms(format!("build certificate set: {e}")))?;
let signed = SignedData {
version: CmsVersion::V1,
digest_algorithms: Default::default(),
encap_content_info: EncapsulatedContentInfo {
econtent_type: ID_DATA,
econtent: None,
},
certificates: Some(CertificateSet(set)),
crls: None,
signer_infos: SignerInfos(Default::default()),
};
let content = der::Any::encode_from(&signed)
.map_err(|e| Error::Cms(format!("encode SignedData: {e}")))?;
let info = ContentInfo { content_type: ID_SIGNED_DATA, content };
info.to_der().map_err(|e| Error::Cms(format!("encode ContentInfo: {e}")))
}
pub fn decode_degenerate_first(body_der: &[u8]) -> Result<Vec<u8>> {
let info = ContentInfo::from_der(body_der)
.map_err(|e| Error::Cms(format!("parse ContentInfo: {e}")))?;
if info.content_type != ID_SIGNED_DATA {
return Err(Error::Cms(format!(
"expected SignedData OID, got {}",
info.content_type
)));
}
let signed: SignedData = info
.content
.decode_as()
.map_err(|e| Error::Cms(format!("decode SignedData: {e}")))?;
let set = signed
.certificates
.ok_or_else(|| Error::Cms("SignedData has no certificates".into()))?;
let first = set.0.iter().next().ok_or_else(|| Error::Cms("empty certificate set".into()))?;
match first {
CertificateChoices::Certificate(c) => c
.to_der()
.map_err(|e| Error::Cms(format!("re-encode leaf cert: {e}"))),
_ => Err(Error::Cms("first certificate choice is not a plain X.509 cert".into())),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "ca")]
#[test]
fn roundtrip_degenerate_single_cert() {
let params = rcgen::CertificateParams::new(vec!["test-roundtrip".into()]).unwrap();
let key = rcgen::KeyPair::generate().unwrap();
let cert = params.self_signed(&key).unwrap();
let der = cert.der().to_vec();
let wrapped = encode_degenerate(&[der.clone()]).unwrap();
let unwrapped = decode_degenerate_first(&wrapped).unwrap();
assert_eq!(unwrapped, der, "first cert should round-trip byte-for-byte");
}
}