Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
tests/fixtures/xmlenc/aleksey-xmlenc-01/*.tmpl -text whitespace=-trailing-space,-space-before-tab
tests/fixtures/xmlenc/01-phaos-xmlenc-3/** -text whitespace=-trailing-space,-space-before-tab
93 changes: 88 additions & 5 deletions scripts/import-donor-fixtures.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
donor_root="${XMLSEC_DONOR_ROOT:-$repo_root/donors/xmlsec/tests}"
fixture_root="$repo_root/tests/fixtures"

replace_target() {
local replacement="$1"
local target="$2"
local target_parent target_name backup=""
target_parent="$(dirname "$target")"
target_name="$(basename "$target")"

if [[ -e "$target" || -L "$target" ]]; then
backup="$(mktemp -d "$target_parent/.${target_name}.backup.XXXXXX")"
rm -rf "$backup"
if ! mv "$target" "$backup"; then
rm -rf "$replacement"
return 1
fi
fi

if mv "$replacement" "$target"; then
if [[ -n "$backup" ]]; then
rm -rf "$backup"
fi
return 0
fi

rm -rf "$replacement"
if [[ -n "$backup" ]] && ! mv "$backup" "$target"; then
printf 'failed to restore fixture target after replacement failure: %s\n' "$target" >&2
fi
return 1
}

fixture_paths=("$@")
if (( ${#fixture_paths[@]} == 0 )); then
fixture_paths=(
Expand All @@ -19,11 +49,64 @@ if (( ${#fixture_paths[@]} == 0 )); then
fi

for relative_path in "${fixture_paths[@]}"; do
donor_path="${relative_path#xmldsig/}"
if [[ "$relative_path" == xmlenc/* ]]; then
donor_path="${relative_path#xmlenc/}"
# Normalize directory notation before deriving source and target prefixes.
while [[ "$relative_path" == */ ]]; do
relative_path="${relative_path%/}"
done
case "$relative_path" in
xmldsig/*) donor_path="${relative_path#xmldsig/}" ;;
xmlenc/*) donor_path="${relative_path#xmlenc/}" ;;
*)
printf 'fixture path must start with xmldsig/ or xmlenc/: %s\n' "$relative_path" >&2
exit 1
;;
esac
if [[ -z "$donor_path"
|| "$donor_path" == /*
|| "$donor_path" == *//*
|| "/$donor_path/" == *"/./"*
|| "/$donor_path/" == *"/../"* ]]; then
printf 'fixture path contains an empty, current, or parent component: %s\n' "$relative_path" >&2
exit 1
fi
target="$fixture_root/$relative_path"
mkdir -p "$(dirname "$target")"
install -m 0644 "$donor_root/$donor_path" "$target"
source="$donor_root/$donor_path"
Comment thread
polaz marked this conversation as resolved.
if [[ -d "$source" ]]; then
# Build the complete snapshot before replacing the last known-good target.
target_parent="$(dirname "$target")"
target_name="$(basename "$target")"
mkdir -p "$target_parent"
staging="$(mktemp -d "$target_parent/.${target_name}.import.XXXXXX")"
manifest="$(mktemp "$target_parent/.${target_name}.files.XXXXXX")"
if ! find "$source" -type f -print0 > "$manifest"; then
rm -rf "$staging" "$manifest"
exit 1
fi
copy_failed=false
while IFS= read -r -d '' donor_file; do
suffix="${donor_file#"$source/"}"
Comment thread
polaz marked this conversation as resolved.
target_file="$staging/$suffix"
if ! mkdir -p "$(dirname "$target_file")" \
|| ! install -m 0644 "$donor_file" "$target_file"; then
copy_failed=true
break
fi
done < "$manifest"
rm -f "$manifest"
if [[ "$copy_failed" == true ]]; then
rm -rf "$staging"
exit 1
fi
replace_target "$staging" "$target"
else
target_parent="$(dirname "$target")"
target_name="$(basename "$target")"
mkdir -p "$target_parent"
staging="$(mktemp "$target_parent/.${target_name}.import.XXXXXX")"
if ! install -m 0644 "$source" "$staging"; then
rm -f "$staging"
exit 1
fi
replace_target "$staging" "$target"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
done
57 changes: 57 additions & 0 deletions src/xmlenc/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fn parse_key_info(node: Node<'_, '_>) -> Result<ParsedKeyInfo, XmlEncError> {
require_element(node, XMLDSIG_NS, "KeyInfo")?;
let mut key_name = None;
let mut encrypted_keys = Vec::new();
let mut unsupported_agreement = None;
for child in node.children().filter(Node::is_element) {
if child.has_tag_name((XMLDSIG_NS, "KeyName")) {
if key_name.is_some() {
Expand All @@ -67,8 +68,24 @@ fn parse_key_info(node: Node<'_, '_>) -> Result<ParsedKeyInfo, XmlEncError> {
key_name = Some(parse_key_name(child)?);
} else if child.has_tag_name((XMLENC_NS, "EncryptedKey")) {
encrypted_keys.push(parse_encrypted_key(child)?);
} else if child.has_tag_name((XMLENC_NS, "AgreementMethod")) {
// Agreement methods require a separate key-derivation trust boundary.
// Keep the URI as a fallback error while allowing another advertised
// key candidate to be selected by the caller's resolver.
let algorithm = child
.attribute("Algorithm")
.ok_or(XmlEncError::MissingRequired(
"AgreementMethod Algorithm attribute",
))?;
unsupported_agreement.get_or_insert_with(|| algorithm.to_owned());
}
}
if key_name.is_none()
&& encrypted_keys.is_empty()
&& let Some(algorithm) = unsupported_agreement
{
return Err(XmlEncError::UnsupportedAlgorithm(algorithm));
}
Ok(ParsedKeyInfo {
key_name,
encrypted_keys,
Expand Down Expand Up @@ -488,6 +505,46 @@ mod tests {
);
}

/// Verifies that a lone unsupported agreement reports its algorithm URI.
#[test]
fn rejects_unsupported_key_agreement_explicitly() {
// AgreementMethod is outside the supported secure profile. Reporting its
// URI avoids misclassifying a present but unsupported key as missing.
let xml = format!(
r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/><ds:KeyInfo><xenc:AgreementMethod Algorithm="http://www.w3.org/2001/04/xmlenc#dh"/></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
);
assert!(matches!(
parse_encrypted_data(&xml),
Err(XmlEncError::UnsupportedAlgorithm(uri))
if uri == "http://www.w3.org/2001/04/xmlenc#dh"
));

let missing_algorithm =
xml.replace(" Algorithm=\"http://www.w3.org/2001/04/xmlenc#dh\"", "");
assert!(matches!(
parse_encrypted_data(&missing_algorithm),
Err(XmlEncError::MissingRequired(
"AgreementMethod Algorithm attribute"
))
));
}

/// Verifies that unsupported agreement metadata does not hide usable keys.
#[test]
fn retains_supported_key_candidates_alongside_unsupported_agreement() {
// Multi-recipient KeyInfo may advertise an unsupported agreement method
// before a key candidate that the configured resolver can actually use.
let xml = format!(
r#"<xenc:EncryptedData xmlns:xenc="{XMLENC_NS}" xmlns:ds="{XMLDSIG_NS}"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/><ds:KeyInfo><xenc:AgreementMethod Algorithm="http://www.w3.org/2001/04/xmlenc#dh"/><ds:KeyName>content-key</ds:KeyName><xenc:EncryptedKey Recipient="alice"><xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</xenc:CipherValue></xenc:CipherData></xenc:EncryptedKey></ds:KeyInfo><xenc:CipherData><xenc:CipherValue>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</xenc:CipherValue></xenc:CipherData></xenc:EncryptedData>"#
);

let parsed = parse_encrypted_data(&xml)
.expect("a supported key candidate must take precedence over agreement fallback");
assert_eq!(parsed.key_name.as_deref(), Some("content-key"));
assert_eq!(parsed.encrypted_keys.len(), 1);
assert_eq!(parsed.encrypted_keys[0].recipient.as_deref(), Some("alice"));
}

#[test]
fn rejects_missing_algorithm_and_duplicate_oaep_parameters() {
// Algorithm selection and OAEP parameter cardinality are security-sensitive,
Expand Down
52 changes: 52 additions & 0 deletions tests/fixtures/xmlenc/01-phaos-xmlenc-3/Readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
This directory contains the following files:

Readme.txt -- This file.

payment.xml -- A sample XML file.

key.txt -- Keys for decrypting the encrypted XML files enc-*-*-*-*.xml.

dh-priv-key.der
-- Diffie-Hellman private key encoded in PKCS#8 PrivateKeyInfo formate.
This key can be used to decrypt the files enc-*-*-ka-dh.xml.
rsa-priv-key.der
-- RSA private key encoded in PKCS#8 PrivateKeyInfo formate.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
This key can be used to decrypt the files enc-*-*-kt-*.xml.

enc-*-*-*-*.xml
-- XML document obtained by encrypting and replacing part of the
sample payment.xml. The data encryption key is in turn
encrypted.

Each file has the form: enc-A-B-C-D.xml, where:

A indicates the type of the data encrypted. This includes:

element = The CreditCard element.
content = The content of the CreditCard element
(multiple elements).
text = The content of the Number element (text node).

B indicates the data encryption algorithm.

C indicates the key encryption protocols:
kt = key transport
kw = key wrap
ka = key agreement

D indicates the key encryption algorithm.

bad-*-enc-*-*-*-*.xml
-- some "screw-up" samples

The sample encrypted XML files are produced from Phaos XML toolkit.

Jiandong Guo
Phaos Technology
http://www.phaos.com

jguo@phaos.com




Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<PaymentInfo xmlns="http://example.org/paymentv2">
<Name>John Smith</Name>
<EncryptedData Id="ED" Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-tripledes"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>my-tripledes-key</ds:KeyName>
</ds:KeyInfo>
<CipherData>
<CipherValue>HgVuHoXxBQWD9fvi0gt9TanywZ5lJokM/12fcMG6gRoMjsCPulH+4A==</CipherValue>
</CipherData>
<ReferenceList>
<DataReference URI="#ED"/>
</ReferenceList>
</EncryptedKey>
</ds:KeyInfo>
<CipherData>
<CipherValue>
yUMIHkj5EETckjZ59fpda4+m4YLCrkJsnuRz+Q3e5sP+VvHKRH1kdeGkXw3kYURV
JM3nQjGl2egW80oUxSykQD2F9iDoIjNhLSgIbyuse64oo/5/v9IiaUpSvrAocwLP
AzFIUmOrxmIagAkRGDOeMR8tdHLD6g84dQj4O/aGfwhL/2wUo/l+7onrbmsd6pVI
fjNyvXm+eITuyUnkDTHrCR+dfb2sHaQ3g3McgyfP6ZjI/L50SPJZ/w==
</CipherValue>
</CipherData>
</EncryptedData>
</PaymentInfo>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<PaymentInfo xmlns="http://example.org/paymentv2">
<Name>John Smith</Name>
<CreditCard Currency="USD" Limit="5,000">
<Number>4019 2445 0277 5567</Number>
<Issuer>Bank of the Internet</Issuer>
<Expiration Time="04/02"/>
</CreditCard>
</PaymentInfo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<EncryptedData Id="ED" Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes192"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>my-aes192-key</ds:KeyName>
</ds:KeyInfo>
<CipherData>
<CipherValue></CipherValue>
</CipherData>
<ReferenceList>
<DataReference URI="#ED"/>
</ReferenceList>
</EncryptedKey>
</ds:KeyInfo>
<CipherData>
<CipherValue>
</CipherValue>
</CipherData>
</EncryptedData>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<PaymentInfo xmlns="http://example.org/paymentv2">
<Name>John Smith</Name>
<CreditCard Currency="USD" Limit="5,000">
<EncryptedData Id="ED" Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes192"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>my-aes192-key</ds:KeyName>
</ds:KeyInfo>
<CipherData>
<CipherValue>5+GpVUQNTAT3uY8pPedEg/PpftiX+fJsTCun+fgmIz0=</CipherValue>
</CipherData>
<ReferenceList>
<DataReference URI="#ED"/>
</ReferenceList>
</EncryptedKey>
</ds:KeyInfo>
<CipherData>
<CipherValue>
QBWlf/iYDXKbBWf0Pu3Bgzt5oLBwIs4NKPb/I0SxDYVnDc34i7tZG5UzHlztsWlX
udfbIW9l7k5WVdy9bfXZWrp6sXamBedUQTrr+z4v4u2jWaUyqEioLe7h36QeoU23
QvkOzRO9AiWm/QCCzePZ3Frp4LM9lXOE
</CipherValue>
</CipherData>
</EncryptedData>
</CreditCard>
</PaymentInfo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<PaymentInfo xmlns="http://example.org/paymentv2">
<Name>John Smith</Name>
<CreditCard Currency="USD" Limit="5,000">
<Number>4019 2445 0277 5567</Number>
<Issuer>Bank of the Internet</Issuer>
<Expiration Time="04/02"/>
</CreditCard>
</PaymentInfo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<EncryptedData Id="ED" Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<EncryptedKey Id="EK" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#kw-tripledes"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>my-3des-key</ds:KeyName>
</ds:KeyInfo>
<CipherData>
<CipherValue></CipherValue>
</CipherData>
<ReferenceList>
<DataReference URI="#ED"/>
</ReferenceList>
</EncryptedKey>
</ds:KeyInfo>
<CipherData>
<CipherValue>
</CipherValue>
</CipherData>
</EncryptedData>
Loading