use base64ct::{Base64, Encoding};
use sha2::{Digest, Sha256};
use crate::{error::AdesError, signer::Signer};
const DS_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
const XADES_NS: &str = "http://uri.etsi.org/01903/v1.3.2#";
const EXC_C14N: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
const SHA256_ALG: &str = "http://www.w3.org/2001/04/xmlenc#sha256";
const RSA_SHA256: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
const ECDSA_SHA256: &str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
const SIGNED_PROPS_TYPE: &str = "http://uri.etsi.org/01903#SignedProperties";
const RSA_OID: &str = "1.2.840.113549.1.1.1";
const EC_OID: &str = "1.2.840.10045.2.1";
pub fn sign<S: Signer>(data: &[u8], signer: &S) -> Result<Vec<u8>, AdesError>
where
S::Error: std::error::Error + Send + Sync + 'static,
{
let cert = signer.certificate();
let digest_algo = signer.digest_algorithm();
let key_alg_oid = cert
.inner()
.tbs_certificate
.subject_public_key_info
.algorithm
.oid
.to_string();
let sig_method_alg = match key_alg_oid.as_str() {
RSA_OID => RSA_SHA256,
EC_OID => ECDSA_SHA256,
_ => {
return Err(AdesError::NotImplemented(
"XAdES: unsupported key type — only RSA and EC are supported",
))
}
};
let sig_id = "id-sig";
let data_obj_id = "id-data-obj";
let signed_props_id = "id-signed-props";
let data_b64 = Base64::encode_string(data);
let cert_der = cert.to_der();
let cert_b64 = Base64::encode_string(cert_der);
let cert_digest_b64 = Base64::encode_string(&Sha256::digest(cert_der));
let signing_time = utc_datetime_now()?;
let data_obj_canon =
format!("<ds:Object xmlns:ds=\"{DS_NS}\" Id=\"{data_obj_id}\">{data_b64}</ds:Object>");
let data_digest_b64 = Base64::encode_string(&Sha256::digest(data_obj_canon.as_bytes()));
let signed_props_canon = format!(
"<xades:SignedProperties xmlns:xades=\"{XADES_NS}\" Id=\"{signed_props_id}\">\
<xades:SignedSignatureProperties>\
<xades:SigningTime>{signing_time}</xades:SigningTime>\
<xades:SigningCertificateV2>\
<xades:Cert>\
<xades:CertDigest>\
<ds:DigestMethod xmlns:ds=\"{DS_NS}\" Algorithm=\"{SHA256_ALG}\"></ds:DigestMethod>\
<ds:DigestValue xmlns:ds=\"{DS_NS}\">{cert_digest_b64}</ds:DigestValue>\
</xades:CertDigest>\
</xades:Cert>\
</xades:SigningCertificateV2>\
</xades:SignedSignatureProperties>\
</xades:SignedProperties>"
);
let props_digest_b64 = Base64::encode_string(&Sha256::digest(signed_props_canon.as_bytes()));
let signed_info_canon = format!(
"<ds:SignedInfo xmlns:ds=\"{DS_NS}\">\
<ds:CanonicalizationMethod Algorithm=\"{EXC_C14N}\"></ds:CanonicalizationMethod>\
<ds:SignatureMethod Algorithm=\"{sig_method_alg}\"></ds:SignatureMethod>\
<ds:Reference URI=\"#{data_obj_id}\">\
<ds:Transforms><ds:Transform Algorithm=\"{EXC_C14N}\"></ds:Transform></ds:Transforms>\
<ds:DigestMethod Algorithm=\"{SHA256_ALG}\"></ds:DigestMethod>\
<ds:DigestValue>{data_digest_b64}</ds:DigestValue>\
</ds:Reference>\
<ds:Reference Type=\"{SIGNED_PROPS_TYPE}\" URI=\"#{signed_props_id}\">\
<ds:Transforms><ds:Transform Algorithm=\"{EXC_C14N}\"></ds:Transform></ds:Transforms>\
<ds:DigestMethod Algorithm=\"{SHA256_ALG}\"></ds:DigestMethod>\
<ds:DigestValue>{props_digest_b64}</ds:DigestValue>\
</ds:Reference>\
</ds:SignedInfo>"
);
let si_digest = digest_algo.hash(signed_info_canon.as_bytes());
let signature_bytes = signer
.sign_digest(&si_digest)
.map_err(|e| AdesError::Signer(Box::new(e)))?;
let signature_bytes = if sig_method_alg == ECDSA_SHA256 {
ecdsa_der_to_raw(&signature_bytes)?
} else {
signature_bytes
};
let sig_b64 = Base64::encode_string(&signature_bytes);
let xml = format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<ds:Signature Id=\"{sig_id}\" xmlns:ds=\"{DS_NS}\">\n\
{signed_info_canon}\n\
<ds:SignatureValue>{sig_b64}</ds:SignatureValue>\n\
<ds:KeyInfo>\n\
<ds:X509Data>\n\
<ds:X509Certificate>{cert_b64}</ds:X509Certificate>\n\
</ds:X509Data>\n\
</ds:KeyInfo>\n\
{data_obj_canon}\n\
<ds:Object>\n\
<xades:QualifyingProperties Target=\"#{sig_id}\" xmlns:xades=\"{XADES_NS}\">\n\
{signed_props_canon}\n\
</xades:QualifyingProperties>\n\
</ds:Object>\n\
</ds:Signature>"
);
Ok(xml.into_bytes())
}
fn ecdsa_der_to_raw(der: &[u8]) -> Result<Vec<u8>, AdesError> {
let err = || AdesError::NotImplemented("XAdES: malformed ECDSA DER signature");
if der.len() < 6 || der[0] != 0x30 {
return Err(err());
}
let seq_len = der[1] as usize;
if der.len() < 2 + seq_len {
return Err(err());
}
let content = &der[2..2 + seq_len];
if content.len() < 2 || content[0] != 0x02 {
return Err(err());
}
let r_len = content[1] as usize;
if content.len() < 2 + r_len {
return Err(err());
}
let r_bytes = &content[2..2 + r_len];
let rest = &content[2 + r_len..];
if rest.len() < 2 || rest[0] != 0x02 {
return Err(err());
}
let s_len = rest[1] as usize;
if rest.len() < 2 + s_len {
return Err(err());
}
let s_bytes = &rest[2..2 + s_len];
let r_trimmed = strip_leading_zero(r_bytes);
let s_trimmed = strip_leading_zero(s_bytes);
let coord = r_trimmed.len().max(s_trimmed.len());
let mut out = vec![0u8; 2 * coord];
out[coord - r_trimmed.len()..coord].copy_from_slice(r_trimmed);
out[2 * coord - s_trimmed.len()..].copy_from_slice(s_trimmed);
Ok(out)
}
fn strip_leading_zero(b: &[u8]) -> &[u8] {
if b.first() == Some(&0x00) {
&b[1..]
} else {
b
}
}
fn utc_datetime_now() -> Result<String, AdesError> {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| AdesError::NotImplemented("system time before Unix epoch"))?
.as_secs();
let s = (secs % 60) as u32;
let m = ((secs / 60) % 60) as u32;
let h = ((secs / 3600) % 24) as u32;
let z = (secs / 86400) as i64 + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let mn = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if mn <= 2 { y + 1 } else { y };
Ok(format!("{year:04}-{mn:02}-{d:02}T{h:02}:{m:02}:{s:02}Z"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utc_datetime_format() {
let secs = 0u64;
let z = secs as i64 + 719_468;
let era = z / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let mn = if mp < 10 { mp + 3 } else { mp - 9 };
let year = if mn <= 2 { y + 1 } else { y };
assert_eq!((year, mn, d), (1970, 1, 1));
}
#[test]
fn ecdsa_der_to_raw_roundtrip() {
let der = &[0x30u8, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02];
let raw = ecdsa_der_to_raw(der).unwrap();
assert_eq!(raw, &[0x01, 0x02]);
}
}