/******************************************************************************* * DISCLAIMER: The sample code or utility or tool described herein * is provided on an "as is" basis, without warranty of any kind. * UIDAI does not warrant or guarantee the individual success * developers may have in implementing the sample code on their * environment. * * UIDAI does not warrant, guarantee or make any representations * of any kind with respect to the sample code and does not make * any representations or warranties regarding the use, results * of use, accuracy, timeliness or completeness of any data or * information relating to the sample code. UIDAI disclaims all * warranties, express or implied, and in particular, disclaims * all warranties of merchantability, fitness for a particular * purpose, and warranties related to the code, or any service * or software related thereto. * * UIDAI is not responsible for and shall not be liable directly * or indirectly for any direct, indirect damages or costs of any * type arising out of use or any action taken by you or others * related to the sample code. * * THIS IS NOT A SUPPORTED SOFTWARE. ******************************************************************************/ package in.gov.uidai.auth.aua.helper; import java.io.FileInputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; 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.dom.DOMValidateContext; 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.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * DigitalSigner class provides a utility method to digitally sign * XML document. This implementation uses .p12 file as a source of signer's * digital certificate. In production environments, a hardware security module * (HSM) should be used for digitally signing. * * @author UIDAI * */ public class DigitalSigner { private static final String MEC_TYPE = "DOM"; private static final String WHOLE_DOC_URI = ""; private static final String KEY_STORE_TYPE = "PKCS12"; private KeyStore.PrivateKeyEntry keyEntry; // used for dongle private Provider provider; private static final String KEY_STORE_TYPE_DONGLE = "PKCS11"; /** * Constructor * * @param keyStoreFile * - Location of .p12 file * @param keyStorePassword * - Password of .p12 file * @param alias * - Alias of the certificate in .p12 file */ public DigitalSigner(String keyStoreFile, char[] keyStorePassword, String alias) { this.keyEntry = getKeyFromKeyStore(keyStoreFile, keyStorePassword, alias); if (keyEntry == null) { throw new RuntimeException( "Key could not be read for digital signature. Please check value of signature " + "alias and signature password, and restart the Auth Client"); } } /** * Constructor * * read key from dongle file * * * @param safesignfile * @param providerName * @param pin */ public DigitalSigner(String safesignfile, char[] pin) { this.provider = new sun.security.pkcs11.SunPKCS11(safesignfile); Security.addProvider(this.provider); this.keyEntry = getPrivateKeyFromDongle(pin); if (keyEntry == null) { throw new RuntimeException( "Key could not be read for digital signature. Please check value of signature " + "alias and signature password, and restart the Auth Client"); } } /** * Method to digitally sign an XML document. * * @param xmlDocument * - Input XML Document. * @return Signed XML document */ public String signXML(String xmlDocument, boolean includeKeyInfo) { if (this.provider == null) { this.provider = new BouncyCastleProvider(); } Security.addProvider(this.provider); try { // Parse the input XML DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); Document inputDocument = dbf.newDocumentBuilder().parse( new InputSource(new StringReader(xmlDocument))); // Sign the input XML's DOM document Document signedDocument = sign(inputDocument, includeKeyInfo); // Convert the signedDocument to XML String StringWriter stringWriter = new StringWriter(); TransformerFactory tf = TransformerFactory.newInstance(); Transformer trans = tf.newTransformer(); trans.transform(new DOMSource(signedDocument), new StreamResult( stringWriter)); return stringWriter.getBuffer().toString(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException( "Error while digitally signing the XML document", e); } } private Document sign(Document xmlDoc, boolean includeKeyInfo) throws Exception { if (System.getenv("SKIP_DIGITAL_SIGNATURE") != null) { return xmlDoc; } // Creating the XMLSignature factory. XMLSignatureFactory fac = XMLSignatureFactory.getInstance(MEC_TYPE); // Creating the reference object, reading the whole document for // signing. Reference ref = fac.newReference(WHOLE_DOC_URI, fac.newDigestMethod( DigestMethod.SHA256, null), Collections.singletonList(fac .newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, null); //Reference ref = fac.newReference(WHOLE_DOC_URI, fac.newDigestMethod(DigestMethod.SHA256, null), //DigestMethod.SHA256 // Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, // null); // Create the SignedInfo. SignedInfo sInfo = fac .newSignedInfo(fac.newCanonicalizationMethod( CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), fac .newSignatureMethod(SignatureMethod.RSA_SHA256, null), Collections.singletonList(ref)); //SignedInfo sInfo = fac.newSignedInfo( // fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), //SignatureMethod.RSA_SHA256 // fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null), Collections.singletonList(ref)); if (keyEntry == null) { throw new RuntimeException( "Key could not be read for digital signature. Please check value of signature alias and signature password, and restart the Auth Client"); } X509Certificate x509Cert = (X509Certificate) keyEntry.getCertificate(); KeyInfo kInfo = getKeyInfo(x509Cert, fac); DOMSignContext dsc = new DOMSignContext(this.keyEntry.getPrivateKey(), xmlDoc.getDocumentElement()); XMLSignature signature = fac.newXMLSignature(sInfo, includeKeyInfo ? kInfo : null); signature.sign(dsc); Node node = dsc.getParent(); return node.getOwnerDocument(); } @SuppressWarnings("unchecked") private KeyInfo getKeyInfo(X509Certificate cert, XMLSignatureFactory fac) { // Create the KeyInfo containing the X509Data. KeyInfoFactory kif = fac.getKeyInfoFactory(); List x509Content = new ArrayList(); x509Content.add(cert.getSubjectX500Principal().getName()); x509Content.add(cert); X509Data xd = kif.newX509Data(x509Content); return kif.newKeyInfo(Collections.singletonList(xd)); } private KeyStore.PrivateKeyEntry getKeyFromKeyStore(String keyStoreFile, char[] keyStorePassword, String alias) { // Load the KeyStore and get the signing key and certificate. FileInputStream keyFileStream = null; try { KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE); keyFileStream = new FileInputStream(keyStoreFile); ks.load(keyFileStream, keyStorePassword); KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) ks .getEntry(alias, new KeyStore.PasswordProtection( keyStorePassword)); return entry; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (keyFileStream != null) { try { keyFileStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static KeyStore.PrivateKeyEntry getPrivateKeyFromDongle( char[] keyStorePassword) { KeyStore ks; try { ks = KeyStore.getInstance(KEY_STORE_TYPE_DONGLE); ks.load(null, keyStorePassword); Enumeration alias = ks.aliases(); String signAlias = ""; while (alias.hasMoreElements()) { String aliasName = alias.nextElement(); X509Certificate cert = (X509Certificate) ks .getCertificate(aliasName); boolean[] keyUsage = cert.getKeyUsage(); for (int i = 0; i < keyUsage.length; i++) { if ((i == 0 || i == 1) && keyUsage[i] == true) { signAlias = aliasName; break; } } } return (KeyStore.PrivateKeyEntry) ks.getEntry(signAlias, new KeyStore.PasswordProtection(keyStorePassword)); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnrecoverableEntryException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } }