/* * Copyright(c) 2002 Center for E-Commerce Infrastructure Development, The * University of Hong Kong (HKU). All Rights Reserved. * * This software is licensed under the Academic Free License Version 1.0 * * Academic Free License * Version 1.0 * * This Academic Free License applies to any software and associated * documentation (the "Software") whose owner (the "Licensor") has placed the * statement "Licensed under the Academic Free License Version 1.0" immediately * after the copyright notice that applies to the Software. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of the Software (1) to use, copy, modify, merge, publish, perform, * distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, and (2) under patent * claims owned or controlled by the Licensor that are embodied in the Software * as furnished by the Licensor, to make, use, sell and offer for sale the * Software and derivative works thereof, subject to the following conditions: * * - Redistributions of the Software in source code form must retain all * copyright notices in the Software as furnished by the Licensor, this list * of conditions, and the following disclaimers. * - Redistributions of the Software in executable form must reproduce all * copyright notices in the Software as furnished by the Licensor, this list * of conditions, and the following disclaimers in the documentation and/or * other materials provided with the distribution. * - Neither the names of Licensor, nor the names of any contributors to the * Software, nor any of their trademarks or service marks, may be used to * endorse or promote products derived from this Software without express * prior written permission of the Licensor. * * DISCLAIMERS: LICENSOR WARRANTS THAT THE COPYRIGHT IN AND TO THE SOFTWARE IS * OWNED BY THE LICENSOR OR THAT THE SOFTWARE IS DISTRIBUTED BY LICENSOR UNDER * A VALID CURRENT LICENSE. EXCEPT AS EXPRESSLY STATED IN THE IMMEDIATELY * PRECEDING SENTENCE, THE SOFTWARE IS PROVIDED BY THE LICENSOR, CONTRIBUTORS * AND COPYRIGHT OWNERS "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE * LICENSOR, CONTRIBUTORS OR COPYRIGHT OWNERS BE LIABLE FOR ANY CLAIM, DAMAGES * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE. * * This license is Copyright (C) 2002 Lawrence E. Rosen. All rights reserved. * Permission is hereby granted to copy and distribute this license without * modification. This license may not be modified without the express written * permission of its copyright owner. */ /* ===== * * $Header: /home/cvsroot/ebxml-pkg/src/hk/hku/cecid/ebms/pkg/pki/ApacheXMLDSigner.java,v 1.1 2005/07/28 09:36:24 dcmsze Exp $ * * Code authored by: * * kcyee [2002-05-16] * * Code reviewed by: * * username [YYYY-MM-DD] * * Remarks: * * ===== */ package hk.hku.cecid.ebms.pkg.pki; import java.io.InputStream; import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import org.apache.log4j.Logger; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.keyresolver.KeyResolverException; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureException; import org.apache.xml.security.transforms.TransformationException; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Constants; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * This class hides the details for digital signature. The digital signature * routines are provided by the Apache XML Security library. We defined a * standard way to have the document signed as interface. Different classes * will implement the interface using different library behind. * * @author kcyee * @version $Revision: 1.1 $ */ public class ApacheXMLDSigner implements XMLDSigner { /** * Logger */ protected static Logger logger = Logger.getLogger(ApacheXMLDSigner.class); /** * Name of the Signature element [ebMSS 4.1.1, XMLDSIG 4.1] */ public static final String ELEMENT_SIGNATURE = "Signature"; /** * Name of the KeyInfo element which enables the recipient(s) to * obtain the key needed to validate the signature [XMLDSIG 4.4] */ public static final String ELEMENT_KEY_INFO = "KeyInfo"; /** * Name of the XPath element [XMLDSIG 6.6.3] */ public static final String ELEMENT_XPATH = "XPath"; /** * Namespace URI of <code>xmlns</code>. */ public static final String NAMESPACE_URI_XML_NS = "http://www.w3.org/2000/xmlns/"; /** * Namespace prefix of <code>Signature</code>. */ public static final String NAMESPACE_PREFIX_DS = "ds"; /** * Namespace URI of <code>Signature</code>. */ public static final String NAMESPACE_URI_DS = Constants.SignatureSpecNS; /** * Name of the digital signature method required, qualified by the * digital signature namespace [XMLDSIG 6.1] */ public static final String SIGNATURE_METHOD = "dsa-sha1"; /** * Name of the Digest method required, qualified by namespace [XMLDSIG 6.1] */ public static final String DIGEST_METHOD = Constants.ALGO_ID_DIGEST_SHA1; /** * Namespace prefix of SOAP envelope. */ public static final String NAMESPACE_PREFIX_SOAP_ENVELOPE = "SOAP-ENV"; /** * Namespace URI of SOAP envelope. */ public static final String NAMESPACE_URI_SOAP_ENVELOPE = "http://schemas.xmlsoap.org/soap/envelope/"; /** * Name of the XPath transform algorithm recommended [XMLDSIG 6.1] */ public static final String TRANSFORM_ALGORITHM_XPATH = "http://www.w3.org/TR/1999/REC-xpath-19991116"; public static final String ACTOR_NEXT_MSH_URN = "urn:oasis:names:tc:ebxml-msg:actor:nextMSH"; public static final String ACTOR_NEXT_MSH_SCHEMAS = "http://schemas.xmlsoap.org/soap/actor/next"; /** * XPath transform string used in the implementation. */ public static final String TRANSFORM_XPATH = "not(ancestor-or-self::node()[@" + NAMESPACE_PREFIX_SOAP_ENVELOPE + ":actor=\"" + ACTOR_NEXT_MSH_URN + "\"] | ancestor-or-self::node()[@" + NAMESPACE_PREFIX_SOAP_ENVELOPE + ":actor=\"" + ACTOR_NEXT_MSH_SCHEMAS + "\"])"; static { org.apache.xml.security.Init.init(); } /** * Internal variable for holding the envelope of the signature. */ protected Document envelope; /** * Internal variable for holding the documents needed to be referred * in the signature. */ protected ArrayList documents; /** * Internal variable for holding the trusted anchor for certificate * path verification. */ protected CompositeKeyStore trusted; /** * Internal variable of the Apache XML Security library signature object * for doing the actual signing/verifying algorithm. */ protected XMLSignature signature; private CertResolver certResolver; private Object obj; private String digestAlgo; private String algo; /** * Default constructor to initialize the internal variables. */ public ApacheXMLDSigner() { documents = new ArrayList(); envelope = null; signature = null; trusted = null; certResolver = null; obj = null; digestAlgo = null; } /** * Set the envelope to host the Signature element. That is the * XML document where the Signature element to be added. The * digital signature here will always be an enveloped signature. * The envelope will be included in the process of signing. * * @param doc the XML document to host the Signature element * @param algo the algorithm used for digital signature. Currently, only * two values are tested: <code>dsa-sha1</code> and * <code>rsa-sha1</code>. * @param digestAlgo the algorithm used for making digest value. Currently, * one value is supported: <code>sha1</code> * @throws SignException internal exception when doing initialization * on Apache XML Security library */ public void setEnvelope(Document doc, String algo, String digestAlgo) throws SignException { setEnvelope(doc, algo); this.digestAlgo = digestAlgo; } /** * Set the envelope to host the Signature element. That is the * XML document where the Signature element to be added. The * digital signature here will always be an enveloped signature. * The envelope will be included in the process of signing. * * @param doc the XML document to host the Signature element * @param algo the algorithm used for digital signature. Currently, only * two values are tested: <code>dsa-sha1</code> and * <code>rsa-sha1</code>. * @throws SignException internal exception when doing initialization * on Apache XML Security library */ public void setEnvelope(Document doc, String algo) throws SignException { envelope = doc; try { if (algo != null) { signature = new XMLSignature(envelope, NAMESPACE_URI_DS, NAMESPACE_URI_DS + algo); } } catch (XMLSecurityException e) { String err = "Cannot create XMLSignature object - " + e.getMessage(); logger.error(err); throw new SignException(err); } this.algo = algo; logger.debug("setEnvelope, using algorithm: " + algo); } /** * Set the envelope to host the Signature element. That is the * XML document where the Signature element to be added. The * digital signature here will always be an enveloped signature. * The envelope will be included in the process of signing. * * @param doc the XML document to host the Signature element * @throws SignException internal exception when doing initialization * on Apache XML Security library */ public void setEnvelope(Document doc) throws SignException { setEnvelope(doc, null); } /** * Adds a reference to a document attachment to the signature. * * @param uri the URI of the document attachment * @param is the input stream of the content of the document * @param contentType the content type of the document */ public void addDocument(String uri, InputStream is, String contentType) { DocumentDetail dd = new DocumentDetail(); dd.uri = uri; dd.stream = is; dd.contentType = contentType; documents.add(dd); logger.debug( "addDocument URI: " + uri + ", contentType: " + contentType); } public void addCertResolver(CertResolver certResolver, Object obj) { this.certResolver = certResolver; this.obj = obj; } /** * Signs the envelope and documents by using the specified key * in the keystore. * * @param ks the keystore holding the key for signing * @param alias the alias of the key for signing * @param password the password for accessing the key for signing * @throws SignException when there is any error in the processing of * signing */ public void sign(CompositeKeyStore ks, String alias, char[] password) throws SignException { logger.debug("start signing"); PrivateKey pk; try { pk = (PrivateKey) ks.getKey(alias, password); } catch (Exception e) { String err = "Cannot get private key: " + alias + " - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("got private key from keystore"); if (envelope == null) { String err = "Envelope element not set"; logger.warn(err); throw new SignException(err); } DocumentDetail[] doc_array = new DocumentDetail[documents.size()]; for (int i=0 ; i<doc_array.length ; i++) { doc_array[i] = (DocumentDetail) documents.get(i); } DocumentResolver resolver = new DocumentResolver(doc_array); signature.getSignedInfo().addResourceResolver(resolver); logger.debug("created DocumentResolver"); Transforms transforms = new Transforms(envelope); try { transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); Element xpath = envelope.createElementNS (NAMESPACE_URI_DS, ELEMENT_XPATH); xpath.setAttributeNS(NAMESPACE_URI_XML_NS, "xmlns:" + NAMESPACE_PREFIX_SOAP_ENVELOPE, NAMESPACE_URI_SOAP_ENVELOPE); xpath.appendChild(envelope.createTextNode(TRANSFORM_XPATH)); xpath.setPrefix(NAMESPACE_PREFIX_DS); transforms.addTransform(TRANSFORM_ALGORITHM_XPATH, xpath); // add canonicalization transform transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); } catch (TransformationException e) { String err = "Cannot add transform - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("created Transform"); try { if (digestAlgo == null) { signature.addDocument("", transforms, DIGEST_METHOD); } else { signature.addDocument("", transforms, NAMESPACE_URI_DS + digestAlgo); } } catch (XMLSignatureException e) { String err = "Cannot add envelope document - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("added main document (envelope)"); for (int i=0 ; i<documents.size() ; i++) { DocumentDetail dd = (DocumentDetail) documents.get(i); try { signature.addDocument(dd.uri); } catch (XMLSignatureException e) { String err = "cannot add document: " + dd.uri + " - " + e.getMessage(); logger.warn(err); throw new SignException(err); } } logger.debug("added " + documents.size() + " attachment documents"); Certificate[] certificates; try { certificates = ks.getCertificateChain(alias); if (certificates == null) { String err = "Cannot get certificate path: " + alias; logger.warn(err); throw new SignException(err); } } catch (KeyStoreException e) { String err = "Cannot get certificate path: " + alias + " - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("got the certificate chain from keystore"); for (int i=0 ; i<certificates.length ; i++) { try { signature.addKeyInfo((X509Certificate) certificates[i]); } catch (XMLSecurityException e) { String err = "Cannot add key info - " + e.getMessage(); logger.warn(err); throw new SignException(err); } } logger.debug("added the certificate chain to signature"); /* PrivateKey pk; try { pk = (PrivateKey) ks.getKey(alias, password); } catch (Exception e) { String err = "Cannot get private key: " + alias + " - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("got private key from keystore"); */ try { signature.sign(pk); } catch (Exception e) { String err = "Cannot sign - " + e.getMessage(); logger.warn(err); throw new SignException(err); } logger.debug("message signed"); } /** * Sets the trust anchor for verfication of certificate path. * * @param ks the keystore providing the trusted certificates */ public void setTrustAnchor(CompositeKeyStore ks) { trusted = ks; } /** * Verifies the signature in the envelope passed in, which may reference * the documents specified using the addDocument method. * * @return true if the signature can be verified successfully, false * if otherwise. * @throws VerifyException when there is any error in the processing of * verification */ public boolean verify() throws VerifyException { logger.debug("start verifying"); if (envelope == null) { String err = "Envelope element not set."; logger.warn(err); throw new VerifyException(err); } NodeList nodeList = envelope.getElementsByTagNameNS (NAMESPACE_URI_DS, ELEMENT_SIGNATURE); if (nodeList.getLength() == 0) { String err = "No <" + NAMESPACE_PREFIX_DS + ":" + ELEMENT_SIGNATURE + "> found"; logger.warn(err); throw new VerifyException(err); } Element signatureElement = (Element) nodeList.item(0); // addNamespaceDeclaration(signatureElement); logger.debug("got the signature element"); try { signature = new XMLSignature(signatureElement, NAMESPACE_URI_DS); } catch (Exception e) { String err = "Cannot create XMLSignature object - " + e.getMessage(); logger.error(err); throw new VerifyException(err); } logger.debug("created signature object"); DocumentDetail[] doc_array = new DocumentDetail[documents.size()]; for (int i=0 ; i<doc_array.length ; i++) { doc_array[i] = (DocumentDetail) documents.get(i); } DocumentResolver resolver = new DocumentResolver(doc_array); signature.addResourceResolver(resolver); logger.debug("created document resolver"); Certificate[] certs = null; if (certResolver != null) { certs = certResolver.resolve(obj); if (certs == null || certs.length <= 0) { String err = "Certificates returned by certResolver is null"; logger.warn(err); throw new VerifyException(err); } } else if (trusted == null) { String err = "Cannot verify cert path, but certResolver is null"; logger.warn(err); throw new VerifyException(err); } KeyInfo keyInfo = null; PublicKey publicKey = null; if (certs != null && certs.length > 0) { publicKey = certs[0].getPublicKey(); logger.debug("got certificate and public key from CertResolver"); } else { keyInfo = signature.getKeyInfo(); } if (keyInfo != null) { try { int certPathLen = keyInfo.lengthX509Data(); if (certPathLen > 0) { certs = new Certificate[certPathLen]; for (int i=0 ; i<certPathLen ; i++) { try { certs[i] = keyInfo.itemX509Data(i) .itemCertificate(0) .getX509Certificate(); } catch (XMLSecurityException e) { String err = "Cannot get X509 certficate from <" + signatureElement.getPrefix() + ":" + ELEMENT_KEY_INFO + ">"; logger.warn(err); throw new VerifyException(err); } } } X509Certificate certificate = keyInfo.getX509Certificate(); if (certificate != null) { publicKey = certificate.getPublicKey(); } logger.debug("got X509 certificate and public key from " + ELEMENT_SIGNATURE + " element in message"); } catch (KeyResolverException e) { String err = "Cannot get X509 certificate from <" + signatureElement.getPrefix() + ":" + ELEMENT_KEY_INFO + ">"; logger.warn(err); throw new VerifyException(err); } } if (publicKey == null) { String err = "No PublicKey found"; logger.warn(err); throw new VerifyException(err); } boolean ret = false; try { ret = signature.checkSignatureValue(publicKey); } catch (Exception e) { String err = "Cannot check signature - " + e.getMessage(); logger.warn(err); throw new VerifyException(err); } logger.debug("checked signature value, result: " + ret); if (ret == true && trusted != null && certs != null && certs.length > 1) { logger.debug("start verifying cert path"); ret = CertPathVerifier.verify(certs, trusted); logger.debug("verified, result: " + ret); } else { logger.debug("verification of cert path skipped"); } return ret; } /** * Gets the DOM element of the signature generated. * * @return the DOM element of the signature */ public Element getElement() { if (signature != null) { return signature.getElement(); } else { return null; } } /* private void addNamespaceDeclaration(Element element) { NodeList nodeList = element.getChildNodes(); for (int i=0 ; i<nodeList.getLength() ; i++) { if (nodeList.item(i).getNodeType() != Node.ELEMENT_NODE) { continue; } Element child = (Element) nodeList.item(i); child.setAttributeNS(NAMESPACE_URI_XML_NS, "xmlns:" + NAMESPACE_PREFIX_DS, NAMESPACE_URI_DS); if (child.getLocalName().equals(ELEMENT_XPATH)) { child.setAttributeNS(NAMESPACE_URI_XML_NS, "xmlns:" + NAMESPACE_PREFIX_SOAP_ENVELOPE, NAMESPACE_URI_SOAP_ENVELOPE); } addNamespaceDeclaration(child); } } */ }