/***********************************************************************
 *
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 *
 *  To contact Novell about this file by physical or electronic mail,
 *  you may find current contact information at www.novell.com.
 *
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/

//package com.novell.casa.authtoksvc;

package com.novell.casa.authtoksvc;

import java.io.*;

import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.keys.content.x509.XMLX509IssuerSerial;
import org.apache.xml.security.keys.content.X509Data;
import org.apache.xerces.parsers.DOMParser;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.apache.xml.security.utils.RFC2253Parser;

import java.util.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;


/**
 * SecureTokenUtil Class.
 * <p>
 * This class provides methods for creating secure tokens. Tokens are secured in SOAP messages
 * by applying a timestamp and signing the appropriate elements. The class utilizes XMlSec and
 * headers defined by WS* specifications.
 *
 */
public final class SecureTokenUtil
{
   private static final Logger m_log = Logger.getLogger(SecureTokenUtil.class);

   // Signing key and related certificate
   private PrivateKey m_signingKey;
   private X509Certificate m_signingCert;

   // Certificate issuer:sn map
   //
   // The map key has the format: "IssuerDN=certissuername SN=certserialnumber"
   private Map<String,X509Certificate> m_x509ISNCertMap;
   private Date m_x509ISNCertMapRefreshDate;
   private int m_x509ISNCertMapRefreshInterval = 360; // seconds

   // SecureToken template
   private static final String m_secureTokenTemplate =
           "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\"" +
           " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"" +
           " xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"" +
           " xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"" +
           " xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">" +
           "<SOAP-ENV:Header>" +
           "<wsse:Security SOAP-ENV:mustUnderstand=\"true\">" +
           "<wsu:Timestamp wsu:Id=\"CASA-TIMESTAMP\">" +
           "<wsu:Created></wsu:Created>" +
           "<wsu:Expires></wsu:Expires>" +
           "</wsu:Timestamp>" +
           "</wsse:Security>" +
           "</SOAP-ENV:Header>" +
           "<SOAP-ENV:Body wsu:Id=\"CASA-SOAPBODY\">" +
           "</SOAP-ENV:Body>" +
           "</SOAP-ENV:Envelope>";

   /**
    * SecureTokenUtil Constructor.
    * <p/>
    * @param serverMode    Running as server and will be creating secure tokens.
    * @throws Exception
    */
   public SecureTokenUtil(boolean serverMode) throws Exception
   {
      // Start by creating the trusted ATS Cert Map
      m_x509ISNCertMap = createTrustedAtsCertMap();
      m_x509ISNCertMapRefreshDate = new Date();
      m_x509ISNCertMapRefreshDate.setTime(m_x509ISNCertMapRefreshDate.getTime() + (m_x509ISNCertMapRefreshInterval * 1000));

      // Obtain the signing key and certificate if we are in server mode
      if (serverMode)
      {
         InputStream inStream = null;
         try
         {
            // Load our crypto properties
            Properties cryptoProperties = new Properties();
            ClassLoader classLoader = SecureTokenUtil.class.getClassLoader();
            inStream = classLoader.getResourceAsStream("casa_crypto.properties");
            cryptoProperties.load(inStream);

            // Get necessary keystore info from the crypto properties
            String keystoreType = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.type", "jks");
            String keystoreFile = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.file");
            String keystorePass = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.password");
            if (keystoreType == null
                || keystoreFile == null
                || keystorePass == null)
            {
               m_log.error("Constructor()- Missing crypto configuration");
               throw new Exception("SecureTokenUtil()- Missing crypto configuration");
            }

            // Instantiate and load the keystore
            KeyStore keyStore = KeyStore.getInstance(keystoreType);
            FileInputStream fis = new FileInputStream(keystoreFile);
            keyStore.load(fis, keystorePass.toCharArray());

            // Get signing key and cert if in server mode
            if (serverMode)
            {
               String privateKeyAlias = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.alias");
               String privateKeyPass = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.alias.password");
               String certificateAlias = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.alias");
               if (privateKeyAlias == null
                   || privateKeyPass == null
                   || certificateAlias == null)
               {
                  m_log.error("Constructor()- Missing crypto configuration");
                  throw new Exception("SecureTokenUtil()- Missing crypto configuration");
               }

               // Get the key that will be used for signing tokens
               m_signingKey = (PrivateKey) keyStore.getKey(privateKeyAlias,
                                                           privateKeyPass.toCharArray());
               if (m_signingKey == null)
               {
                  m_log.error("Constructor()- Signing key not found in keystore");
                  throw new Exception("SecureTokenUtil()- Signing key not found in keystore");
               }

               // Get the signing certificate
               m_signingCert = (X509Certificate) keyStore.getCertificate(certificateAlias);
               if (m_signingCert == null)
               {
                  m_log.error("Constructor()- Signing cert not found in keystore");
                  throw new Exception("SecureTokenUtil()- Signing cert not found in keystore");
               }
            }
         }
         finally
         {
            // Make sure that the input stream has been closed
            if (inStream != null)
               inStream.close();
         }
      }
   }

   /**
    * Static Create Trusted ATS Certificate Map.
    * <p/>
    * @return Trusted ATS Certificate Map
    * @throws Exception
    */
   private static Map<String,X509Certificate> createTrustedAtsCertMap() throws Exception
   {
      Map<String,X509Certificate> x509ISNCertMap;
      InputStream inStream = null;
      FileInputStream fis = null;
      try
      {
         // Load our crypto properties
         Properties cryptoProperties = new Properties();
         ClassLoader classLoader = SecureTokenUtil.class.getClassLoader();
         inStream = classLoader.getResourceAsStream("casa_crypto.properties");
         cryptoProperties.load(inStream);

         // Get necessary keystore info from the crypto properties
         String keystoreType = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.keystore.type", "jks");
         String keystoreFile = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.trusted_ats_keystore.file");
         String keystorePass = cryptoProperties.getProperty("com.novell.casa.authtoksvc.crypto.trusted_ats_keystore.password");
         if (keystoreType == null
             || keystoreFile == null
             || keystorePass == null)
         {
            m_log.error("SecureTokenUtil.createTrustedAtsCertMap()- Missing crypto configuration");
            throw new Exception("SecureTokenUtil.createTrustedAtsCertMap()- Missing crypto configuration");
         }

         // Instantiate and load the keystore
         KeyStore keyStore = KeyStore.getInstance(keystoreType);
         fis = new FileInputStream(keystoreFile);
         keyStore.load(fis, keystorePass.toCharArray());

         // Create the Certificate issuer:sn map
         x509ISNCertMap = new HashMap<String,X509Certificate>();
         Enumeration<String> aliases = keyStore.aliases();
         while (aliases.hasMoreElements())
         {
            X509Certificate cert = (X509Certificate) keyStore.getCertificate(aliases.nextElement());
            if (cert != null)
            {
               // Add this certificate to our map
               x509ISNCertMap.put("IssuerDN=" + RFC2253Parser.normalize(cert.getIssuerDN().getName()) + " SN=" + cert.getSerialNumber().toString(), cert);
            }
         }
      }
      finally
      {
         // Make sure that the streams have been closed
         if (inStream != null)
            inStream.close();

         if (fis != null)
            fis.close();
      }

      return x509ISNCertMap;
   }

   /**
    * Check Trusted ATS Certificate Map.
    */
   private synchronized Map<String,X509Certificate> checkTrustedAtsCertMap()
   {
      // Check if we need to refresh the trusted ATS Cert map
      Date currDate = new Date();
      if (currDate.compareTo(m_x509ISNCertMapRefreshDate) > 0)
      {
         // It is time to update the trusted ATS certificate map
         try
         {
            // Set up the next refresh date
            m_x509ISNCertMapRefreshDate.setTime(currDate.getTime() + (m_x509ISNCertMapRefreshInterval * 1000));

            // Re-create the trusted ATS certificate map
            Map<String,X509Certificate> newX509ISNCertMap = createTrustedAtsCertMap();
            m_x509ISNCertMap = newX509ISNCertMap;
         }
         catch (Exception e)
         {
            m_log.error("SecureTokenUtil.checkTrustedAtsCertMap()- Exception caught, msg = " + e.getMessage());
         }
      }

      return m_x509ISNCertMap;
   }

   /**
    * Returns the referenced X509Certificate.
    *
    * @param issuerName    Name of the certificate issuer.
    * @param serialNumber  Certificate serial number.
    * @return              Referenced X509Certificate.
    */
   private X509Certificate getCertWithX509IssuerSerialData(String issuerName, String serialNumber)
   {
      Map<String,X509Certificate> x509ISNCertMap = checkTrustedAtsCertMap();
      return x509ISNCertMap.get("IssuerDN=" + issuerName + " SN=" + serialNumber);
   }

   /**
    * Remove white space from XML.
    * <p/>
    * Note that this operation is recursively performed,
    * thus limiting document depth.
    *
    * @param startNode     Start node.
    */
   private void removeWhiteSpace(Node startNode)
   {
      // Loop through child nodes
      Node childNode;
      Node nextNode = startNode.getFirstChild();
      while ((childNode = nextNode) != null) {

         // Set next before we change anything
         nextNode = childNode.getNextSibling();

         // Handle child by node type
         if (childNode.getNodeType() == Node.TEXT_NODE)
         {
            // Trim whitespace from content text
            String trimmed = childNode.getNodeValue().trim();
            if (trimmed.length() == 0)
            {
               // Delete child if nothing but whitespace
               startNode.removeChild(childNode);
            }
         }
         else if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            // Handle child elements with recursive call
            removeWhiteSpace(childNode);
         }
      }
   }

   /**
    * Set the specified prefix on elements in the specified namespace
    * and remove the namespace attribute from them.
    * <p/>
    * Note that this operation is recursively performed,
    * thus limiting document depth.
    *
    * @param startNode     Start node.
    * @param prefix        Prefix.
    * @param nsURI         Name space URI.
    */
   private void setPrefixSubtree(Node startNode, String prefix, String nsURI)
   {
      // Set the prefix on the element if necessary
      if (startNode.getNodeType() == Node.ELEMENT_NODE)
      {
         NamedNodeMap attributes = startNode.getAttributes();
         Node nsAttr = attributes.getNamedItem("xmlns");
         if (nsAttr != null)
         {
            if (nsAttr.getTextContent().equalsIgnoreCase(nsURI))
            {
               attributes.removeNamedItem("xmlns");
               startNode.setPrefix(prefix);
            }
         }
      }

      // Loop through child nodes
      Node childNode;
      Node nextNode = startNode.getFirstChild();
      while ((childNode = nextNode) != null) {

         // Set next before we change anything
         nextNode = childNode.getNextSibling();

         // Handle child by node type
         if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            // Handle child elements with recursive call
            setPrefixSubtree(childNode, prefix, nsURI);
         }
      }
   }

   /**
    * Find first child node with matching node name.
    *
    * @param parentNode    Parent node.
    * @param nodeName      Name of node.
    * @return              Child node found or null.
    */
   public static Node findChildNode(Node parentNode, String nodeName)
   {
      Node retNode = null;
      Node childNode;
      Node nextNode = parentNode.getFirstChild();
      while ((childNode = nextNode) != null)
      {
         // Set next before we change anything
         nextNode = childNode.getNextSibling();

         // Handle child by node type
         if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            // Check if this is the element node wanted
            if (childNode.getNodeName().equalsIgnoreCase(nodeName))
            {
               // Found the node
               retNode = childNode;
               break;
            }
         }
      }

      return retNode;
   }

   /**
    * Find first child node with matching node name.
    *
    * @param parentNode    Parent node.
    * @param nsURI         Namespace URI.
    * @param nodeLocalName Local name of node.
    * @return              Child node found or null.
    */
   public static Node findChildNodeNS(Node parentNode, String nsURI, String nodeLocalName)
   {
      Node retNode = null;
      Node childNode;
      Node next = parentNode.getFirstChild();
      while ((childNode = next) != null)
      {
         // Set next before we change anything
         next = childNode.getNextSibling();

         // Handle child by node type
         if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            // Check if this is the element node wanted
            if (childNode.getLocalName().equalsIgnoreCase(nodeLocalName)
                && childNode.getNamespaceURI().equals(nsURI))
            {
               // Found the node
               retNode = childNode;
               break;
            }
         }
      }

      return retNode;
   }

   /**
    * Sets the timestamp element.
    *
    * @param timeStampNode Timestamp node.
    * @param ttl           Time to live in seconds.
    * @throws Exception
    */
   private void setTimeStamp(Node timeStampNode, long ttl) throws Exception
   {
      boolean success = false;

      // Find the Created and Expires nodes
      Node wsCreated = findChildNode(timeStampNode, "wsu:Created");
      Node wsExpires = findChildNode(timeStampNode, "wsu:Expires");
      if (wsCreated != null
          && wsExpires != null)
      {
         // We found the nodes, now set the needed information in them.
         SimpleDateFormat ISO8601UTC = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
         ISO8601UTC.setTimeZone(TimeZone.getTimeZone("UTC"));

         Date date = new Date();

         wsCreated.setTextContent(ISO8601UTC.format(date));

         date.setTime(date.getTime() + (ttl * 1000));
         wsExpires.setTextContent(ISO8601UTC.format(date));

         success = true;
      }

      // Throw exception if not successful
      if (!success)
      {
         m_log.error("setTimeStamp()- Failed to set timestamp");
         throw new Exception("SecureTokenUtil.setTimeStamp()- Failed to set timestamp");
      }
   }

   /**
    * Validates timestamp element.
    *
    * @param timeStampNode Timestamp node.
    * @throws Exception
    */
   private void validateTimeStamp(Node timeStampNode) throws Exception
   {
      // Find Expires node
      Node wsExpires = findChildNodeNS(timeStampNode,
                                       "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
                                       "Expires");
      if (wsExpires != null)
      {
         // We found the node, now check if it has expired.
         SimpleDateFormat ISO8601UTC = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
         ISO8601UTC.setTimeZone(TimeZone.getTimeZone("UTC"));

         Date currDate = new Date();
         Date expiresDate = ISO8601UTC.parse(wsExpires.getTextContent());
         if (currDate.compareTo(expiresDate) > 0)
         {
            m_log.warn("validateTimeStamp()- Timestamp expired");
            throw new Exception("SecureTokenUtil.validateTimeStamp()- Timestamp expired");
         }
      }
      else
      {
         m_log.error("validateTimeStamp()- Bad timestamp element");
         throw new Exception("SecureTokenUtil.validateTimeStamp()- Bad timestamp element");
      }
   }

   /**
    * Validates secure token.
    *
    * @param tokenDoc   Token document.
    * @return           True if validation succeeded.
    */
   public boolean valid(Document tokenDoc)
   {
      boolean msgVerificationStatus = false;

      try
      {
         // Get document element
         Element docElement = tokenDoc.getDocumentElement();

         // Find the relevant nodes in the document
         Node soapHeaderNode = findChildNodeNS(docElement, "http://www.w3.org/2003/05/soap-envelope", "Header");
         Node soapBodyNode = findChildNodeNS(docElement, "http://www.w3.org/2003/05/soap-envelope", "Body");
         if (soapHeaderNode != null && soapBodyNode != null)
         {
            // We found the header of the message, now find the
            // WS-Security node.
            Node wsSecurityNode = findChildNodeNS(soapHeaderNode,
                                                  "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
                                                  "Security");
            if (wsSecurityNode != null)
            {
               // We found the WS-Security node, now find the
               // Signature and Timestamp nodes.
               Node signatureNode = findChildNodeNS(wsSecurityNode,
                                                    "http://www.w3.org/2000/09/xmldsig#",
                                                    "Signature");
               Node timeStampNode = findChildNodeNS(wsSecurityNode,
                                                    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
                                                    "Timestamp");
               if (signatureNode != null && timeStampNode != null)
               {
                  // Validate the timestamp before doing any other processing
                  validateTimeStamp(timeStampNode);

                  Node signedInfoNode = findChildNodeNS(signatureNode,
                                                        "http://www.w3.org/2000/09/xmldsig#",
                                                        "SignedInfo");
                  Node keyInfoNode = findChildNodeNS(signatureNode,
                                                     "http://www.w3.org/2000/09/xmldsig#",
                                                     "KeyInfo");
                  if (signedInfoNode != null && keyInfoNode != null)
                  {
                     // Make sure that we are referencing the Timestamp and Soap Body nodes in the signature
                     boolean timeStampReferenced = false;
                     boolean soapBodyReferenced = false;
                     NamedNodeMap attributes = timeStampNode.getAttributes();
                     Node timeStampIdNode = attributes.getNamedItemNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
                                                                      "Id");
                     attributes = soapBodyNode.getAttributes();
                     Node soapBodyIdNode = attributes.getNamedItemNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
                                                                     "Id");
                     if (timeStampIdNode != null && soapBodyIdNode != null)
                     {
                        Node child;
                        Node next = signedInfoNode.getFirstChild();
                        while ((child = next) != null)
                        {
                           // Set next
                           next = child.getNextSibling();

                           // Check if we are dealinf with a reference node
                           if (child.getNodeType() == Node.ELEMENT_NODE
                               && child.getLocalName().equalsIgnoreCase("Reference")
                               && child.getNamespaceURI().equals("http://www.w3.org/2000/09/xmldsig#"))
                           {
                              // Found a reference node, get its attributes.
                              attributes = child.getAttributes();
                              Node refUriNode = attributes.getNamedItem("URI");
                              if (refUriNode != null)
                              {
                                 if (refUriNode.getTextContent().equals("#" + timeStampIdNode.getTextContent()))
                                 {
                                    timeStampReferenced = true;
                                 }
                                 else if (refUriNode.getTextContent().equals("#" + soapBodyIdNode.getTextContent()))
                                 {
                                    soapBodyReferenced = true;
                                 }
                              }
                           }
                        }
                     }

                     // Proceed if both the Timestamp and the Soap Body are referenced in the signature
                     if (timeStampReferenced && soapBodyReferenced)
                     {
                        Node secTokenRefNode = findChildNodeNS(keyInfoNode,
                                                               "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
                                                               "SecurityTokenReference");
                        if (secTokenRefNode != null)
                        {
                           Node x509DataNode = findChildNodeNS(secTokenRefNode,
                                                               "http://www.w3.org/2000/09/xmldsig#",
                                                               "X509Data");
                           if (x509DataNode != null)
                           {
                              Node x509IssuerSerialNode = findChildNodeNS(x509DataNode,
                                                                          "http://www.w3.org/2000/09/xmldsig#",
                                                                          "X509IssuerSerial");
                              if (x509IssuerSerialNode != null)
                              {
                                 Element x509IssuerNameNode = (Element) findChildNodeNS(x509IssuerSerialNode,
                                                                                        "http://www.w3.org/2000/09/xmldsig#",
                                                                                        "X509IssuerName");
                                 Element x509SerialNumberNode = (Element) findChildNodeNS(x509IssuerSerialNode,
                                                                                          "http://www.w3.org/2000/09/xmldsig#",
                                                                                          "X509SerialNumber");
                                 if (x509IssuerNameNode != null && x509SerialNumberNode != null)
                                 {
                                    // Find the certificate in our KeyStore
                                    X509Certificate cert = getCertWithX509IssuerSerialData(x509IssuerNameNode.getTextContent(),
                                                                                           x509SerialNumberNode.getTextContent());

                                    // Verify signature that we found the referenced certificate
                                    if (cert != null)
                                    {
                                       // Verify the signature
                                       XMLSignature sig = new XMLSignature((Element) signatureNode, "");
                                       boolean signatureOk = sig.checkSignatureValue(cert);
                                       if (signatureOk)
                                       {
                                          // Success
                                          msgVerificationStatus = true;
                                       }
                                       else
                                       {
                                          m_log.warn("valid()- Signature verification failed");
                                       }
                                    }
                                    else
                                    {
                                       m_log.warn("valid()- Referenced certificate not found");
                                    }
                                 }
                                 else
                                 {
                                    m_log.warn("valid()- Not able to find X509IssuerSerial node child");
                                 }
                              }
                              else
                              {
                                 m_log.warn("valid()- Not able to find X509IssuerSerial node");
                              }
                           }
                           else
                           {
                              m_log.warn("valid()- Not able to find X509Data node");
                           }
                        }
                        else
                        {
                           m_log.warn("valid()- Not able to find SecurityTokenReference node");
                        }
                     }
                     else
                     {
                        m_log.warn("valid()- Signature does not cover necessary elements");
                     }
                  }
                  else
                  {
                     m_log.warn("valid()- Not able to find Signature node child");
                  }
               }
               else
               {
                  m_log.warn("valid()- Not able to find Security node child");
               }
            }
            else
            {
               m_log.warn("valid()- Not able to find Security node");
            }
         }
         else
         {
            m_log.warn("valid()- Not able to find SOAP Header node");
         }
      }
      catch (Exception e)
      {
         m_log.warn("valid()- Exception caught, msg = " + e.getMessage());
         e.printStackTrace();
      }

      return msgVerificationStatus;
   }

   /**
    * Returns a secure token template document.
    *
    * @return           Secure token template document.
    */
   public static Document getSecureTokenTemplate()
   {
      Document tokenDoc = null;

      try
      {
         InputStream inStream = null;
         try
         {
            // Prevent the xml namespace for xmldsig from being
            // specified multiple times.
            org.apache.xml.security.utils.Constants.setSignatureSpecNSprefix("");

            // Get document from our template
            inStream = new ByteArrayInputStream(m_secureTokenTemplate.getBytes());

            DOMParser parser = new DOMParser();
            parser.setFeature("http://xml.org/sax/features/namespaces", true);
            parser.parse(new InputSource(inStream));
            tokenDoc = parser.getDocument();
         }
         finally
         {
            // Close input stream if necessary
            if (inStream != null)
               inStream.close();
         }
      }
      catch (Exception e)
      {
         m_log.error("getSecureTokenTemplate()- Exception caught, msg = " + e.getMessage());
         e.printStackTrace();
      }

      // Return token if successful
      return tokenDoc;
   }

   /**
    * Secure token.
    *
    * @param tokenDoc   Token document.
    * @param timeToLive Token time to live in seconds.
    * @throws Exception
    */
   public void secure(Document tokenDoc,
                      int timeToLive) throws Exception
   {
      try
      {
         // Find all of the relevant nodes in the document
         Element tokenElement = tokenDoc.getDocumentElement();
         Node soapHeaderNode = findChildNode(tokenElement, "SOAP-ENV:Header");
         Node soapBodyNode = findChildNode(tokenElement, "SOAP-ENV:Body");
         if (soapHeaderNode != null && soapBodyNode != null)
         {
            // We found the header of the message, now find the WS-Security node.
            Node wsSecurityNode = findChildNode(soapHeaderNode, "wsse:Security");
            if (wsSecurityNode != null)
            {
               // We found the WS-Security node, now find the Timestamp node.
               Node timeStampNode = findChildNode(wsSecurityNode, "wsu:Timestamp");
               if (timeStampNode != null)
               {
                  // Set the timestamp
                  setTimeStamp(timeStampNode, timeToLive);

                  // Get ready to sign the body and the timestamp elements.
                  XMLSignature sig;
                  if (m_signingKey.getAlgorithm().equalsIgnoreCase("DSA"))
                  {
                     sig = new XMLSignature(tokenDoc, "", XMLSignature.ALGO_ID_SIGNATURE_DSA, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
                  }
                  else if (m_signingKey.getAlgorithm().equalsIgnoreCase("RSA"))
                  {
                     sig = new XMLSignature(tokenDoc, "", XMLSignature.ALGO_ID_SIGNATURE_RSA, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
                  }
                  else
                  {
                     m_log.error("secure()- Unsupported signature algorithm - " + m_signingKey.getAlgorithm());
                     throw new Exception("SecureTokenUtil.secure()- Unsupported signature algorithm - " + m_signingKey.getAlgorithm());
                  }

                  wsSecurityNode.insertBefore(sig.getElement(), timeStampNode);

                  // Specify the transforms and the elements to be signed
                  Transforms transforms = new Transforms(tokenDoc);
                  transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
                  Transforms transforms2 = new Transforms(tokenDoc);
                  transforms2.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
                  sig.addDocument("#CASA-SOAPBODY", transforms, org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
                  sig.addDocument("#CASA-TIMESTAMP", transforms2, org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);

                  Node sigNode = sig.getElement();

                  // Add X509 stuff
                  XMLX509IssuerSerial x509IssuerSerial = new XMLX509IssuerSerial(tokenDoc, m_signingCert);

                  X509Data x509Data = new X509Data(tokenDoc);
                  x509Data.add(x509IssuerSerial);
                  Node x509Element = x509Data.getElement();

                  Node keyInfoNode = tokenDoc.createElementNS("http://www.w3.org/2000/09/xmldsig#", "ds:KeyInfo");

                  Node secTokenRefNode = tokenDoc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "wsse:SecurityTokenReference");

                  keyInfoNode.appendChild(secTokenRefNode);
                  secTokenRefNode.appendChild(x509Element);
                  sigNode.appendChild(keyInfoNode);

                  // Perform some cleanup
                  setPrefixSubtree(sigNode, "ds", "http://www.w3.org/2000/09/xmldsig#");
                  removeWhiteSpace(tokenElement);

                  // Sign the appropriate elements
                  sig.sign(m_signingKey);
               }
               else
               {
                  m_log.error("secure()- Missing template element");
                  throw new Exception("SecureTokenUtil.secure()- Missing template element.");
               }
            }
            else
            {
               m_log.error("secure()- Missing template element");
               throw new Exception("SecureTokenUtil.secure()- Missing template element.");
            }
         }
         else
         {
            m_log.error("secure()- Missing template element");
            throw new Exception("SecureTokenUtil.secure()- Missing template element.");
         }
      }
      catch (Exception e)
      {
         m_log.error("secure()- Exception caught, msg = " + e.getMessage());
         e.printStackTrace();
         throw e;
      }
   }

   // Initialize xml security engine when class loads
   static
   {
      org.apache.xml.security.Init.init();
   }
}
