use base64::Engine;
use getrandom::SysRng;
use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
use p256::pkcs8::DecodePrivateKey;
use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
use roxmltree::{Document, Node};
use rsa::RsaPrivateKey;
use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
use rsa::signature::{RandomizedSigner, SignatureEncoding, Signer};
use sha2::{Sha256, Sha384, Sha512};
use std::collections::HashSet;
use crate::c14n::canonicalize;
use super::builder::{SignatureBuilder, SignatureBuilderError};
use super::digest::{DigestAlgorithm, compute_digest};
use super::mutation::{
XmlMutationError, append_signature_to_root, fill_signature_value,
fill_signed_info_digest_values,
};
use super::parse::{SignatureAlgorithm, XMLDSIG_NS, parse_signed_info};
use super::transforms::{Transform, execute_transforms, parse_transforms};
use super::types::TransformError;
use super::uri::UriReferenceResolver;
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
pub struct ComputedReferenceDigest {
pub index: usize,
pub uri: String,
pub digest_method: DigestAlgorithm,
pub digest_value: String,
}
#[derive(Debug, thiserror::Error)]
pub enum SigningDigestError {
#[error("XML parse error: {0}")]
XmlParse(#[from] roxmltree::Error),
#[error("missing required element: <{element}>")]
MissingElement {
element: &'static str,
},
#[error("invalid signing template: {0}")]
InvalidStructure(String),
#[error("unsupported digest algorithm: {uri}")]
UnsupportedAlgorithm {
uri: String,
},
#[error("digest algorithm is disabled for signing: {uri}")]
SigningAlgorithmDisabled {
uri: &'static str,
},
#[error("reference processing error: {0}")]
Transform(#[from] TransformError),
#[error("XML mutation error: {0}")]
XmlMutation(#[from] XmlMutationError),
}
#[derive(Debug, thiserror::Error)]
pub enum SigningError {
#[error("signing digest pass failed: {0}")]
Digest(#[from] SigningDigestError),
#[error("failed to parse SignedInfo after digest fill: {0}")]
ParseSignedInfo(#[from] super::parse::ParseError),
#[error("SignedInfo canonicalization failed: {0}")]
Canonicalization(#[from] crate::c14n::C14nError),
#[error("signing key error: {0}")]
Key(#[from] SigningKeyError),
#[error("XML mutation error: {0}")]
XmlMutation(#[from] XmlMutationError),
#[error("signature template error: {0}")]
Template(#[from] SignatureBuilderError),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SigningKeyError {
#[error("invalid PEM private key")]
InvalidKeyPem,
#[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
InvalidKeyFormat {
label: String,
},
#[error("invalid PKCS#8 private key DER")]
InvalidKeyDer,
#[error("signing key does not support algorithm: {uri}")]
UnsupportedAlgorithm {
uri: String,
},
#[error("private-key signing operation failed")]
SigningFailed,
}
pub trait SigningKey {
fn sign(
&self,
algorithm: SignatureAlgorithm,
canonical_signed_info: &[u8],
) -> Result<Vec<u8>, SigningKeyError>;
}
pub struct RsaSigningKey {
key: RsaPrivateKey,
}
impl RsaSigningKey {
pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
let private_key_der = parse_private_key_pem(private_key_pem)?;
Self::from_pkcs8_der(&private_key_der)
}
pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
.map_err(|_| SigningKeyError::InvalidKeyDer)?;
Ok(Self { key })
}
}
impl SigningKey for RsaSigningKey {
fn sign(
&self,
algorithm: SignatureAlgorithm,
canonical_signed_info: &[u8],
) -> Result<Vec<u8>, SigningKeyError> {
match algorithm {
SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
canonical_signed_info,
),
SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
canonical_signed_info,
),
SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
canonical_signed_info,
),
_ => Err(SigningKeyError::UnsupportedAlgorithm {
uri: algorithm.uri().to_string(),
}),
}
}
}
fn sign_rsa_pkcs1v15_with_rng(
key: impl RandomizedSigner<RsaPkcs1v15Signature>,
canonical_signed_info: &[u8],
) -> Result<Vec<u8>, SigningKeyError> {
let signature = key
.try_sign_with_rng(&mut SysRng, canonical_signed_info)
.map_err(|_| SigningKeyError::SigningFailed)?;
Ok(signature.to_vec())
}
pub struct EcdsaP256SigningKey {
key: P256SigningKey,
}
impl EcdsaP256SigningKey {
pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
let private_key_der = parse_private_key_pem(private_key_pem)?;
Self::from_pkcs8_der(&private_key_der)
}
pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
let key = P256SigningKey::from_pkcs8_der(private_key_der)
.map_err(|_| SigningKeyError::InvalidKeyDer)?;
Ok(Self { key })
}
}
impl SigningKey for EcdsaP256SigningKey {
fn sign(
&self,
algorithm: SignatureAlgorithm,
canonical_signed_info: &[u8],
) -> Result<Vec<u8>, SigningKeyError> {
if algorithm != SignatureAlgorithm::EcdsaP256Sha256 {
return Err(SigningKeyError::UnsupportedAlgorithm {
uri: algorithm.uri().to_string(),
});
}
let signature: P256Signature = self
.key
.try_sign(canonical_signed_info)
.map_err(|_| SigningKeyError::SigningFailed)?;
Ok(signature.to_bytes().to_vec())
}
}
pub struct EcdsaP384SigningKey {
key: P384SigningKey,
}
impl EcdsaP384SigningKey {
pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
let private_key_der = parse_private_key_pem(private_key_pem)?;
Self::from_pkcs8_der(&private_key_der)
}
pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
let key = P384SigningKey::from_pkcs8_der(private_key_der)
.map_err(|_| SigningKeyError::InvalidKeyDer)?;
Ok(Self { key })
}
}
impl SigningKey for EcdsaP384SigningKey {
fn sign(
&self,
algorithm: SignatureAlgorithm,
canonical_signed_info: &[u8],
) -> Result<Vec<u8>, SigningKeyError> {
if algorithm != SignatureAlgorithm::EcdsaP384Sha384 {
return Err(SigningKeyError::UnsupportedAlgorithm {
uri: algorithm.uri().to_string(),
});
}
let signature: P384Signature = self
.key
.try_sign(canonical_signed_info)
.map_err(|_| SigningKeyError::SigningFailed)?;
Ok(signature.to_bytes().to_vec())
}
}
pub struct SignContext<'a> {
signing_key: &'a dyn SigningKey,
}
impl<'a> SignContext<'a> {
pub fn new(signing_key: &'a dyn SigningKey) -> Self {
Self { signing_key }
}
pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
let with_digests = fill_reference_digest_values(xml)?;
let (algorithm, canonical_signed_info) = canonicalize_signed_info(&with_digests)?;
let signature_value = self.signing_key.sign(algorithm, &canonical_signed_info)?;
let signature_b64 = base64::engine::general_purpose::STANDARD.encode(signature_value);
Ok(fill_signature_value(&with_digests, &signature_b64)?)
}
pub fn sign_with_builder(
&self,
xml: &str,
builder: &SignatureBuilder,
) -> Result<String, SigningError> {
let template = builder.build_template()?;
let templated = append_signature_to_root(xml, &template)?;
self.sign_template(&templated)
}
}
#[derive(Debug)]
struct SigningReference {
uri: String,
transforms: Vec<Transform>,
digest_method: DigestAlgorithm,
}
pub fn compute_reference_digest_values(
xml: &str,
) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
let doc = Document::parse(xml)?;
let signature = find_single_signature_node(&doc)?;
let signed_info = find_required_child(signature, "SignedInfo")?;
let references = parse_signing_references(signed_info)?;
let resolver = UriReferenceResolver::new(&doc);
references
.into_iter()
.enumerate()
.map(|(index, reference)| {
let initial_data = resolver.dereference(&reference.uri)?;
let pre_digest = execute_transforms(signature, initial_data, &reference.transforms)?;
let digest = compute_digest(reference.digest_method, &pre_digest);
let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
Ok(ComputedReferenceDigest {
index,
uri: reference.uri,
digest_method: reference.digest_method,
digest_value,
})
})
.collect()
}
pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
let digest_values = compute_reference_digest_values(xml)?
.into_iter()
.map(|digest| digest.digest_value);
Ok(fill_signed_info_digest_values(xml, digest_values)?)
}
fn canonicalize_signed_info(xml: &str) -> Result<(SignatureAlgorithm, Vec<u8>), SigningError> {
let doc = Document::parse(xml).map_err(SigningDigestError::XmlParse)?;
let signature = find_single_signature_node(&doc).map_err(SigningError::Digest)?;
let signed_info_node =
find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
let signed_info = parse_signed_info(signed_info_node)?;
let signed_info_subtree: HashSet<_> = signed_info_node
.descendants()
.map(|node: Node<'_, '_>| node.id())
.collect();
let mut canonical_signed_info = Vec::new();
canonicalize(
&doc,
Some(&|node| signed_info_subtree.contains(&node.id())),
&signed_info.c14n_method,
&mut canonical_signed_info,
)?;
Ok((signed_info.signature_method, canonical_signed_info))
}
fn parse_private_key_pem(private_key_pem: &str) -> Result<Vec<u8>, SigningKeyError> {
let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
.map_err(|_| SigningKeyError::InvalidKeyPem)?;
if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
return Err(SigningKeyError::InvalidKeyPem);
}
if pem.label != "PRIVATE KEY" {
return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
}
Ok(pem.contents)
}
fn find_single_signature_node<'a>(
doc: &'a Document<'a>,
) -> Result<Node<'a, 'a>, SigningDigestError> {
let mut signatures = doc.descendants().filter(|node| {
node.is_element()
&& node.tag_name().name() == "Signature"
&& node.tag_name().namespace() == Some(XMLDSIG_NS)
});
let signature = signatures
.next()
.ok_or(SigningDigestError::MissingElement {
element: "Signature",
})?;
if signatures.next().is_some() {
return Err(SigningDigestError::InvalidStructure(
"expected exactly one <ds:Signature> element".into(),
));
}
Ok(signature)
}
fn parse_signing_references(
signed_info: Node<'_, '_>,
) -> Result<Vec<SigningReference>, SigningDigestError> {
verify_ds_element(signed_info, "SignedInfo")?;
let mut children = element_children(signed_info);
let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
element: "CanonicalizationMethod",
})?;
verify_ds_element(c14n_node, "CanonicalizationMethod")?;
required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
element: "SignatureMethod",
})?;
verify_ds_element(signature_method_node, "SignatureMethod")?;
required_algorithm_attr(signature_method_node, "SignatureMethod")?;
let mut references = Vec::new();
for child in children {
verify_ds_element(child, "Reference")?;
references.push(parse_signing_reference(child)?);
}
if references.is_empty() {
return Err(SigningDigestError::MissingElement {
element: "Reference",
});
}
Ok(references)
}
fn parse_signing_reference(
reference_node: Node<'_, '_>,
) -> Result<SigningReference, SigningDigestError> {
let uri = reference_node
.attribute("URI")
.ok_or_else(|| {
SigningDigestError::InvalidStructure(
"signing Reference must include URI attribute".into(),
)
})?
.to_string();
let mut children = element_children(reference_node);
let mut transforms = Vec::new();
let mut next = children.next().ok_or(SigningDigestError::MissingElement {
element: "DigestMethod",
})?;
if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
transforms = parse_transforms(next)?;
next = children.next().ok_or(SigningDigestError::MissingElement {
element: "DigestMethod",
})?;
}
verify_ds_element(next, "DigestMethod")?;
let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
SigningDigestError::UnsupportedAlgorithm {
uri: digest_uri.to_string(),
}
})?;
if !digest_method.signing_allowed() {
return Err(SigningDigestError::SigningAlgorithmDisabled {
uri: digest_method.uri(),
});
}
let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
element: "DigestValue",
})?;
verify_ds_element(digest_value_node, "DigestValue")?;
if let Some(unexpected) = children.next() {
return Err(SigningDigestError::InvalidStructure(format!(
"unexpected element <{}> after <DigestValue> in <Reference>",
unexpected.tag_name().name()
)));
}
Ok(SigningReference {
uri,
transforms,
digest_method,
})
}
fn find_required_child<'a>(
parent: Node<'a, 'a>,
child_name: &'static str,
) -> Result<Node<'a, 'a>, SigningDigestError> {
parent
.children()
.find(|node| {
node.is_element()
&& node.tag_name().name() == child_name
&& node.tag_name().namespace() == Some(XMLDSIG_NS)
})
.ok_or(SigningDigestError::MissingElement {
element: child_name,
})
}
fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
node.children().filter(Node::is_element)
}
fn verify_ds_element(
node: Node<'_, '_>,
expected_name: &'static str,
) -> Result<(), SigningDigestError> {
if !node.is_element() {
return Err(SigningDigestError::InvalidStructure(format!(
"expected element <{expected_name}>, got non-element node"
)));
}
let tag = node.tag_name();
if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
return Err(SigningDigestError::InvalidStructure(format!(
"expected <ds:{expected_name}>, got <{}>",
tag.name()
)));
}
Ok(())
}
fn required_algorithm_attr<'a>(
node: Node<'a, 'a>,
element_name: &'static str,
) -> Result<&'a str, SigningDigestError> {
node.attribute("Algorithm").ok_or_else(|| {
SigningDigestError::InvalidStructure(format!(
"missing Algorithm attribute on <{element_name}>"
))
})
}