use std::str::FromStr;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::{
Decoder, Encoding, ExplicitTag, Integer, ObjectIdentifier, OctetStringRef, RawDer, SetOf, Tag,
ToDer,
};
use synta_certificate::{
cms_2010_types::IssuerAndSerialNumber,
cms_rfc5652_types::{Attribute, EncapsulatedContentInfo, SignedData, SignerInfo},
digest_alg_id, pkcs7_types, AlgorithmIdentifier, Certificate, DataHasher as _, Name,
PrivateKey as _,
};
use crate::crypto_keys::PyPrivateKey;
struct SignerSpec {
key: synta_certificate::BackendPrivateKey,
cert_der: Vec<u8>,
hash_algorithm: String,
}
#[pyclass(name = "SignedDataBuilder")]
pub struct PySignedDataBuilder {
content: Vec<u8>,
content_oid: ObjectIdentifier,
detached: bool,
signers: Vec<SignerSpec>,
extra_certs: Vec<Vec<u8>>,
}
#[pymethods]
impl PySignedDataBuilder {
#[new]
#[pyo3(signature = (content, *, content_type = None, detached = false))]
fn new(content: &[u8], content_type: Option<&str>, detached: bool) -> PyResult<Self> {
let oid_str = content_type.unwrap_or("1.2.840.113549.1.7.1");
let content_oid = ObjectIdentifier::from_str(oid_str).map_err(|e| {
PyValueError::new_err(format!("invalid content_type OID {oid_str:?}: {e}"))
})?;
Ok(Self {
content: content.to_vec(),
content_oid,
detached,
signers: Vec::new(),
extra_certs: Vec::new(),
})
}
#[pyo3(signature = (key, cert_der, hash_algorithm = "sha256"))]
fn add_signer<'py>(
slf: Bound<'py, Self>,
key: &PyPrivateKey,
cert_der: &[u8],
hash_algorithm: &str,
) -> PyResult<Bound<'py, Self>> {
match hash_algorithm {
"sha1" | "sha256" | "sha384" | "sha512" => {}
other => {
return Err(PyValueError::new_err(format!(
"unsupported hash_algorithm {other:?}; use sha1, sha256, sha384, or sha512"
)));
}
}
slf.borrow_mut().signers.push(SignerSpec {
key: key.inner.clone(),
cert_der: cert_der.to_vec(),
hash_algorithm: hash_algorithm.to_owned(),
});
Ok(slf)
}
fn add_cert<'py>(slf: Bound<'py, Self>, cert_der: &[u8]) -> Bound<'py, Self> {
slf.borrow_mut().extra_certs.push(cert_der.to_vec());
slf
}
fn build<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
if self.signers.is_empty() {
return Err(PyValueError::new_err(
"at least one signer must be added before calling build()",
));
}
let hasher = synta_certificate::default_data_hasher();
struct SignerBuffers {
ias_der: Vec<u8>,
signed_attrs_content: Vec<u8>,
digest_alg: AlgorithmIdentifier<'static>,
sig_alg_bytes: Vec<u8>,
signature: Vec<u8>,
cert_der: Vec<u8>,
}
let mut signer_bufs: Vec<SignerBuffers> = Vec::with_capacity(self.signers.len());
let mut seen_alg_oids: Vec<ObjectIdentifier> = Vec::new();
let mut digest_algs: Vec<AlgorithmIdentifier<'static>> = Vec::new();
for spec in &self.signers {
let alg = digest_alg_id(&spec.hash_algorithm).ok_or_else(|| {
PyValueError::new_err(format!(
"unsupported hash_algorithm {:?}; use sha1, sha256, sha384, or sha512",
spec.hash_algorithm
))
})?;
if !seen_alg_oids.contains(&alg.algorithm) {
seen_alg_oids.push(alg.algorithm.clone());
digest_algs.push(alg.clone());
}
let ias_der = issuer_and_serial_der(&spec.cert_der)?;
let digest_bytes = hasher
.hash_data(&spec.hash_algorithm, &self.content)
.map_err(|e| PyValueError::new_err(format!("hash failed: {e}")))?;
let (to_sign, signed_attrs_content) =
build_signed_attrs(&self.content_oid, &digest_bytes)?;
let signer = spec.key.as_signer(&spec.hash_algorithm);
let sig_alg_bytes = signer
.signature_algorithm_der_erased()
.map_err(|e| PyValueError::new_err(format!("sig alg: {e}")))?;
let signature = signer
.sign_tbs_erased(&to_sign)
.map_err(|e| PyValueError::new_err(format!("sign: {e}")))?;
signer_bufs.push(SignerBuffers {
ias_der,
signed_attrs_content,
digest_alg: alg,
sig_alg_bytes,
signature,
cert_der: spec.cert_der.clone(),
});
}
let mut signer_infos: Vec<SignerInfo<'_>> = Vec::with_capacity(signer_bufs.len());
for buf in &signer_bufs {
let sig_alg = AlgorithmIdentifier::from_der(&buf.sig_alg_bytes)
.map_err(|e| PyValueError::new_err(format!("decode sig alg: {e}")))?;
signer_infos.push(SignerInfo {
version: Integer::from_i64(1),
sid: RawDer(&buf.ias_der),
digest_algorithm: buf.digest_alg.clone(),
signed_attrs: Some(RawDer(&buf.signed_attrs_content)),
signature_algorithm: sig_alg,
signature: OctetStringRef::new(&buf.signature),
unsigned_attrs: None,
});
}
let mut all_cert_slices: Vec<&[u8]> =
signer_bufs.iter().map(|b| b.cert_der.as_slice()).collect();
for extra in &self.extra_certs {
all_cert_slices.push(extra.as_slice());
}
let certs_content: Vec<u8> = all_cert_slices.concat();
let eci = EncapsulatedContentInfo {
e_content_type: self.content_oid.clone(),
e_content: if self.detached {
None
} else {
Some(OctetStringRef::new(&self.content))
},
};
let signed_data = SignedData {
version: Integer::from_i64(1),
digest_algorithms: SetOf::from_vec(digest_algs),
encap_content_info: eci,
certificates: if certs_content.is_empty() {
None
} else {
Some(RawDer(&certs_content))
},
crls: None,
signer_infos: SetOf::from_vec(signer_infos),
};
let sd_der = signed_data
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode SignedData: {e}")))?;
let explicit_0 = ExplicitTag::context_specific(0, RawDer(sd_der.as_slice()))
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode [0] EXPLICIT wrapper: {e}")))?;
let id_signed_data = ObjectIdentifier::new(pkcs7_types::ID_SIGNED_DATA)
.map_err(|_| PyValueError::new_err("invalid id-signedData OID"))?;
let content_info = pkcs7_types::ContentInfo {
content_type: id_signed_data,
content: RawDer(&explicit_0),
};
let ci_der = content_info
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode ContentInfo: {e}")))?;
Ok(PyBytes::new(py, &ci_der))
}
}
fn issuer_and_serial_der(cert_der: &[u8]) -> PyResult<Vec<u8>> {
let mut dec = Decoder::new(cert_der, Encoding::Ber);
let cert: Certificate = dec
.decode()
.map_err(|e| PyValueError::new_err(format!("failed to parse signing certificate: {e}")))?;
let issuer_name = Name::from_der(cert.tbs_certificate.issuer.as_bytes())
.map_err(|e| PyValueError::new_err(format!("parse issuer Name: {e}")))?;
IssuerAndSerialNumber {
issuer: issuer_name,
serial_number: cert.tbs_certificate.serial_number.clone(),
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode IssuerAndSerialNumber: {e}")))
}
fn build_signed_attrs(
content_oid: &ObjectIdentifier,
digest: &[u8],
) -> PyResult<(Vec<u8>, Vec<u8>)> {
let id_ct = ObjectIdentifier::new(synta_certificate::oids::PKCS9_CONTENT_TYPE)
.map_err(|_| PyValueError::new_err("invalid id-contentType OID"))?;
let id_md = ObjectIdentifier::new(synta_certificate::oids::PKCS9_MESSAGE_DIGEST)
.map_err(|_| PyValueError::new_err("invalid id-messageDigest OID"))?;
let ct_vals = SetOf::from_vec(vec![content_oid.clone()])
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode contentType attrValues: {e}")))?;
let ct_attr_der = Attribute {
attr_type: id_ct,
attr_values: RawDer(&ct_vals),
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode contentType Attribute: {e}")))?;
let md_vals = SetOf::from_vec(vec![OctetStringRef::new(digest)])
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode messageDigest attrValues: {e}")))?;
let md_attr_der = Attribute {
attr_type: id_md,
attr_values: RawDer(&md_vals),
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode messageDigest Attribute: {e}")))?;
let to_sign = SetOf::from_vec(vec![
RawDer(ct_attr_der.as_slice()),
RawDer(md_attr_der.as_slice()),
])
.to_der()
.map_err(|e| PyValueError::new_err(format!("encode signedAttrs SET: {e}")))?;
let mut tmp = Decoder::new(&to_sign, Encoding::Der);
let inner = tmp
.enter_constructed(Tag::universal_constructed(17))
.map_err(|e| PyValueError::new_err(format!("strip SET outer tag: {e}")))?;
let signed_attrs_content = inner.remaining().to_vec();
Ok((to_sign, signed_attrs_content))
}