Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Sign Just a part of XML File - Java XMLSignature

I'm newbie and I'm in a hurry. I'm just trying to digital sign a part of an XML.

The XML to sign is like this:

    <?xml version="1.0" encoding="UTF-8"?><ns0:CEE_Adenda xmlns:ns0="http://adenda.es" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://adenda.es Test_v1.xsd">
<ns0:CEE version="1.0" xmlns:ns0="http://adenda.es">//XML to sing
............ 
</ns0:CEE>
<ns0:Adenda> //Part to exclude
......
</ns0:Adenda>
</ns0:CEE_Adenda>

, in order to get an structure like this:

        <?xml version="1.0" encoding="UTF-8"?><ns0:CEE_Adenda xmlns:ns0="http://adenda.es" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://adenda.es Test_v1.xsd">
    <ns0:CEE version="1.0" xmlns:ns0="http://adenda.es">//XML to sing
    ............ 
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    <Reference URI="">
        <Transforms>
            <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <DigestValue>vMg+tzKiwC8epApusLGo23at0ss=</DigestValue>
    </Reference>
</SignedInfo>
<SignatureValue>dVqqHp437r7jAeEOB6mxgSOKnpT6EITRscd0mzA/zDep3Wkg1CM/m0ojDHnlkC7l
    </ns0:CEE>
    <ns0:Adenda> //Part to exclude
    ......
    </ns0:Adenda>
    </ns0:CEE_Adenda>

This is the code I'm using to try to sign it, but I need to sign only the part of XML and put the result inside this tag.

This is the code I'm using:

//Create a DOM XMLSignatureFactory that will be used to
        // generate the enveloped signature.
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        // Create a Reference to the enveloped document (in this case,
        // you are signing the whole document, so a URI of "" signifies
        // that, and also specify the SHA1 digest algorithm and
        // the ENVELOPED Transform.
        try {
            List<XPathType> xpaths = new ArrayList<XPathType>();
            xpaths.add(new XPathType("//ns0:CFE", XPathType.Filter.INTERSECT));
            Reference ref = fac.newReference("", 
                                             fac.newDigestMethod(DigestMethod.SHA1, null),
                                             Collections.singletonList(fac.newTransform(Transform.ENVELOPED, 
                                                                                        (TransformParameterSpec) null)),null,null);
            //ori
            //Reference ref = fac.newReference("", fac.newDigestMethod(DigestMethod.SHA1, null));
            // Create the SignedInfo
            SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, 
                                                                            (C14NMethodParameterSpec)null), 
                                              fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), 
                                              Collections.singletonList(ref));

            // Load the KeyStore and get the signing key and certificate.
            String p12Password = clave;
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(new FileInputStream(keyStoreName), p12Password.toCharArray());
            KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)ks.getEntry(alias, 
                                                                                      new KeyStore.PasswordProtection(p12Password.toCharArray()));
            X509Certificate cert = (X509Certificate)keyEntry.getCertificate();

            // Create the KeyInfo containing the X509Data.
            KeyInfoFactory kif = fac.getKeyInfoFactory();
            List<Serializable> x509Content = new ArrayList<Serializable>();
            x509Content.add(cert.getSubjectX500Principal().getName());
            x509Content.add(cert);
            X509Data xd = kif.newX509Data(x509Content);
            javax.xml.crypto.dsig.keyinfo.KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

            // Instantiate the document to be signed.
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(xmlEntrada));
            // Create a DOMSignContext and specify the RSA PrivateKey and
            // location of the resulting XMLSignature's parent element.
            DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());
            // 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);

            // Output the resulting document.
            OutputStream os = new FileOutputStream(xmlSalida);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer trans = tf.newTransformer();
            trans.transform(new DOMSource(doc), new StreamResult(os));

Any help will be very appreciated.

Regards

like image 539
user1748166 Avatar asked Mar 14 '23 03:03

user1748166


1 Answers

It's probably too late for you but if it can help anybody, here is the code I finally used to sign a SAML Assertion where the signature must be applied only on a part of my XML Document. My first tests was done with org.apache.xml.security but I did not find how to sign just a part of the my XML DOM. Finally I found an other way to sign XML using the Java XML Digital Signature API: http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html

Please note that I'm not a Java Developer. So the code here-under can surely be improved.

import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/*
 * This Class contains method to sign SAML assertion.
 */
public class SAMLSigner {

    // Sign an Assertion using the Private Key & Public Certificate from a KeyStore.
    public static Document signAssertion(Document doc, SAMLKeyStore samlKeyStore) throws Exception {

        // Instance main XML Signature Toolkit.
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
        XPathFactory xPathfactory = XPathFactory.newInstance();

        // Retreive PrivateKey and Public Certificate from Specified KeyStore.
        PrivateKey privateKey = samlKeyStore.getPrivateKey();
        X509Certificate publicCertificate = samlKeyStore.getPublicCertificate();

        // Retreive Assertion Node to be signed.
        XPath xpath = xPathfactory.newXPath();
        XPathExpression exprAssertion = xpath.compile("//*[local-name()='Response']//*[local-name()='Assertion']");
        Element assertionNode = (Element) exprAssertion.evaluate(doc, XPathConstants.NODE);        
        // Must mark ID Atrribute as XML ID to avoid BUG in Java 1.7.25.
        assertionNode.setIdAttribute("ID", true);

        // Retreive Assertion ID because it is used in the URI attribute of the signature.
        XPathExpression exprAssertionID = xpath.compile("//*[local-name()='Response']//*[local-name()='Assertion']//@ID");
        String assertionID = (String) exprAssertionID.evaluate(doc, XPathConstants.STRING);

        // Retreive Subject Node because the signature will be inserted before.
        XPathExpression exprAssertionSubject = xpath.compile("//*[local-name()='Response']//*[local-name()='Assertion']//*[local-name()='Subject']");
        Node insertionNode = (Node) exprAssertionSubject.evaluate(doc, XPathConstants.NODE);        

        // Create the DOMSignContext by specifying the signing informations: Private Key, Node to be signed, Where to insert the Signature.
        DOMSignContext dsc = new DOMSignContext(privateKey, assertionNode, insertionNode);
        dsc.setDefaultNamespacePrefix("ds");

        // Create a CanonicalizationMethod which specify how the XML will be canonicalized before signed.
        CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null);
        // Create a SignatureMethod which specify how the XML will be signed.
        SignatureMethod signatureMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);

        // Create an Array of Transform, add it one Transform which specify the Signature ENVELOPED method.         
        List<Transform> transformList = new ArrayList<Transform>(1);
        transformList.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));  

        // Create a Reference which contain: An URI to the Assertion ID, the Digest Method and the Transform List which specify the Signature ENVELOPED method.
        Reference reference = fac.newReference("#" + assertionID, fac.newDigestMethod(DigestMethod.SHA1, null), transformList, null, null);        
        List<Reference> referenceList = Collections.singletonList(reference);                
        // Create a SignedInfo with the pre-specified: Canonicalization Method, Signature Method and List of References.
        SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethod, referenceList);

        // Create a new KeyInfo and add it the Public Certificate.
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        List x509Content = new ArrayList();
        x509Content.add(publicCertificate);
        X509Data xd = kif.newX509Data(x509Content);
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

        // Create a new XML Signature with the pre-created : Signed Info & Key Info
        XMLSignature signature = fac.newXMLSignature(si, ki);
        signature.sign(dsc);

        // Return the Signed Assertion.
        return doc;        
    }
}

My XML before signing.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://tmoobiee11gv9.kitrybe.dyndns.org:9704/saml2/sp/acs/post" ID="_0f3f5675-7a3a-4659-be6d-d3cdb0923fc2" IssueInstant="2016-10-19T12:43:40Z" Version="2.0">
    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">MyIssuer</saml:Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_9990f3f5675-7a3a-4659-be6d-d3cdb0923fc2" IssueInstant="2016-10-19T12:43:40Z" Version="2.0">
        <saml:Issuer>MyIssuer</saml:Issuer>
        <saml:Subject>
            <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">MyUser</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2016-10-19T12:53:40Z" Recipient="http://tmoobiee11gv9.kitrybe.dyndns.org:9704/saml2/sp/acs/post"/>
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2016-10-19T12:33:40Z" NotOnOrAfter="2016-10-19T12:53:40Z">
            <saml:AudienceRestriction>
                <saml:Audience>MyAudience</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2016-10-19T12:43:40Z">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
    </saml:Assertion>
</samlp:Response>

Here is the XML after signing.

>     <?xml version="1.0" encoding="UTF-8" standalone="no"?>
>     <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Destination="http://tmoobiee11gv9.kitrybe.dyndns.org:9704/saml2/sp/acs/post"
> ID="_0f3f5675-7a3a-4659-be6d-d3cdb0923fc2"
> IssueInstant="2016-10-19T12:43:40Z" Version="2.0">
>       <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">MyIssuer</saml:Issuer>
>       <samlp:Status>
>           <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
>       </samlp:Status>
>       <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
> ID="_9990f3f5675-7a3a-4659-be6d-d3cdb0923fc2"
> IssueInstant="2016-10-19T12:43:40Z" Version="2.0">
>           <saml:Issuer>MyIssuer</saml:Issuer>
>           <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
>               <ds:SignedInfo>
>                   <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
>                   <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
>                   <ds:Reference URI="#_9990f3f5675-7a3a-4659-be6d-d3cdb0923fc2">
>                       <ds:Transforms>
>                           <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
>                       </ds:Transforms>
>                       <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
>                       <ds:DigestValue>8uL7NzYK...</ds:DigestValue>
>                   </ds:Reference>
>               </ds:SignedInfo>
>               <ds:SignatureValue>BRsAX3XPvFcDw1...</ds:SignatureValue>
>               <ds:KeyInfo>
>                   <ds:X509Data>
>                       <ds:X509Certificate>MIICvzCCAae...</ds:X509Certificate>
>                   </ds:X509Data>
>               </ds:KeyInfo>
>           </ds:Signature>
>           <saml:Subject>
>               <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">MyUser</saml:NameID>
>               <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
>                   <saml:SubjectConfirmationData NotOnOrAfter="2016-10-19T12:53:40Z"
> Recipient="http://tmoobiee11gv9.kitrybe.dyndns.org:9704/saml2/sp/acs/post"/>
>               </saml:SubjectConfirmation>
>           </saml:Subject>
>           <saml:Conditions NotBefore="2016-10-19T12:33:40Z" NotOnOrAfter="2016-10-19T12:53:40Z">
>               <saml:AudienceRestriction>
>                   <saml:Audience>MyAudience</saml:Audience>
>               </saml:AudienceRestriction>
>           </saml:Conditions>
>           <saml:AuthnStatement AuthnInstant="2016-10-19T12:43:40Z">
>               <saml:AuthnContext>
>                   <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef>
>               </saml:AuthnContext>
>           </saml:AuthnStatement>
>       </saml:Assertion>
>     </samlp:Response>

The signature only applied on Assertion Tag and is placed before the Subject Tag.

Hope it will help.

like image 92
Tim Avatar answered Mar 29 '23 13:03

Tim