Search code examples
javaencryptionkeydigital-signaturexml-signature

Questions on Java XML Digital Signature


I need to digitally sign an XML document. The requirement given is that the input is an XML file and a private key. The signing should use SHA256/RSA-2048, and the signature should be Enveloping. I have come up with the following method to do just that. On testing, the KeyPair is generated using KeyPairGenerator class with RSA as algorithm and key size set to 2048. The input stream passed is a FileInputStream object and the output stream is a FileOutputStream object. The method seems to work fine.

public void sign(InputStream inputStream, OutputStream outputStream, KeyPair kp) throws Exception {
    XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

    Reference ref = fac.newReference("#object", fac.newDigestMethod(DigestMethod.SHA256, null));

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    org.w3c.dom.Document doc = dbf.newDocumentBuilder().parse(inputStream);
    XMLStructure content = new DOMStructure(doc.getDocumentElement());
    XMLObject obj = fac.newXMLObject(Collections.singletonList(content), "object", null, null);

    SignatureMethod signatureMethod = fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null);
    CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null);

    SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(ref));

    KeyInfoFactory kif = fac.getKeyInfoFactory();
    KeyValue kv = kif.newKeyValue(kp.getPublic());

    KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));

    XMLSignature signature = fac.newXMLSignature(si, ki, Collections.singletonList(obj), null, null);

    DOMSignContext dsc = new DOMSignContext(kp.getPrivate(), doc);

    signature.sign(dsc);

    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer trans = tf.newTransformer();
    trans.transform(new DOMSource(doc), new StreamResult(outputStream));       
}

I have some questions:

  1. I did not specify in the code that RSA should be used in the signing, so how does it know? Also, I understand that in encrypting, there is also mode and padding. Again, these are also not specified, so what would they be?

  2. I don't really know how the canonicalization of the XML work. Both CanonicalizationMethod.INCLUSIVE and CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS produce the same output (both have same DigestValue value). What do these different methods mean?

  3. The enveloped message in the object tag seems to be exactly the same as the input XML file content. Is the enveloped message supposed to be the same as the original XML or is it supposed to be the canonicalized version? I know that the canonicalized version is used in the digest but not sure if the enveloped one should be canonicalized or not. My original XML has comments tag, but the comments tag will always be present in the output, regardless of whether I put INCLUSIVE or INCLUSIVE_WITH_COMMENTS.

  4. Is the public key required at all? The requirement given to me was that the private key is an input, but I am not sure if I should ask about the public key as well. It seems that the public key is required for the KeyValue object.

Thanks in advance.


Solution

  • 1) Signature algorithm is inferred from the URI you are using with newSignatureMethod ("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")

    2) You don't specify any canonicalization algorithm for the Reference in newReference. Hence the default one (Canonical XML without comments) is used before computing the digest value of your XML data. Your canonicalizationMethod is used with SignedInfo: the SignedInfo element will be canonicalized with this algorithm before computing the signature value.

    To specify a canonicalization algorithm for the signed XML data, you need to say it when you are creating your Reference:

    Transform tr = fac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null);
    
    fac.newReference("#object", fac.newDigestMethod(DigestMethod.SHA256, null), Collections.singletonList(tr), null, null);
    

    This code will add a Transform with the canonicalization algorithm in the Reference referencing the #object that will be hashed.

    3) You will have in the Object element the very same content you have added to it. Canonicalization (or any other transform) will be applied on this content before computing the digest value.

    4) KeyValue is Optional and is hint for the verifier of the signature. Usual signatures will have the certificate in an X509Certificate instead of the key value.

    Hope this helps. Moez