use std::fs;
use std::path::Path;
use openssl::asn1::Asn1Time;
use openssl::x509::{X509NameRef, X509};
use rsa::{
pkcs1::DecodeRsaPrivateKey,
pkcs1v15::{Signature as RsaPkcs1v15Signature, SigningKey, VerifyingKey},
pkcs8::DecodePrivateKey,
RsaPrivateKey, RsaPublicKey,
};
use sha2::{Digest, Sha256};
use signature::{SignatureEncoding, Signer, Verifier};
use crate::core::{ErrorKind, XmlError, XmlResult};
use super::base64::decode_standard_base64;
use super::validation_data::StaticValidationDataProvider;
use super::SignatureAlgorithm;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pkcs12Credential {
private_key_der: Vec<u8>,
certificate_der: Vec<u8>,
certificate_chain_der: Vec<Vec<u8>>,
}
impl Pkcs12Credential {
pub fn from_file(path: impl AsRef<Path>, password: &str) -> XmlResult<Self> {
let path = path.as_ref();
let pkcs12_der = read_file_bytes(path, "cannot read PKCS#12/PFX file")?;
Self::from_der(&pkcs12_der, password)
}
pub fn from_der(pkcs12_der: &[u8], password: &str) -> XmlResult<Self> {
let pfx = openssl::pkcs12::Pkcs12::from_der(pkcs12_der)
.map_err(|error| credential_error("invalid PKCS#12/PFX DER", error))?;
let parsed = pfx
.parse2(password)
.map_err(|error| credential_error("cannot decrypt PKCS#12/PFX", error))?;
let private_key = parsed.pkey.ok_or_else(|| {
XmlError::new(
ErrorKind::Signature,
"PKCS#12/PFX does not contain a private key",
)
})?;
let certificate = parsed.cert.ok_or_else(|| {
XmlError::new(
ErrorKind::Signature,
"PKCS#12/PFX does not contain an X.509 certificate",
)
})?;
let private_key_der = private_key
.private_key_to_der()
.map_err(|error| credential_error("cannot export PKCS#12/PFX private key", error))?;
let certificate_der = certificate
.to_der()
.map_err(|error| credential_error("cannot export PKCS#12/PFX certificate", error))?;
let mut certificate_chain_der = vec![certificate_der.clone()];
if let Some(ca) = parsed.ca {
for certificate in ca {
certificate_chain_der.push(certificate.to_der().map_err(|error| {
credential_error("cannot export PKCS#12/PFX CA certificate", error)
})?);
}
}
Ok(Self {
private_key_der,
certificate_der,
certificate_chain_der,
})
}
pub fn private_key_der(&self) -> &[u8] {
&self.private_key_der
}
pub fn certificate_der(&self) -> &[u8] {
&self.certificate_der
}
pub fn certificate_chain_der(&self) -> &[Vec<u8>] {
&self.certificate_chain_der
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CertificateDetails {
der: Vec<u8>,
issuer_name: Option<String>,
serial_number: Option<String>,
}
impl CertificateDetails {
pub fn new(der: impl Into<Vec<u8>>) -> Self {
Self {
der: der.into(),
issuer_name: None,
serial_number: None,
}
}
pub fn with_issuer_serial(
mut self,
issuer_name: impl Into<String>,
serial_number: impl Into<String>,
) -> Self {
self.issuer_name = Some(issuer_name.into());
self.serial_number = Some(serial_number.into());
self
}
pub fn der(&self) -> &[u8] {
&self.der
}
pub fn issuer_name(&self) -> Option<&str> {
self.issuer_name.as_deref()
}
pub fn serial_number(&self) -> Option<&str> {
self.serial_number.as_deref()
}
pub fn issuer_serial(&self) -> Option<(&str, &str)> {
Some((self.issuer_name()?, self.serial_number()?))
}
}
pub trait SigningProvider {
fn certificate_der(&self) -> XmlResult<Vec<u8>>;
fn certificate_details(&self) -> XmlResult<CertificateDetails> {
Ok(CertificateDetails::new(self.certificate_der()?))
}
fn certificate_chain_der(&self) -> XmlResult<Vec<Vec<u8>>> {
Ok(vec![self.certificate_der()?])
}
fn certificate_chain_details(&self) -> XmlResult<Vec<CertificateDetails>> {
Ok(self
.certificate_chain_der()?
.into_iter()
.map(CertificateDetails::new)
.collect())
}
fn ensure_certificate_valid_at(&self, _unix_timestamp: i64) -> XmlResult<()> {
Ok(())
}
fn sign(&self, algorithm: SignatureAlgorithm, data: &[u8]) -> XmlResult<Vec<u8>>;
fn verify(
&self,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> XmlResult<bool> {
Ok(self.sign(algorithm, data)? == signature)
}
}
#[derive(Debug, Clone)]
pub struct RsaSha256SigningProvider {
private_key: RsaPrivateKey,
public_key: RsaPublicKey,
certificate_details: CertificateDetails,
certificate_chain_details: Vec<CertificateDetails>,
}
impl RsaSha256SigningProvider {
pub fn from_private_key(
private_key: RsaPrivateKey,
certificate_der: impl Into<Vec<u8>>,
) -> Self {
let public_key = RsaPublicKey::from(&private_key);
Self::from_key_pair(private_key, public_key, certificate_der)
}
pub fn from_key_pair(
private_key: RsaPrivateKey,
public_key: RsaPublicKey,
certificate_der: impl Into<Vec<u8>>,
) -> Self {
Self {
private_key,
public_key,
certificate_details: CertificateDetails::new(certificate_der),
certificate_chain_details: Vec::new(),
}
}
pub fn from_private_key_der(
private_key_der: &[u8],
certificate_der: impl Into<Vec<u8>>,
) -> XmlResult<Self> {
let private_key = RsaPrivateKey::from_pkcs8_der(private_key_der)
.or_else(|_| RsaPrivateKey::from_pkcs1_der(private_key_der))
.map_err(|error| credential_error("invalid RSA private key DER", error))?;
Ok(Self::from_private_key(private_key, certificate_der))
}
pub fn from_private_key_pem(
private_key_pem: &str,
certificate_der: impl Into<Vec<u8>>,
) -> XmlResult<Self> {
let private_key = RsaPrivateKey::from_pkcs8_pem(private_key_pem)
.or_else(|_| RsaPrivateKey::from_pkcs1_pem(private_key_pem))
.map_err(|error| credential_error("invalid RSA private key PEM", error))?;
Ok(Self::from_private_key(private_key, certificate_der))
}
pub fn from_pkcs12_der(pkcs12_der: &[u8], password: &str) -> XmlResult<Self> {
let credential = Pkcs12Credential::from_der(pkcs12_der, password)?;
let provider = Self::from_private_key_der(
credential.private_key_der(),
credential.certificate_der().to_vec(),
)?;
Ok(provider.with_certificate_chain_der(credential.certificate_chain_der().to_vec()))
}
pub fn from_pkcs12_file(path: impl AsRef<Path>, password: &str) -> XmlResult<Self> {
Ok(Pkcs12SigningCredentials::from_file(path, password)?.into_provider())
}
pub fn with_certificate_details(mut self, certificate_details: CertificateDetails) -> Self {
if self
.certificate_chain_details
.first()
.is_some_and(|first| first.der() == self.certificate_details.der())
{
self.certificate_chain_details[0] = certificate_details.clone();
}
self.certificate_details = certificate_details;
self
}
pub fn with_certificate_issuer_serial(
self,
issuer_name: impl Into<String>,
serial_number: impl Into<String>,
) -> Self {
let certificate_details = self
.certificate_details
.clone()
.with_issuer_serial(issuer_name, serial_number);
self.with_certificate_details(certificate_details)
}
pub fn with_certificate_chain_der(mut self, certificate_chain_der: Vec<Vec<u8>>) -> Self {
self.certificate_chain_details = certificate_chain_der
.into_iter()
.map(CertificateDetails::new)
.collect();
self
}
pub fn with_certificate_chain_details(
mut self,
certificate_chain_details: Vec<CertificateDetails>,
) -> Self {
self.certificate_chain_details = certificate_chain_details;
self
}
pub fn public_key(&self) -> &RsaPublicKey {
&self.public_key
}
}
impl SigningProvider for RsaSha256SigningProvider {
fn certificate_der(&self) -> XmlResult<Vec<u8>> {
Ok(self.certificate_details.der.clone())
}
fn certificate_details(&self) -> XmlResult<CertificateDetails> {
Ok(self.certificate_details.clone())
}
fn certificate_chain_der(&self) -> XmlResult<Vec<Vec<u8>>> {
if self.certificate_chain_details.is_empty() {
Ok(vec![self.certificate_details.der.clone()])
} else {
Ok(self
.certificate_chain_details
.iter()
.map(|certificate| certificate.der.clone())
.collect())
}
}
fn certificate_chain_details(&self) -> XmlResult<Vec<CertificateDetails>> {
if self.certificate_chain_details.is_empty() {
Ok(vec![self.certificate_details.clone()])
} else {
Ok(self.certificate_chain_details.clone())
}
}
fn ensure_certificate_valid_at(&self, unix_timestamp: i64) -> XmlResult<()> {
ensure_certificate_der_valid_at(self.certificate_details.der(), unix_timestamp)
}
fn sign(&self, algorithm: SignatureAlgorithm, data: &[u8]) -> XmlResult<Vec<u8>> {
algorithm.ensure_allowed_for_generation()?;
match algorithm {
SignatureAlgorithm::RsaSha256 => {
let signing_key = SigningKey::<Sha256>::new(self.private_key.clone());
Ok(signing_key.sign(data).to_vec())
}
SignatureAlgorithm::RsaSha1 => Err(XmlError::new(
ErrorKind::Signature,
"RSA-SHA1 signatures are not allowed for generation",
)),
}
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> XmlResult<bool> {
match algorithm {
SignatureAlgorithm::RsaSha256 => {
let signature = RsaPkcs1v15Signature::try_from(signature).map_err(|error| {
credential_error("invalid RSA-SHA256 signature value", error)
})?;
let verifying_key = VerifyingKey::<Sha256>::new(self.public_key.clone());
Ok(verifying_key.verify(data, &signature).is_ok())
}
SignatureAlgorithm::RsaSha1 => Ok(false),
}
}
}
#[derive(Debug, Clone)]
pub struct Pkcs12SigningCredentials {
provider: RsaSha256SigningProvider,
certificate_details: CertificateDetails,
additional_certificate_der: Vec<Vec<u8>>,
}
impl Pkcs12SigningCredentials {
pub fn from_file(path: impl AsRef<Path>, password: &str) -> XmlResult<Self> {
Self::from_credential(Pkcs12Credential::from_file(path, password)?)
}
pub fn from_der(pkcs12_der: &[u8], password: &str) -> XmlResult<Self> {
Self::from_credential(Pkcs12Credential::from_der(pkcs12_der, password)?)
}
pub fn from_credential(credential: Pkcs12Credential) -> XmlResult<Self> {
let certificate_details = certificate_details_from_der(credential.certificate_der())?;
let provider = RsaSha256SigningProvider::from_private_key_der(
credential.private_key_der(),
credential.certificate_der().to_vec(),
)?
.with_certificate_details(certificate_details.clone());
let additional_certificate_der = credential
.certificate_chain_der()
.iter()
.skip(1)
.cloned()
.collect();
Ok(Self {
provider,
certificate_details,
additional_certificate_der,
}
.with_refreshed_chain())
}
pub fn with_issuer_serial(
mut self,
issuer_name: impl Into<String>,
serial_number: impl Into<String>,
) -> Self {
self.certificate_details = self
.certificate_details
.clone()
.with_issuer_serial(issuer_name, serial_number);
self.provider = self
.provider
.clone()
.with_certificate_details(self.certificate_details.clone());
self.with_refreshed_chain()
}
pub fn with_certificate_details(mut self, certificate_details: CertificateDetails) -> Self {
self.certificate_details = certificate_details;
self.provider = self
.provider
.clone()
.with_certificate_details(self.certificate_details.clone());
self.with_refreshed_chain()
}
pub fn with_additional_certificate_der(mut self, certificate_der: impl Into<Vec<u8>>) -> Self {
self.additional_certificate_der.push(certificate_der.into());
self.with_refreshed_chain()
}
pub fn with_additional_certificate_file(self, path: impl AsRef<Path>) -> XmlResult<Self> {
Ok(self.with_additional_certificate_der(read_certificate_der_file(path)?))
}
pub fn with_additional_certificate_files<I, P>(mut self, paths: I) -> XmlResult<Self>
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
for path in paths {
self = self.with_additional_certificate_file(path)?;
}
Ok(self)
}
pub fn provider(&self) -> &RsaSha256SigningProvider {
&self.provider
}
pub fn certificate_details(&self) -> &CertificateDetails {
&self.certificate_details
}
pub fn certificate_der(&self) -> &[u8] {
self.certificate_details.der()
}
pub fn additional_certificate_der(&self) -> &[Vec<u8>] {
&self.additional_certificate_der
}
pub fn has_additional_certificate_chain(&self) -> bool {
!self.additional_certificate_der.is_empty()
}
pub fn validation_data_provider(&self) -> StaticValidationDataProvider {
let mut provider =
StaticValidationDataProvider::new().with_certificate(self.certificate_der().to_vec());
for certificate in &self.additional_certificate_der {
provider = provider.with_certificate(certificate.clone());
}
provider
}
pub fn into_provider(self) -> RsaSha256SigningProvider {
self.provider
}
fn with_refreshed_chain(mut self) -> Self {
let chain = if self.additional_certificate_der.is_empty() {
Vec::new()
} else {
let mut chain = Vec::with_capacity(self.additional_certificate_der.len() + 1);
chain.push(self.certificate_details.clone());
chain.extend(
self.additional_certificate_der
.iter()
.cloned()
.map(CertificateDetails::new),
);
chain
};
self.provider = self.provider.clone().with_certificate_chain_details(chain);
self
}
}
impl SigningProvider for Pkcs12SigningCredentials {
fn certificate_der(&self) -> XmlResult<Vec<u8>> {
self.provider.certificate_der()
}
fn certificate_details(&self) -> XmlResult<CertificateDetails> {
self.provider.certificate_details()
}
fn certificate_chain_der(&self) -> XmlResult<Vec<Vec<u8>>> {
self.provider.certificate_chain_der()
}
fn certificate_chain_details(&self) -> XmlResult<Vec<CertificateDetails>> {
self.provider.certificate_chain_details()
}
fn ensure_certificate_valid_at(&self, unix_timestamp: i64) -> XmlResult<()> {
self.provider.ensure_certificate_valid_at(unix_timestamp)
}
fn sign(&self, algorithm: SignatureAlgorithm, data: &[u8]) -> XmlResult<Vec<u8>> {
self.provider.sign(algorithm, data)
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> XmlResult<bool> {
self.provider.verify(algorithm, data, signature)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeterministicSigningProvider {
certificate_details: CertificateDetails,
certificate_chain_details: Vec<CertificateDetails>,
secret: Vec<u8>,
}
impl DeterministicSigningProvider {
pub fn new(certificate_der: impl Into<Vec<u8>>, secret: impl Into<Vec<u8>>) -> Self {
Self {
certificate_details: CertificateDetails::new(certificate_der),
certificate_chain_details: Vec::new(),
secret: secret.into(),
}
}
pub fn with_certificate_issuer_serial(
self,
issuer_name: impl Into<String>,
serial_number: impl Into<String>,
) -> Self {
let certificate_details = self
.certificate_details
.clone()
.with_issuer_serial(issuer_name, serial_number);
Self {
certificate_details,
..self
}
}
pub fn with_certificate_chain_details(
mut self,
certificate_chain_details: Vec<CertificateDetails>,
) -> Self {
self.certificate_chain_details = certificate_chain_details;
self
}
}
impl SigningProvider for DeterministicSigningProvider {
fn certificate_der(&self) -> XmlResult<Vec<u8>> {
Ok(self.certificate_details.der.clone())
}
fn certificate_details(&self) -> XmlResult<CertificateDetails> {
Ok(self.certificate_details.clone())
}
fn certificate_chain_der(&self) -> XmlResult<Vec<Vec<u8>>> {
if self.certificate_chain_details.is_empty() {
Ok(vec![self.certificate_details.der.clone()])
} else {
Ok(self
.certificate_chain_details
.iter()
.map(|certificate| certificate.der.clone())
.collect())
}
}
fn certificate_chain_details(&self) -> XmlResult<Vec<CertificateDetails>> {
if self.certificate_chain_details.is_empty() {
Ok(vec![self.certificate_details.clone()])
} else {
Ok(self.certificate_chain_details.clone())
}
}
fn sign(&self, algorithm: SignatureAlgorithm, data: &[u8]) -> XmlResult<Vec<u8>> {
algorithm.ensure_allowed_for_generation()?;
let mut hasher = Sha256::new();
hasher.update(algorithm.uri().as_bytes());
hasher.update([0]);
hasher.update(&self.secret);
hasher.update([0]);
hasher.update(self.certificate_details.der());
hasher.update([0]);
hasher.update(data);
Ok(hasher.finalize().to_vec())
}
}
fn credential_error(context: &str, error: impl std::fmt::Display) -> XmlError {
XmlError::new(ErrorKind::Signature, format!("{context}: {error}"))
}
fn ensure_certificate_der_valid_at(certificate_der: &[u8], unix_timestamp: i64) -> XmlResult<()> {
let certificate = X509::from_der(certificate_der)
.map_err(|error| credential_error("invalid X.509 certificate DER", error))?;
let signing_time = Asn1Time::from_unix(unix_timestamp as _)
.map_err(|error| credential_error("invalid certificate validation time", error))?;
let not_before = certificate.not_before();
let not_after = certificate.not_after();
if not_before
.compare(&signing_time)
.map_err(|error| credential_error("cannot compare certificate NotBefore", error))?
== std::cmp::Ordering::Greater
{
return Err(XmlError::new(
ErrorKind::Signature,
format!(
"XAdES SigningTime is before certificate NotBefore: signing_time={}, not_before={}",
signing_time.as_ref(),
not_before
),
));
}
if not_after
.compare(&signing_time)
.map_err(|error| credential_error("cannot compare certificate NotAfter", error))?
== std::cmp::Ordering::Less
{
return Err(XmlError::new(
ErrorKind::Signature,
format!(
"XAdES SigningTime is after certificate NotAfter: signing_time={}, not_after={}",
signing_time.as_ref(),
not_after
),
));
}
Ok(())
}
fn certificate_details_from_der(certificate_der: &[u8]) -> XmlResult<CertificateDetails> {
let certificate = X509::from_der(certificate_der)
.map_err(|error| credential_error("invalid X.509 certificate DER", error))?;
let issuer_name = x509_name_to_rfc4514_like_string(certificate.issuer_name())?;
let serial_number = certificate
.serial_number()
.to_bn()
.map_err(|error| credential_error("cannot read X.509 certificate serial", error))?
.to_dec_str()
.map_err(|error| credential_error("cannot format X.509 certificate serial", error))?
.to_string();
Ok(CertificateDetails::new(certificate_der.to_vec())
.with_issuer_serial(issuer_name, serial_number))
}
fn x509_name_to_rfc4514_like_string(name: &X509NameRef) -> XmlResult<String> {
let mut parts = Vec::new();
for entry in name.entries() {
let key = entry
.object()
.nid()
.short_name()
.map_err(|error| credential_error("cannot read X.509 name attribute", error))?;
let value = entry
.data()
.as_utf8()
.map_err(|error| credential_error("cannot read X.509 name value", error))?
.to_string();
parts.push(format!(
"{key}={}",
escape_x509_name_value_for_rfc4514(&value)
));
}
Ok(parts.join(","))
}
fn escape_x509_name_value_for_rfc4514(value: &str) -> String {
let chars: Vec<char> = value.chars().collect();
let mut escaped = String::new();
for (index, ch) in chars.iter().copied().enumerate() {
let is_first = index == 0;
let is_last = index + 1 == chars.len();
if matches!(ch, ',' | '+' | '"' | '\\' | '<' | '>' | ';')
|| (is_first && (ch == ' ' || ch == '#'))
|| (is_last && ch == ' ')
{
escaped.push('\\');
}
escaped.push(ch);
}
escaped
}
fn read_file_bytes(path: &Path, context: &str) -> XmlResult<Vec<u8>> {
fs::read(path).map_err(|error| {
XmlError::new(
ErrorKind::Signature,
format!("{context} `{}`: {error}", path.display()),
)
})
}
fn read_certificate_der_file(path: impl AsRef<Path>) -> XmlResult<Vec<u8>> {
let path = path.as_ref();
certificate_der_from_pem_or_der(read_file_bytes(path, "cannot read certificate file")?)
}
fn certificate_der_from_pem_or_der(bytes: Vec<u8>) -> XmlResult<Vec<u8>> {
if let Ok(text) = std::str::from_utf8(&bytes) {
if text.contains("-----BEGIN CERTIFICATE-----") {
return decode_pem_block(text, "CERTIFICATE");
}
}
Ok(bytes)
}
fn decode_pem_block(text: &str, label: &str) -> XmlResult<Vec<u8>> {
let begin = format!("-----BEGIN {label}-----");
let end = format!("-----END {label}-----");
let start = text
.find(&begin)
.ok_or_else(|| XmlError::new(ErrorKind::Signature, "missing PEM begin marker"))?
+ begin.len();
let finish = text[start..]
.find(&end)
.ok_or_else(|| XmlError::new(ErrorKind::Signature, "missing PEM end marker"))?
+ start;
let encoded: String = text[start..finish]
.chars()
.filter(|ch| !ch.is_whitespace())
.collect();
decode_standard_base64(&encoded)
}
#[cfg(test)]
mod tests {
use std::fs;
use crate::parser::parse_str;
use crate::signature::{
sign_enveloped, sign_xades_bes_enveloped, verify_enveloped, verify_xades_bes_enveloped,
XadesConfig, XadesValidationDataProvider, XmlDsigConfig,
};
use crate::writer::to_string_compact;
use openssl::asn1::Asn1Time;
use openssl::bn::BigNum;
use openssl::hash::MessageDigest;
use openssl::pkcs12::Pkcs12;
use openssl::pkey::PKey;
use openssl::x509::{X509NameBuilder, X509};
use super::*;
const RSA_PRIVATE_KEY_PEM: &str = r#"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDhxyLXp0r/ySjk
ZRA/uP+qvy3PtpmHBawMcqhqkcTULTA0VN09cISj/9NUQeDooamqIDQQwGuK30R/
MW/23gkDuyrtzeouICJafWNIj1g0QBuq351pais4DKl8sRj9lSp1Vl5EVDAlfw6D
k/I/i5XGOauH61CST7IB2WqVppMbNq7j6nEYor21E3/rRE0pqrhPAPfeGskqOJQ9
NhaaeQt0EPqc9tnlws6ZHo44nTlegYv83/kWV9HOX1l6GmF7QMTzmi6wnkmojKKh
wM5tGEv1VG/5owv7IDneeouK+oosg3aX2B6aMbxnLoQpgjUPjXVP6O1Oo/H4ucua
4nQ+vaJXAgMBAAECggEAToM3RJLya7yCMdpLKj2k+rV8ssym2uNIDxQYKOcD8Vy9
dJVGUkU8euNNk/FMytjc7UJBmMmxHBvD8A87Bjf0Ho4JwaRnmR6nk5wi2Gqwm4rn
lCYq0SuQV+9fSPM044npt+AO6+fyzjc+zta12Q8rSEFputxDqn14Q1hdziic40sb
Zk2+t8tYe1ntFVAW+rp5RktW1o/9B/+pi/PrOb8lP9c5eoK5HIfDxm6aC1b6uEvk
FhBEGDC04TENR7qWpmXDgGUANQC9WvvnhYOO/7S+J1NyCT2WPcTQtKudJ4PiBfuS
mr2OklkmXLQi6Kydol+Ri1pfX1UZZ/l9eGKmhV2t0QKBgQD77ofCY0yfs1WVIfsy
OjE9p70mt3b52AwCPVLap5AO9W6lPVJkUmHMaeixHu9MnW3u59bdbpc3JUNct29r
3aWziZ9ezyMgtrdDnOJn9d/mI+tlXcVTkRbnKtyx/JbQab3GyCN8brhYcZtaLIoc
JM5tBWCBEKQZEfzQ0YE40lEFowKBgQDlbHzBBfdUgGDKa9vZjNregY9hP7mg/wrJ
XC8Wrnl59McHMeY+c9lsPoFpVW1sncaSyxLVguYtf9zOw4Xi4/EuanIlBuBXvpML
vn49c3isrMuVnuc6s6VG0qk4Nxj2x6yhbfoJ5XkoZ1CIw1lk4sedhbtSlt6cnO1m
bDCEAs8zvQKBgEK0+Rt+gYZzzMBtO/8jq3Ag3xPGVml3TE63gB3Hanybfg2gvBU2
PxEKJgPoJgLKWJZ+qsT9CGsgocKGC6mrCboNQbav8CQ0XTg47TYLw5pDfovblWk4
LLFPBxrVv/U1Wnus+MB07Lj2c+Ufj/49vK7fUps6FclRmviL0MSD49IzAoGBALTK
2kDN78sCY8QAXT7B9hRT6uZK7oCFmz10zJLGKWpGz7TGyNc8OgFH/HlCXbmzV7GE
IDJrNfJzCVFS2SYkVIIsVgkBszbSMlSV6CuK3HTOspwUnckvmjYGel2XZa/LSCnq
XZkA4YpKaDduIfsTjxR+N1DtHT4zmA0Xgt/0ys3NAoGBAIyaAw4uHkcv/ukB7C61
vNxNKlzQGmIFENaFZnV1xIybIEXRYpU/W5FJPn0mexSE7rcW21s3XhPSirez4ctV
Rqp4dm3rUuCm0un6gc3ARoj7VKIE8EIBI6f6l12CdNd7NMDaP7uhO8RHCuC5NW4A
75CcGa3kvBm08ghSr6XTg21R
-----END PRIVATE KEY-----"#;
fn real_crypto_provider() -> XmlResult<RsaSha256SigningProvider> {
RsaSha256SigningProvider::from_private_key_pem(
RSA_PRIVATE_KEY_PEM,
b"fixture-cert-der".to_vec(),
)
}
fn pkcs12_fixture(password: &str) -> Vec<u8> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("system clock after UNIX epoch")
.as_secs() as i64;
pkcs12_fixture_with_validity(password, now, now + 30 * 86_400)
}
fn pkcs12_fixture_with_validity(
password: &str,
not_before_unix: i64,
not_after_unix: i64,
) -> Vec<u8> {
let pkey = PKey::private_key_from_pem(RSA_PRIVATE_KEY_PEM.as_bytes()).expect("fixture key");
let mut name = X509NameBuilder::new().expect("x509 name builder");
name.append_entry_by_text("CN", "xdoc test")
.expect("x509 cn");
let name = name.build();
let mut certificate = X509::builder().expect("x509 builder");
let serial = BigNum::from_dec_str("42")
.expect("serial")
.to_asn1_integer()
.expect("asn1 serial");
certificate.set_version(2).expect("x509 version");
certificate.set_serial_number(&serial).expect("x509 serial");
certificate.set_subject_name(&name).expect("x509 subject");
certificate.set_issuer_name(&name).expect("x509 issuer");
certificate.set_pubkey(&pkey).expect("x509 public key");
certificate
.set_not_before(
Asn1Time::from_unix(not_before_unix as _)
.expect("not before")
.as_ref(),
)
.expect("x509 not before");
certificate
.set_not_after(
Asn1Time::from_unix(not_after_unix as _)
.expect("not after")
.as_ref(),
)
.expect("x509 not after");
certificate
.sign(&pkey, MessageDigest::sha256())
.expect("x509 sign");
let certificate = certificate.build();
Pkcs12::builder()
.name("xdoc-test")
.pkey(&pkey)
.cert(&certificate)
.build2(password)
.expect("PFX fixture")
.to_der()
.expect("PFX DER")
}
fn temp_path(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("xdoc-{name}-{}", std::process::id()))
}
#[test]
fn signature_provider_signs_deterministically() -> XmlResult<()> {
let provider = DeterministicSigningProvider::new(b"cert".to_vec(), b"secret".to_vec());
let first = provider.sign(SignatureAlgorithm::RsaSha256, b"payload")?;
let second = provider.sign(SignatureAlgorithm::RsaSha256, b"payload")?;
assert_eq!(first, second);
assert!(provider.verify(SignatureAlgorithm::RsaSha256, b"payload", &first)?);
assert!(!provider.verify(SignatureAlgorithm::RsaSha256, b"tampered", &first)?);
Ok(())
}
#[test]
fn signature_provider_rejects_legacy_generation_algorithm() {
let provider = DeterministicSigningProvider::new(b"cert".to_vec(), b"secret".to_vec());
assert!(provider
.sign(SignatureAlgorithm::RsaSha1, b"payload")
.is_err());
}
#[test]
fn real_crypto_provider_signs_and_verifies_rsa_sha256() -> XmlResult<()> {
let provider = real_crypto_provider()?;
let signature = provider.sign(SignatureAlgorithm::RsaSha256, b"payload")?;
assert!(provider.verify(SignatureAlgorithm::RsaSha256, b"payload", &signature)?);
assert!(!provider.verify(SignatureAlgorithm::RsaSha256, b"tampered", &signature)?);
Ok(())
}
#[test]
fn real_crypto_provider_rejects_invalid_pem() {
let error = RsaSha256SigningProvider::from_private_key_pem("not a key", b"cert".to_vec())
.expect_err("invalid PEM should fail");
assert_eq!(error.kind(), &ErrorKind::Signature);
assert!(error.message().contains("invalid RSA private key PEM"));
}
#[test]
fn real_crypto_provider_rejects_legacy_generation_algorithm() -> XmlResult<()> {
let provider = real_crypto_provider()?;
let error = provider
.sign(SignatureAlgorithm::RsaSha1, b"payload")
.expect_err("RSA-SHA1 should not be generated");
assert_eq!(error.kind(), &ErrorKind::Signature);
Ok(())
}
#[test]
fn real_crypto_provider_exposes_certificate_chain() -> XmlResult<()> {
let provider = real_crypto_provider()?
.with_certificate_chain_der(vec![b"leaf".to_vec(), b"issuer".to_vec()]);
assert_eq!(
provider.certificate_chain_der()?,
vec![b"leaf".to_vec(), b"issuer".to_vec()]
);
Ok(())
}
#[test]
fn pkcs12_credential_extracts_private_key_certificate_and_chain() -> XmlResult<()> {
let pfx = pkcs12_fixture("secret");
let credential = Pkcs12Credential::from_der(&pfx, "secret")?;
assert!(!credential.private_key_der().is_empty());
assert!(!credential.certificate_der().is_empty());
assert_eq!(credential.certificate_chain_der().len(), 1);
Ok(())
}
#[test]
fn pkcs12_signing_credentials_extract_x509_issuer_serial() -> XmlResult<()> {
let pfx = pkcs12_fixture("secret");
let credentials = Pkcs12SigningCredentials::from_der(&pfx, "secret")?;
assert_eq!(
credentials.certificate_details().issuer_serial(),
Some(("CN=xdoc test", "42"))
);
Ok(())
}
#[test]
fn pkcs12_credential_rejects_wrong_password() {
let pfx = pkcs12_fixture("secret");
let error =
Pkcs12Credential::from_der(&pfx, "wrong").expect_err("wrong password must fail");
assert_eq!(error.kind(), &ErrorKind::Signature);
assert!(error.message().contains("PKCS#12/PFX"));
}
#[test]
fn real_crypto_provider_loads_pkcs12_and_signs() -> XmlResult<()> {
let pfx = pkcs12_fixture("secret");
let provider = RsaSha256SigningProvider::from_pkcs12_der(&pfx, "secret")?;
let signature = provider.sign(SignatureAlgorithm::RsaSha256, b"payload")?;
assert!(provider.verify(SignatureAlgorithm::RsaSha256, b"payload", &signature)?);
assert_eq!(provider.certificate_chain_der()?.len(), 1);
Ok(())
}
#[test]
fn pkcs12_signing_credentials_load_file_and_delegate_provider() -> XmlResult<()> {
let path = temp_path("credential.pfx");
fs::write(&path, pkcs12_fixture("secret")).expect("write PFX fixture");
let credentials = Pkcs12SigningCredentials::from_file(&path, "secret")?
.with_additional_certificate_der(b"issuer-cert".to_vec());
let signature = credentials.sign(SignatureAlgorithm::RsaSha256, b"payload")?;
let validation_data = credentials.validation_data_provider();
assert!(credentials.verify(SignatureAlgorithm::RsaSha256, b"payload", &signature)?);
assert_eq!(
credentials.certificate_details().issuer_serial(),
Some(("CN=xdoc test", "42"))
);
assert!(credentials.has_additional_certificate_chain());
assert_eq!(credentials.additional_certificate_der().len(), 1);
assert_eq!(credentials.certificate_chain_der()?.len(), 2);
assert_eq!(validation_data.certificate_values()?.len(), 2);
let _ = fs::remove_file(path);
Ok(())
}
#[test]
fn xades_signing_time_is_validated_against_pkcs12_certificate() -> XmlResult<()> {
let signing_time = 1_800_000_000;
let pfx = pkcs12_fixture_with_validity("secret", signing_time - 60, signing_time + 60);
let credentials = Pkcs12SigningCredentials::from_der(&pfx, "secret")?;
let document = parse_str(r#"<Root Id="doc-1"><Item>value</Item></Root>"#)?;
let config = XadesConfig::new().with_signing_time_unix_timestamp(signing_time);
let signed = sign_xades_bes_enveloped(&document, &credentials, &config)?;
let report = verify_xades_bes_enveloped(&signed, &credentials, &config)?;
let xml = to_string_compact(&signed)?;
assert!(report.valid);
assert!(xml.contains("<xades:SigningTime>2027-01-15T08:00:00Z</xades:SigningTime>"));
Ok(())
}
#[test]
fn xades_signing_time_before_certificate_validity_is_rejected() -> XmlResult<()> {
let signing_time = 1_800_000_000;
let pfx = pkcs12_fixture_with_validity("secret", signing_time + 60, signing_time + 120);
let credentials = Pkcs12SigningCredentials::from_der(&pfx, "secret")?;
let document = parse_str(r#"<Root Id="doc-1"><Item>value</Item></Root>"#)?;
let config = XadesConfig::new().with_signing_time_unix_timestamp(signing_time);
let error = sign_xades_bes_enveloped(&document, &credentials, &config)
.expect_err("signing before NotBefore must fail");
assert_eq!(error.kind(), &ErrorKind::Signature);
assert!(error.message().contains("before certificate NotBefore"));
Ok(())
}
#[test]
fn xades_signing_time_after_certificate_validity_is_rejected() -> XmlResult<()> {
let signing_time = 1_800_000_000;
let pfx = pkcs12_fixture_with_validity("secret", signing_time - 120, signing_time - 60);
let credentials = Pkcs12SigningCredentials::from_der(&pfx, "secret")?;
let document = parse_str(r#"<Root Id="doc-1"><Item>value</Item></Root>"#)?;
let config = XadesConfig::new().with_signing_time_unix_timestamp(signing_time);
let error = sign_xades_bes_enveloped(&document, &credentials, &config)
.expect_err("signing after NotAfter must fail");
assert_eq!(error.kind(), &ErrorKind::Signature);
assert!(error.message().contains("after certificate NotAfter"));
Ok(())
}
#[test]
fn real_crypto_provider_signs_and_verifies_enveloped_document() -> XmlResult<()> {
let document = parse_str(r#"<Root Id="doc-1"><Item>value</Item></Root>"#)?;
let provider = real_crypto_provider()?;
let signed = sign_enveloped(&document, &provider, &XmlDsigConfig::new())?;
let report = verify_enveloped(&signed, &provider, &XmlDsigConfig::new())?;
assert!(report.valid);
assert!(report.signature_value_valid);
Ok(())
}
}