use openssl::hash::MessageDigest;
use openssl::pkey::{PKey, Private};
use openssl::sign::Signer;
use openssl::x509::X509;
use crate::core::{AsxError, ErrorCode, ErrorContext, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SignatureAlgorithm {
RsaSha256,
RsaSha384,
RsaSha512,
EcdsaSha256,
EcdsaSha384,
EcdsaSha512,
}
impl SignatureAlgorithm {
pub fn algorithm_uri(self) -> &'static str {
use crate::crypto::wssec::{
ECDSA_SHA256_URI, ECDSA_SHA384_URI, ECDSA_SHA512_URI, RSA_SHA256_URI, RSA_SHA384_URI,
RSA_SHA512_URI,
};
match self {
Self::RsaSha256 => RSA_SHA256_URI,
Self::RsaSha384 => RSA_SHA384_URI,
Self::RsaSha512 => RSA_SHA512_URI,
Self::EcdsaSha256 => ECDSA_SHA256_URI,
Self::EcdsaSha384 => ECDSA_SHA384_URI,
Self::EcdsaSha512 => ECDSA_SHA512_URI,
}
}
pub(crate) fn message_digest(self) -> MessageDigest {
match self {
Self::RsaSha256 | Self::EcdsaSha256 => MessageDigest::sha256(),
Self::RsaSha384 | Self::EcdsaSha384 => MessageDigest::sha384(),
Self::RsaSha512 | Self::EcdsaSha512 => MessageDigest::sha512(),
}
}
pub(crate) fn from_pkey(key: &PKey<Private>) -> Option<Self> {
match key.id() {
openssl::pkey::Id::RSA | openssl::pkey::Id::RSA_PSS => Some(Self::RsaSha256),
openssl::pkey::Id::EC => Some(Self::EcdsaSha256),
_ => None,
}
}
}
pub trait SigningKeyProvider: Send + Sync + std::fmt::Debug {
fn sign(&self, data: &[u8], algorithm: SignatureAlgorithm) -> Result<Vec<u8>>;
fn certificate_der(&self) -> Result<Vec<u8>>;
fn preferred_algorithm(&self) -> SignatureAlgorithm;
}
pub struct PemSigningKeyProvider {
key: PKey<Private>,
cert: X509,
preferred: SignatureAlgorithm,
key_pem: Vec<u8>,
}
impl std::fmt::Debug for PemSigningKeyProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PemSigningKeyProvider")
.field("algorithm", &self.preferred)
.finish_non_exhaustive()
}
}
impl Drop for PemSigningKeyProvider {
fn drop(&mut self) {
use zeroize::Zeroize;
self.key_pem.zeroize();
}
}
impl PemSigningKeyProvider {
pub fn from_pem(key_pem: &[u8], cert_pem: &[u8]) -> Result<Self> {
let ctx = || ErrorContext::new("signing_key_provider_from_pem");
let key = PKey::private_key_from_pem(key_pem).map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"signing key PEM is not a valid private key (RSA or EC)",
ctx(),
)
})?;
let cert = X509::from_pem(cert_pem).map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"signing certificate PEM is not a valid X.509 certificate",
ctx(),
)
})?;
let cert_pubkey = cert.public_key().map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"signing certificate does not contain a usable public key",
ctx(),
)
})?;
if !key.public_eq(&cert_pubkey) {
return Err(AsxError::new(
ErrorCode::SecurityVerificationFailed,
"signing certificate public key does not match the private key",
ctx(),
));
}
let preferred = SignatureAlgorithm::from_pkey(&key).ok_or_else(|| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"signing key type is not supported (use RSA or EC P-256/P-384/P-521)",
ctx(),
)
})?;
Ok(Self {
key,
cert,
preferred,
key_pem: key_pem.to_vec(),
})
}
pub fn with_algorithm(mut self, algorithm: SignatureAlgorithm) -> Self {
self.preferred = algorithm;
self
}
}
impl SigningKeyProvider for PemSigningKeyProvider {
fn sign(&self, data: &[u8], algorithm: SignatureAlgorithm) -> Result<Vec<u8>> {
let mut signer = Signer::new(algorithm.message_digest(), &self.key).map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"failed to initialize XMLDSig signer",
ErrorContext::new("pem_signing_key_provider_sign"),
)
})?;
signer.update(data).map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"failed to feed data to XMLDSig signer",
ErrorContext::new("pem_signing_key_provider_sign"),
)
})?;
signer.sign_to_vec().map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"XMLDSig signing operation failed",
ErrorContext::new("pem_signing_key_provider_sign"),
)
})
}
fn certificate_der(&self) -> Result<Vec<u8>> {
self.cert.to_der().map_err(|_err| {
AsxError::new(
ErrorCode::SecurityVerificationFailed,
"failed to DER-encode signing certificate",
ErrorContext::new("pem_signing_key_provider_cert_der"),
)
})
}
fn preferred_algorithm(&self) -> SignatureAlgorithm {
self.preferred
}
}
#[cfg(test)]
mod tests {
use super::*;
use openssl::asn1::Asn1Time;
use openssl::ec::{EcGroup, EcKey};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::{X509Builder, X509NameBuilder};
fn gen_rsa_pem_pair() -> (Vec<u8>, Vec<u8>) {
let rsa = Rsa::generate(2048).unwrap();
let pkey = PKey::from_rsa(rsa).unwrap();
let key_pem = pkey.private_key_to_pem_pkcs8().unwrap();
let mut name = X509NameBuilder::new().unwrap();
name.append_entry_by_text("CN", "test").unwrap();
let name = name.build();
let mut builder = X509Builder::new().unwrap();
builder.set_subject_name(&name).unwrap();
builder.set_issuer_name(&name).unwrap();
builder.set_pubkey(&pkey).unwrap();
builder
.set_not_before(&Asn1Time::days_from_now(0).unwrap())
.unwrap();
builder
.set_not_after(&Asn1Time::days_from_now(365).unwrap())
.unwrap();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
let cert_pem = builder.build().to_pem().unwrap();
(key_pem, cert_pem)
}
fn gen_ec_pem_pair() -> (Vec<u8>, Vec<u8>) {
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
let ec = EcKey::generate(&group).unwrap();
let pkey = PKey::from_ec_key(ec).unwrap();
let key_pem = pkey.private_key_to_pem_pkcs8().unwrap();
let mut name = X509NameBuilder::new().unwrap();
name.append_entry_by_text("CN", "test-ec").unwrap();
let name = name.build();
let mut builder = X509Builder::new().unwrap();
builder.set_subject_name(&name).unwrap();
builder.set_issuer_name(&name).unwrap();
builder.set_pubkey(&pkey).unwrap();
builder
.set_not_before(&Asn1Time::days_from_now(0).unwrap())
.unwrap();
builder
.set_not_after(&Asn1Time::days_from_now(365).unwrap())
.unwrap();
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
let cert_pem = builder.build().to_pem().unwrap();
(key_pem, cert_pem)
}
#[test]
fn rsa_pem_provider_round_trips() {
let (key_pem, cert_pem) = gen_rsa_pem_pair();
let provider = PemSigningKeyProvider::from_pem(&key_pem, &cert_pem).unwrap();
assert_eq!(
provider.preferred_algorithm(),
SignatureAlgorithm::RsaSha256
);
let sig = provider
.sign(b"hello world", SignatureAlgorithm::RsaSha256)
.unwrap();
assert!(!sig.is_empty());
let der = provider.certificate_der().unwrap();
assert!(!der.is_empty());
}
#[test]
fn ec_pem_provider_round_trips() {
let (key_pem, cert_pem) = gen_ec_pem_pair();
let provider = PemSigningKeyProvider::from_pem(&key_pem, &cert_pem).unwrap();
assert_eq!(
provider.preferred_algorithm(),
SignatureAlgorithm::EcdsaSha256
);
let sig = provider
.sign(b"test data", SignatureAlgorithm::EcdsaSha256)
.unwrap();
assert!(!sig.is_empty());
}
#[test]
fn mismatched_cert_key_is_rejected() {
let (key_pem_a, _) = gen_rsa_pem_pair();
let (_, cert_pem_b) = gen_rsa_pem_pair();
let err = PemSigningKeyProvider::from_pem(&key_pem_a, &cert_pem_b).unwrap_err();
assert_eq!(err.code, ErrorCode::SecurityVerificationFailed);
assert!(err.message.contains("does not match"));
}
#[test]
fn with_algorithm_overrides_preferred() {
let (key_pem, cert_pem) = gen_rsa_pem_pair();
let provider = PemSigningKeyProvider::from_pem(&key_pem, &cert_pem)
.unwrap()
.with_algorithm(SignatureAlgorithm::RsaSha384);
assert_eq!(
provider.preferred_algorithm(),
SignatureAlgorithm::RsaSha384
);
}
#[test]
fn algorithm_uri_matches_wssec_constants() {
use crate::crypto::wssec::{ECDSA_SHA256_URI, RSA_SHA256_URI};
assert_eq!(
SignatureAlgorithm::RsaSha256.algorithm_uri(),
RSA_SHA256_URI
);
assert_eq!(
SignatureAlgorithm::EcdsaSha256.algorithm_uri(),
ECDSA_SHA256_URI
);
}
#[test]
fn sign_produces_verifiable_signature() {
let (key_pem, cert_pem) = gen_rsa_pem_pair();
let provider = PemSigningKeyProvider::from_pem(&key_pem, &cert_pem).unwrap();
let data = b"canonical signed info bytes";
let sig = provider.sign(data, SignatureAlgorithm::RsaSha256).unwrap();
let cert_der = provider.certificate_der().unwrap();
let cert = openssl::x509::X509::from_der(&cert_der).unwrap();
let pubkey = cert.public_key().unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &pubkey).unwrap();
verifier.update(data).unwrap();
assert!(verifier.verify(&sig).unwrap(), "signature must verify");
}
#[test]
fn sign_with_sha384_produces_verifiable_signature() {
let (key_pem, cert_pem) = gen_rsa_pem_pair();
let provider = PemSigningKeyProvider::from_pem(&key_pem, &cert_pem).unwrap();
let data = b"sha384 test payload";
let sig = provider.sign(data, SignatureAlgorithm::RsaSha384).unwrap();
let cert_der = provider.certificate_der().unwrap();
let cert = openssl::x509::X509::from_der(&cert_der).unwrap();
let pubkey = cert.public_key().unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha384(), &pubkey).unwrap();
verifier.update(data).unwrap();
assert!(verifier.verify(&sig).unwrap());
}
}