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
24 changes: 23 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install tooling
run: sudo apt-get update -qq && sudo apt-get install -y -qq libxml2-utils xsltproc
run: sudo apt-get update -qq && sudo apt-get install -y -qq libxml2-utils xsltproc xmlsec1

- uses: actions/setup-java@v5
with:
Expand Down Expand Up @@ -223,6 +223,28 @@ jobs:
fi
echo "tamper correctly detected"

- name: XML signature - xmlsec1 CLI signs the skeleton template, cross-verifies with Java
run: |
set -e
SH=XML_Signature/cli/sign-verify-xmlsec1.sh
SKEL=FundsXML_Files/4.2.9/signed/Signed_Fund_Skeleton.xml
M="./mvnw -q -B -pl XML_Signature/java exec:java"
# sign the committed template in place (keys from the GenerateTestKey step above)
$SH sign "$SKEL" xs-signed.xml
xmllint --noout --nonet --schema ci-schemas/4.2.9/FundsXML.xsd xs-signed.xml
$SH verify xs-signed.xml
# Java verifies the xmlsec1 output: pinned cert AND embedded certificate
$M -Dexec.mainClass=VerifyFundsXml \
-Dexec.args="xs-signed.xml XML_Signature/keys/test-signing-cert.pem"
$M -Dexec.mainClass=VerifyFundsXml -Dexec.args="xs-signed.xml"
# xmlsec1 verifies the Java-signed file from the previous step
$SH verify signed.xml
sed 's/<Amount ccy="EUR">125000000</<Amount ccy="EUR">125000001</' xs-signed.xml > xs-tampered.xml
if $SH verify xs-tampered.xml; then
echo "::error::xmlsec1-signed tampered file unexpectedly verified"; exit 1
fi
echo "xmlsec1: sign/verify/tamper ok, cross-verified with Java"

- name: DB integration - multi-fund import+export in 4 languages
run: |
set -e
Expand Down
14 changes: 9 additions & 5 deletions FundsXML_Files/4.2.9/signed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ local `FundsXML.xsd` in any complete copy of the release).
> ⚠️ **Placeholder:** `DigestValue` and `SignatureValue` are schema-valid base64
> strings but **not cryptographically verifiable**. Real signing and
> verification (Apache Santuario / .NET `SignedXml` / `xmlsec1` / Python
> `signxml`) lives in [`XML_Signature/`](../../../XML_Signature/README.md);
> note that this template uses inclusive C14N and a `ds:KeyName`, whereas the
> Java/.NET examples sign with exclusive C14N and embed the X.509 certificate.

Algorithms used (enveloped signature): C14N 2001-03-15, RSA-SHA256, SHA-256.
> `signxml`) lives in [`XML_Signature/`](../../../XML_Signature/README.md).
> The `ds:Signature` here doubles as the **signing template** for the
> `xmlsec1` CLI example: it already carries the shared profile, so once
> signed in place it cross-verifies with the Java and .NET verifiers.

Algorithms (enveloped signature, same profile as every `XML_Signature/`
stack): exclusive C14N (`xml-exc-c14n#`) for both canonicalization and the
second transform, RSA-SHA256, SHA-256 digest, signer certificate embedded as
`ds:X509Certificate` (empty placeholder until signed).

## Validation

Expand Down
17 changes: 13 additions & 4 deletions FundsXML_Files/4.2.9/signed/Signed_Fund_Skeleton.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<!-- FundsXML 4.2.9 — skeleton for a SIGNED FundsXML file (enveloped XMLDSig).
ds:Signature is the last optional child of <FundsXML4> (xmldsig-core-schema.xsd
is imported from release 4.2.9 on). The DigestValue/SignatureValue here are
PLACEHOLDERS — schema-valid but NOT cryptographically verifiable. Real
signing/verification (Apache Santuario etc.) follows in Phase 3 under
XML_Signature/. Version: see ControlData/Version and
PLACEHOLDERS — schema-valid but NOT cryptographically verifiable. The
ds:Signature is a signing TEMPLATE in the profile every stack in
XML_Signature/ uses (RSA-SHA256, exclusive C14N, enveloped, signer cert
embedded as X509Certificate): xmlsec1 fills DigestValue, SignatureValue
and the empty X509Certificate in place (XML_Signature/cli/), and the
result cross-verifies with the Java (Santuario) and .NET (SignedXml)
verifiers. Version: see ControlData/Version and
xsi:noNamespaceSchemaLocation. -->
<FundsXML4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Expand Down Expand Up @@ -47,11 +51,12 @@
</Funds>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>UExBQ0VIT0xERVItRElHRVNU</ds:DigestValue>
Expand All @@ -60,6 +65,10 @@
<ds:SignatureValue>UExBQ0VIT0xERVItU0lHTkFUVVJFLVZBTFVFLU5PVC1DUllQVE9HUkFQSElDQUxMWS1WQUxJRA==</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyName>erste-am-funds-signing-2025</ds:KeyName>
<ds:X509Data>
<!-- empty placeholder: xmlsec1 writes the signer certificate here -->
<ds:X509Certificate/>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</FundsXML4>
17 changes: 9 additions & 8 deletions XML_Signature/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Windows too) to write a throwaway self-signed RSA-2048 keystore
| Stack | Entry point | Status |
|-------|-------------|--------|
| Java — Apache Santuario | [`java/SignFundsXml.java`](java/SignFundsXml.java) / [`java/VerifyFundsXml.java`](java/VerifyFundsXml.java) | ✅ verified (sign, verify, tamper-detect) |
| CLI — `xmlsec1` | [`cli/sign-verify-xmlsec1.sh`](cli/sign-verify-xmlsec1.sh) | reference (needs `xmlsec1`) |
| CLI — `xmlsec1` | [`cli/sign-verify-xmlsec1.sh`](cli/sign-verify-xmlsec1.sh) | ✅ verified (xmlsec1 1.2.33, in CI): sign, verify, tamper; cross-verifies with Java and .NET both ways |
| Python — `signxml` | [`python/sign_verify_signxml.py`](python/sign_verify_signxml.py) | reference (`pip install -e ".[signature]"` adds `signxml`) |
| .NET — `SignedXml` | [`dotnet/SignVerify.cs`](dotnet/SignVerify.cs) | verified (.NET SDK 8): sign, verify, tamper; cross-verifies with Java both ways |

Expand Down Expand Up @@ -65,13 +65,14 @@ flipping one digit in a signed file → `INVALID`). Santuario verification runs
with **secure validation** enabled.

> **Note on `xmlsec1`:** it signs an *existing* `ds:Signature` template, so it
> pairs naturally with the committed signed skeleton; the Java/.NET/Python
> examples instead build and append the `ds:Signature` themselves. Because
> the skeleton's template uses **inclusive** C14N (`REC-xml-c14n-20010315`),
> only the enveloped transform and a `ds:KeyName` in `KeyInfo`, the xmlsec1
> output does **not** follow the exclusive-C14N / embedded-X509 profile above:
> Java and .NET can still verify it against the pinned certificate, but not
> from the embedded `KeyInfo` (which carries no key).
> pairs naturally with the committed signed skeleton
> (`FundsXML_Files/4.2.9/signed/Signed_Fund_Skeleton.xml`); the
> Java/.NET/Python examples instead build and append the `ds:Signature`
> themselves. The skeleton's template carries exactly the profile above
> (exclusive C14N, enveloped + exc-C14N transforms, RSA-SHA256, empty
> `X509Certificate` placeholder that xmlsec1 fills), so the signed result
> verifies in Java and .NET both pinned and from the embedded certificate,
> and xmlsec1 verifies the Java/.NET output.

A real signed file is **not committed** — the signature is bound to the
throwaway key, which is regenerated per run. CI signs → verifies as a roundtrip.
19 changes: 13 additions & 6 deletions XML_Signature/cli/sign-verify-xmlsec1.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@
# sign-verify-xmlsec1.sh sign <in.xml> <out.xml>
# sign-verify-xmlsec1.sh verify <signed.xml> [cert.pem]
#
# Reference example — xmlsec1 is a native CLI tool (not bundled):
# xmlsec1 is a native CLI tool (not bundled):
# Debian/Ubuntu: sudo apt-get install xmlsec1
# macOS: brew install xmlsec1
# (The verified, fully cross-platform signing path is the Java example,
# XML_Signature/java — run via the Maven Wrapper.)
# Verified (xmlsec1 1.2.33, also in CI): sign -> verify, tamper detected, and
# the output cross-verifies with the Java (Santuario) and .NET (SignedXml)
# verifiers, both pinned and from the embedded certificate; xmlsec1 in turn
# verifies files signed by Java and .NET.
#
# Keys: generated cross-platform by the Java GenerateTestKey (no openssl):
# ./mvnw -q -pl XML_Signature/java compile exec:java -Dexec.mainClass=GenerateTestKey
# It writes test-signing.p12, test-signing-cert.pem AND test-signing-key.pem
# (PKCS#8) into XML_Signature/keys/ — the last two are what xmlsec1 needs.
#
# Unlike the Java/Python examples, xmlsec1 signs an EXISTING <ds:Signature>
# template in the document, so it pairs naturally with the committed
# FundsXML_Files/4.2.9/signed/Signed_Fund_Skeleton.xml (placeholder signature).
# Unlike the Java/.NET/Python examples, xmlsec1 signs an EXISTING <ds:Signature>
# template in the document. The committed
# FundsXML_Files/4.2.9/signed/Signed_Fund_Skeleton.xml is exactly that
# template, already in the shared profile: SignedInfo with exclusive C14N,
# enveloped + exclusive-C14N transforms, RSA-SHA256 / SHA-256, and an empty
# <ds:X509Certificate/> that xmlsec1 fills with the signer cert (that is why
# --privkey-pem gets "key,cert"). To sign any other FundsXML file, append that
# ds:Signature block as the last child of <FundsXML4> first.
#
# POSIX sh; the Windows counterpart is sign-verify.ps1 in this directory.

Expand Down
13 changes: 12 additions & 1 deletion XML_Signature/java/VerifyFundsXml.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ public static void main(String[] args) throws Exception {

// xmlsec 4.x: secure validation is a constructor argument (blocks
// RetrievalMethod loops, dangerous transforms, weak algorithms, etc.).
// Everything from here on is "INVALID, exit 1" territory when Santuario
// throws: a malformed SignatureValue, an empty or unparsable
// X509Certificate (the committed skeleton is an unsigned template with
// exactly those placeholders), a broken transform chain, and so on.
// They are properties of the document under test, not setup errors.
boolean ok;
try {
XMLSignature signature = new XMLSignature(sigEl, "", true);

boolean ok;
if (args.length >= 2) {
try (FileInputStream cf = new FileInputStream(args[1])) {
X509Certificate pinned = (X509Certificate) CertificateFactory
Expand Down Expand Up @@ -80,6 +86,11 @@ public static void main(String[] args) throws Exception {
+ (embedded != null ? " (cert: "
+ embedded.getSubjectX500Principal() + ")" : ""));
}
} catch (org.apache.xml.security.exceptions.XMLSecurityException e) {
System.out.println("INVALID: " + e.getMessage());
System.exit(1);
return;
}

System.out.println(ok ? "VALID: signature OK" : "INVALID: signature check failed");
System.exit(ok ? 0 : 1);
Expand Down