/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.xml.security.samples.signature.contract;

import java.io.File;
import java.io.FileOutputStream;

import org.apache.xml.security.keys.content.KeyName;
import org.apache.xml.security.signature.SignedInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.transforms.params.XPathContainer;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Element;


/**
 * In the past the protokol to sign data (like a contract) from more than one people
 * looks like this:
 * 1. A signs the hash of the data => SignatureA
 * 2. B signs SignatureA => SignatureB
 * 3. C signs SignatureB => SignatureC
 *
 * To verify e.g. signature C the following steps were necessary:
 * 1. Verify signature C thereby decrypt SignatureC (SignatureB)
 * 2. Verify signature B thereby decrypt SignatureB (SignatureA)
 * 3. Verify signature A thereby decrypt SignatureA (hash of the data)
 * 4. Compare the calculated hash of the sent contract with the decrypted SignatureA result
 *
 * XML-Signatures are more flexible in this way.
 * It is possible to sign data in steps from different signers and
 * verify a signature independent from the others signatures.
 * Furthermore all the signed data and the signatures can be hold in one file.
 *
 * @author Rene Kollmorgen <Rene.Kollmorgen@softwareag.com>
 */
public class ThreeSignerContractSign {
    
    static {
        org.apache.xml.security.Init.init();

        // org.apache.xml.security.utils.Constants.setSignatureSpecNSprefix("");
    }

    /**
     * Method main
     *
     * @param unused
     * @throws Exception
     */
    public static void main(String unused[]) throws Exception {
        File signatureFile = new File("build/threeSignerContract.xml");
        String BaseURI = signatureFile.toURI().toURL().toString();
        javax.xml.parsers.DocumentBuilderFactory dbf =
            javax.xml.parsers.DocumentBuilderFactory.newInstance();

        dbf.setNamespaceAware(true);

        javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
        org.w3c.dom.Document doc = db.newDocument();
        Element contract = doc.createElementNS(null, "contract");

        // create contract ////////////////////////////////////////////
        doc.appendChild(contract);

        // beautifying //////
        Element condition1 = doc.createElementNS(null, "condition1");

        condition1.setAttributeNS(null, "Id", "cond1");
        condition1.appendChild(
            doc.createTextNode(
                "condition1 not covered in first signature, only "
                + "binding for the second and third signer"));

        Element condition2 = doc.createElementNS(null, "condition2");

        condition2.appendChild(doc.createTextNode("condition2"));

        Element condition3 = doc.createElementNS(null, "condition3");

        condition3.appendChild(doc.createTextNode("condition3"));
        contract.appendChild(doc.createTextNode("\n"));
        contract.appendChild(condition1);
        contract.appendChild(doc.createTextNode("\n"));
        contract.appendChild(condition2);
        contract.appendChild(doc.createTextNode("\n"));
        contract.appendChild(condition3);
        contract.appendChild(doc.createTextNode("\n"));

        String id1 = "firstSigner";
        String id2 = "secondSigner";
        String id3 = "thirdSigner";

        // sign the whole contract and no signature and exclude condition1
        String xp1Old = "not(ancestor-or-self::ds:Signature)"
            + " and not(ancestor-or-self::node()[@Id='cond1'])";

        // sign the contract with condition2 and codition3 and no signature
        String xp1 = "not(ancestor-or-self::ds:Signature)" + "\n"
        + " and (" + "\n"
        + "    (ancestor-or-self::node() = /contract/condition2) " + "\n"
        + " or (ancestor-or-self::node() = /contract/condition3) " + "\n"
        + " or (self::node() = /contract) " + "\n"
        + " or ((parent::node() = /contract) and (self::text()))" + "\n"
        + ")";

        // sign the whole contract and no signature but the first
        String xp2 = "not(ancestor-or-self::ds:Signature)" + "\n"
        + " or ancestor-or-self::ds:Signature[@Id='" + id1 + "']";

        // sign the whole contract and no signature but the first and the second
        String xp3 = "not(ancestor-or-self::ds:Signature)" + "\n"
        + " or ancestor-or-self::ds:Signature[@Id='" + id1 + "']" + "\n"
        + " or ancestor-or-self::ds:Signature[@Id='" + id2 + "']";
        //////////////////////////////////////////////////////////////////
        // first signer //////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////
        {
            XMLSignature firstSigner =
                new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_MAC_HMAC_SHA1);

            firstSigner.setId(id1);
            contract.appendChild(firstSigner.getElement());

            String rootnamespace = contract.getNamespaceURI();
            boolean rootprefixed = (rootnamespace != null) && (rootnamespace.length() > 0);
            String rootlocalname = contract.getNodeName();
            Transforms transforms = new Transforms(doc);
            XPathContainer xpath = new XPathContainer(doc);

            xpath.setXPathNamespaceContext("ds", Constants.SignatureSpecNS);
            xpath.setXPath("\n" + xp1 + "\n");
            transforms.addTransform(Transforms.TRANSFORM_XPATH,
                                    xpath.getElementPlusReturns());
            firstSigner.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);

            {
                // not really secure ///////////////////
                firstSigner.getKeyInfo().add(new KeyName(doc, "First signer key"));

                ////////////////////////////////////////////////
                System.out.println("First signer: Start signing");
                firstSigner.sign(
                    firstSigner.createSecretKey("First signer key".getBytes()));
                System.out.println("First signer: Finished signing");
            }

            SignedInfo s = firstSigner.getSignedInfo();

            for (int i = 0; i < s.getSignedContentLength(); i++) {
                System.out.println("################ Signed Resource " + i
                                   + " ################");
                System.out.println(new String(s.getSignedContentItem(i)));
                System.out.println();
            }
        }

        //////////////////////////////////////////////////////////////////
        // second signer /////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////
        {
            XMLSignature secondSigner = 
                new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_MAC_HMAC_SHA1);

            secondSigner.setId(id2);
            contract.appendChild(secondSigner.getElement());

            Transforms transforms2 = new Transforms(doc);
            XPathContainer xpath2 = new XPathContainer(doc);

            xpath2.setXPathNamespaceContext("ds", Constants.SignatureSpecNS);
            xpath2.setXPath("\n" + xp2 + "\n");
            transforms2.addTransform(Transforms.TRANSFORM_XPATH,
                                     xpath2.getElementPlusReturns());
            secondSigner.addDocument("", transforms2, Constants.ALGO_ID_DIGEST_SHA1);

            {
                secondSigner.getKeyInfo().add(new KeyName(doc,
                "Second signer key"));
                System.out.println("Second signer: Start signing");
                secondSigner.sign(
                    secondSigner.createSecretKey("Second signer key".getBytes()));
                System.out.println("Second signer: Finished signing");
            }

            SignedInfo s2 = secondSigner.getSignedInfo();

            for (int i = 0; i < s2.getSignedContentLength(); i++) {
                System.out.println("################ Signed Resource " + i
                                   + " ################");
                System.out.println(new String(s2.getSignedContentItem(i)));
                System.out.println();
            }
        }

        //////////////////////////////////////////////////////////////////
        // third signer //////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////
        {
            XMLSignature thirdSigner =
                new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_MAC_HMAC_SHA1);

            thirdSigner.setId(id3);
            contract.appendChild(thirdSigner.getElement());

            Transforms transforms3 = new Transforms(doc);
            XPathContainer xpath3 = new XPathContainer(doc);

            xpath3.setXPathNamespaceContext("ds", Constants.SignatureSpecNS);
            xpath3.setXPath("\n" + xp3 + "\n");
            transforms3.addTransform(Transforms.TRANSFORM_XPATH,
                                     xpath3.getElementPlusReturns());
            thirdSigner.addDocument("", transforms3, Constants.ALGO_ID_DIGEST_SHA1);

            {
                thirdSigner.getKeyInfo().add(new KeyName(doc, "Third signer key"));
                System.out.println("Third signer: Start signing");
                thirdSigner.sign(
                    thirdSigner.createSecretKey("Third signer key".getBytes()));
                System.out.println("Third signer: Finished signing");
            }

            SignedInfo s3 = thirdSigner.getSignedInfo();

            for (int i = 0; i < s3.getSignedContentLength(); i++) {
                System.out.println("################ Signed Resource " + i
                                   + " ################");
                System.out.println(new String(s3.getSignedContentItem(i)));
                System.out.println();
            }
        }

        //////////////////////////////////////////////////////////////////
        // forth signer //////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////
        {
            XMLSignature forthSigner =
                new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_MAC_HMAC_SHA1);

            forthSigner.setId("sig4");
            contract.appendChild(forthSigner.getElement());

            {
                // first of all, add the basic document without signatures
                Transforms transforms4 = new Transforms(doc);
                XPathContainer xpath4 = new XPathContainer(doc);

                xpath4.setXPathNamespaceContext("ds", Constants.SignatureSpecNS);
                xpath4.setXPath("\n" + "not(ancestor-or-self::ds:Signature)"
                                + "\n");
                transforms4.addTransform(Transforms.TRANSFORM_XPATH,
                                         xpath4.getElementPlusReturns());
                forthSigner.addDocument("", transforms4,
                                        Constants.ALGO_ID_DIGEST_SHA1);
            }

            {
                // then add the different signatures
                forthSigner.addDocument("#xpointer(id('firstSigner'))", null,
                                        Constants.ALGO_ID_DIGEST_SHA1, null,
                "ds:Signature");
            }

            {
                forthSigner.getKeyInfo().add(new KeyName(doc, "Forth signer key"));
                System.out.println("Forth signer: Start signing");
                forthSigner.sign(
                    forthSigner.createSecretKey("Forth signer key".getBytes()));
                System.out.println("Forth signer: Finished signing");
            }

            SignedInfo s4 = forthSigner.getSignedInfo();

            for (int i = 0; i < s4.getSignedContentLength(); i++) {
                System.out.println("################ Signed Resource " + i
                                   + " ################");
                System.out.println(new String(s4.getSignedContentItem(i)));
                System.out.println();
            }
        }

        //////////////////////////////////////////////////////////////////
        // write away files
        //////////////////////////////////////////////////////////////////
        {
            FileOutputStream f = new FileOutputStream(signatureFile);

            XMLUtils.outputDOMc14nWithComments(doc, f);
            f.close();
            System.out.println("Wrote signature to " + BaseURI);
        }
    }

}
