Wrong digest value for xml signatures using Java XML Digital Signature API
Asked Answered
A

2

26

I need to send a signed XML file to a government agency in Brazil. The problem is that the digest calculated by my Java code (using the Java XML Digital Signature API is different from the one generated with another tool like XMLSEC.

Here's the code I use to generate a XML signature for some XML node:

private synchronized void sign(XmlObject obj) throws Exception {
        initKeystore();
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        List<Transform> transformList = new ArrayList<Transform>();
        Transform envelopedTransform = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
        Transform c14NTransform = fac.newTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
                (TransformParameterSpec) null);
        transformList.add(envelopedTransform);
        transformList.add(c14NTransform);
        Reference ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA1, null),
                Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null,
                null);
        SignedInfo si = fac.newSignedInfo(
                fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
                fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), Collections.singletonList(ref));
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(new FileInputStream(System.getProperty("javax.net.ssl.keyStore")),
                System.getProperty("javax.net.ssl.keyStorePassword").toCharArray());
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry("entry",
                new KeyStore.PasswordProtection(System.getProperty("javax.net.ssl.keyStorePassword").toCharArray()));

        X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

        // Create the KeyInfo containing the X509Data.
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        X509Data xd = kif.newX509Data(Collections.singletonList(cert));
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
        // Instantiate the document to be signed.

        Element el = (Element) obj.getDomNode().getFirstChild();
        String id = el.getAttribute("Id");

        DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), el);
        // Create the XMLSignature, but don't sign it yet.
        XMLSignature signature = fac.newXMLSignature(si, ki);
        // Marshal, generate, and sign the enveloped signature.
        signature.sign(dsc);

    }

If I try to validate the generated XML with xmlsec, I get the following error:

$ xmlsec1 --verify consulta.xml 
func=xmlSecOpenSSLEvpDigestVerify:file=digests.c:line=229:obj=sha1:subj=unknown:error=12:invalid data:data and digest do not match
FAIL

But if I try to sign the very same file (consult.xml) with xmlsec (using the same private key), that error goes away:

xmlsec1 --sign --output doc-signed.xml --privkey-pem cert.pem consulta.xml

The differences between consult.xml and doc-signed.xml (generated by xmlsec) are the contents of the SignatureValue and DigestValue tags:

consulta.xml:

<DigestValue>Ajn+tfX7JQc0HPNJ8KbTy7Q2f8I=</DigestValue>
...
<SignatureValue>Q1Ys0Rtj8yL2SA2NaQWQPtmNuHKK8q2anPiyLWlH7mOIjwOs0GEcD0WLUM/BZU0Q
T0kSbDTuJeTR2Ec9wu+hqXXbJ76FpX9/IyHrdyx2hLg0VhB5RRCdyBEuGlmnsFDf
XCyBotP+ZyEzolbTCN9TjCUnXNDWtFP1YapMxAIA0sth0lTpYgGJd8CSvFlHdFj+
ourf8ZGiDmSTkVkKnqDsj8O0ZLmbZfJpH2CBKicX+Ct7MUz2sqVli4XAHs6WXX+E
HJpbOKthS3WCcpG3Kw4K50yIYGTkTbWCYFxOVsMfiVy4W/Qz15Vxb8chD8LM58Ep
m/szmvnTAESxv/piDr7hyw==</SignatureValue>

doc-signed.xml:

<DigestValue>w6xElXJrZw3G86OsNkWav+pcKJo=</DigestValue>
...
<SignatureValue>YmUsnlnAY9uLhlfVBLhB8K8ArxMOkOKZJoQ6zgz55ggU6vJCO9+HWJCKQJp6Rvn/w5PCAFY0KJRb
r6/WhHML0Z+Q6TSuIL8OTvJ3iPoROAK6uy07YAflKOUklqk4uxgfMkR+hWMCyfITJVCVZo/MXmPy
g7YwmztoSlGH+p6+ND5n2u47Y2k6SpIvw3CUxwAVQkD0Hsj3G58cbUbrFCoyPVGOe4zJ9c1HPsMW
KzBEFe3QETzPJ8I1B7EEVi5oDvzXE2rMTH4K7zvNGnXpBNGwnSjEOticlqKVP5wyUD7CPwgF1Wgy
Z0njvlaW3K8YmAY8fc70v/+wSO6Fu+0zj18Xeg==</SignatureValue>

I won't post the rest of either file because they're equal and would make this post even more verbose.

From what I can gather, the web app that receives this XML file is a .NET application and it calculates a different signature digest that my Java code (much like xmlsec does). Any ideas?

Aboard answered 1/5, 2012 at 21:11 Comment(6)
Sorry, but are you sure the digest hash algorithm is SHA1? It can be something else and the signature can still be RSA_SHA1 (as I read your code).Fulmis
That's what I am telling the Java API to do. One thing that I noticed is that if I save the xmls document to a file, read that file and signing what I read, the digest is calculated correctly. So I am thinking maybe whitespaces are somehow considered on either the Java or the XMLSEC side. That would solve my problem if I needed to sign the xml only once; the problem is that I need to do it at least twice...Aboard
Did you cheched the '\n'?Avrom
Did you check the newlines and character encondings?Dished
You may want to try using Apache Santurario rather than the lower level Java API to see how the signatures compare: santuario.apache.orgBigeye
If you are using some java I/O Writer like BufferedWriter or PrintWriter and calling flush() to write your XML files on disk then it will add (unintended) new-line character e.g. \nat the end. Signature hash could change due to this. Pls review your code carefully.Greed
P
1

If it is not too late to answer:

You create 2 Transforms in code (envelopedTransform and c14NTransform), but do not use them.

You create the reference with a single new Transform.ENVELOPED. http://www.w3.org/TR/2001/REC-xml-c14n-20010315 (C14N) transform is not applied.

Now, I do not know for sure what the XML security standard says the behaviour should be in this case. Maybe other tools automatically apply C14N transform as well.

I know for sure if you do NOT specify any transform JDK will apply at least C14N transform.

Basically change that fac.newReference("", ...) and pass transformList into it instead of Collections.singletonList().

Placido answered 6/7, 2013 at 18:54 Comment(0)
T
0

Ideally the DigestValue element contains the actual base64-encoded digest value in Java XML signature API. Could you please verify your digest value created from XMLSec is also base64-encoded.

Torsi answered 18/7, 2013 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.