use crate::c2pa_cbor::{decode, Value};
use crate::c2pa_crypto::CoseAlg;
use const_oid::ObjectIdentifier;
use der::Decode;
use time::OffsetDateTime;
use x509_cert::Certificate;
const OID_AT_COMMON_NAME: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.3");
const OID_AT_ORGANIZATION: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.5.4.10");
#[derive(Debug, Clone, Default)]
pub struct SignatureInfo {
pub alg: Option<String>,
pub common_name: Option<String>,
pub issuer: Option<String>,
pub cert_serial_number: Option<String>,
}
pub fn cose_alg(cose_sign1: &[u8]) -> Option<CoseAlg> {
let value = decode(cose_sign1).ok()?;
let array = match &value {
Value::Tag(18, inner) => inner.as_ref(),
other => other,
};
let items = match array {
Value::Array(items) => items,
_ => return None,
};
let protected = items.first()?.as_bytes()?;
let header = decode(protected).ok()?;
let map = header.as_map()?;
for (k, v) in map {
if let (Value::Integer(1), Value::Integer(id)) = (k, v) {
return CoseAlg::from_cose_id(*id);
}
}
None
}
pub fn alg_name(alg: CoseAlg) -> &'static str {
match alg {
CoseAlg::Es256 => "Es256",
CoseAlg::Es384 => "Es384",
CoseAlg::Es512 => "Es512",
CoseAlg::Ps256 => "Ps256",
CoseAlg::Ps384 => "Ps384",
CoseAlg::Ps512 => "Ps512",
CoseAlg::EdDsa => "Ed25519",
}
}
pub fn signature_info(leaf_der: &[u8], cose_sign1: &[u8]) -> SignatureInfo {
let mut info = SignatureInfo {
alg: cose_alg(cose_sign1).map(|a| alg_name(a).to_string()),
..SignatureInfo::default()
};
if let Ok(cert) = Certificate::from_der(leaf_der) {
info.common_name = attribute(&cert, OID_AT_COMMON_NAME, true);
info.issuer = attribute(&cert, OID_AT_ORGANIZATION, false);
info.cert_serial_number = Some(serial_decimal(
cert.tbs_certificate.serial_number.as_bytes(),
));
}
info
}
fn attribute(cert: &Certificate, oid: ObjectIdentifier, subject: bool) -> Option<String> {
let name = if subject {
&cert.tbs_certificate.subject
} else {
&cert.tbs_certificate.issuer
};
for rdn in name.0.iter() {
for atav in rdn.0.iter() {
if atav.oid == oid {
let raw = atav.value.value();
return Some(String::from_utf8_lossy(raw).into_owned());
}
}
}
None
}
pub fn valid_at(leaf_der: &[u8], t: OffsetDateTime) -> bool {
let Ok(cert) = Certificate::from_der(leaf_der) else {
return false;
};
let nb = cert
.tbs_certificate
.validity
.not_before
.to_unix_duration()
.as_secs() as i64;
let na = cert
.tbs_certificate
.validity
.not_after
.to_unix_duration()
.as_secs() as i64;
let now = t.unix_timestamp();
nb <= now && now <= na
}
fn serial_decimal(be_bytes: &[u8]) -> String {
let mut digits = be_bytes.to_vec();
let start = digits.iter().position(|&b| b != 0).unwrap_or(digits.len());
digits.drain(..start);
if digits.is_empty() {
return "0".to_string();
}
let mut out = Vec::new();
while !digits.is_empty() {
let mut remainder: u16 = 0;
let mut quotient = Vec::with_capacity(digits.len());
for &byte in digits.iter() {
let acc = (remainder << 8) | byte as u16;
quotient.push((acc / 10) as u8);
remainder = acc % 10;
}
let q_start = quotient
.iter()
.position(|&b| b != 0)
.unwrap_or(quotient.len());
digits = quotient[q_start..].to_vec();
out.push(b'0' + remainder as u8);
}
out.reverse();
String::from_utf8(out).expect("ascii digits")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serial_decimal_handles_zero_and_sign_guard() {
assert_eq!(serial_decimal(&[]), "0");
assert_eq!(serial_decimal(&[0x00]), "0");
assert_eq!(serial_decimal(&[0x00, 0x01]), "1");
assert_eq!(serial_decimal(&[0x01, 0x00]), "256");
assert_eq!(serial_decimal(&[0xFF, 0xFF]), "65535");
}
}