org.guanxi.common.security.SecUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.guanxi.common.security.SecUtils.java

Source

//: "The contents of this file are subject to the Mozilla Public License
//: Version 1.1 (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.mozilla.org/MPL/
//:
//: Software distributed under the License is distributed on an "AS IS"
//: basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//: License for the specific language governing rights and limitations
//: under the License.
//:
//: The Original Code is Guanxi (http://www.guanxi.uhi.ac.uk).
//:
//: The Initial Developer of the Original Code is Alistair Young alistair@codebrane.com
//: All Rights Reserved.
//:

package org.guanxi.common.security;

import org.w3c.dom.Document;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.transforms.params.InclusiveNamespaces;
import org.guanxi.common.GuanxiException;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.security.*;
import java.security.cert.X509Certificate;
import java.io.*;
import java.util.Hashtable;
import java.util.Date;
import java.util.Random;
import java.util.Vector;
import java.math.BigInteger;

/**
 * <font size=5><b></b></font>
 *
 * @author Alistair Young alistair@smo.uhi.ac.uk
 */
public class SecUtils {
    static private SecUtils _instance = null;

    static public SecUtils getInstance() {
        if (_instance == null) {
            synchronized (SecUtils.class) {
                if (_instance == null) {
                    _instance = new SecUtils();
                }
            }
        }

        return _instance;
    }

    private SecUtils() {
        org.apache.xml.security.Init.init();
    }

    public Document sign(SecUtilsConfig config, Document inDocToSign, String inElementToSign)
            throws GuanxiException {
        String keystoreType = config.getKeystoreType();
        String keystoreFile = config.getKeystoreFile();
        String keystorePass = config.getKeystorePass();
        String privateKeyAlias = config.getPrivateKeyAlias();
        String privateKeyPass = config.getPrivateKeyPass();
        String certificateAlias = config.getCertificateAlias();

        try {
            KeyStore ks = null;
            ks = KeyStore.getInstance(keystoreType);

            FileInputStream fis = null;
            fis = new FileInputStream(keystoreFile);

            ks.load(fis, keystorePass.toCharArray());
            fis.close();

            PrivateKey privateKey = null;
            privateKey = (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());

            String keyType = privateKey.getAlgorithm();
            if (keyType.equalsIgnoreCase("dsa"))
                keyType = XMLSignature.ALGO_ID_SIGNATURE_DSA;
            if (keyType.equalsIgnoreCase("rsa"))
                keyType = XMLSignature.ALGO_ID_SIGNATURE_RSA;

            XMLSignature sig = null;
            sig = new XMLSignature(inDocToSign, "", keyType, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

            // For Shibboleth 1.2.1, <ds:Signature> must be the first element after the root
            inDocToSign.getDocumentElement().insertBefore(sig.getElement(),
                    inDocToSign.getDocumentElement().getFirstChild());

            Transforms transforms = new Transforms(sig.getDocument());

            /* First we have to strip away the signature element (it's not part of the
             * signature calculations). The enveloped transform can be used for this.
             */
            transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
            transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
            /* Have to use a text node for compatibility with Internet2 Shibboleth SP 1.3
             * otherwise the signature fails to verify. The correct way do add it is:
             * transforms.item(1).getElement().appendChild(new InclusiveNamespaces(inDocToSign, "#default saml samlp ds code kind rw typens").getElement());
             */
            transforms.item(1).getElement()
                    .appendChild(inDocToSign.createTextNode(
                            new InclusiveNamespaces(inDocToSign, "#default saml samlp ds code kind rw typens")
                                    .getInclusiveNamespaces()));

            sig.addDocument(inElementToSign, transforms);

            //Add in the KeyInfo for the certificate that we used the private key of
            X509Certificate cert = null;
            cert = (X509Certificate) ks.getCertificate(certificateAlias);

            sig.addKeyInfo(cert);

            sig.addKeyInfo(cert.getPublicKey());

            sig.sign(privateKey);
        } catch (Exception e) {
            throw new GuanxiException(e);
        }

        return inDocToSign;
    }

    public Document saml2Sign(SecUtilsConfig config, Document inDocToSign, String elementIDToSign)
            throws GuanxiException {
        String keystoreType = config.getKeystoreType();
        String keystoreFile = config.getKeystoreFile();
        String keystorePass = config.getKeystorePass();
        String privateKeyAlias = config.getPrivateKeyAlias();
        String privateKeyPass = config.getPrivateKeyPass();
        String certificateAlias = config.getCertificateAlias();

        try {
            KeyStore ks = null;
            ks = KeyStore.getInstance(keystoreType);

            FileInputStream fis = null;
            fis = new FileInputStream(keystoreFile);

            ks.load(fis, keystorePass.toCharArray());
            fis.close();

            PrivateKey privateKey = null;
            privateKey = (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());

            String keyType = privateKey.getAlgorithm();
            if (keyType.equalsIgnoreCase("dsa"))
                keyType = XMLSignature.ALGO_ID_SIGNATURE_DSA;
            if (keyType.equalsIgnoreCase("rsa"))
                keyType = XMLSignature.ALGO_ID_SIGNATURE_RSA;

            XMLSignature sig = null;
            sig = new XMLSignature(inDocToSign, "", keyType, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

            // For SAML2, <ds:Signature> must be the first element after the Issuer
            NodeList nodes = inDocToSign.getDocumentElement().getChildNodes();
            Node issuerNode = null;
            for (int c = 0; c < nodes.getLength(); c++) {
                issuerNode = nodes.item(c);
                if (issuerNode.getLocalName() != null) {
                    if (issuerNode.getLocalName().equals("Issuer")) {
                        break;
                    }
                }
            }
            inDocToSign.getDocumentElement().insertBefore(sig.getElement(),
                    issuerNode.getNextSibling().getNextSibling());

            Transforms transforms = new Transforms(sig.getDocument());

            /* First we have to strip away the signature element (it's not part of the
             * signature calculations). The enveloped transform can be used for this.
             */
            transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
            transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
            /* Have to use a text node for compatibility with Internet2 Shibboleth SP 1.3
             * otherwise the signature fails to verify. The correct way do add it is:
             * transforms.item(1).getElement().appendChild(new InclusiveNamespaces(inDocToSign, "#default saml samlp ds code kind rw typens").getElement());
             */
            transforms.item(1).getElement()
                    .appendChild(inDocToSign.createTextNode(
                            new InclusiveNamespaces(inDocToSign, "#default saml samlp ds code kind rw typens")
                                    .getInclusiveNamespaces()));

            if ((elementIDToSign != null) && (!elementIDToSign.equals(""))) {
                sig.addDocument("#" + elementIDToSign, transforms);
            } else {
                sig.addDocument("", transforms);
            }

            //Add in the KeyInfo for the certificate that we used the private key of
            X509Certificate cert = null;
            cert = (X509Certificate) ks.getCertificate(certificateAlias);

            sig.addKeyInfo(cert);

            sig.addKeyInfo(cert.getPublicKey());

            sig.sign(privateKey);
        } catch (Exception e) {
            throw new GuanxiException(e);
        }

        return inDocToSign;
    }

    public String encrypt(String data) {
        try {
            char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.reset();
            md5.update(data.getBytes());
            byte[] hashBytes = md5.digest();
            String hex = "";
            int msb;
            int lsb = 0;
            int i;
            for (i = 0; i < hashBytes.length; i++) {
                msb = ((int) hashBytes[i] & 0x000000FF) / 16;
                lsb = ((int) hashBytes[i] & 0x000000FF) % 16;
                hex = hex + hexChars[msb] + hexChars[lsb];
            }
            return (hex);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }

    /**
     * Generates a self signed public/private key pair and puts them and the associated certificate in
     * a KeyStore.
     *
     * @param cn The CN of the X509 containing the public key, e.g. "cn=guanxi_sp,ou=smo,o=uhi"
     * @param keystoreFile The full path and name of the KeyStore to create or add the certificate to
     * @param keystorePassword The password for the KeyStore
     * @param privateKeyPassword The password for the private key associated with the public key certificate
     * @param privateKeyAlias The alias under which the private key will be stored
     * @param keyType The type of key, RSA or DSA
     * @throws GuanxiException if an error occurred
     */
    public void createSelfSignedKeystore(String cn, String keystoreFile, String keystorePassword,
            String privateKeyPassword, String privateKeyAlias, String keyType) throws GuanxiException {
        try {
            KeyStore ks = KeyStore.getInstance("JKS");

            // Does the keystore exist?
            File keyStore = new File(keystoreFile);
            if (keyStore.exists()) {
                FileInputStream fis = new FileInputStream(keystoreFile);
                ks.load(fis, keystorePassword.toCharArray());
                fis.close();
            } else
                ks.load(null, null);

            // Generate a new public/private key pair
            KeyPairGenerator keyGen = null;
            if (keyType.toLowerCase().equals("rsa")) {
                keyGen = KeyPairGenerator.getInstance("RSA");
            } else if (keyType.toLowerCase().equals("dsa")) {
                keyGen = KeyPairGenerator.getInstance("DSA");
            }
            keyGen.initialize(1024, new SecureRandom());
            KeyPair keypair = keyGen.generateKeyPair();
            PrivateKey privkey = keypair.getPrivate();
            PublicKey pubkey = keypair.getPublic();

            /* Set the attributes of the X509 Certificate that will contain the public key.
             * This is a self signed certificate so the issuer and subject will be the same.
             */
            Hashtable<DERObjectIdentifier, String> attrs = new Hashtable<DERObjectIdentifier, String>();
            Vector<DERObjectIdentifier> ordering = new Vector<DERObjectIdentifier>();
            ordering.add(X509Name.CN);

            attrs.put(X509Name.CN, cn);
            X509Name issuerDN = new X509Name(ordering, attrs);
            X509Name subjectDN = new X509Name(ordering, attrs);

            // Certificate valid from now
            Date validFrom = new Date();
            validFrom.setTime(validFrom.getTime() - (10 * 60 * 1000));
            Date validTo = new Date();
            validTo.setTime(validTo.getTime() + (20 * (24 * 60 * 60 * 1000)));

            // Initialise the X509 Certificate information...
            X509V3CertificateGenerator x509 = new X509V3CertificateGenerator();
            if (keyType.toLowerCase().equals("rsa")) {
                x509.setSignatureAlgorithm("SHA1withRSA");
            } else if (keyType.toLowerCase().equals("dsa")) {
                x509.setSignatureAlgorithm("SHA1withDSA");
            }
            x509.setIssuerDN(issuerDN);
            x509.setSubjectDN(subjectDN);
            x509.setPublicKey(pubkey);
            x509.setNotBefore(validFrom);
            x509.setNotAfter(validTo);
            x509.setSerialNumber(new BigInteger(128, new Random()));

            // ...generate it...
            X509Certificate[] cert = new X509Certificate[1];
            cert[0] = x509.generate(privkey, "BC");

            // ...and add the self signed certificate as the certificate chain
            java.security.cert.Certificate[] chain = new java.security.cert.Certificate[1];
            chain[0] = cert[0];

            // Under the alias, store the X509 Certificate and it's public key...
            ks.setKeyEntry(privateKeyAlias, privkey, privateKeyPassword.toCharArray(), cert);
            // ...and the chain...
            ks.setKeyEntry(privateKeyAlias, privkey, privateKeyPassword.toCharArray(), chain);
            // ...and write the keystore to disk
            FileOutputStream fos = new FileOutputStream(keystoreFile);
            ks.store(fos, keystorePassword.toCharArray());
            fos.close();
        } catch (Exception se) {
            /* We'll end up here if a security manager is installed and it refuses us
             * permission to add the BouncyCastle provider
             */
            throw new GuanxiException(se);
        }
    }

    /**
     * Creates an empty truststore
     *
     * @param trustStoreFile Full path and name of the truststore to create
     * @param trustStorePassword Password for the truststore
     * @throws GuanxiException if an error occurred
     */
    public void createTrustStore(String trustStoreFile, String trustStorePassword) throws GuanxiException {
        try {
            KeyStore trustStore = KeyStore.getInstance("JKS");
            File truststoreFile = new File(trustStoreFile);
            trustStore.load(null, null);
            FileOutputStream fos = new FileOutputStream(truststoreFile);
            trustStore.store(fos, trustStorePassword.toCharArray());
            fos.close();
        } catch (Exception se) {
            throw new GuanxiException(se);
        }
    }
}