I can successfully do manual reference validation (canonicalize every referenced element --> SHA1 --> Base64 --> check if it's the same of DigestValue content) but I fail with the verification of the SignatureValue. Here's the SignedInfo to canonicalize and hash:
<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
<ds:Reference URI="#element-1-1291739860070-11803898">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>d2cIarD4atw3HFADamfO9YTKkKs=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#timestamp">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>YR/fZlwJdw+KbyP24UYiyDv8/Dc=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
Ater removing all the spaces between tags (and so getting the whole element on a single line), I obtain this sha1 digest (in Base64):
6l26iBH7il/yrCQW6eEfv/VqAVo=
Now I expect to find the same digest after the decryption of the SignatureValue content, but I get a differente and longer value:
MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig=
Here's some java code for the decyption:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(new File(inputFilePath));
NodeList nl = doc.getElementsByTagName("ds:SignatureValue");
if (nl.getLength() == 0) {
throw new Exception("Cannot find SignatureValue element");
}
String signature = "OZg96GMrGh0cEwbpHwv3KDhFtFcnzPxbwp9Xv0pgw8Mr9+NIjRlg/G1OyIZ3SdcOYqqzF4/TVLDi5VclwnjBAFl3SEdkyUbbjXVAGkSsxPQcC4un9UYcecESETlAgV8UrHV3zTrjAWQvDg/YBKveoH90FIhfAthslqeFu3h9U20=";
X509Certificate cert = X509Certificate.getInstance(new FileInputStream(<a file path>));
PublicKey pubkey = cert.getPublicKey();
Cipher cipher = Cipher.getInstance("RSA","SunJCE");
cipher.init(Cipher.DECRYPT_MODE, pubkey);
byte[] decodedSignature = Base64Coder.decode(signature);
cipher.update(decodedSignature);
byte[] sha1 = cipher.doFinal();
System.out.println(Base64Coder.encode(sha1));
The thing that confuses me much is that the two digests have different size, but of course I also need to obtain exactly the same value from the two calculations. Any suggestions? Thank you.
XML Signatures can be applied to any digital content (data object), including XML. An XML Signature may be applied to the content of one or more resources. Enveloped or enveloping signatures are over data within the same XML document as the signature; detached signatures are over data external to the signature element.
Digitally verifying (validating) a message based on XML Signature works the following way: The receiver decrypts the encrypted digest (which is part of the SignatureValue element of the received message) using the sender's public key. The receiver calculates the digest out of the SignedInfo element of the message.
MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig=
is Base64 encoding for a DER-encoded ASN.1 structure: a SEQUENCE
containing first an AlgorithmIdentifier
(which states that this is SHA-1, with no parameters since SHA-1 accepts none), then an OCTET STRING
which contains the actual 20-byte value. In hexadecimal, the value is: dccdb8570286d36c94bba8e5107faee91e0df088
.
This ASN.1 structure is part of the standard RSA signature mechanism. You are using RSA decryption to access that structure, which is non-standard. You are actually lucky to get anything at all, since RSA encryption and RSA signature are two distinct algorithms. It so happens that they both feed on the same kind of key pairs, and that the "old-style" (aka "PKCS#1 v1.5") signature and encryption schemes use similar padding techniques (similar but not identical; it is already a bit surprising that the Java implementation of RSA did not choke on the signature padding when used in decryption mode).
Anyway, 6l26iBH7il/yrCQW6eEfv/VqAVo=
is Base64 encoding for a 20-byte value, which, in hexadecimal, is: ea5dba8811fb8a5ff2ac2416e9e11fbff56a015a
. This is what you get by hashing the XML structure you show above, after having removed all whitespace between tags. Removing all whitespace is not proper canonicalization. Actually, as far as I know, whitespace is affected only between attributes, within the tags, but external whitespace must be kept unchanged (except for line ending normalization [the LF / CR+LF thing]).
The value which was used for the signature generation (the dccdb85...
) can be obtained by using the XML object you show and by removing the leading spaces. To be clear: you copy+paste the XML into a file, then remove the leading spaces (0 to 3 spaces) on each line. You make sure that all end-of-lines use a single LF (0x0A byte) and you remove the final LF (the one just after </ds:SignedInfo>
). The resulting file must have length 930 bytes, and its SHA-1 hash is the expected dccdb85...
value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With