use crate::cms::signed_data::{AlgorithmIdentifier, SignedData, SignerIdentifier, SignerInfo};
#[derive(Debug, Default)]
pub struct SignedDataBuilder {
content_type: Option<String>,
content: Option<Vec<u8>>,
signers: Vec<SignerInfo>,
certificates: Vec<Vec<u8>>,
}
impl SignedDataBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn content_type(mut self, oid: impl Into<String>) -> Self {
self.content_type = Some(oid.into());
self
}
pub fn content(mut self, content: Option<Vec<u8>>) -> Self {
self.content = content;
self
}
pub fn signer(mut self, signer: SignerInfo) -> Self {
self.signers.push(signer);
self
}
pub fn certificate(mut self, cert_der: Vec<u8>) -> Self {
self.certificates.push(cert_der);
self
}
pub fn build(self) -> Result<SignedData, CmsError> {
let content_type = self
.content_type
.ok_or(CmsError::MissingField("content_type"))?;
let mut sd = SignedData::new(content_type, self.content);
for cert in self.certificates {
sd.add_certificate(cert);
}
for signer in self.signers {
sd.add_signer(signer);
}
Ok(sd)
}
}
#[derive(Debug, thiserror::Error)]
pub enum CmsError {
#[error("missing required field: {0}")]
MissingField(&'static str),
#[error("serialization error: {0}")]
Serialize(String),
#[error("verification failure: {0}")]
Verify(String),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
pub fn build_detached_signature(
payload_hash: Vec<u8>,
signer_algorithm: impl Into<String>,
signature: Vec<u8>,
cert_chain_der: Vec<Vec<u8>>,
) -> Result<SignedData, CmsError> {
let _ = payload_hash; let signer = SignerInfo {
version: 1,
sid: SignerIdentifier::SubjectKeyIdentifier {
key_identifier: cert_chain_der
.first()
.map(|c| c[..20].to_vec())
.unwrap_or_default(),
},
digest_algorithm: AlgorithmIdentifier {
oid: "2.16.840.1.101.3.4.2.1".into(), parameters: None,
},
signed_attrs: Vec::new(),
signature_algorithm: AlgorithmIdentifier {
oid: signer_algorithm.into(),
parameters: None,
},
signature,
unsigned_attrs: Vec::new(),
};
let mut builder = SignedDataBuilder::new()
.content_type("1.2.840.113549.1.7.1")
.content(None)
.signer(signer);
for cert in cert_chain_der {
builder = builder.certificate(cert);
}
builder.build()
}
pub use crate::cms::signed_data::EncapContentInfo as _ReExportEncapContentInfo;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_constructs_signed_data() {
let sd = SignedDataBuilder::new()
.content_type("1.2.840.113549.1.7.1")
.content(Some(b"hello".to_vec()))
.build()
.unwrap();
assert_eq!(sd.encap_content_info.content_type, "1.2.840.113549.1.7.1");
assert_eq!(sd.encap_content_info.content, Some(b"hello".to_vec()));
}
#[test]
fn detached_signature_helper() {
let sd = build_detached_signature(
vec![0u8; 32],
"1.2.840.113549.1.1.11",
vec![0u8; 256],
vec![vec![0u8; 100]],
)
.unwrap();
assert_eq!(sd.signer_count(), 1);
assert!(sd.encap_content_info.content.is_none()); }
#[test]
fn missing_content_type_fails() {
let result = SignedDataBuilder::new().build();
assert!(result.is_err());
}
}