use std::str::FromStr;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use synta::types::string::OctetStringRef;
use synta::{Element, ExplicitTag, Integer, Null, ObjectIdentifier, RawDer, SetOf, Tag};
use synta::{Encode, Encoder, Encoding};
use synta_certificate::cms_rfc5652_types::{
Attribute, EncapsulatedContentInfo, SignedData, SignerInfo,
};
use synta_certificate::pkcs7_types::ContentInfo;
use synta_certificate::AlgorithmIdentifier;
use synta_certificate::{DataHasher as _, PrivateKey as _};
use crate::crypto_keys::PyPrivateKey;
fn digest_alg_oid(name: &str) -> Option<&'static str> {
match name {
"sha1" => Some("1.3.14.3.2.26"),
"sha256" => Some("2.16.840.1.101.3.4.2.1"),
"sha384" => Some("2.16.840.1.101.3.4.2.2"),
"sha512" => Some("2.16.840.1.101.3.4.2.3"),
_ => None,
}
}
fn make_digest_alg(name: &str) -> Option<AlgorithmIdentifier<'static>> {
let oid_str = digest_alg_oid(name)?;
let oid = ObjectIdentifier::from_str(oid_str).ok()?;
Some(AlgorithmIdentifier {
algorithm: oid,
parameters: Some(Element::Null(Null)),
})
}
fn build_iasn_der(cert_der: &[u8]) -> PyResult<Vec<u8>> {
use synta_certificate::cms_2010_types::IssuerAndSerialNumber;
use synta_certificate::{Certificate, Name};
let cert: Certificate = synta::Decoder::new(cert_der, Encoding::Ber)
.decode()
.map_err(|e| PyValueError::new_err(format!("failed to parse signing certificate: {e}")))?;
let issuer_raw = cert.tbs_certificate.issuer.as_bytes();
let issuer = Name::from_der(issuer_raw)
.map_err(|e| PyValueError::new_err(format!("issuer decode: {e}")))?;
let serial_number = Integer::from_bytes(cert.tbs_certificate.serial_number.as_bytes());
IssuerAndSerialNumber {
issuer,
serial_number,
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("IssuerAndSerialNumber encode: {e}")))
}
fn encode_set_of_one<T: Encode>(value: T) -> PyResult<Vec<u8>> {
let mut set: SetOf<T> = SetOf::new();
set.push(value);
let mut enc = Encoder::new(Encoding::Der);
set.encode(&mut enc)
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
enc.finish()
.map_err(|e| PyValueError::new_err(format!("{e}")))
}
fn build_content_type_attr(content_oid: &ObjectIdentifier) -> PyResult<Vec<u8>> {
let attr_type =
ObjectIdentifier::from_str("1.2.840.113549.1.9.3").expect("contentType OID is valid");
let set_bytes = encode_set_of_one(content_oid.clone())?;
Attribute {
attr_type,
attr_values: RawDer(&set_bytes),
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("{e}")))
}
fn build_message_digest_attr(digest: &[u8]) -> PyResult<Vec<u8>> {
let attr_type =
ObjectIdentifier::from_str("1.2.840.113549.1.9.4").expect("messageDigest OID is valid");
let set_bytes = encode_set_of_one(OctetStringRef::new(digest))?;
Attribute {
attr_type,
attr_values: RawDer(&set_bytes),
}
.to_der()
.map_err(|e| PyValueError::new_err(format!("{e}")))
}
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"
)));
}
}
let spec = SignerSpec {
key: key.inner.clone(),
cert_der: cert_der.to_vec(),
hash_algorithm: hash_algorithm.to_owned(),
};
slf.borrow_mut().signers.push(spec);
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 {
iasn_der: Vec<u8>,
signed_attrs_content: Vec<u8>,
sig_alg_der: Vec<u8>,
signature: Vec<u8>,
cert_der: Vec<u8>,
hash_algorithm: String,
}
let mut signer_bufs: Vec<SignerBuffers> = Vec::with_capacity(self.signers.len());
for spec in &self.signers {
let iasn_der = build_iasn_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 content_type_attr = build_content_type_attr(&self.content_oid)?;
let msg_digest_attr = build_message_digest_attr(&digest_bytes)?;
let signed_attrs_content: Vec<u8> =
[content_type_attr.as_slice(), msg_digest_attr.as_slice()].concat();
let to_sign = {
let mut enc = Encoder::new(Encoding::Der);
enc.write_tag(Tag::universal_constructed(17))
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
enc.write_length(signed_attrs_content.len())
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
enc.write_bytes(&signed_attrs_content);
enc.finish()
.map_err(|e| PyValueError::new_err(format!("{e}")))?
};
let signer = spec.key.as_signer(&spec.hash_algorithm);
let sig_alg_der = 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 {
iasn_der,
signed_attrs_content,
sig_alg_der,
signature,
cert_der: spec.cert_der.clone(),
hash_algorithm: spec.hash_algorithm.clone(),
});
}
let mut si_ders: Vec<Vec<u8>> = Vec::with_capacity(signer_bufs.len());
let mut seen_hash_algs: Vec<String> = Vec::new();
let mut digest_algs: SetOf<AlgorithmIdentifier<'static>> = SetOf::new();
for buf in &signer_bufs {
let digest_alg = make_digest_alg(&buf.hash_algorithm).ok_or_else(|| {
PyValueError::new_err(format!(
"unsupported hash_algorithm {:?}",
buf.hash_algorithm
))
})?;
if !seen_hash_algs.contains(&buf.hash_algorithm) {
seen_hash_algs.push(buf.hash_algorithm.clone());
digest_algs.push(digest_alg.clone());
}
let sig_alg: AlgorithmIdentifier<'_> =
synta::Decoder::new(&buf.sig_alg_der, Encoding::Der)
.decode()
.map_err(|e| PyValueError::new_err(format!("sig alg decode: {e}")))?;
let si = SignerInfo {
version: Integer::from(1),
sid: RawDer(&buf.iasn_der),
digest_algorithm: digest_alg,
signed_attrs: Some(RawDer(&buf.signed_attrs_content)),
signature_algorithm: sig_alg,
signature: OctetStringRef::new(&buf.signature),
unsigned_attrs: None,
};
let si_der = si
.to_der()
.map_err(|e| PyValueError::new_err(format!("SignerInfo encode: {e}")))?;
si_ders.push(si_der);
}
let mut signer_infos: SetOf<SignerInfo<'_>> = SetOf::new();
for si_der in &si_ders {
let si: SignerInfo<'_> = SignerInfo::from_der(si_der)
.map_err(|e| PyValueError::new_err(format!("SignerInfo re-parse: {e}")))?;
signer_infos.push(si);
}
let eci = if self.detached {
EncapsulatedContentInfo {
e_content_type: self.content_oid.clone(),
e_content: None,
}
} else {
EncapsulatedContentInfo {
e_content_type: self.content_oid.clone(),
e_content: Some(OctetStringRef::new(&self.content)),
}
};
let certs_content: Vec<u8> = signer_bufs
.iter()
.map(|b| b.cert_der.as_slice())
.chain(self.extra_certs.iter().map(Vec::as_slice))
.flat_map(|s| s.iter().copied())
.collect();
let certificates: Option<RawDer<'_>> = if certs_content.is_empty() {
None
} else {
Some(RawDer(&certs_content))
};
let signed_data = SignedData {
version: Integer::from(1),
digest_algorithms: digest_algs,
encap_content_info: eci,
certificates,
crls: None,
signer_infos,
};
let sd_der = signed_data
.to_der()
.map_err(|e| PyValueError::new_err(format!("SignedData encode: {e}")))?;
let sd_rawder = RawDer(&sd_der);
let explicit0 = ExplicitTag::context_specific(0, &sd_rawder);
let mut exp_enc = Encoder::new(Encoding::Der);
explicit0
.encode(&mut exp_enc)
.map_err(|e| PyValueError::new_err(format!("explicit tag encode: {e}")))?;
let content_bytes = exp_enc
.finish()
.map_err(|e| PyValueError::new_err(format!("{e}")))?;
let id_signed_data =
ObjectIdentifier::from_str("1.2.840.113549.1.7.2").expect("id-signedData OID is valid");
let content_info = ContentInfo {
content_type: id_signed_data,
content: RawDer(&content_bytes),
};
let ci_der = content_info
.to_der()
.map_err(|e| PyValueError::new_err(format!("ContentInfo encode: {e}")))?;
Ok(PyBytes::new(py, &ci_der))
}
}