#[cfg(feature = "as4")]
pub(crate) mod canonicalize;
#[cfg(feature = "as4")]
pub(crate) use canonicalize::swa_attachment_digest_input;
pub(crate) mod ocsp;
#[cfg(feature = "as4")]
pub(crate) mod sign;
#[cfg(feature = "as4")]
pub(crate) mod verify;
pub(crate) mod x509;
#[cfg(feature = "as4")]
pub mod xmlenc;
#[cfg(feature = "as4")]
pub(crate) const DS_NS: &str = "http://www.w3.org/2000/09/xmldsig#";
#[cfg(feature = "as4")]
pub(crate) const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
pub(crate) const XML_EXC_C14N_URI: &str = "http://www.w3.org/2001/10/xml-exc-c14n#";
pub(crate) const XML_INC_C14N_URI: &str = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
pub(crate) const SHA256_URI: &str = "http://www.w3.org/2001/04/xmlenc#sha256";
pub(crate) const SHA384_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#sha384";
pub(crate) const SHA512_URI: &str = "http://www.w3.org/2001/04/xmlenc#sha512";
pub(crate) const RSA_SHA256_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
pub(crate) const RSA_SHA384_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
pub(crate) const RSA_SHA512_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
pub(crate) const ECDSA_SHA256_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
pub(crate) const ECDSA_SHA384_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384";
pub(crate) const ECDSA_SHA512_URI: &str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512";
#[cfg(feature = "as4")]
pub(crate) const SWA_ATTACHMENT_CONTENT_TRANSFORM_URI: &str = "http://docs.oasis-open.org/wss/oasis-wss-SwAProfile-1.1#Attachment-Content-Signature-Transform";
use crate::core::{OcspFailureMode, OcspMode};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default)]
pub struct RevocationPolicy<'a> {
pub trust_anchor_pems: &'a [String],
pub revocation_crl_pems: &'a [String],
pub ocsp_mode: OcspMode,
pub ocsp_failure_mode: OcspFailureMode,
pub stapled_ocsp_responses_der: &'a [Vec<u8>],
pub responder_ocsp_responses_der: &'a [Vec<u8>],
pub ocsp_cache_namespace: &'a str,
pub require_chain_validation: bool,
pub pre_parsed_trust_anchors: Option<Vec<openssl::x509::X509>>,
pub pre_built_x509_store: Option<std::sync::Arc<openssl::x509::store::X509Store>>,
}
impl std::fmt::Debug for RevocationPolicy<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RevocationPolicy")
.field("require_chain_validation", &self.require_chain_validation)
.field("ocsp_mode", &self.ocsp_mode)
.field("ocsp_failure_mode", &self.ocsp_failure_mode)
.field(
"pre_parsed_trust_anchors",
&self.pre_parsed_trust_anchors.as_ref().map(|v| v.len()),
)
.field("pre_built_x509_store", &self.pre_built_x509_store.is_some())
.finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub struct OwnedRevocationPolicy {
pub trust_anchor_pems: Vec<String>,
pub revocation_crl_pems: Vec<String>,
pub ocsp_mode: OcspMode,
pub ocsp_failure_mode: OcspFailureMode,
pub stapled_ocsp_responses_der: Vec<Vec<u8>>,
pub responder_ocsp_responses_der: Vec<Vec<u8>>,
pub ocsp_cache_namespace: String,
pub require_chain_validation: bool,
}
impl OwnedRevocationPolicy {
pub fn production(trust_anchor_pems: Vec<String>) -> Self {
Self {
trust_anchor_pems,
revocation_crl_pems: Vec::new(),
ocsp_mode: OcspMode::ResponderOnly,
ocsp_failure_mode: OcspFailureMode::SoftFail,
stapled_ocsp_responses_der: Vec::new(),
responder_ocsp_responses_der: Vec::new(),
ocsp_cache_namespace: String::new(),
require_chain_validation: true,
}
}
pub fn test_unsafe_no_chain_validation() -> Self {
Self {
trust_anchor_pems: Vec::new(),
revocation_crl_pems: Vec::new(),
ocsp_mode: OcspMode::Disabled,
ocsp_failure_mode: OcspFailureMode::SoftFail,
stapled_ocsp_responses_der: Vec::new(),
responder_ocsp_responses_der: Vec::new(),
ocsp_cache_namespace: String::new(),
require_chain_validation: false,
}
}
pub fn with_ocsp_mode(mut self, mode: OcspMode) -> Self {
self.ocsp_mode = mode;
self
}
pub fn with_cache_namespace(mut self, namespace: impl Into<String>) -> Self {
self.ocsp_cache_namespace = namespace.into();
self
}
}
impl<'a> From<&'a OwnedRevocationPolicy> for RevocationPolicy<'a> {
fn from(owned: &'a OwnedRevocationPolicy) -> Self {
Self {
trust_anchor_pems: &owned.trust_anchor_pems,
revocation_crl_pems: &owned.revocation_crl_pems,
ocsp_mode: owned.ocsp_mode,
ocsp_failure_mode: owned.ocsp_failure_mode,
stapled_ocsp_responses_der: &owned.stapled_ocsp_responses_der,
responder_ocsp_responses_der: &owned.responder_ocsp_responses_der,
ocsp_cache_namespace: &owned.ocsp_cache_namespace,
require_chain_validation: owned.require_chain_validation,
pre_parsed_trust_anchors: None,
pre_built_x509_store: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub enum WsSecOutboundKeyInfoProfile {
#[default]
BinarySecurityTokenX509v3,
X509DataAndRsaKeyValue,
X509DataOnly,
X509PKIPathv1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[non_exhaustive]
pub enum WsSecCanonicalizationKind {
#[default]
Exclusive,
Inclusive,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WsSecCanonicalizationProfile {
pub kind: WsSecCanonicalizationKind,
pub include_comments: bool,
pub inclusive_ns_prefixes: Vec<String>,
}
impl Default for WsSecCanonicalizationProfile {
fn default() -> Self {
Self {
kind: WsSecCanonicalizationKind::Exclusive,
include_comments: false,
inclusive_ns_prefixes: Vec::new(),
}
}
}
impl WsSecCanonicalizationProfile {
pub fn algorithm_uri(&self) -> &'static str {
match self.kind {
WsSecCanonicalizationKind::Exclusive => XML_EXC_C14N_URI,
WsSecCanonicalizationKind::Inclusive => XML_INC_C14N_URI,
}
}
pub fn inclusive() -> Self {
Self {
kind: WsSecCanonicalizationKind::Inclusive,
include_comments: false,
inclusive_ns_prefixes: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WsSecDigestMethod {
Sha256,
Sha384,
Sha512,
}
impl WsSecDigestMethod {
pub fn from_algorithm_uri(uri: &str) -> crate::core::Result<Self> {
match uri {
SHA256_URI => Ok(Self::Sha256),
SHA384_URI => Ok(Self::Sha384),
SHA512_URI => Ok(Self::Sha512),
_ => Err(crate::core::AsxError::new(
crate::core::ErrorCode::InteropViolation,
format!(
"unsupported digest algorithm URI: {uri} (supported: sha256, sha384, sha512)"
),
crate::core::ErrorContext::new("wssec_digest_method"),
)),
}
}
pub fn algorithm_uri(self) -> &'static str {
match self {
Self::Sha256 => SHA256_URI,
Self::Sha384 => SHA384_URI,
Self::Sha512 => SHA512_URI,
}
}
#[cfg(feature = "as4")]
pub(crate) fn output_len(self) -> usize {
match self {
Self::Sha256 => 32,
Self::Sha384 => 48,
Self::Sha512 => 64,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WsSecSignatureReference {
pub uri: String,
pub digest_method: WsSecDigestMethod,
pub digest_value_base64: String,
pub c14n_kind: WsSecCanonicalizationKind,
pub inclusive_ns_prefixes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WsSecCanonicalizedReference {
pub uri: String,
pub canonical_bytes: Vec<u8>,
pub digest_value_base64: String,
}
#[cfg(all(test, feature = "as4"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct WsSecSignatureMaterial {
pub(crate) signed_info_c14n: Vec<u8>,
pub(crate) signature_value: Vec<u8>,
pub(crate) signature_method_algorithm: String,
pub(crate) rsa_modulus: Option<Vec<u8>>,
pub(crate) rsa_exponent: Option<Vec<u8>>,
pub(crate) x509_certificates_der: Vec<Vec<u8>>,
}
#[cfg(feature = "as4")]
pub use xmlenc::{
XmlEncPayloadAlgorithm, decrypt_payload_xmlenc, encrypt_payload_xmlenc,
encrypt_payload_xmlenc_preparsed, encrypt_soap_header_xmlenc_preparsed,
};
#[cfg(feature = "as4")]
pub use canonicalize::{
SameDocumentReferenceIndex, canonical_vector_diff, canonicalize_enveloped_document,
canonicalize_reference, canonicalize_reference_digest_from_doc_with_inclusive_ns_and_index,
canonicalize_reference_from_doc, canonicalize_reference_from_doc_with_inclusive_ns,
};
pub use ocsp::CertOcspOutcome;
#[cfg(feature = "as4")]
pub use sign::{
generate_xmlsig_signature, generate_xmlsig_signature_with_external_references,
generate_xmlsig_signature_with_external_references_preparsed,
};
#[cfg(feature = "as4")]
pub use verify::{
ENVELOPED_SIGNATURE_URI, VerifiedEnvelopedSignature, WsSecVerifyOptions,
parse_signature_references, verify_enveloped_document_signature, verify_enveloped_signature,
verify_signature_references_strict,
};
pub use x509::validate_certificate_chain as validate_certificate_chain_with_revocation_vectors;
#[cfg(all(test, feature = "as4"))]
mod tests {
use super::*;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use openssl::asn1::Asn1Time;
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;
use openssl::x509::X509;
use verify::parse_signature_material;
use x509::validate_x509_certificate;
const TEST_CA_CERT_B64: &str = concat!(
"MIIDBzCCAe+gAwIBAgIUK+LZMsfX026W1bjcNh7Itso/uIowDQYJKoZIhvcNAQELBQAwEzERMA8GA1UEAwwIYXN4LXRlc3QwHhcNMjYwNTE3MTA0ND",
"U3WhcNMjcwNTE3MTA0NDU3WjATMREwDwYDVQQDDAhhc3gtdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ5HIVNm97sqbUIurm2p",
"fhcvhXyKxRY/eGr8Ohs4h5UvpOtFjlMDoQZEqihyeq8dzFU51FpTuwU+xprCLNwYkBTT7M9J2t6VQZdK2+CobUzk56rRAH1J3io+v3abpYro3bYexU",
"Zh4aow7Oy5T7rouEdAqes6ozt9v6WouyHcY0LxSIR2WYjaGDZRbJCdySdGWgsznjNOkYjKaRUySvtSHAHXFM544ZR0xIJf94OqnFYjWPx3RYM0ttIi",
"lcZgem9T+K8MMb6BNWBWM5n+KXSjph+6PlO0txjLMW2GO8xcjeHplM0j/0B7NbtF1NGJpNTXS8XY8OjNpVy6QQj5DAVG1HUCAwEAAaNTMFEwHQYDVR",
"0OBBYEFAsog+EedA+IQsJhrXUwEP2ltMJHMB8GA1UdIwQYMBaAFAsog+EedA+IQsJhrXUwEP2ltMJHMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN",
"AQELBQADggEBACm6hPG6kdwcNEHBMccjW0elgdjhBcygn4FTnMTR3Vfxyr69grzhtiyo7IoSm6KPpx88D33PH3s8w2ZCRg8js7wRRZkugHZUNl1lcI",
"pjJcelhZim9oKnklpnjgH4YE14mIFlEN5OhOZMuLSjA//iw+fQ3U+Xqnv+TnaSPbop0JqPZGQW4a8tGOfK55wPU9JSRQ5OrBgP+tMM8TYSNDler4Xs",
"Dk4+exqNprjVO1457CfiDPAYWnkfByAoTgw9ffdSxdiZRKBgJcWrJyp/AWxeqO6rP8x9xo3WRwe0X+GynUet3hSskSfyQX45vXqoGL+uv+9m9pfWAe",
"Rmb40yqB4UT/g="
);
fn make_test_rsa_key() -> (openssl::pkey::PKey<openssl::pkey::Private>, String, String) {
let rsa = openssl::rsa::Rsa::generate(2048).expect("rsa key generation");
let modulus_b64 = BASE64_STANDARD.encode(rsa.n().to_vec());
let exponent_b64 = BASE64_STANDARD.encode(rsa.e().to_vec());
let pkey = openssl::pkey::PKey::from_rsa(rsa).expect("pkey from rsa");
(pkey, modulus_b64, exponent_b64)
}
fn make_test_rsa_key_and_cert() -> (
openssl::pkey::PKey<openssl::pkey::Private>,
String,
String,
String,
) {
let (pkey, modulus_b64, exponent_b64) = make_test_rsa_key();
let mut name = openssl::x509::X509NameBuilder::new().expect("name builder");
name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, "asx-wssec-test")
.expect("cn");
let name = name.build();
let mut builder = openssl::x509::X509::builder().expect("x509 builder");
builder.set_version(2).expect("version");
let mut serial = openssl::bn::BigNum::new().expect("serial");
serial
.pseudo_rand(64, openssl::bn::MsbOption::MAYBE_ZERO, false)
.expect("serial rand");
builder
.set_serial_number(&serial.to_asn1_integer().expect("serial asn1"))
.expect("serial");
builder.set_subject_name(&name).expect("subject");
builder.set_issuer_name(&name).expect("issuer");
builder.set_pubkey(&pkey).expect("pubkey");
builder
.set_not_before(&openssl::asn1::Asn1Time::days_from_now(0).expect("nb"))
.expect("nb");
builder
.set_not_after(&openssl::asn1::Asn1Time::days_from_now(365).expect("na"))
.expect("na");
builder
.sign(&pkey, openssl::hash::MessageDigest::sha256())
.expect("sign cert");
let cert_b64 = BASE64_STANDARD.encode(builder.build().to_der().expect("cert der"));
(pkey, modulus_b64, exponent_b64, cert_b64)
}
fn rsa_sha256_sign(pkey: &openssl::pkey::PKey<openssl::pkey::Private>, data: &[u8]) -> String {
let mut signer = openssl::sign::Signer::new(openssl::hash::MessageDigest::sha256(), pkey)
.expect("signer");
signer.update(data).expect("signer update");
BASE64_STANDARD.encode(signer.sign_to_vec().expect("sign"))
}
fn reference_transforms(reference_uri: &str) -> &'static str {
if reference_uri.starts_with("cid:") {
""
} else {
"\n <ds:Transforms>\
\n <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\
\n </ds:Transforms>"
}
}
fn signed_xml_with_rsa_keyvalue(
reference_uri: &str,
payload_xml: &str,
digest_base64: &str,
signature_value_base64: &str,
modulus_base64: &str,
exponent_base64: &str,
) -> String {
let transforms = reference_transforms(reference_uri);
format!(
r#"<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:eb="urn:example:eb">
<soap:Header>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="{RSA_SHA256_URI}"/>
<ds:Reference URI="{reference_uri}">{transforms}
<ds:DigestMethod Algorithm="{SHA256_URI}"/>
<ds:DigestValue>{digest_base64}</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>{signature_value_base64}</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>{modulus_base64}</ds:Modulus>
<ds:Exponent>{exponent_base64}</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
</ds:KeyInfo>
</ds:Signature>
</soap:Header>
<soap:Body>
{payload_xml}
</soap:Body>
</soap:Envelope>"#
)
}
fn signed_xml_with_rsa_keyvalue_and_x509(
reference_uri: &str,
payload_xml: &str,
digest_base64: &str,
signature_value_base64: &str,
modulus_base64: &str,
exponent_base64: &str,
x509_certificate_base64: &str,
) -> String {
let transforms = reference_transforms(reference_uri);
format!(
r#"<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:eb="urn:example:eb">
<soap:Header>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="{RSA_SHA256_URI}"/>
<ds:Reference URI="{reference_uri}">{transforms}
<ds:DigestMethod Algorithm="{SHA256_URI}"/>
<ds:DigestValue>{digest_base64}</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>{signature_value_base64}</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyValue>
<ds:RSAKeyValue>
<ds:Modulus>{modulus_base64}</ds:Modulus>
<ds:Exponent>{exponent_base64}</ds:Exponent>
</ds:RSAKeyValue>
</ds:KeyValue>
<ds:X509Data>
<ds:X509Certificate>{x509_certificate_base64}</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</soap:Header>
<soap:Body>
{payload_xml}
</soap:Body>
</soap:Envelope>"#
)
}
fn signed_xml_with_x509_only(
reference_uri: &str,
payload_xml: &str,
digest_base64: &str,
signature_value_base64: &str,
x509_certificate_base64: &str,
) -> String {
let transforms = reference_transforms(reference_uri);
format!(
r#"<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:eb="urn:example:eb">
<soap:Header>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="{RSA_SHA256_URI}"/>
<ds:Reference URI="{reference_uri}">{transforms}
<ds:DigestMethod Algorithm="{SHA256_URI}"/>
<ds:DigestValue>{digest_base64}</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>{signature_value_base64}</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>{x509_certificate_base64}</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</soap:Header>
<soap:Body>
{payload_xml}
</soap:Body>
</soap:Envelope>"#
)
}
#[test]
fn transformless_same_document_reference_digests_over_inclusive_c14n() {
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let skeleton = signed_xml_with_x509_only("#payload-1", payload, "placeholder", "AA==", "");
let bare = skeleton
.replace(
"\n <ds:Transforms>\
\n <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\
\n </ds:Transforms>",
"",
);
assert!(!bare.contains("ds:Transforms"), "the fixture must be bare");
let inclusive = canonicalize_reference(
&bare,
"#payload-1",
WsSecCanonicalizationProfile {
kind: WsSecCanonicalizationKind::Inclusive,
include_comments: false,
inclusive_ns_prefixes: Vec::new(),
},
)
.expect("inclusive digest")
.digest_value_base64;
let exclusive =
canonicalize_reference(&bare, "#payload-1", WsSecCanonicalizationProfile::default())
.expect("exclusive digest")
.digest_value_base64;
assert_ne!(
inclusive, exclusive,
"the fixture must be one where the two algorithms disagree, or it \
proves nothing"
);
let references = parse_signature_references(&bare).expect("references parse");
let reference = references
.iter()
.find(|r| r.uri == "#payload-1")
.expect("the reference is present");
assert_eq!(
reference.c14n_kind,
WsSecCanonicalizationKind::Inclusive,
"a transform-less same-document reference defaults to inclusive C14N"
);
}
#[test]
fn parser_rejects_missing_references() {
let xml = "<Envelope xmlns=\"urn:example\"></Envelope>";
let err = parse_signature_references(xml).expect_err("missing refs should fail");
assert_eq!(err.code, crate::core::ErrorCode::ParseFailed);
}
#[test]
fn parser_accepts_digest_value_with_padding_whitespace() {
let xml = r##"
<d:Envelope xmlns:d="urn:example" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:Reference URI="#x">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>
abcd==
</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>stub-signature</ds:SignatureValue>
</ds:Signature>
</d:Envelope>
"##;
let refs = parse_signature_references(xml).expect("reference parse should pass");
assert_eq!(refs.len(), 1);
assert_eq!(refs[0].digest_value_base64, "abcd==");
}
#[test]
fn parser_rejects_missing_signature_value() {
let xml = r##"
<d:Envelope xmlns:d="urn:example" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:Reference URI="#x">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>abcd==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
</ds:Signature>
</d:Envelope>
"##;
let err = parse_signature_references(xml).expect_err("missing signature value should fail");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn parser_rejects_duplicate_reference_uris() {
let xml = r##"
<d:Envelope xmlns:d="urn:example" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:Reference URI="#x">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>abcd==</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#x">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>abcd==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>stub-signature</ds:SignatureValue>
</ds:Signature>
</d:Envelope>
"##;
let err = parse_signature_references(xml).expect_err("duplicate references should fail");
assert_eq!(err.code, crate::core::ErrorCode::InteropViolation);
}
#[test]
fn parser_rejects_transforms_in_reference() {
let xml = r##"
<d:Envelope xmlns:d="urn:example" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:Reference URI="#x">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>abcd==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>stub-signature</ds:SignatureValue>
</ds:Signature>
</d:Envelope>
"##;
let err = parse_signature_references(xml).expect_err("transforms should fail");
assert_eq!(err.code, crate::core::ErrorCode::InteropViolation);
}
#[test]
fn verify_enveloped_signature_strict_accepts_valid_rsa_signature_value() {
let (test_pkey, modulus_base64, exponent_base64, cert_base64) =
make_test_rsa_key_and_cert();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
&cert_base64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
&cert_base64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let mut signer = Signer::new(MessageDigest::sha256(), &test_pkey).expect("signer");
signer
.update(&material.signed_info_c14n)
.expect("signer update");
let signature_bytes = signer.sign_to_vec().expect("sign");
let signature_base64 = BASE64_STANDARD.encode(&signature_bytes);
let signed = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
&cert_base64,
);
verify_enveloped_signature(&signed, WsSecVerifyOptions::new())
.expect("strict verification");
}
#[test]
fn verify_enveloped_signature_accepts_x509data_without_rsa_keyvalue() {
let rsa = openssl::rsa::Rsa::generate(2048).expect("rsa");
let pkey = PKey::from_rsa(rsa).expect("pkey");
let mut name = openssl::x509::X509NameBuilder::new().expect("name builder");
name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, "asx-wssec-test")
.expect("cn");
let name = name.build();
let mut serial = openssl::bn::BigNum::new().expect("serial");
serial
.pseudo_rand(64, openssl::bn::MsbOption::MAYBE_ZERO, false)
.expect("serial rand");
let serial = serial.to_asn1_integer().expect("serial asn1");
let mut cert_builder = X509::builder().expect("x509 builder");
cert_builder.set_version(2).expect("version");
cert_builder.set_serial_number(&serial).expect("serial");
cert_builder.set_subject_name(&name).expect("subject");
cert_builder.set_issuer_name(&name).expect("issuer");
cert_builder.set_pubkey(&pkey).expect("pubkey");
let not_before = Asn1Time::days_from_now(0).expect("not_before");
let not_after = Asn1Time::days_from_now(365).expect("not_after");
cert_builder.set_not_before(¬_before).expect("nb");
cert_builder.set_not_after(¬_after).expect("na");
cert_builder
.sign(&pkey, MessageDigest::sha256())
.expect("cert sign");
let cert = cert_builder.build();
let cert_der_b64 = BASE64_STANDARD.encode(cert.to_der().expect("cert der"));
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned =
signed_xml_with_x509_only("#payload-1", payload, "placeholder", "AA==", &cert_der_b64);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_x509_only("#payload-1", payload, &digest, "AA==", &cert_der_b64),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let mut signer =
openssl::sign::Signer::new(MessageDigest::sha256(), &pkey).expect("signer");
signer
.update(&material.signed_info_c14n)
.expect("signer update");
let signature = signer.sign_to_vec().expect("signature");
let signature_base64 = BASE64_STANDARD.encode(signature);
let signed = signed_xml_with_x509_only(
"#payload-1",
payload,
&digest,
&signature_base64,
&cert_der_b64,
);
verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_expected_fingerprint(None),
)
.expect("verification should pass");
}
#[test]
fn verify_enveloped_signature_rejects_tampered_external_reference_bytes() {
let (test_pkey, modulus_base64, exponent_base64, cert_base64) =
make_test_rsa_key_and_cert();
let payload = b"payload-attachment";
let payload_digest = openssl::hash::hash(MessageDigest::sha256(), payload).expect("digest");
let payload_digest_b64 = BASE64_STANDARD.encode(payload_digest);
let unsigned = signed_xml_with_rsa_keyvalue_and_x509(
"cid:payload-1@example.com",
" <eb:Payload>Attachment</eb:Payload>",
&payload_digest_b64,
"AA==",
&modulus_base64,
&exponent_base64,
&cert_base64,
);
let material = parse_signature_material(&unsigned, WsSecCanonicalizationProfile::default())
.expect("signature material");
let mut signer = Signer::new(MessageDigest::sha256(), &test_pkey).expect("signer");
signer
.update(&material.signed_info_c14n)
.expect("signer update");
let signature_base64 = BASE64_STANDARD.encode(signer.sign_to_vec().expect("sign"));
let signed = signed_xml_with_rsa_keyvalue_and_x509(
"cid:payload-1@example.com",
" <eb:Payload>Attachment</eb:Payload>",
&payload_digest_b64,
&signature_base64,
&modulus_base64,
&exponent_base64,
&cert_base64,
);
let good_refs: [(&str, &[u8]); 1] = [("cid:payload-1@example.com", payload.as_slice())];
verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_external_references(&good_refs),
)
.expect("valid external reference bytes should pass");
let bad_refs: [(&str, &[u8]); 1] = [("cid:payload-1@example.com", b"payload-tampered")];
let err = verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_external_references(&bad_refs),
)
.expect_err("tampered external reference bytes must fail");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
assert!(
err.message
.contains("digest mismatch for reference cid:payload-1@example.com")
);
}
#[test]
fn bare_rsa_keyvalue_without_a_certificate_is_rejected() {
let (test_pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let signature_base64 = rsa_sha256_sign(&test_pkey, &material.signed_info_c14n);
let signed = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
);
let err = verify_enveloped_signature(&signed, WsSecVerifyOptions::new())
.expect_err("a self-asserted RSAKeyValue must never satisfy verification");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
assert!(
err.message.contains("no X.509 certificate"),
"error must name the cause: {}",
err.message
);
}
#[test]
fn xmlenc_encrypt_decrypt_roundtrip() {
let rsa = openssl::rsa::Rsa::generate(2048).expect("rsa");
let pkey = PKey::from_rsa(rsa).expect("pkey");
let mut name = openssl::x509::X509NameBuilder::new().expect("name builder");
name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, "asx-xmlenc-test")
.expect("cn");
let name = name.build();
let mut cert_builder = X509::builder().expect("x509 builder");
cert_builder.set_version(2).expect("version");
let mut serial = openssl::bn::BigNum::new().expect("serial");
serial
.pseudo_rand(64, openssl::bn::MsbOption::MAYBE_ZERO, false)
.expect("serial rand");
let serial = serial.to_asn1_integer().expect("serial asn1");
cert_builder.set_serial_number(&serial).expect("serial");
cert_builder.set_subject_name(&name).expect("subject");
cert_builder.set_issuer_name(&name).expect("issuer");
cert_builder.set_pubkey(&pkey).expect("pubkey");
let not_before = Asn1Time::days_from_now(0).expect("not_before");
let not_after = Asn1Time::days_from_now(365).expect("not_after");
cert_builder.set_not_before(¬_before).expect("nb");
cert_builder.set_not_after(¬_after).expect("na");
cert_builder
.sign(&pkey, MessageDigest::sha256())
.expect("cert sign");
let cert_pem = cert_builder.build().to_pem().expect("cert pem");
let key_pem = pkey.private_key_to_pem_pkcs8().expect("key pem");
let ciphertext =
encrypt_payload_xmlenc(b"payload", &cert_pem, XmlEncPayloadAlgorithm::Aes128Gcm)
.expect("encrypt");
let plaintext = decrypt_payload_xmlenc(&ciphertext, &key_pem).expect("decrypt");
assert_eq!(plaintext, b"payload");
}
#[test]
fn verify_enveloped_signature_strict_rejects_tampered_signature_value() {
let (pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let mut signature = BASE64_STANDARD
.decode(rsa_sha256_sign(&pkey, &material.signed_info_c14n))
.expect("decode sig");
signature[0] ^= 0x01;
let signature_base64 = BASE64_STANDARD.encode(signature);
let signed = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
);
let err = verify_enveloped_signature(&signed, WsSecVerifyOptions::new())
.expect_err("tampered signature should fail");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn trust_binding_requires_x509_certificate_when_fingerprint_is_configured() {
let (pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let signature_base64 = rsa_sha256_sign(&pkey, &material.signed_info_c14n);
let signed = signed_xml_with_rsa_keyvalue(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
);
let err = verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_expected_fingerprint(Some("ab:cd")),
)
.expect_err("missing x509 certificate should fail trust binding");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn malformed_x509_certificate_is_rejected() {
let (pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
"AQID",
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
"AQID",
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let signature_base64 = rsa_sha256_sign(&pkey, &material.signed_info_c14n);
let signed = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
"AQID",
);
let err = verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_expected_fingerprint(None),
)
.expect_err("malformed x509 certificate must fail");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn mismatched_x509_and_keyvalue_are_rejected() {
let (pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let signature_base64 = rsa_sha256_sign(&pkey, &material.signed_info_c14n);
let signed = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
);
let err = verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_expected_fingerprint(None),
)
.expect_err("key mismatch must fail");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn ca_certificate_is_rejected_for_message_signing() {
let cert_der = BASE64_STANDARD
.decode(TEST_CA_CERT_B64)
.expect("decode test certificate");
let err = validate_x509_certificate(&cert_der)
.expect_err("CA certificate must be rejected for end-entity signing");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
#[test]
fn signer_policy_rejects_cert_without_compatible_eku() {
let (pkey, modulus_base64, exponent_base64) = make_test_rsa_key();
let payload = " <eb:Payload wsu:Id=\"payload-1\">ABC</eb:Payload>";
let unsigned = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
"placeholder",
"AA==",
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
);
let digest = canonicalize_reference(
&unsigned,
"#payload-1",
WsSecCanonicalizationProfile::default(),
)
.expect("digest")
.digest_value_base64;
let material = parse_signature_material(
&signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
"AA==",
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
),
WsSecCanonicalizationProfile::default(),
)
.expect("signature material");
let signature_base64 = rsa_sha256_sign(&pkey, &material.signed_info_c14n);
let signed = signed_xml_with_rsa_keyvalue_and_x509(
"#payload-1",
payload,
&digest,
&signature_base64,
&modulus_base64,
&exponent_base64,
TEST_CA_CERT_B64,
);
let err = verify_enveloped_signature(
&signed,
WsSecVerifyOptions::new().with_expected_fingerprint(None),
)
.expect_err("certificate with incompatible EKU must fail signer policy");
assert_eq!(err.code, crate::core::ErrorCode::SecurityVerificationFailed);
}
fn c14n_element(envelope: &str, id: &str) -> String {
let result = canonicalize_reference(
envelope,
id,
WsSecCanonicalizationProfile {
kind: WsSecCanonicalizationKind::Exclusive,
include_comments: false,
inclusive_ns_prefixes: Vec::new(),
},
)
.expect("canonicalize_reference");
String::from_utf8(result.canonical_bytes).expect("valid UTF-8")
}
#[test]
fn w3c_exc_c14n_simple_namespace_propagation() {
let envelope = r#"<root xmlns:n1="http://www.w3.org">
<elem wsu:Id="e1"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:n1="http://www.w3.org"
n1:attr="v">text</elem>
</root>"#;
let c14n = c14n_element(envelope, "#e1");
assert!(
c14n.contains(r#"xmlns:n1="http://www.w3.org""#),
"n1 ns missing: {c14n}"
);
assert!(c14n.contains(r#"n1:attr="v""#), "attr missing: {c14n}");
assert!(c14n.contains("text"), "text missing: {c14n}");
}
#[test]
fn w3c_exc_c14n_attribute_ordering() {
let envelope = r#"<r xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:ns="urn:test">
<e wsu:Id="e1" ns:z="last" ns:a="first" plain="plain"/>
</r>"#;
let c14n = c14n_element(envelope, "#e1");
let a_pos = c14n.find(r#"ns:a="first""#).expect("ns:a missing");
let z_pos = c14n.find(r#"ns:z="last""#).expect("ns:z missing");
assert!(
a_pos < z_pos,
"ns:a must precede ns:z in C14N output:\n{c14n}"
);
}
#[test]
fn w3c_c14n_text_and_attr_escaping() {
let envelope = r#"<r xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<e wsu:Id="e1" a=""&	

">text <&></e>
</r>"#;
let c14n = c14n_element(envelope, "#e1");
assert!(
c14n.contains(r#""&	

"#),
"attr escaping wrong: {c14n}"
);
assert!(
c14n.contains("text <&>"),
"text escaping wrong: {c14n}"
);
}
#[test]
fn c14n_preserves_processing_instructions() {
let envelope = r#"<r xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<e wsu:Id="e1"><?xml-stylesheet type="text/xsl" href="style.xsl"?>body</e>
</r>"#;
let c14n = c14n_element(envelope, "#e1");
assert!(
c14n.contains(r#"<?xml-stylesheet type="text/xsl" href="style.xsl"?>"#),
"PI missing from C14N output: {c14n}"
);
}
#[test]
fn c14n_strips_comments_by_default() {
let envelope = r#"<r xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<e wsu:Id="e1"><!-- secret -->visible</e>
</r>"#;
let c14n = c14n_element(envelope, "#e1");
assert!(!c14n.contains("secret"), "comment not stripped: {c14n}");
assert!(c14n.contains("visible"), "text missing: {c14n}");
}
#[test]
fn c14n_preserves_comments_when_requested() {
let envelope = r#"<r xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<e wsu:Id="e1"><!-- keep-me -->text</e>
</r>"#;
let result = canonicalize_reference(
envelope,
"#e1",
WsSecCanonicalizationProfile {
kind: WsSecCanonicalizationKind::Exclusive,
include_comments: true,
inclusive_ns_prefixes: Vec::new(),
},
)
.expect("canonicalize");
let c14n = String::from_utf8(result.canonical_bytes).unwrap();
assert!(c14n.contains("<!-- keep-me -->"), "comment missing: {c14n}");
}
#[test]
fn c14n_inclusive_ns_prefixes_renders_ancestor_binding() {
let envelope = r#"<root xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<bar wsu:Id="e1"/>
</root>"#;
let result = canonicalize_reference(
envelope,
"#e1",
WsSecCanonicalizationProfile {
kind: WsSecCanonicalizationKind::Exclusive,
include_comments: false,
inclusive_ns_prefixes: vec!["xsi".to_string()],
},
)
.expect("canonicalize");
let c14n = String::from_utf8(result.canonical_bytes).unwrap();
assert!(
c14n.contains(r#"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance""#),
"xsi declaration missing from inclusive-prefix output:\n{c14n}"
);
}
#[test]
fn c14n_inclusive_ns_prefix_not_in_scope_is_not_emitted() {
let envelope = r#"<root xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<bar wsu:Id="e1"/>
</root>"#;
let result = canonicalize_reference(
envelope,
"#e1",
WsSecCanonicalizationProfile {
kind: WsSecCanonicalizationKind::Exclusive,
include_comments: false,
inclusive_ns_prefixes: vec!["xsi".to_string()],
},
)
.expect("canonicalize");
let c14n = String::from_utf8(result.canonical_bytes).unwrap();
assert!(
!c14n.contains("xmlns:xsi"),
"xsi declaration must not appear when prefix is not in scope:\n{c14n}"
);
}
}