CertTools.java
/* 
 * ################################################################
 
 * ProActive: The Java(TM) library for Parallel, Distributed, 
 *            Concurrent computing with Security and Mobility
 
 * Copyright (C) 1997-2007 INRIA/University of Nice-Sophia Antipolis
 * Contact: proactive@objectweb.org
 
 * 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; either
 * version 2.1 of the License, or any later version.
 *  
 * 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
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *  
 *  Initial developer(s):               The ProActive Team
 *                        http://www.inria.fr/oasis/ProActive/contacts.html
 *  Contributor(s): 
 
 * ################################################################
 */ 
package org.objectweb.proactive.ext.security;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x509.X509NameTokenizer;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.objectweb.proactive.core.util.log.Loggers;
import org.objectweb.proactive.core.util.log.ProActiveLogger;


/**
 * Tools to handle common certificate operations.
 *
 */
public class CertTools {
    static Logger log = ProActiveLogger.getLogger(Loggers.SECURITY);
    public static final String EMAIL = "rfc822name";
    public static final String EMAIL1 = "email";
    public static final String EMAIL2 = "EmailAddress";
    public static final String EMAIL3 = "E";
    public static final String DNS = "dNSName";
    public static final String URI = "uniformResourceIdentifier";
    public static final String URI1 = "uri";

    /** Microsoft altName for windows smart card logon */
    public static final String UPN = "upn";

    /** ObjectID for upn altName for windows smart card logon */
    public static final String UPN_OBJECTID = "1.3.6.1.4.1.311.20.2.3";
    private static final String[] EMAILIDS = EMAIL, EMAIL1, EMAIL2, EMAIL3 };

    /**
     * inhibits creation of new CertTools
     */
    private CertTools() {
    }

    /** BC X509Name contains some lookup tables that could maybe be used here. */
    private static final HashMap<String,DERObjectIdentifier> oids = new HashMap<String,DERObjectIdentifier>();
  
    static {
        oids.put("c", X509Name.C);
        oids.put("dc", X509Name.DC);
        oids.put("st", X509Name.ST);
        oids.put("l", X509Name.L);
        oids.put("o", X509Name.O);
        oids.put("ou", X509Name.OU);
        oids.put("t", X509Name.T);
        oids.put("surname", X509Name.SURNAME);
        oids.put("initials", X509Name.INITIALS);
        oids.put("givenname", X509Name.GIVENNAME);
        oids.put("gn", X509Name.GIVENNAME);
        oids.put("sn", X509Name.SN);
        oids.put("serialnumber", X509Name.SN);
        oids.put("cn", X509Name.CN);
        oids.put("uid", X509Name.UID);
        oids.put("emailaddress", X509Name.EmailAddress);
        oids.put("e", X509Name.EmailAddress);
        oids.put("email", X509Name.EmailAddress);
    }
    ;
    private static final String[] dNObjectsForward = {
            "emailaddress""e""email""uid""cn""sn""serialnumber",
            "gn""givenname""initials""surname""t""ou""o""l""st",
            "dc""c"
        };
    private static final String[] dNObjectsReverse = {
            "c""dc""st""l""o""ou""t""surname""initials",
            "givenname""gn""serialnumber""sn""cn""uid""email""e",
            "emailaddress"
        };

    /** Change this if you want reverse order */
    private static final String[] dNObjects = dNObjectsForward;

    private static DERObjectIdentifier getOid(String o) {
        return (DERObjectIdentifieroids.get(o.toLowerCase());
    // getOid

    /**
     * Creates a (Bouncycastle) X509Name object from a string with a DN. Known OID (with order)
     * are: <code> EmailAddress, UID, CN, SN (SerialNumber), GivenName, Initials, SurName, T, OU,
     * O, L, ST, DC, C </code>
     * To change order edit 'dnObjects' in this source file.
     *
     @param dn String containing DN that will be transformed into X509Name, The DN string has the
     *        format "CN=zz,OU=yy,O=foo,C=SE". Unknown OIDs in the string will be silently
     *        dropped.
     *
     @return X509Name
     */
    public static X509Name stringToBcX509Name(String dn) {
        //log.debug(">stringToBcX509Name: " + dn);
        // first make two vectors, one with all the C, O, OU etc specifying
        // the order and one holding the actual values
        ArrayList<String> oldordering = new ArrayList<String>();
        ArrayList<String> oldvalues = new ArrayList<String>();
        X509NameTokenizer xt = new X509NameTokenizer(dn);

        while (xt.hasMoreTokens()) {
            // This is a pair (CN=xx)
            String pair = xt.nextToken();
            int ix = pair.indexOf("=");

            if (ix != -1) {
                // make lower case so we can easily compare later
                oldordering.add(pair.substring(0, ix).toLowerCase());
                oldvalues.add(pair.substring(ix + 1));
            else {
                // Huh, what's this?
            }
        }

        // Now in the specified order, move from oldordering to newordering,
        // reshuffling as we go along
        Vector<DERObjectIdentifier> ordering = new Vector<DERObjectIdentifier>();
        Vector<String> values = new Vector<String>();
        int index = -1;

        for (int i = 0; i < dNObjects.length; i++) {
            //log.debug("Looking for "+objects[i]);
            String object = dNObjects[i];

            while ((index = oldordering.indexOf(object)) != -1) {
                //log.debug("Found 1 "+object+" at index " + index);
                DERObjectIdentifier oid = getOid(object);

                if (oid != null) {
                    //log.debug("Added "+object+", "+oldvalues.elementAt(index));
                    ordering.add(oid);

                    // remove from the old vectors, so we start clean the next round
                    values.add(oldvalues.remove(index));
                    oldordering.remove(index);
                    index = -1;
                }
            }
        }

        /*
           if (log.isDebugEnabled()) {
               Iterator i1 = ordering.iterator();
               Iterator i2 = values.iterator();
               log.debug("Order: ");
               while (i1.hasNext()) {
                   log.debug(((DERObjectIdentifier)i1.next()).getId());
               }
               log.debug("Values: ");
               while (i2.hasNext()) {
                   log.debug((String)i2.next());
               }
           } */

        //log.debug("<stringToBcX509Name");
        return new X509Name(ordering, values);
    // stringToBcX509Name

    /**
     * Every DN-string should look the same. Creates a name string ordered and looking like we want
     * it...
     *
     @param dn String containing DN
     *
     @return String containing DN
     */
    public static String stringToBCDNString(String dn) {
        //log.debug(">stringToBcDNString: "+dn);
        String ret = stringToBcX509Name(dn).toString();

        //log.debug("<stringToBcDNString: "+ret);
        return ret;
    }

    //    * Convenience method for getting an email address from a DN. Uses {@link
    //    * getPartFromDN(String,String)} internally, and searches for {@link EMAIL}, {@link EMAIL1},
    //    * {@link EMAIL2}, {@link EMAIL3} and returns the first one found.

    /**
     * Convenience method for getting an email address from a DN.
     @param dn the DN
     @return the found email address, or <code>null</code> if none is found
     */
    public static String getEmailFromDN(String dn) {
        log.debug(">getEmailFromDN(" + dn + ")");

        String email = null;

        for (int i = 0(i < EMAILIDS.length&& (email == null); i++) {
            email = getPartFromDN(dn, EMAILIDS[i]);
        }

        log.debug("<getEmailFromDN(" + dn + "): " + email);

        return email;
    }

    /**
     * Gets a specified part of a DN. Specifically the first occurrence it the DN contains several
     * instances of a part (i.e. cn=x, cn=y returns x).
     *
     @param dn String containing DN, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
     @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc.
     *
     @return String containing dnpart or null if dnpart is not present
     */
    public static String getPartFromDN(String dn, String dnpart) {
        log.debug(">getPartFromDN: dn:'" + dn + "', dnpart=" + dnpart);

        String part = null;

        if ((dn != null&& (dnpart != null)) {
            String o;
            dnpart += "="// we search for 'CN=' etc.

            X509NameTokenizer xt = new X509NameTokenizer(dn);

            while (xt.hasMoreTokens()) {
                o = xt.nextToken();

                //log.debug("checking: "+o.substring(0,dnpart.length()));
                if ((o.length() > dnpart.length()) &&
                        o.substring(0, dnpart.length()).equalsIgnoreCase(dnpart)) {
                    part = o.substring(dnpart.length());

                    break;
                }
            }
        }

        log.debug("<getpartFromDN: resulting DN part=" + part);

        return part;
    //getPartFromDN

    /**
     * Gets subject DN in the format we are sure about (BouncyCastle),supporting UTF8.
     *
     @param cert X509Certificate
     *
     @return String containing the subjects DN.
     */
    public static String getSubjectDN(X509Certificate cert) {
        return getDN(cert, 1);
    }

    /**
     * Gets issuer DN in the format we are sure about (BouncyCastle),supporting UTF8.
     *
     @param cert X509Certificate
     *
     @return String containing the issuers DN.
     */
    public static String getIssuerDN(X509Certificate cert) {
        return getDN(cert, 2);
    }

    /**
     * Gets subject or issuer DN in the format we are sure about (BouncyCastle),supporting UTF8.
     *
     @param cert X509Certificate
     @param which DOCUMENT ME!
     *
     @return String containing the DN.
     */
    private static String getDN(X509Certificate cert, int which) {
        //log.debug(">getDN("+which+")");
        String dn = null;
        if (cert == null) {
            return dn;
        }
        try {
            CertificateFactory cf = CertTools.getCertificateFactory();
            X509Certificate x509cert = (X509Certificatecf.generateCertificate(new ByteArrayInputStream(
                        cert.getEncoded()));

            //log.debug("Created certificate of class: " + x509cert.getClass().getName());
            if (which == 1) {
                dn = x509cert.getSubjectDN().toString();
            else {
                dn = x509cert.getIssuerDN().toString();
            }
        catch (CertificateException ce) {
            log.error("CertificateException: ", ce);
            return null;
        }

        //log.debug("<getDN("+which+"):"+dn);
        return stringToBCDNString(dn);
    // getDN

    /**
     * Gets issuer DN for CRL in the format we are sure about (BouncyCastle),supporting UTF8.
     *
     @param crl X509RL
     *
     @return String containing the DN.
     */
    public static String getIssuerDN(X509CRL crl) {
        //log.debug(">getIssuerDN(crl)");
        String dn = null;
        try {
            CertificateFactory cf = CertTools.getCertificateFactory();
            X509CRL x509crl = (X509CRLcf.generateCRL(new ByteArrayInputStream(
                        crl.getEncoded()));

            //log.debug("Created certificate of class: " + x509crl.getClass().getName());
            dn = x509crl.getIssuerDN().toString();
        catch (CRLException ce) {
            log.error("CRLException: ", ce);

            return null;
        }

        //log.debug("<getIssuerDN(crl):"+dn);
        return stringToBCDNString(dn);
    // getIssuerDN

    public static CertificateFactory getCertificateFactory() {
        try {
            return CertificateFactory.getInstance("X.509""BC");
        catch (NoSuchProviderException nspe) {
            log.error("NoSuchProvider: ", nspe);
        catch (CertificateException ce) {
            log.error("CertificateException: ", ce);
        }
        return null;
    }

    public static void installBCProvider() {
        // we need to check if the BouncyCastle provider is already installed
        // before installing it
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME== null) {
            Security.addProvider(new BouncyCastleProvider());
        }
        
     
    }

    /**
     * Reads a certificate in PEM-format from a file. The file may contain other things,
     * the first certificate in the file is read.
     *
     @param certFile the file containing the certificate in PEM-format
     @return Ordered Collection of X509Certificate, first certificate first, or empty Collection
     @exception IOException if the filen cannot be read.
     @exception CertificateException if the filen does not contain a correct certificate.
     */
    public static Collection getCertsFromPEM(String certFile)
        throws IOException, CertificateException {
        log.debug(">getCertfromPEM: certFile=" + certFile);
        InputStream inStrm = new FileInputStream(certFile);
        Collection certs = getCertsFromPEM(inStrm);
        log.debug("<getCertfromPEM: certFile=" + certFile);
        return certs;
    }

    /**
     * Reads a certificate in PEM-format from an InputStream. The stream may contain other things,
     * the first certificate in the stream is read.
     *
     @param certstream the input stream containing the certificate in PEM-format
     @return Ordered Collection of X509Certificate, first certificate first, or empty Collection
     @exception IOException if the stream cannot be read.
     @exception CertificateException if the stream does not contain a correct certificate.
     */
    public static Collection getCertsFromPEM(InputStream certstream)
        throws IOException, CertificateException {
        log.debug(">getCertfromPEM:");
        ArrayList<X509Certificate> ret = new ArrayList<X509Certificate>();
        String beginKey = "-----BEGIN CERTIFICATE-----";
        String endKey = "-----END CERTIFICATE-----";
        BufferedReader bufRdr = new BufferedReader(new InputStreamReader(
                    certstream));
        while (bufRdr.ready()) {
            ByteArrayOutputStream ostr = new ByteArrayOutputStream();
            PrintStream opstr = new PrintStream(ostr);
            String temp;
            while (((temp = bufRdr.readLine()) != null&&
                    !temp.equals(beginKey))
                continue;
            if (temp == null) {
                throw new IOException("Error in " + certstream.toString() +
                    ", missing " + beginKey + " boundary");
            }
            while (((temp = bufRdr.readLine()) != null&&
                    !temp.equals(endKey))
                opstr.print(temp);
            if (temp == null) {
                throw new IOException("Error in " + certstream.toString() +
                    ", missing " + endKey + " boundary");
            }
            opstr.close();

            byte[] certbuf = Base64.decode(ostr.toByteArray());
            ostr.close();
            // Phweeew, were done, now decode the cert from file back to X509Certificate object
            CertificateFactory cf = CertTools.getCertificateFactory();
            X509Certificate x509cert = (X509Certificatecf.generateCertificate(new ByteArrayInputStream(
                        certbuf));
            String dn = x509cert.getSubjectDN().toString();
            ret.add(x509cert);
        }

        log.debug("<getcertfromPEM:" + ret.size());
        return ret;
    // getCertsFromPEM

    /**
     * Returns a certificate in PEM-format.
     *
     @param certs the certificate to convert to PEM
     @return byte array containing PEM certificate
     @exception IOException if the stream cannot be read.
     @exception CertificateException if the stream does not contain a correct certificate.
     */
    public static byte[] getPEMFromCerts(Collection certs)
        throws CertificateException {
        String beginKey = "-----BEGIN CERTIFICATE-----";
        String endKey = "-----END CERTIFICATE-----";
        ByteArrayOutputStream ostr = new ByteArrayOutputStream();
        PrintStream opstr = new PrintStream(ostr);
        Iterator iter = certs.iterator();
        while (iter.hasNext()) {
            X509Certificate cert = (X509Certificateiter.next();
            byte[] certbuf = Base64.encode(cert.getEncoded());
            opstr.println("Subject: " + cert.getSubjectDN());
            opstr.println("Issuer: " + cert.getIssuerDN());
            opstr.println(beginKey);
            opstr.println(new String(certbuf));
            opstr.println(endKey);
        }
        opstr.close();
        byte[] ret = ostr.toByteArray();
        return ret;
    }

    /**
     * Creates X509Certificate from byte[].
     *
     @param cert byte array containing certificate in DER-format
     *
     @return X509Certificate
     *
     @throws CertificateException if the byte array does not contain a proper certificate.
     @throws IOException if the byte array cannot be read.
     */
    public static X509Certificate getCertfromByteArray(byte[] cert)
        throws IOException, CertificateException {
        log.debug(">getCertfromByteArray:");

        CertificateFactory cf = CertTools.getCertificateFactory();
        X509Certificate x509cert = (X509Certificatecf.generateCertificate(new ByteArrayInputStream(
                    cert));
        log.debug("<getCertfromByteArray:");

        return x509cert;
    // getCertfromByteArray

    /**
     * Creates X509CRL from byte[].
     *
     @param crl byte array containing CRL in DER-format
     *
     @return X509CRL
     *
     @throws IOException if the byte array can not be read.
     @throws CertificateException if the byte arrayen does not contani a correct CRL.
     @throws CRLException if the byte arrayen does not contani a correct CRL.
     */
    public static X509CRL getCRLfromByteArray(byte[] crl)
        throws IOException, CertificateException, CRLException {
        log.debug(">getCRLfromByteArray:");

        if (crl == null) {
            throw new IOException("Cannot read byte[] that is 'null'!");
        }

        CertificateFactory cf = CertTools.getCertificateFactory();
        X509CRL x509crl = (X509CRLcf.generateCRL(new ByteArrayInputStream(crl));
        log.debug("<getCRLfromByteArray:");

        return x509crl;
    // getCRLfromByteArray

    /**
     * Checks if a certificate is self signed by verifying if subject and issuer are the same.
     *
     @param cert the certificate that skall be checked.
     *
     @return boolean true if the certificate has the same issuer and subject, false otherwise.
     */
    public static boolean isSelfSigned(X509Certificate cert) {
        log.debug(">isSelfSigned: cert: " + CertTools.getIssuerDN(cert"\n" +
            CertTools.getSubjectDN(cert));

        boolean ret = CertTools.getSubjectDN(cert).equals(CertTools.getIssuerDN(
                    cert));
        log.debug("<isSelfSigned:" + ret);

        return ret;
    // isSelfSigned

    /**
     * DOCUMENT ME!
     *
     @param dn DOCUMENT ME!
     @param validity DOCUMENT ME!
     @param policyId DOCUMENT ME!
     @param privKey DOCUMENT ME!
     @param pubKey DOCUMENT ME!
     @param isCA DOCUMENT ME!
     *
     @return DOCUMENT ME!
     *
     @throws NoSuchAlgorithmException DOCUMENT ME!
     @throws SignatureException DOCUMENT ME!
     @throws InvalidKeyException DOCUMENT ME!
     */
    public static X509Certificate genSelfCert(String dn, long validity,
        String policyId, PrivateKey privKey, PublicKey pubKey, boolean isCA)
        throws NoSuchAlgorithmException, SignatureException, 
            InvalidKeyException {
        // Create self signed certificate
        String sigAlg = "SHA1WithRSA";
        Date firstDate = new Date();

        // Set back startdate ten minutes to avoid some problems with wrongly set clocks.
        firstDate.setTime(firstDate.getTime() (10 60 1000));

        Date lastDate = new Date();

        // validity in days = validity*24*60*60*1000 milliseconds
        lastDate.setTime(lastDate.getTime() +
            (validity * (24 60 60 1000)));

        X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();

        // Serialnumber is random bits, where random generator is initialized with Date.getTime() when this
        // bean is created.
        byte[] serno = new byte[8];
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed((long) (new Date().getTime()));
        random.nextBytes(serno);
        certgen.setSerialNumber((new java.math.BigInteger(serno)).abs());
        certgen.setNotBefore(firstDate);
        certgen.setNotAfter(lastDate);
        certgen.setSignatureAlgorithm(sigAlg);
        certgen.setSubjectDN(CertTools.stringToBcX509Name(dn));
        certgen.setIssuerDN(CertTools.stringToBcX509Name(dn));
        certgen.setPublicKey(pubKey);

        // Basic constranits is always critical and MUST be present at-least in CA-certificates.
        BasicConstraints bc = new BasicConstraints(isCA);
        certgen.addExtension(X509Extensions.BasicConstraints.getId(), true, bc);

        // Put critical KeyUsage in CA-certificates
        if (isCA == true) {
            int keyusage = X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
            X509KeyUsage ku = new X509KeyUsage(keyusage);
            certgen.addExtension(X509Extensions.KeyUsage.getId(), true, ku);
        }

        // Subject and Authority key identifier is always non-critical and MUST be present for certificates to verify in Mozilla.
        try {
            if (isCA == true) {
                SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo((ASN1Sequencenew ASN1InputStream(
                            new ByteArrayInputStream(pubKey.getEncoded())).readObject());
                SubjectKeyIdentifier ski = new SubjectKeyIdentifier(spki);

                SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((ASN1Sequencenew ASN1InputStream(
                            new ByteArrayInputStream(pubKey.getEncoded())).readObject());
                AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);

                certgen.addExtension(X509Extensions.SubjectKeyIdentifier.getId(),
                    false, ski);
                certgen.addExtension(X509Extensions.AuthorityKeyIdentifier.getId(),
                    false, aki);
            }
        catch (IOException e) { // do nothing
        }

        // CertificatePolicies extension if supplied policy ID, always non-critical
        if (policyId != null) {
            PolicyInformation pi = new PolicyInformation(new DERObjectIdentifier(
                        policyId));
            DERSequence seq = new DERSequence(pi);
            certgen.addExtension(X509Extensions.CertificatePolicies.getId(),
                false, seq);
        }

        X509Certificate selfcert = certgen.generateX509Certificate(privKey);

        return selfcert;
    //genselfCert

    public static X509Certificate genCert(String dn, long validity,
        String policyId, PrivateKey privKey, PublicKey pubKey, boolean isCA,
        String caDn, PrivateKey caPrivateKey, PublicKey acPubKey)
        throws NoSuchAlgorithmException, SignatureException, 
            InvalidKeyException {
        // Create self signed certificate
        String sigAlg = "SHA1WithRSA";
        Date firstDate = new Date();

        // Set back startdate ten minutes to avoid some problems with wrongly set clocks.
        firstDate.setTime(firstDate.getTime() (10 60 1000));

        Date lastDate = new Date();

        // validity in days = validity*24*60*60*1000 milliseconds
        lastDate.setTime(lastDate.getTime() +
            (validity * (24 60 60 1000)));

        X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();

        // Serialnumber is random bits, where random generator is initialized with Date.getTime() when this
        // bean is created.
        byte[] serno = new byte[8];
        SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
        random.setSeed((long) (new Date().getTime()));
        random.nextBytes(serno);
        certgen.setSerialNumber((new java.math.BigInteger(serno)).abs());
        certgen.setNotBefore(firstDate);
        certgen.setNotAfter(lastDate);
        certgen.setSignatureAlgorithm(sigAlg);
        certgen.setSubjectDN(CertTools.stringToBcX509Name(dn));
        certgen.setIssuerDN(CertTools.stringToBcX509Name(caDn));
        certgen.setPublicKey(pubKey);

        // Basic constranits is always critical and MUST be present at-least in CA-certificates.
        BasicConstraints bc = new BasicConstraints(isCA);
        certgen.addExtension(X509Extensions.BasicConstraints.getId(), true, bc);

        // Put critical KeyUsage in CA-certificates
        if (false) {
            //if (isCA == true) {
            int keyusage = X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
            X509KeyUsage ku = new X509KeyUsage(keyusage);
            certgen.addExtension(X509Extensions.KeyUsage.getId(), true, ku);
        }

        // Subject and Authority key identifier is always non-critical and MUST be present for certificates to verify in Mozilla.
        try {
            if (false) {
                //if (isCA == true) {
                SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo((ASN1Sequencenew ASN1InputStream(
                            new ByteArrayInputStream(pubKey.getEncoded())).readObject());
                SubjectKeyIdentifier ski = new SubjectKeyIdentifier(spki);

                SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((ASN1Sequencenew ASN1InputStream(
                            new ByteArrayInputStream(acPubKey.getEncoded())).readObject());
                AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);

                certgen.addExtension(X509Extensions.SubjectKeyIdentifier.getId(),
                    false, ski);
                certgen.addExtension(X509Extensions.AuthorityKeyIdentifier.getId(),
                    false, aki);
            }
        catch (IOException e) { // do nothing
        }

        // CertificatePolicies extension if supplied policy ID, always non-critical
        if (policyId != null) {
            PolicyInformation pi = new PolicyInformation(new DERObjectIdentifier(
                        policyId));
            DERSequence seq = new DERSequence(pi);
            certgen.addExtension(X509Extensions.CertificatePolicies.getId(),
                false, seq);
        }

        X509Certificate selfcert = certgen.generateX509Certificate(caPrivateKey);

        return selfcert;
    //genselfCert

    /**
     * Get the authority key identifier from a certificate extensions
     *
     @param cert certificate containing the extension
     @return byte[] containing the authority key identifier
     @throws IOException if extension can not be parsed
     */
    public static byte[] getAuthorityKeyId(X509Certificate cert)
        throws IOException {
        byte[] extvalue = cert.getExtensionValue("2.5.29.35");
        if (extvalue == null) {
            return null;
        }
        DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(
                    extvalue)).readObject());
        AuthorityKeyIdentifier keyId = new AuthorityKeyIdentifier((ASN1Sequencenew ASN1InputStream(
                    new ByteArrayInputStream(oct.getOctets())).readObject());
        return keyId.getKeyIdentifier();
    // getAuthorityKeyId

    /**
     * Get the subject key identifier from a certificate extensions
     *
     @param cert certificate containing the extension
     @return byte[] containing the subject key identifier
     @throws IOException if extension can not be parsed
     */
    public static byte[] getSubjectKeyId(X509Certificate cert)
        throws IOException {
        byte[] extvalue = cert.getExtensionValue("2.5.29.14");
        if (extvalue == null) {
            return null;
        }
        ASN1OctetString str = ASN1OctetString.getInstance(new ASN1InputStream(
                    new ByteArrayInputStream(extvalue)).readObject());
        SubjectKeyIdentifier keyId = SubjectKeyIdentifier.getInstance(new ASN1InputStream(
                    new ByteArrayInputStream(str.getOctets())).readObject());
        return keyId.getKeyIdentifier();
    // getSubjectKeyId

    /**
     * Get a certificate policy ID from a certificate policies extension
     *
     @param cert certificate containing the extension
     @param pos position of the policy id, if several exist, the first is as pos 0
     @return String with the certificate policy OID
     @throws IOException if extension can not be parsed
     */
    public static String getCertificatePolicyId(X509Certificate cert, int pos)
        throws IOException {
        byte[] extvalue = cert.getExtensionValue(X509Extensions.CertificatePolicies.getId());
        if (extvalue == null) {
            return null;
        }
        DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(
                    extvalue)).readObject());
        ASN1Sequence seq = (ASN1Sequencenew ASN1InputStream(new ByteArrayInputStream(
                    oct.getOctets())).readObject();

        // Check the size so we don't ArrayIndexOutOfBounds
        if (seq.size() (pos + 1)) {
            return null;
        }
        PolicyInformation pol = new PolicyInformation((ASN1Sequenceseq.getObjectAt(
                    pos));
        String id = pol.getPolicyIdentifier().getId();
        return id;
    // getCertificatePolicyId

    /**
     * Gets the Microsoft specific UPN altName.
     *
     @param cert certificate containing the extension
     @return String with the UPN name
     */
    public static String getUPNAltName(X509Certificate cert)
        throws IOException, CertificateParsingException {
        Collection altNames = cert.getSubjectAlternativeNames();
        if (altNames != null) {
            Iterator i = altNames.iterator();
            while (i.hasNext()) {
                List listitem = (Listi.next();
                Integer no = (Integerlistitem.get(0);
                if (no.intValue() == 0) {
                    byte[] altName = (byte[]) listitem.get(1);
                    DERObject oct = (DERObject) (new ASN1InputStream(new ByteArrayInputStream(
                                altName)).readObject());
                    ASN1Sequence seq = ASN1Sequence.getInstance(oct);
                    ASN1TaggedObject obj = (ASN1TaggedObjectseq.getObjectAt(1);
                    DERUTF8String str = DERUTF8String.getInstance(obj.getObject());
                    return str.getString();
                }
            }
        }
        return null;
    // getUPNAltName

    /**
     * Return the CRL distribution point URL form a certificate.
     */
    public static URL getCrlDistributionPoint(X509Certificate certificate)
        throws CertificateParsingException {
        try {
            DERObject obj = getExtensionValue(certificate,
                    X509Extensions.CRLDistributionPoints.getId());
            if (obj == null) {
                return null;
            }
            ASN1Sequence distributionPoints = (ASN1Sequenceobj;
            for (int i = 0; i < distributionPoints.size(); i++) {
                ASN1Sequence distrPoint = (ASN1SequencedistributionPoints.getObjectAt(i);
                for (int j = 0; j < distrPoint.size(); j++) {
                    ASN1TaggedObject tagged = (ASN1TaggedObjectdistrPoint.getObjectAt(j);
                    if (tagged.getTagNo() == 0) {
                        String url = getStringFromGeneralNames(tagged.getObject());
                        if (url != null) {
                            return new URL(url);
                        }
                    }
                }
            }
        catch (Exception e) {
            e.printStackTrace();
            throw new CertificateParsingException(e.toString());
        }
        return null;
    }

    /**
     * Return an Extension DERObject from a certificate
     */
    private static DERObject getExtensionValue(X509Certificate cert, String oid)
        throws IOException {
        byte[] bytes = cert.getExtensionValue(oid);
        if (bytes == null) {
            return null;
        }
        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(
                    bytes));
        ASN1OctetString octs = (ASN1OctetStringaIn.readObject();
        aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
        return aIn.readObject();
    //getExtensionValue

    private static String getStringFromGeneralNames(DERObject names) {
        ASN1Sequence namesSequence = ASN1Sequence.getInstance((ASN1TaggedObjectnames,
                false);
        if (namesSequence.size() == 0) {
            return null;
        }
        DERTaggedObject taggedObject = (DERTaggedObjectnamesSequence.getObjectAt(0);
        return new String(ASN1OctetString.getInstance(taggedObject, false)
                                         .getOctets());
    //getStringFromGeneralNames

    /**
     * Generate SHA1 fingerprint in string representation.
     *
     @param ba Byte array containing DER encoded X509Certificate.
     *
     @return String containing hex format of SHA1 fingerprint.
     */
    public static String getCertFingerprintAsString(byte[] ba) {
        try {
            X509Certificate cert = getCertfromByteArray(ba);
            byte[] res = generateSHA1Fingerprint(cert.getEncoded());

            return Hex.encode(res).toString();
        catch (CertificateEncodingException cee) {
            log.error("Error encoding X509 certificate.", cee);
        catch (CertificateException cee) {
            log.error("Error decoding X509 certificate.", cee);
        catch (IOException ioe) {
            log.error("Error reading byte array for X509 certificate.", ioe);
        }

        return null;
    }

    /**
     * Generate SHA1 fingerprint of certificate in string representation.
     *
     @param cert X509Certificate.
     *
     @return String containing hex format of SHA1 fingerprint.
     */
    public static String getFingerprintAsString(X509Certificate cert) {
        try {
            byte[] res = generateSHA1Fingerprint(cert.getEncoded());

            return Hex.encode(res).toString();
        catch (CertificateEncodingException cee) {
            log.error("Error encoding X509 certificate.", cee);
        }

        return null;
    }

    /**
     * Generate SHA1 fingerprint of CRL in string representation.
     *
     @param crl X509CRL.
     *
     @return String containing hex format of SHA1 fingerprint.
     */
    public static String getFingerprintAsString(X509CRL crl) {
        try {
            byte[] res = generateSHA1Fingerprint(crl.getEncoded());

            return Hex.encode(res).toString();
        catch (CRLException ce) {
            log.error("Error encoding X509 CRL.", ce);
        }

        return null;
    }

    /**
     * Generate a SHA1 fingerprint from a byte array containing a X.509 certificate
     *
     @param ba Byte array containing DER encoded X509Certificate.
     *
     @return Byte array containing SHA1 hash of DER encoded certificate.
     */
    public static byte[] generateSHA1Fingerprint(byte[] ba) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA1");

            return md.digest(ba);
        catch (NoSuchAlgorithmException nsae) {
            log.error("SHA1 algorithm not supported", nsae);
        }

        return null;
    // generateSHA1Fingerprint

    /**
     * Generate a MD5 fingerprint from a byte array containing a X.509 certificate
     *
     @param ba Byte array containing DER encoded X509Certificate.
     *
     @return Byte array containing MD5 hash of DER encoded certificate.
     */
    public static byte[] generateMD5Fingerprint(byte[] ba) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");

            return md.digest(ba);
        catch (NoSuchAlgorithmException nsae) {
            log.error("MD5 algorithm not supported", nsae);
        }

        return null;
    // generateMD5Fingerprint

    public static KeyPair keyPair(int size) {
        KeyPair kp = null;

        // o = ProActiveSecurity.generateGenericCertificate();
        try {
            //acCert = (X509Certificate) o[0];
            //acPrivateKey = (PrivateKey) o[1];
            kp = KeyTools.genKeys(size);
        catch (NoSuchAlgorithmException e4) {
            e4.printStackTrace();
        catch (NoSuchProviderException e4) {
            e4.printStackTrace();
        }
        return kp;
    }
// CertTools