use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedData {
pub version: u32,
pub digest_algorithms: Vec<AlgorithmIdentifier>,
pub encap_content_info: EncapContentInfo,
pub certificates: Vec<Vec<u8>>,
pub signer_infos: Vec<SignerInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncapContentInfo {
pub content_type: String,
#[serde(default)]
pub content: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlgorithmIdentifier {
pub oid: String,
#[serde(default)]
pub parameters: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignerInfo {
pub version: u32,
pub sid: SignerIdentifier,
pub digest_algorithm: AlgorithmIdentifier,
#[serde(default)]
pub signed_attrs: Vec<Attribute>,
pub signature_algorithm: AlgorithmIdentifier,
pub signature: Vec<u8>,
#[serde(default)]
pub unsigned_attrs: Vec<Attribute>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SignerIdentifier {
IssuerAndSerialNumber {
issuer_der: Vec<u8>,
serial_number: Vec<u8>,
},
SubjectKeyIdentifier {
key_identifier: Vec<u8>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attribute {
pub oid: String,
pub values: Vec<Vec<u8>>,
}
impl SignedData {
pub fn new(content_type: impl Into<String>, content: Option<Vec<u8>>) -> Self {
Self {
version: 1,
digest_algorithms: Vec::new(),
encap_content_info: EncapContentInfo {
content_type: content_type.into(),
content,
},
certificates: Vec::new(),
signer_infos: Vec::new(),
}
}
pub fn add_signer(&mut self, signer_info: SignerInfo) {
let oid = &signer_info.digest_algorithm.oid;
if !self.digest_algorithms.iter().any(|a| &a.oid == oid) {
self.digest_algorithms
.push(signer_info.digest_algorithm.clone());
}
self.signer_infos.push(signer_info);
}
pub fn add_certificate(&mut self, cert_der: Vec<u8>) {
self.certificates.push(cert_der);
}
pub fn signer_count(&self) -> usize {
self.signer_infos.len()
}
pub fn signing_time(&self) -> Option<DateTime<Utc>> {
self.signer_infos.first().and_then(|s| {
s.signed_attrs.iter().find_map(|a| {
if a.oid == "1.2.840.113549.1.9.5" {
None
} else {
None
}
})
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signed_data_construction() {
let mut sd = SignedData::new("1.2.840.113549.1.7.1", Some(b"hello".to_vec()));
let signer = SignerInfo {
version: 1,
sid: SignerIdentifier::SubjectKeyIdentifier {
key_identifier: vec![1, 2, 3],
},
digest_algorithm: AlgorithmIdentifier {
oid: "2.16.840.1.101.3.4.2.1".into(), parameters: None,
},
signed_attrs: Vec::new(),
signature_algorithm: AlgorithmIdentifier {
oid: "1.2.840.113549.1.1.11".into(), parameters: None,
},
signature: vec![0u8; 256],
unsigned_attrs: Vec::new(),
};
sd.add_signer(signer);
sd.add_certificate(vec![0u8; 100]);
assert_eq!(sd.signer_count(), 1);
assert_eq!(sd.digest_algorithms.len(), 1);
assert_eq!(sd.certificates.len(), 1);
}
}