org.ejbca.util.CertTools.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.util.CertTools.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA: The OpenSource Certificate Authority                          *
 *                                                                       *
 *  This software 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.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/

package org.ejbca.util;

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.math.BigInteger;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
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.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorResult;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERGeneralString;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
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.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.ReasonFlags;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x509.X509NameEntryConverter;
import org.bouncycastle.asn1.x509.X509NameTokenizer;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.ejbca.core.model.AlgorithmConstants;
import org.ejbca.core.model.ca.crl.RevokedCertInfo;
import org.ejbca.cvc.AlgorithmUtil;
import org.ejbca.cvc.AuthorizationRoleEnum;
import org.ejbca.cvc.CVCAuthorizationTemplate;
import org.ejbca.cvc.CVCObject;
import org.ejbca.cvc.CVCPublicKey;
import org.ejbca.cvc.CVCertificate;
import org.ejbca.cvc.CardVerifiableCertificate;
import org.ejbca.cvc.CertificateParser;
import org.ejbca.cvc.OIDField;
import org.ejbca.cvc.ReferenceField;
import org.ejbca.cvc.exception.ConstructionException;
import org.ejbca.cvc.exception.ParseException;
import org.ejbca.util.dn.DnComponents;

/**
 * Tools to handle common certificate operations.
 *
 * @version $Id: CertTools.java 15845 2012-11-21 16:26:12Z branko $
 */
public class CertTools {
    private static final Logger log = Logger.getLogger(CertTools.class);

    // Initialize dnComponents
    static {
        DnComponents.getDnObjects(true);
    }
    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";
    public static final String URI2 = "uniformResourceId";
    public static final String IPADDR = "iPAddress";
    public static final String DIRECTORYNAME = "directoryName";

    /** Kerberos altName for smart card logon */
    public static final String KRB5PRINCIPAL = "krb5principal";
    /** OID for Kerberos altName for smart card logon */
    public static final String KRB5PRINCIPAL_OBJECTID = "1.3.6.1.5.2.2";
    /** 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";
    /** Microsoft altName for windows domain controller guid */
    public static final String GUID = "guid";
    /** ObjectID for upn altName for windows domain controller guid */
    public static final String GUID_OBJECTID = "1.3.6.1.4.1.311.25.1";
    /** ObjectID for Microsoft Encrypted File System Certificates extended key usage */
    public static final String EFS_OBJECTID = "1.3.6.1.4.1.311.10.3.4";
    /** ObjectID for Microsoft Encrypted File System Recovery Certificates extended key usage */
    public static final String EFSR_OBJECTID = "1.3.6.1.4.1.311.10.3.4.1";
    /** ObjectID for Microsoft Signer of documents extended key usage */
    public static final String MS_DOCUMENT_SIGNING_OBJECTID = "1.3.6.1.4.1.311.10.3.12";
    /** Object id id-pkix */
    public static final String id_pkix = "1.3.6.1.5.5.7";
    /** Object id id-kp */
    public static final String id_kp = id_pkix + ".3";
    /** Object id id-pda */
    public static final String id_pda = id_pkix + ".9";
    /** Object id id-pda-dateOfBirth 
     * DateOfBirth ::= GeneralizedTime
     */
    public static final String id_pda_dateOfBirth = id_pda + ".1";
    /** Object id id-pda-placeOfBirth 
     * PlaceOfBirth ::= DirectoryString 
     */
    public static final String id_pda_placeOfBirth = id_pda + ".2";
    /** Object id id-pda-gender
     *  Gender ::= PrintableString (SIZE(1))
     *          -- "M", "F", "m" or "f"
     */
    public static final String id_pda_gender = id_pda + ".3";
    /** Object id id-pda-countryOfCitizenship
     * CountryOfCitizenship ::= PrintableString (SIZE (2))
     *                      -- ISO 3166 Country Code 
     */
    public static final String id_pda_countryOfCitizenship = id_pda + ".4";
    /** Object id id-pda-countryOfResidence
     * CountryOfResidence ::= PrintableString (SIZE (2))
     *                    -- ISO 3166 Country Code 
     */
    public static final String id_pda_countryOfResidence = id_pda + ".5";
    /** OID used for creating MS Templates certificate extension */
    public static final String OID_MSTEMPLATE = "1.3.6.1.4.1.311.20.2";
    /** extended key usage OID Intel AMT (out of band) network management */
    public static final String Intel_amt = "2.16.840.1.113741.1.2.3";

    private static final String[] EMAILIDS = { EMAIL, EMAIL1, EMAIL2, EMAIL3 };
    /** ObjectID for unstructuredName DN attribute */
    //public static final DERObjectIdentifier unstructuredName = new DERObjectIdentifier("1.2.840.113549.1.9.2");
    /** ObjectID for unstructuredAddress DN attribute */
    //public static final DERObjectIdentifier unstructuredAddress = new DERObjectIdentifier("1.2.840.113549.1.9.8");

    public static final String BEGIN_CERTIFICATE_REQUEST = "-----BEGIN CERTIFICATE REQUEST-----";
    public static final String END_CERTIFICATE_REQUEST = "-----END CERTIFICATE REQUEST-----";
    public static final String BEGIN_KEYTOOL_CERTIFICATE_REQUEST = "-----BEGIN NEW CERTIFICATE REQUEST-----";
    public static final String END_KEYTOOL_CERTIFICATE_REQUEST = "-----END NEW CERTIFICATE REQUEST-----";
    public static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
    public static final String END_CERTIFICATE = "-----END CERTIFICATE-----";
    public static final String BEGIN_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----";
    public static final String END_PUBLIC_KEY = "-----END PUBLIC KEY-----";

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

    /** See stringToBcX509Name(String, X509NameEntryConverter, boolean), this method uses the default BC converter (X509DefaultEntryConverter) and ldap order
     * @see #stringToBcX509Name(String, X509NameEntryConverter, boolean)
     * @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 added to the end positions of OID array.
     * 
     * @return X509Name or null if input is null
     */
    public static X509Name stringToBcX509Name(String dn) {
        X509NameEntryConverter converter = new X509DefaultEntryConverter();
        return stringToBcX509Name(dn, converter, true);
    }

    /**
     * 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. Important NOT to mess
     * with the ordering within this class, since cert vierification on some
     * clients (IE :-() might depend on order.
     * 
     * @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 added to the end positions of OID array.
     * @param converter BC converter for DirectoryStrings, that determines which encoding is chosen
     * @param ldaporder true if LDAP ordering of DN should be used (default in EJBCA), false for X.500 order, ldap order is CN=A,OU=B,O=C,C=SE, x.500 order is the reverse
     * @return X509Name or null if input is null
     */
    public static X509Name stringToBcX509Name(String dn, X509NameEntryConverter converter, boolean ldaporder) {

        if (dn == null) {
            return null;
        }

        Vector<DERObjectIdentifier> defaultOrdering = new Vector<DERObjectIdentifier>();
        Vector<String> values = new Vector<String>();
        X509NameTokenizer x509NameTokenizer = new X509NameTokenizer(dn);

        while (x509NameTokenizer.hasMoreTokens()) {
            // This is a pair key=val (CN=xx)
            String pair = x509NameTokenizer.nextToken(); // Will escape '+' and initial '#' chars
            int index = pair.indexOf('=');

            if (index != -1) {
                String key = pair.substring(0, index).toLowerCase().trim();
                String val = pair.substring(index + 1);
                if (val != null) {
                    // String whitespace from the beginning of the value, to handle the case
                    // where someone type CN = Foo Bar
                    val = StringUtils.stripStart(val, null);
                }

                // -- First search the OID by name in declared OID's
                DERObjectIdentifier oid = DnComponents.getOid(key);

                try {
                    // -- If isn't declared, we try to create it
                    if (oid == null) {
                        oid = new DERObjectIdentifier(key);
                    }
                    defaultOrdering.add(oid);
                    values.add(getUnescapedPlus(val));
                } catch (IllegalArgumentException e) {
                    // If it is not an OID we will ignore it
                    log.warn("Unknown DN component ignored and silently dropped: " + key);
                }

            } else {
                log.warn("No 'key=value' pair encountered in token '" + pair + "' while converting subject DN '"
                        + dn + "' into X509Name.");
            }
        }

        X509Name x509Name = new X509Name(defaultOrdering, values, converter);

        //-- Reorder fields
        X509Name orderedX509Name = getOrderedX509Name(x509Name, ldaporder, converter);

        //log.trace("<stringToBcX509Name");
        return orderedX509Name;
    } // stringToBcX509Name

    // Remove extra '+' character escaping
    private static String getUnescapedPlus(final String value) {
        final StringBuilder buf = new StringBuilder(value);
        int index = 0;
        int end = buf.length();
        while (index < end) {
            if (buf.charAt(index) == '\\' && index + 1 != end) {
                char c = buf.charAt(index + 1);
                if (c == '+') {
                    buf.deleteCharAt(index);
                    end--;
                }
            }
            index++;
        }
        return buf.toString();
    }

    /**
     *  Check if the String contains any unescaped '+'. RFC 2253, section 2.2
     *  states that '+' is used for multi-valued RelativeDistinguishedName. BC (version 1.45)
     *  currently does not support multi-valued RelativeDistinguishedName, and automatically
     *  escapes it instead. We want to detect unescaped '+' chars and warn that this might not
     *  be supported in the future if support for multi-valued RDNs is implemented.
     */
    private static void detectUnescapedPlus(String dn) {
        if (dn == null) {
            return;
        }
        final StringBuilder buf = new StringBuilder(dn);
        int index = 0;
        int end = buf.length();
        while (index < end) {
            if (buf.charAt(index) == '+') {
                // Found an unescaped '+' character.
                log.warn("DN \"" + dn
                        + "\" contains an unescaped '+'-character that will be automatically escaped. RFC 2253 reservs this "
                        + "for multi-valued RelativeDistinguishedNames. Encourage clients to use '\\+' instead, since future behaviour might change.");
            } else if (buf.charAt(index) == '\\') {
                // Found an escape character.
                index++;
            }
            index++;
        }
    }

    /**
     * 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, or null if input is null
     */
    public static String stringToBCDNString(String dn) {
        /*if (log.isTraceEnabled()) {
           log.trace(">stringToBcDNString: "+dn);
        }*/
        detectUnescapedPlus(dn); // Log warning if dn contains unescaped '+'
        if (isDNReversed(dn)) {
            dn = reverseDN(dn);
        }
        String ret = null;
        X509Name name = stringToBcX509Name(dn);
        if (name != null) {
            ret = name.toString();
        }
        // For some databases (MySQL for instance) the database column holding subjectDN
        // is only 250 chars long. There have been strange error reported (clipping DN natuarally)
        // that is hard to debug if DN is more than 250 chars and we don't have a good message
        if ((ret != null) && (ret.length() > 250)) {
            log.info(
                    "Warning! DN is more than 250 characters long. Some databases have only 250 characters in the database for SubjectDN. Clipping may occur! DN ("
                            + ret.length() + " chars): " + ret);
        }
        /*if (log.isTraceEnabled()) {
        log.trace("<stringToBcDNString: "+ret);
        }*/
        return ret;
    }

    /**
     * Convenience method for getting an email addresses from a DN. Uses {@link
     * #getPartsFromDN(String,String)} internally, and searches for {@link #EMAIL}, {@link #EMAIL1},
     * {@link #EMAIL2}, {@link #EMAIL3} and returns the first one found.
     *
     * @param dn the DN
     *
    * @return ArrayList containing email or empty list if email is not present
     */
    public static ArrayList<String> getEmailFromDN(String dn) {
        if (log.isTraceEnabled()) {
            log.trace(">getEmailFromDN(" + dn + ")");
        }
        ArrayList<String> ret = new ArrayList<String>();
        for (int i = 0; i < EMAILIDS.length; i++) {
            ArrayList<String> emails = getPartsFromDN(dn, EMAILIDS[i]);
            if (!emails.isEmpty()) {
                ret.addAll(emails);
            }

        }
        if (log.isTraceEnabled()) {
            log.trace("<getEmailFromDN(" + dn + "): " + ret.size());
        }
        return ret;
    }

    /**
     * Search for e-mail address, first in SubjectAltName (as in PKIX
     * recommendation) then in subject DN.
     * Original author: Marco Ferrante, (c) 2005 CSITA - University of Genoa (Italy)
     * 
     * @param certificate
     * @return subject email or null if not present in certificate
     */
    public static String getEMailAddress(Certificate certificate) {
        log.debug("Searching for EMail Address in SubjectAltName");
        if (certificate == null) {
            return null;
        }
        if (certificate instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) certificate;
            try {
                if (x509cert.getSubjectAlternativeNames() != null) {
                    for (List<?> item : x509cert.getSubjectAlternativeNames()) {
                        Integer type = (Integer) item.get(0);
                        if (type.intValue() == 1) {
                            return (String) item.get(1);
                        }
                    }
                }
            } catch (CertificateParsingException e) {
                log.error("Error parsing certificate: ", e);
            }
            log.debug("Searching for EMail Address in Subject DN");
            ArrayList<String> emails = CertTools.getEmailFromDN(x509cert.getSubjectDN().getName());
            if (!emails.isEmpty()) {
                return (String) emails.get(0);
            }
        }
        return null;
    }

    /**
     * Takes a DN and reverses it completely so the first attribute ends up last. 
     * C=SE,O=Foo,CN=Bar becomes CN=Bar,O=Foo,C=SE.
     *
     * @param dn String containing DN to be reversed, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
     *
     * @return String containing reversed DN
     */
    public static String reverseDN(String dn) {
        if (log.isTraceEnabled()) {
            log.trace(">reverseDN: dn: " + dn);
        }
        String ret = null;
        if (dn != null) {
            String o;
            final BasicX509NameTokenizer xt = new BasicX509NameTokenizer(dn);
            final StringBuilder buf = new StringBuilder();
            boolean first = true;
            while (xt.hasMoreTokens()) {
                o = xt.nextToken();
                //log.debug("token: "+o);
                if (!first) {
                    buf.insert(0, ",");
                } else {
                    first = false;
                }
                buf.insert(0, o);
            }
            if (buf.length() > 0) {
                ret = buf.toString();
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<reverseDN: resulting dn: " + ret);
        }
        return ret;
    } //reverseDN

    /**
     * Tries to determine if a DN is in reversed form. It does this by taking the last attribute 
     * and the first attribute. If the last attribute comes before the first in the dNObjects array
     * the DN is assumed to be in reversed order.
     * 
     * The default ordering is:
     * "CN=Tomas, O=PrimeKey, C=SE" (dNObjectsForward ordering in EJBCA) a dn or form "C=SE, O=PrimeKey, CN=Tomas" is reversed.
     * 
     *
     * @param dn String containing DN to be checked, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
     *
     * @return true if the DN is believed to be in reversed order, false otherwise
     */
    protected static boolean isDNReversed(String dn) {
        /*if (log.isTraceEnabled()) {
           log.trace(">isDNReversed: dn: " + dn);
        }*/
        boolean ret = false;
        if (dn != null) {
            String first = null;
            String last = null;
            X509NameTokenizer xt = new X509NameTokenizer(dn);
            if (xt.hasMoreTokens()) {
                first = xt.nextToken();
            }
            while (xt.hasMoreTokens()) {
                last = xt.nextToken();
            }
            String[] dNObjects = DnComponents.getDnObjects(true);
            if ((first != null) && (last != null)) {
                first = first.substring(0, first.indexOf('='));
                last = last.substring(0, last.indexOf('='));
                int firsti = 0, lasti = 0;
                for (int i = 0; i < dNObjects.length; i++) {
                    if (first.toLowerCase().equals(dNObjects[i])) {
                        firsti = i;
                    }
                    if (last.toLowerCase().equals(dNObjects[i])) {
                        lasti = i;
                    }
                }
                if (lasti < firsti) {
                    ret = true;
                }

            }
        }
        /*if (log.isTraceEnabled()) {
           log.trace("<isDNReversed: " + ret);
        }*/
        return ret;
    } //isDNReversed

    /**
     * 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) {
        if (log.isTraceEnabled()) {
            log.trace(">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;
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<getpartFromDN: resulting DN part=" + part);
        }
        return part;
    } //getPartFromDN

    /**
    * Gets a specified parts of a DN. Returns all occurences as an ArrayList, also works if DN contains several
    * instances of a part (i.e. cn=x, cn=y returns {x, y, null}).
    *
    * @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 ArrayList containing dnparts or empty list if dnpart is not present
    */
    public static ArrayList<String> getPartsFromDN(String dn, String dnpart) {
        if (log.isTraceEnabled()) {
            log.trace(">getPartsFromDN: dn:'" + dn + "', dnpart=" + dnpart);
        }
        ArrayList<String> parts = new ArrayList<String>();
        if ((dn != null) && (dnpart != null)) {
            String o;
            dnpart += "="; // we search for 'CN=' etc.
            X509NameTokenizer xt = new X509NameTokenizer(dn);
            while (xt.hasMoreTokens()) {
                o = xt.nextToken();
                if ((o.length() > dnpart.length()) && o.substring(0, dnpart.length()).equalsIgnoreCase(dnpart)) {
                    parts.add(o.substring(dnpart.length()));
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<getpartsFromDN: resulting DN part=" + parts.toString());
        }
        return parts;
    } //getPartFromDN

    /**
    * Gets a list of all custom OIDs defined in the string. A custom OID is defined as an OID, simply as that. Otherwise, if it is not a custom oid, the DNpart is defined by a name such as CN och rfc822Name.
    * This method only returns a oid once, so if the input string has multiple of the same oid, only one value is returned.
    *
    * @param dn String containing DN, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz", or "rfc822Name=foo@bar.com", etc.
    * @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc.
    *
    * @return ArrayList containing unique oids or empty list if no custom OIDs are present
    */
    public static ArrayList<String> getCustomOids(String dn) {
        if (log.isTraceEnabled()) {
            log.trace(">getCustomOids: dn:'" + dn);
        }
        ArrayList<String> parts = new ArrayList<String>();
        if (dn != null) {
            String o;
            X509NameTokenizer xt = new X509NameTokenizer(dn);
            while (xt.hasMoreTokens()) {
                o = xt.nextToken();
                // Try to see if it is a valid OID
                try {
                    int i = o.indexOf('=');
                    // An oid is never shorter than 3 chars and must start with 1.
                    if ((i > 2) && (o.charAt(1) == '.')) {
                        String oid = o.substring(0, i);
                        // If we have multiple of the same custom oid, don't claim that we have more
                        // This method will only return "unique" custom oids.
                        if (!parts.contains(oid)) {
                            // Check if it is a real oid, if it is not we will ignore it (IllegalArgumentException will be thrown)
                            new DERObjectIdentifier(oid);
                            parts.add(oid);
                        }
                    }
                } catch (IllegalArgumentException e) {
                    // Not a valid oid
                }
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<getpartsFromDN: resulting DN part=" + parts.toString());
        }
        return parts;
    } //getPartFromDN

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

    /**
     * Gets issuer DN in the format we are sure about (BouncyCastle),supporting UTF8.
     *
     * @param cert Certificate
     *
     * @return String containing the issuers DN.
     */
    public static String getIssuerDN(Certificate 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 1 = subjectDN, anything else = issuerDN
     *
     * @return String containing the DN.
     */
    private static String getDN(Certificate cert, int which) {
        /*if (log.isTraceEnabled()) {
           log.trace(">getDN("+which+")");
        }*/
        String ret = null;
        if (cert == null) {
            return null;
        }
        if (cert instanceof X509Certificate) {
            // cert.getType=X.509
            try {
                CertificateFactory cf = CertTools.getCertificateFactory();
                X509Certificate x509cert = (X509Certificate) cf
                        .generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
                //log.debug("Created certificate of class: " + x509cert.getClass().getName());
                String dn = null;
                if (which == 1) {
                    dn = x509cert.getSubjectDN().toString();
                } else {
                    dn = x509cert.getIssuerDN().toString();
                }
                ret = stringToBCDNString(dn);
            } catch (CertificateException ce) {
                log.info("Could not get DN from X509Certificate. " + ce.getMessage());
                log.debug("", ce);
                return null;
            }
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                ReferenceField rf = null;
                if (which == 1) {
                    rf = cvccert.getCVCertificate().getCertificateBody().getHolderReference();
                } else {
                    rf = cvccert.getCVCertificate().getCertificateBody().getAuthorityReference();
                }
                if (rf != null) {
                    // Construct a "fake" DN which can be used in EJBCA
                    // Use only mnemonic and country, since sequence is more of a serialnumber than a DN part
                    String dn = "";
                    //                if (rf.getSequence() != null) {
                    //                   dn += "SERIALNUMBER="+rf.getSequence();
                    //                }
                    if (rf.getMnemonic() != null) {
                        if (StringUtils.isNotEmpty(dn)) {
                            dn += ", ";
                        }
                        dn += "CN=" + rf.getMnemonic();
                    }
                    if (rf.getCountry() != null) {
                        if (StringUtils.isNotEmpty(dn)) {
                            dn += ", ";
                        }
                        dn += "C=" + rf.getCountry();
                    }
                    ret = stringToBCDNString(dn);
                }
            } catch (NoSuchFieldException e) {
                log.error("NoSuchFieldException: ", e);
                return null;
            }
        }
        /*if (log.isTraceEnabled()) {
           log.trace("<getDN("+which+"):"+dn);
        }*/
        return ret;
    } // getDN

    /**
     * Gets Serial number of the certificate.
     *
     * @param cert Certificate
     *
     * @return BigInteger containing the certificate serialNumber. Can be 0 for CVC certificates with alphanumering serialnumbers if the sequence does not contain any number characters at all.
     */
    public static BigInteger getSerialNumber(Certificate cert) {
        BigInteger ret = null;
        if (cert instanceof X509Certificate) {
            X509Certificate xcert = (X509Certificate) cert;
            ret = xcert.getSerialNumber();
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            // For CVC certificates the sequence field of the HolderReference is kind of a serial number,
            // but if can be alphanumeric which means it can not be made into a BigInteger
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                String sequence = cvccert.getCVCertificate().getCertificateBody().getHolderReference()
                        .getSequence();
                ret = getSerialNumberFromString(sequence);
            } catch (NoSuchFieldException e) {
                log.error("getSerialNumber: NoSuchFieldException: ", e);
                ret = BigInteger.valueOf(0);
            }
        } else {
            throw new IllegalArgumentException(
                    "getSerialNumber: Certificate of type " + cert.getType() + " is not implemented");
        }
        return ret;
    }

    /** Gets a serial number in numeric form, it takes
     * - either a hex encoded integer with length != 5 (x.509 certificate)
     * - 5 letter numeric string (cvc), will convert the number to an int
     * - 5 letter alfanumeric string vi some numbers in it (cvc), will convert the numbers in it to a numeric string (remove the letters) and convert to int
     * - 5 letter alfanumeric string with only letters (cvc), will convert to integer from string with radix 36
     * 
     * @param sernoString
     * @return BigInteger
     */
    public static BigInteger getSerialNumberFromString(String sernoString) {
        BigInteger ret;
        if (sernoString.length() != 5) {
            // This can not be a CVC certificate sequence, so it must be a hex encoded regular certificate serial number
            ret = new BigInteger(sernoString, 16);
        } else {
            // We try to handle the different cases of CVC certificate sequences, see StringTools.KEY_SEQUENCE_FORMAT
            try {
                if (NumberUtils.isNumber(sernoString)) {
                    ret = NumberUtils.createBigInteger(sernoString);
                } else {
                    // check if input is hexadecimal
                    log.info(
                            "getSerialNumber: Sequence is not a numeric string, trying to extract numerical sequence part.");
                    final StringBuilder buf = new StringBuilder();
                    for (int i = 0; i < sernoString.length(); i++) {
                        char c = sernoString.charAt(i);
                        if (CharUtils.isAsciiNumeric(c)) {
                            buf.append(c);
                        }
                    }
                    if (buf.length() > 0) {
                        ret = NumberUtils.createBigInteger(buf.toString());
                    } else {
                        log.info(
                                "getSerialNumber: can not extract numeric sequence part, trying alfanumeric value (radix 36).");
                        if (sernoString.matches("[0-9A-Z]{1,5}")) {
                            int numSeq = Integer.parseInt(sernoString, 36);
                            ret = BigInteger.valueOf(numSeq);
                        } else {
                            log.info("getSerialNumber: Sequence does not contain any numeric parts, returning 0.");
                            ret = BigInteger.valueOf(0);
                        }
                    }
                }
            } catch (NumberFormatException e) {
                // If we can't make the sequence into a serial number big integer, set it to 0
                log.debug("getSerialNumber: NumberFormatException for sequence: " + sernoString);
                ret = BigInteger.valueOf(0);
            }
        }
        return ret;
    }

    /**
     * Gets Serial number of the certificate as a string. For X509 Certificate this means a HEX encoded BigInteger, and for CVC certificate is
     * means the sequence field of the holder reference.
     *
     * @param cert Certificate
     *
     * @return String to be displayed
     */
    public static String getSerialNumberAsString(Certificate cert) {
        String ret = null;
        if (cert == null) {
            throw new IllegalArgumentException("getSerialNumber: cert is null");
        }
        if (cert instanceof X509Certificate) {
            X509Certificate xcert = (X509Certificate) cert;
            ret = xcert.getSerialNumber().toString(16).toUpperCase();
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            // For CVC certificates the sequence field of the HolderReference is kind of a serial number,
            // but if can be alphanumeric which means it can not be made into a BigInteger
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                ret = cvccert.getCVCertificate().getCertificateBody().getHolderReference().getSequence();
            } catch (NoSuchFieldException e) {
                log.error("getSerialNumber: NoSuchFieldException: ", e);
                ret = "N/A";
            }
        } else {
            throw new IllegalArgumentException(
                    "getSerialNumber: Certificate of type " + cert.getType() + " is not implemented");
        }
        return ret;
    }

    /**
     * Gets the signature value (the raw signature bits) from the certificate. 
     * For an X509 certificate this is the ASN.1 definition which is:
     * signature     BIT STRING  
     *
     * @param cert Certificate
     *
     * @return byte[] containing the certificate signature bits, if cert is null a byte[] of size 0 is returned.
     */
    public static byte[] getSignature(Certificate cert) {
        byte[] ret = null;
        if (cert == null) {
            ret = new byte[0];
        } else {
            if (cert instanceof X509Certificate) {
                X509Certificate xcert = (X509Certificate) cert;
                ret = xcert.getSignature();
            } else if (StringUtils.equals(cert.getType(), "CVC")) {
                CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
                try {
                    ret = cvccert.getCVCertificate().getSignature();
                } catch (NoSuchFieldException e) {
                    log.error("NoSuchFieldException: ", e);
                    return null;
                }
            }
        }
        return ret;
    }

    /**
     * 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) {
        /*if (log.isTraceEnabled()) {
           log.trace(">getIssuerDN(crl)");
        }*/
        String dn = null;
        try {
            CertificateFactory cf = CertTools.getCertificateFactory();
            X509CRL x509crl = (X509CRL) cf.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;
        }
        /*if (log.isTraceEnabled()) {
           log.trace("<getIssuerDN(crl):"+dn);
        }*/
        return stringToBCDNString(dn);
    } // getIssuerDN

    public static Date getNotBefore(Certificate cert) {
        Date ret = null;
        if (cert == null) {
            throw new IllegalArgumentException("getNotBefore: cert is null");
        }
        if (cert instanceof X509Certificate) {
            X509Certificate xcert = (X509Certificate) cert;
            ret = xcert.getNotBefore();
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                ret = cvccert.getCVCertificate().getCertificateBody().getValidFrom();
            } catch (NoSuchFieldException e) {
                // it is not uncommon that this field is missing in CVC certificate requests (it's not in the EAC standard so)
                log.debug("NoSuchFieldException: " + e.getMessage());
                return null;
            }
        }
        return ret;
    }

    public static Date getNotAfter(Certificate cert) {
        Date ret = null;
        if (cert == null) {
            throw new IllegalArgumentException("getNotAfter: cert is null");
        }
        if (cert instanceof X509Certificate) {
            X509Certificate xcert = (X509Certificate) cert;
            ret = xcert.getNotAfter();
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                ret = cvccert.getCVCertificate().getCertificateBody().getValidTo();
            } catch (NoSuchFieldException e) {
                // it is not uncommon that this field is missing in CVC certificate requests (it's not in the EAC standard so)
                log.debug("NoSuchFieldException: " + e.getMessage());
                return null;
            }
        }
        return ret;
    }

    public static CertificateFactory getCertificateFactory(String provider) {
        String prov = provider;
        if (provider == null) {
            prov = "BC";
        }
        if (StringUtils.equals(prov, "BC")) {
            installBCProviderIfNotAvailable();
        }
        try {
            return CertificateFactory.getInstance("X.509", prov);
        } catch (NoSuchProviderException nspe) {
            log.error("NoSuchProvider: ", nspe);
        } catch (CertificateException ce) {
            log.error("CertificateException: ", ce);
        }
        return null;
    }

    public static CertificateFactory getCertificateFactory() {
        return getCertificateFactory("BC");
    }

    /**
     * @deprecated Use CryptoProviderTools.installBCProviderIfNotAvailable() instead
     */
    public static synchronized void installBCProviderIfNotAvailable() {
        CryptoProviderTools.installBCProviderIfNotAvailable();
    }

    /**
     * @deprecated Use CryptoProviderTools.removeBCProvider() instead
     */
    public static synchronized void removeBCProvider() {
        CryptoProviderTools.removeBCProvider();
    }

    /**
     * @deprecated Use CryptoProviderTools.installBCProvider() instead
     */
    public static synchronized void installBCProvider() {
        CryptoProviderTools.installBCProvider();
    }

    /**
     * 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 Certificate, 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<Certificate> getCertsFromPEM(String certFile)
            throws IOException, CertificateException {
        if (log.isTraceEnabled()) {
            log.trace(">getCertfromPEM: certFile=" + certFile);
        }
        InputStream inStrm = null;
        Collection<Certificate> certs;
        try {
            inStrm = new FileInputStream(certFile);
            certs = getCertsFromPEM(inStrm);
        } finally {
            if (inStrm != null) {
                inStrm.close();
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<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 Certificate, 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<Certificate> getCertsFromPEM(InputStream certstream)
            throws IOException, CertificateException {
        if (log.isTraceEnabled()) {
            log.trace(">getCertfromPEM");
        }
        ArrayList<Certificate> ret = new ArrayList<Certificate>();
        String beginKeyTrust = "-----BEGIN TRUSTED CERTIFICATE-----";
        String endKeyTrust = "-----END TRUSTED CERTIFICATE-----";
        BufferedReader bufRdr = null;
        ByteArrayOutputStream ostr = null;
        PrintStream opstr = null;
        try {
            bufRdr = new BufferedReader(new InputStreamReader(certstream));
            while (bufRdr.ready()) {
                ostr = new ByteArrayOutputStream();
                opstr = new PrintStream(ostr);
                String temp;
                while ((temp = bufRdr.readLine()) != null
                        && !(temp.equals(CertTools.BEGIN_CERTIFICATE) || temp.equals(beginKeyTrust))) {
                    continue;
                }
                if (temp == null) {
                    if (ret.isEmpty()) {
                        // There was no certificate in the file
                        throw new IOException("Error in " + certstream.toString() + ", missing "
                                + CertTools.BEGIN_CERTIFICATE + " boundary");
                    } else {
                        // There were certificates, but some blank lines or something in the end
                        // anyhow, the file has ended so we can break here.
                        break;
                    }
                }
                while ((temp = bufRdr.readLine()) != null
                        && !(temp.equals(CertTools.END_CERTIFICATE) || temp.equals(endKeyTrust))) {
                    opstr.print(temp);
                }
                if (temp == null) {
                    throw new IOException("Error in " + certstream.toString() + ", missing "
                            + CertTools.END_CERTIFICATE + " boundary");
                }
                opstr.close();

                byte[] certbuf = Base64.decode(ostr.toByteArray());
                ostr.close();
                // Phweeew, were done, now decode the cert from file back to Certificate object
                Certificate cert = getCertfromByteArray(certbuf);
                ret.add(cert);
            }
        } finally {
            if (bufRdr != null) {
                bufRdr.close();
            }
            if (opstr != null) {
                opstr.close();
            }
            if (ostr != null) {
                ostr.close();
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<getcertfromPEM:" + ret.size());
        }
        return ret;
    } // getCertsFromPEM

    /** Converts a regular array of certificates into an ArrayList, using the provided provided.
     * 
     * @param certs Certificate[] of certificates to convert
     * @param provider provider for example "SUN" or "BC", use null for the default provider (BC)
     * @return An ArrayList of certificates in the same order as the passed in array
     * @throws NoSuchProviderException 
     * @throws CertificateException 
     */
    public static ArrayList<Certificate> getCertCollectionFromArray(Certificate[] certs, String provider)
            throws CertificateException, NoSuchProviderException {
        if (log.isTraceEnabled()) {
            log.trace(">getCertCollectionFromArray: " + provider);
        }
        ArrayList<Certificate> ret = new ArrayList<Certificate>();
        String prov = provider;
        if (prov == null) {
            prov = "BC";
        }
        for (int i = 0; i < certs.length; i++) {
            Certificate cert = certs[i];
            Certificate newcert = getCertfromByteArray(cert.getEncoded(), prov);
            ret.add(newcert);
        }
        if (log.isTraceEnabled()) {
            log.trace("<getCertCollectionFromArray: " + ret.size());
        }
        return ret;
    }

    /**
     * Returns a certificate in PEM-format.
     *
     * @param certs Collection of Certificate to convert to PEM
     * @return byte array containing PEM certificate
     * @exception CertificateException if the stream does not contain a correct certificate.
     */
    public static byte[] getPEMFromCerts(Collection<Certificate> certs) throws CertificateException {
        ByteArrayOutputStream ostr = new ByteArrayOutputStream();
        PrintStream opstr = new PrintStream(ostr);
        Iterator<Certificate> iter = certs.iterator();
        while (iter.hasNext()) {
            Certificate cert = (Certificate) iter.next();
            byte[] certbuf = Base64.encode(cert.getEncoded());
            opstr.println("Subject: " + CertTools.getSubjectDN(cert));
            opstr.println("Issuer: " + CertTools.getIssuerDN(cert));
            opstr.println(CertTools.BEGIN_CERTIFICATE);
            opstr.println(new String(certbuf));
            opstr.println(CertTools.END_CERTIFICATE);
        }
        opstr.close();
        byte[] ret = ostr.toByteArray();
        return ret;
    }

    /**
     * Returns a CRL in PEM-format.
     *
     * @param crlbytes the der encoded crl bytes to convert to PEM
     * @return byte array containing PEM CRL
     * @exception IOException if the stream cannot be read.
     */
    public static byte[] getPEMFromCrl(byte[] crlbytes) {
        String beginKey = "-----BEGIN X509 CRL-----";
        String endKey = "-----END X509 CRL-----";
        ByteArrayOutputStream ostr = new ByteArrayOutputStream();
        PrintStream opstr = new PrintStream(ostr);
        byte[] crlb64 = Base64.encode(crlbytes);
        opstr.println(beginKey);
        opstr.println(new String(crlb64));
        opstr.println(endKey);
        opstr.close();
        byte[] ret = ostr.toByteArray();
        return ret;
    }

    /**
     * Creates Certificate from byte[], can be either an X509 certificate or a CVCCertificate
     *
     * @param cert byte array containing certificate in binary (DER) format, or PEM encoded X.509 certificate
     * @param provider provider for example "SUN" or "BC", use null for the default provider (BC)
     *
     * @return Certificate
     *
     * @throws CertificateException if the byte array does not contain a proper certificate.
     * @throws IOException if the byte array cannot be read.
     */
    public static Certificate getCertfromByteArray(byte[] cert, String provider) throws CertificateException {
        /*if (log.isTraceEnabled()) {
           log.trace(">getCertfromByteArray");
        }*/
        Certificate ret = null;
        String prov = provider;
        if (provider == null) {
            prov = "BC";
        }
        try {
            CertificateFactory cf = CertTools.getCertificateFactory(prov);
            ret = cf.generateCertificate(new ByteArrayInputStream(cert));
        } catch (CertificateException e) {
            log.debug("CertificateException trying to read X509Certificate.");
        }
        if (ret == null) {
            // We could not create an X509Certificate, see if it is a CVC certificate instead
            try {
                CVCertificate parsedObject = CertificateParser.parseCertificate(cert);
                ret = new CardVerifiableCertificate(parsedObject);
            } catch (ParseException e) {
                log.debug("ParseException trying to read CVCCertificate.");
                throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
            } catch (ConstructionException e) {
                log.debug("ConstructionException trying to read CVCCertificate.");
                throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
            } catch (IllegalArgumentException e) {
                log.debug("CertificateException trying to read CVCCertificate.");
                throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
            }
        }
        if (ret == null) {
            throw new CertificateException("No certificate found");
        }
        //log.trace("<getCertfromByteArray");
        return ret;
    } // getCertfromByteArray

    public static Certificate getCertfromByteArray(byte[] cert) throws CertificateException {
        return getCertfromByteArray(cert, "BC");
    }

    /**
     * Creates X509CRL from byte[].
     *
     * @param crl byte array containing CRL in DER-format
     *
     * @return X509CRL
     *
     * @throws CertificateException if the byte array does not contain a correct CRL.
     * @throws CRLException if the byte array does not contain a correct CRL.
     */
    public static X509CRL getCRLfromByteArray(byte[] crl) throws CRLException {
        log.trace(">getCRLfromByteArray");
        CertificateFactory cf = CertTools.getCertificateFactory();
        X509CRL x509crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(crl));
        log.trace("<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(Certificate cert) {
        if (log.isTraceEnabled()) {
            log.trace(">isSelfSigned: cert: " + CertTools.getIssuerDN(cert) + "\n" + CertTools.getSubjectDN(cert));
        }
        boolean ret = CertTools.getSubjectDN(cert).equals(CertTools.getIssuerDN(cert));
        if (log.isTraceEnabled()) {
            log.trace("<isSelfSigned:" + ret);
        }
        return ret;
    } // isSelfSigned

    /**
     * Checks if a certificate is a CA certificate according to BasicConstraints (X.509), or role (CVC).
     * If there is no basic constraints extension on a X.509 certificate, false is returned.
     *
     * @param cert the certificate that skall be checked.
     *
     * @return boolean true if the certificate belongs to a CA.
     */
    public static boolean isCA(Certificate cert) {
        log.trace(">isCA");
        boolean ret = false;
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            if (x509cert.getBasicConstraints() > -1) {
                ret = true;
            }
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            try {
                CVCAuthorizationTemplate templ = cvccert.getCVCertificate().getCertificateBody()
                        .getAuthorizationTemplate();
                AuthorizationRoleEnum role = templ.getAuthorizationField().getRole();
                if (role.equals(AuthorizationRoleEnum.CVCA) || role.equals(AuthorizationRoleEnum.DV_D)
                        || role.equals(AuthorizationRoleEnum.DV_F)) {
                    ret = true;
                }
            } catch (NoSuchFieldException e) {
                log.error("NoSuchFieldException: ", e);
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<isCA:" + ret);
        }
        return ret;
    } // isSelfSigned

    /**
     * Generate a selfsigned certiicate.
     *
     * @param dn subject and issuer DN
     * @param validity in days
     * @param policyId policy string ('2.5.29.32.0') or null
     * @param privKey private key
     * @param pubKey public key
     * @param sigAlg signature algorithm, you can use one of the contants AlgorithmConstants.SIGALG_XXX
     * @param isCA boolean true or false
     *
     * @return X509Certificate, self signed
     *
     * @throws NoSuchAlgorithmException DOCUMENT ME!
     * @throws SignatureException DOCUMENT ME!
     * @throws InvalidKeyException DOCUMENT ME!
     * @throws IllegalStateException 
     * @throws CertificateEncodingException 
     * @throws NoSuchProviderException 
     */
    public static X509Certificate genSelfCert(String dn, long validity, String policyId, PrivateKey privKey,
            PublicKey pubKey, String sigAlg, boolean isCA) throws NoSuchAlgorithmException, SignatureException,
            InvalidKeyException, CertificateEncodingException, IllegalStateException, NoSuchProviderException {
        return genSelfCert(dn, validity, policyId, privKey, pubKey, sigAlg, isCA, "BC");
    }

    public static X509Certificate genSelfCert(String dn, long validity, String policyId, PrivateKey privKey,
            PublicKey pubKey, String sigAlg, boolean isCA, String provider)
            throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CertificateEncodingException,
            IllegalStateException, NoSuchProviderException {
        int keyusage = X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
        return genSelfCertForPurpose(dn, validity, policyId, privKey, pubKey, sigAlg, isCA, keyusage, provider);
    } //genselfCert

    /**
     * Generate a selfsigned certiicate with possibility to specify key usage.
     *
     * @param dn subject and issuer DN
     * @param validity in days
     * @param policyId policy string ('2.5.29.32.0') or null
     * @param privKey private key
     * @param pubKey public key
     * @param sigAlg signature algorithm, you can use one of the contants AlgorithmConstants.SIGALG_XXX
     * @param isCA boolean true or false
     * @param keyusage as defined by constants in X509KeyUsage
     *
     * @return X509Certificate, self signed
     *
     * @throws NoSuchAlgorithmException DOCUMENT ME!
     * @throws SignatureException DOCUMENT ME!
     * @throws InvalidKeyException DOCUMENT ME!
     * @throws IllegalStateException 
     * @throws CertificateEncodingException 
     * @throws NoSuchProviderException 
     */
    public static X509Certificate genSelfCertForPurpose(String dn, long validity, String policyId,
            PrivateKey privKey, PublicKey pubKey, String sigAlg, boolean isCA, int keyusage)
            throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CertificateEncodingException,
            IllegalStateException, NoSuchProviderException {
        return genSelfCertForPurpose(dn, validity, policyId, privKey, pubKey, sigAlg, isCA, keyusage, "BC");
    }

    public static X509Certificate genSelfCertForPurpose(String dn, long validity, String policyId,
            PrivateKey privKey, PublicKey pubKey, String sigAlg, boolean isCA, int keyusage, String provider)
            throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CertificateEncodingException,
            IllegalStateException, NoSuchProviderException {
        // Create self signed certificate
        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();

        // Transform the PublicKey to be sure we have it in a format that the X509 certificate generator handles, it might be 
        // a CVC public key that is passed as parameter
        PublicKey publicKey = null;
        if (pubKey instanceof RSAPublicKey) {
            RSAPublicKey rsapk = (RSAPublicKey) pubKey;
            RSAPublicKeySpec rSAPublicKeySpec = new RSAPublicKeySpec(rsapk.getModulus(), rsapk.getPublicExponent());
            try {
                publicKey = KeyFactory.getInstance("RSA").generatePublic(rSAPublicKeySpec);
            } catch (InvalidKeySpecException e) {
                log.error("Error creating RSAPublicKey from spec: ", e);
                publicKey = pubKey;
            }
        } else if (pubKey instanceof ECPublicKey) {
            ECPublicKey ecpk = (ECPublicKey) pubKey;
            try {
                ECPublicKeySpec ecspec = new ECPublicKeySpec(ecpk.getW(), ecpk.getParams()); // will throw NPE if key is "implicitlyCA"
                publicKey = KeyFactory.getInstance("EC").generatePublic(ecspec);
            } catch (InvalidKeySpecException e) {
                log.error("Error creating ECPublicKey from spec: ", e);
                publicKey = pubKey;
            } catch (NullPointerException e) {
                log.debug("NullPointerException, probably it is implicitlyCA generated keys: " + e.getMessage());
                publicKey = pubKey;
            }
        } else {
            log.debug("Not converting key of class. " + pubKey.getClass().getName());
            publicKey = pubKey;
        }

        // 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(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(publicKey);

        // 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) {
            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 Firefox.
        try {
            if (isCA) {
                SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(
                        (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(publicKey.getEncoded()))
                                .readObject());
                SubjectKeyIdentifier ski = new SubjectKeyIdentifier(spki);

                SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo(
                        (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(publicKey.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.generate(privKey, provider);

        return selfcert;
    } //genselfCertForPurpose

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

    /**
     * Get the subject key identifier from a certificate extensions
     *
     * @param cert certificate containing the extension
     * @return byte[] containing the subject key identifier, or null if it does not exist
     * @throws IOException if extension can not be parsed
     */
    public static byte[] getSubjectKeyId(Certificate cert) throws IOException {
        if (cert == null) {
            return null;
        }
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            byte[] extvalue = x509cert.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();
        }
        return null;
    } // 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(Certificate cert, int pos) throws IOException {
        String ret = null;
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            byte[] extvalue = x509cert.getExtensionValue(X509Extensions.CertificatePolicies.getId());
            if (extvalue == null) {
                return null;
            }
            DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(extvalue))
                    .readObject());
            ASN1Sequence seq = (ASN1Sequence) new 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((ASN1Sequence) seq.getObjectAt(pos));
            ret = pol.getPolicyIdentifier().getId();
        }
        return ret;
    } // getCertificatePolicyId

    /**
     * Gets the Microsoft specific UPN altName (altName, OtherName).
     * 
     * UPN is an OtherName Subject Alternative Name:
     * 
     * OtherName ::= SEQUENCE {
     *       type-id    OBJECT IDENTIFIER,
     *       value      [0] EXPLICIT ANY DEFINED BY type-id }
     *       
     * UPN ::= UTF8String
     *
     * @param cert certificate containing the extension
     * @return String with the UPN name or null if the altName does not exist
     */
    public static String getUPNAltName(Certificate cert) throws IOException, CertificateParsingException {
        String ret = null;
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            Collection<List<?>> altNames = x509cert.getSubjectAlternativeNames();
            if (altNames != null) {
                Iterator<List<?>> i = altNames.iterator();
                while (i.hasNext()) {
                    ASN1Sequence seq = getAltnameSequence((List<?>) i.next());
                    ret = getUPNStringFromSequence(seq);
                    if (ret != null) {
                        break;
                    }
                }
            }
        }
        return ret;
    } // getUPNAltName

    /** Helper method for the above method
     * @param seq the OtherName sequence
     */
    private static String getUPNStringFromSequence(ASN1Sequence seq) {
        if (seq != null) {
            // First in sequence is the object identifier, that we must check
            DERObjectIdentifier id = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
            if (id.getId().equals(CertTools.UPN_OBJECTID)) {
                ASN1TaggedObject obj = (ASN1TaggedObject) seq.getObjectAt(1);
                DERUTF8String str = DERUTF8String.getInstance(obj.getObject());
                return str.getString();
            }
        }
        return null;
    }

    /** Helper method for getting kerberos 5 principal name (altName, OtherName)
     * 
     * Krb5PrincipalName is an OtherName Subject Alternative Name
     * 
     * String representation is in form "principalname1/principalname2@realm"
     * 
     * KRB5PrincipalName ::= SEQUENCE {
     *      realm [0] Realm,
     *      principalName [1] PrincipalName
     * }
     * 
     * Realm ::= KerberosString
     *
     * PrincipalName ::= SEQUENCE {
     *      name-type [0] Int32,
     *      name-string [1] SEQUENCE OF KerberosString
     * }
     *
     * The new (post-RFC 1510) type KerberosString, defined below, is a
     * GeneralString that is constrained to contain only characters in IA5String.
     *
     * KerberosString ::= GeneralString (IA5String)
     * 
     * Int32 ::= INTEGER (-2147483648..2147483647)
     *                  -- signed values representable in 32 bits 
     *  
     * @param seq the OtherName sequence
     * @return String with the krb5 name in the form of "principal1/principal2@realm" or null if the altName does not exist
     */
    @SuppressWarnings("unchecked")
    protected static String getKrb5PrincipalNameFromSequence(ASN1Sequence seq) {
        String ret = null;
        if (seq != null) {
            // First in sequence is the object identifier, that we must check
            DERObjectIdentifier id = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
            if (id.getId().equals(CertTools.KRB5PRINCIPAL_OBJECTID)) {
                // Get the KRB5PrincipalName sequence
                ASN1TaggedObject oobj = (ASN1TaggedObject) seq.getObjectAt(1);
                // After encoding in a cert, it is tagged an extra time...
                DERObject obj = oobj.getObject();
                if (obj instanceof ASN1TaggedObject) {
                    obj = ASN1TaggedObject.getInstance(obj).getObject();
                }
                ASN1Sequence krb5Seq = ASN1Sequence.getInstance(obj);
                // Get the Realm tagged as 0
                ASN1TaggedObject robj = (ASN1TaggedObject) krb5Seq.getObjectAt(0);
                DERGeneralString realmObj = DERGeneralString.getInstance(robj.getObject());
                String realm = realmObj.getString();
                // Get the PrincipalName tagged as 1
                ASN1TaggedObject pobj = (ASN1TaggedObject) krb5Seq.getObjectAt(1);
                // This is another sequence of type and name
                ASN1Sequence nseq = ASN1Sequence.getInstance(pobj.getObject());
                // Get the name tagged as 1
                ASN1TaggedObject nobj = (ASN1TaggedObject) nseq.getObjectAt(1);
                // The name is yet another sequence of GeneralString
                ASN1Sequence sseq = ASN1Sequence.getInstance(nobj.getObject());
                Enumeration<ASN1Object> en = sseq.getObjects();
                while (en.hasMoreElements()) {
                    ASN1Object o = (ASN1Object) en.nextElement();
                    DERGeneralString str = DERGeneralString.getInstance(o);
                    if (ret != null) {
                        ret += "/" + str.getString();
                    } else {
                        ret = str.getString();
                    }
                }
                // Add the realm in the end so we have "principal@realm"
                ret += "@" + realm;
            }
        }
        return ret;
    }

    /**
     * Gets the Microsoft specific GUID altName, that is encoded as an octect string.
     *
     * @param cert certificate containing the extension
     * @return String with the hex-encoded GUID byte array or null if the altName does not exist
     */
    public static String getGuidAltName(Certificate cert) throws IOException, CertificateParsingException {
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            Collection<List<?>> altNames = x509cert.getSubjectAlternativeNames();
            if (altNames != null) {
                Iterator<List<?>> i = altNames.iterator();
                while (i.hasNext()) {
                    ASN1Sequence seq = getAltnameSequence((List<?>) i.next());
                    if (seq != null) {
                        // First in sequence is the object identifier, that we must check
                        DERObjectIdentifier id = DERObjectIdentifier.getInstance(seq.getObjectAt(0));
                        if (id.getId().equals(CertTools.GUID_OBJECTID)) {
                            ASN1TaggedObject obj = (ASN1TaggedObject) seq.getObjectAt(1);
                            ASN1OctetString str = ASN1OctetString.getInstance(obj.getObject());
                            return new String(Hex.encode(str.getOctets()));
                        }
                    }
                }
            }
        }
        return null;
    } // getGuidAltName

    /** Helper for the above methods 
     */
    private static ASN1Sequence getAltnameSequence(List<?> listitem) throws IOException {
        Integer no = (Integer) listitem.get(0);
        if (no.intValue() == 0) {
            byte[] altName = (byte[]) listitem.get(1);
            return getAltnameSequence(altName);
        }
        return null;
    }

    private static ASN1Sequence getAltnameSequence(byte[] value) throws IOException {
        DERObject oct = (new ASN1InputStream(new ByteArrayInputStream(value)).readObject());
        ASN1Sequence seq = ASN1Sequence.getInstance(oct);
        return seq;
    }

    /** Gets an altName string from an X509Extension
     * 
     * @param ext X509Extension with AlternativeNames
     * @return String as defined in method getSubjectAlternativeName
     */
    public static String getAltNameStringFromExtension(X509Extension ext) {
        String altName = null;
        //GeneralNames
        ASN1OctetString octs = ext.getValue();
        if (octs != null) {
            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
            DERObject obj;
            try {
                obj = aIn.readObject();
                GeneralNames gan = GeneralNames.getInstance(obj);
                GeneralName[] gns = gan.getNames();
                for (int i = 0; i < gns.length; i++) {
                    GeneralName gn = gns[i];
                    int tag = gn.getTagNo();
                    DEREncodable name = gn.getName();
                    String str = CertTools.getGeneralNameString(tag, name);
                    if (altName == null) {
                        altName = str;
                    } else {
                        altName += ", " + str;
                    }
                }
            } catch (IOException e) {
                log.error("IOException parsing altNames: ", e);
                return null;
            }
        }
        return altName;
    }

    /**
     * SubjectAltName ::= GeneralNames
     *
     * GeneralNames :: = SEQUENCE SIZE (1..MAX) OF GeneralName
     *
     * GeneralName ::= CHOICE {
     * otherName                       [0]     OtherName,
     * rfc822Name                      [1]     IA5String,
     * dNSName                         [2]     IA5String,
     * x400Address                     [3]     ORAddress,
     * directoryName                   [4]     Name,
     * ediPartyName                    [5]     EDIPartyName,
     * uniformResourceIdentifier       [6]     IA5String,
     * iPAddress                       [7]     OCTET STRING,
     * registeredID                    [8]     OBJECT IDENTIFIER}
     * 
     * SubjectAltName is of form \"rfc822Name=<email>,
     * dNSName=<host name>, uniformResourceIdentifier=<http://host.com/>,
     * iPAddress=<address>, guid=<globally unique id>, directoryName=<CN=testDirName|dir|name>
      * 
      * Supported altNames are upn, rfc822Name, uniformResourceIdentifier, dNSName, iPAddress, directoryName
     *
     * @author Marco Ferrante, (c) 2005 CSITA - University of Genoa (Italy)
      * @author Tomas Gustavsson
     * @param certificate containing alt names
     * @return String containing altNames of form "rfc822Name=email, dNSName=hostname, uniformResourceIdentifier=uri, iPAddress=ip, upn=upn, directoryName=CN=testDirName|dir|name" or null if no altNames exist. Values in returned String is from CertTools constants. AltNames not supported are simply not shown in the resulting string.  
     * @throws java.lang.Exception
     */
    public static String getSubjectAlternativeName(Certificate certificate)
            throws CertificateParsingException, IOException {
        log.debug("Search for SubjectAltName");
        String result = "";
        if (certificate instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) certificate;

            Collection<List<?>> altNames = x509cert.getSubjectAlternativeNames();
            if (altNames == null) {
                return null;
            }
            Iterator<List<?>> iter = altNames.iterator();
            String append = "";
            while (iter.hasNext()) {
                List<?> item = iter.next();
                Integer type = (Integer) item.get(0);
                Object value = item.get(1);
                if (!StringUtils.isEmpty(result)) {
                    // Result already contains one altname, so we have to add comma if there are more altNames
                    append = ", ";
                }
                switch (type.intValue()) {
                case 0:
                    ASN1Sequence seq = getAltnameSequence(item);
                    String upn = getUPNStringFromSequence(seq);
                    // OtherName can be something else besides UPN
                    if (upn != null) {
                        result += append + CertTools.UPN + "=" + upn;
                    } else {
                        String krb5Principal = getKrb5PrincipalNameFromSequence(seq);
                        if (krb5Principal != null) {
                            result += append + CertTools.KRB5PRINCIPAL + "=" + krb5Principal;
                        }
                    }
                    break;
                case 1:
                    result += append + CertTools.EMAIL + "=" + (String) value;
                    break;
                case 2:
                    result += append + CertTools.DNS + "=" + (String) value;
                    break;
                case 3: // SubjectAltName of type x400Address not supported
                    break;
                case 4:
                    result += append + CertTools.DIRECTORYNAME + "=" + (String) value;
                    break;
                case 5: // SubjectAltName of type ediPartyName not supported
                    break;
                case 6:
                    result += append + CertTools.URI + "=" + (String) value;
                    break;
                case 7:
                    result += append + CertTools.IPADDR + "=" + (String) value;
                    break;
                default: // SubjectAltName of unknown type
                    break;
                }
            }
            if (StringUtils.isEmpty(result)) {
                return null;
            }
        }
        return result;
    }

    /**
     * From an altName string as defined in getSubjectAlternativeName 
     * @param altName
     * @return ASN.1 GeneralNames
     * @see #getSubjectAlternativeName
     */
    public static GeneralNames getGeneralNamesFromAltName(String altName) {
        if (log.isTraceEnabled()) {
            log.trace(">getGeneralNamesFromAltName: " + altName);
        }
        ASN1EncodableVector vec = new ASN1EncodableVector();

        ArrayList<String> emails = CertTools.getEmailFromDN(altName);
        if (!emails.isEmpty()) {
            Iterator<String> iter = emails.iterator();
            while (iter.hasNext()) {
                GeneralName gn = new GeneralName(1, new DERIA5String((String) iter.next()));
                vec.add(gn);
            }
        }

        ArrayList<String> dns = CertTools.getPartsFromDN(altName, CertTools.DNS);
        if (!dns.isEmpty()) {
            Iterator<String> iter = dns.iterator();
            while (iter.hasNext()) {
                GeneralName gn = new GeneralName(2, new DERIA5String((String) iter.next()));
                vec.add(gn);
            }
        }

        String directoryName = getDirectoryStringFromAltName(altName);
        if (directoryName != null) {
            X509Name x509DirectoryName = new X509Name(directoryName);
            GeneralName gn = new GeneralName(4, x509DirectoryName);
            vec.add(gn);
        }

        ArrayList<String> uri = CertTools.getPartsFromDN(altName, CertTools.URI);
        if (!uri.isEmpty()) {
            Iterator<String> iter = uri.iterator();
            while (iter.hasNext()) {
                GeneralName gn = new GeneralName(6, new DERIA5String((String) iter.next()));
                vec.add(gn);
            }
        }
        uri = CertTools.getPartsFromDN(altName, CertTools.URI1);
        if (!uri.isEmpty()) {
            Iterator<String> iter = uri.iterator();
            while (iter.hasNext()) {
                GeneralName gn = new GeneralName(6, new DERIA5String((String) iter.next()));
                vec.add(gn);
            }
        }
        uri = CertTools.getPartsFromDN(altName, CertTools.URI2);
        if (!uri.isEmpty()) {
            Iterator<String> iter = uri.iterator();
            while (iter.hasNext()) {
                GeneralName gn = new GeneralName(6, new DERIA5String((String) iter.next()));
                vec.add(gn);
            }
        }

        ArrayList<String> ipstr = CertTools.getPartsFromDN(altName, CertTools.IPADDR);
        if (!ipstr.isEmpty()) {
            Iterator<String> iter = ipstr.iterator();
            while (iter.hasNext()) {
                byte[] ipoctets = StringTools.ipStringToOctets((String) iter.next());
                GeneralName gn = new GeneralName(7, new DEROctetString(ipoctets));
                vec.add(gn);
            }
        }

        // UPN is an OtherName see method getUpn... for asn.1 definition
        ArrayList<String> upn = CertTools.getPartsFromDN(altName, CertTools.UPN);
        if (!upn.isEmpty()) {
            Iterator<String> iter = upn.iterator();
            while (iter.hasNext()) {
                ASN1EncodableVector v = new ASN1EncodableVector();
                v.add(new DERObjectIdentifier(CertTools.UPN_OBJECTID));
                v.add(new DERTaggedObject(true, 0, new DERUTF8String((String) iter.next())));
                //GeneralName gn = new GeneralName(new DERSequence(v), 0);
                DERObject gn = new DERTaggedObject(false, 0, new DERSequence(v));
                vec.add(gn);
            }
        }

        ArrayList<String> guid = CertTools.getPartsFromDN(altName, CertTools.GUID);
        if (!guid.isEmpty()) {
            Iterator<String> iter = guid.iterator();
            while (iter.hasNext()) {
                ASN1EncodableVector v = new ASN1EncodableVector();
                byte[] guidbytes = Hex.decode((String) iter.next());
                if (guidbytes != null) {
                    v.add(new DERObjectIdentifier(CertTools.GUID_OBJECTID));
                    v.add(new DERTaggedObject(true, 0, new DEROctetString(guidbytes)));
                    DERObject gn = new DERTaggedObject(false, 0, new DERSequence(v));
                    vec.add(gn);
                } else {
                    log.error("Cannot decode hexadecimal guid: " + guid);
                }
            }
        }

        // Krb5PrincipalName is an OtherName, see method getKrb5Principal...for ASN.1 definition
        ArrayList<String> krb5principalname = CertTools.getPartsFromDN(altName, CertTools.KRB5PRINCIPAL);
        if (!krb5principalname.isEmpty()) {
            Iterator<String> iter = krb5principalname.iterator();
            while (iter.hasNext()) {
                // Start by parsing the input string to separate it in different parts
                String principalString = (String) iter.next();
                if (log.isDebugEnabled()) {
                    log.debug("principalString: " + principalString);
                }
                // The realm is the last part moving back until an @
                int index = principalString.lastIndexOf('@');
                String realm = "";
                if (index > 0) {
                    realm = principalString.substring(index + 1);
                }
                if (log.isDebugEnabled()) {
                    log.debug("realm: " + realm);
                }
                // Now we can have several principals separated by /
                ArrayList<String> principalarr = new ArrayList<String>();
                int jndex = 0;
                int bindex = 0;
                while (jndex < index) {
                    // Loop and add all strings separated by /
                    jndex = principalString.indexOf('/', bindex);
                    if (jndex == -1) {
                        jndex = index;
                    }
                    String s = principalString.substring(bindex, jndex);
                    if (log.isDebugEnabled()) {
                        log.debug("adding principal name: " + s);
                    }
                    principalarr.add(s);
                    bindex = jndex + 1;
                }

                // Now we must construct the rather complex asn.1...
                ASN1EncodableVector v = new ASN1EncodableVector(); // this is the OtherName
                v.add(new DERObjectIdentifier(CertTools.KRB5PRINCIPAL_OBJECTID));

                // First the Krb5PrincipalName sequence
                ASN1EncodableVector krb5p = new ASN1EncodableVector();
                // The realm is the first tagged GeneralString
                krb5p.add(new DERTaggedObject(true, 0, new DERGeneralString(realm)));
                // Second is the sequence of principal names, which is at tagged position 1 in the krb5p 
                ASN1EncodableVector principals = new ASN1EncodableVector();
                // According to rfc4210 the type NT-UNKNOWN is 0, and according to some other rfc this type should be used...
                principals.add(new DERTaggedObject(true, 0, new DERInteger(0)));
                // The names themselves are yet another sequence
                Iterator<String> i = principalarr.iterator();
                ASN1EncodableVector names = new ASN1EncodableVector();
                while (i.hasNext()) {
                    String principalName = (String) i.next();
                    names.add(new DERGeneralString(principalName));
                }
                principals.add(new DERTaggedObject(true, 1, new DERSequence(names)));
                krb5p.add(new DERTaggedObject(true, 1, new DERSequence(principals)));

                v.add(new DERTaggedObject(true, 0, new DERSequence(krb5p)));
                DERObject gn = new DERTaggedObject(false, 0, new DERSequence(v));
                vec.add(gn);
            }
        }

        // To support custom OIDs in altNames, they must be added as an OtherName of plain type UTF8String
        ArrayList<String> customoids = CertTools.getCustomOids(altName);
        if (!customoids.isEmpty()) {
            Iterator<String> iter = customoids.iterator();
            while (iter.hasNext()) {
                String oid = (String) iter.next();
                ArrayList<String> oidval = CertTools.getPartsFromDN(altName, oid);
                if (!oidval.isEmpty()) {
                    Iterator<String> valiter = oidval.iterator();
                    while (valiter.hasNext()) {
                        ASN1EncodableVector v = new ASN1EncodableVector();
                        v.add(new DERObjectIdentifier(oid));
                        v.add(new DERTaggedObject(true, 0, new DERUTF8String((String) valiter.next())));
                        DERObject gn = new DERTaggedObject(false, 0, new DERSequence(v));
                        vec.add(gn);
                    }
                }
            }
        }

        GeneralNames ret = null;
        if (vec.size() > 0) {
            ret = new GeneralNames(new DERSequence(vec));
        }
        return ret;
    }

    /**
     * GeneralName ::= CHOICE {
     * otherName                       [0]     OtherName,
     * rfc822Name                      [1]     IA5String,
     * dNSName                         [2]     IA5String,
     * x400Address                     [3]     ORAddress,
     * directoryName                   [4]     Name,
     * ediPartyName                    [5]     EDIPartyName,
     * uniformResourceIdentifier       [6]     IA5String,
     * iPAddress                       [7]     OCTET STRING,
     * registeredID                    [8]     OBJECT IDENTIFIER}
     * 
     * @param tag the no tag 0-8
     * @param value the DEREncodable value as returned by GeneralName.getName()
     * @return String in form rfc822Name=<email> or uri=<uri> etc 
     * @throws IOException 
     * @see #getSubjectAlternativeName
     */
    public static String getGeneralNameString(int tag, DEREncodable value) throws IOException {
        String ret = null;
        switch (tag) {
        case 0:
            ASN1Sequence seq = getAltnameSequence(value.getDERObject().getEncoded());
            String upn = getUPNStringFromSequence(seq);
            // OtherName can be something else besides UPN
            if (upn != null) {
                ret = CertTools.UPN + "=" + upn;
            } else {
                String krb5Principal = getKrb5PrincipalNameFromSequence(seq);
                if (krb5Principal != null) {
                    ret = CertTools.KRB5PRINCIPAL + "=" + krb5Principal;
                }
            }
            break;
        case 1:
            ret = CertTools.EMAIL + "=" + DERIA5String.getInstance(value).getString();
            break;
        case 2:
            ret = CertTools.DNS + "=" + DERIA5String.getInstance(value).getString();
            break;
        case 3: // SubjectAltName of type x400Address not supported
            break;
        case 4: // SubjectAltName of type directoryName not supported
            break;
        case 5: // SubjectAltName of type ediPartyName not supported
            break;
        case 6:
            ret = CertTools.URI + "=" + DERIA5String.getInstance(value).getString();
            break;
        case 7:
            ASN1OctetString oct = ASN1OctetString.getInstance(value);
            ret = CertTools.IPADDR + "=" + StringTools.ipOctetsToString(oct.getOctets());
            break;
        default: // SubjectAltName of unknown type
            break;
        }
        return ret;
    }

    /**
     * Check the certificate with CA certificate.
     *
     * @param certificate cert to verify
     * @param caCertPath collection of X509Certificate
     * @return true if verified OK
     * @throws Exception if verification failed
     */
    public static boolean verify(Certificate certificate, Collection<Certificate> caCertPath) throws Exception {
        try {
            ArrayList<Certificate> certlist = new ArrayList<Certificate>();
            // Create CertPath
            certlist.add(certificate);
            // Add other certs...         
            CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
            java.security.cert.CertPath cp = cf.generateCertPath(certlist);
            // Create TrustAnchor. Since EJBCA use BouncyCastle provider, we assume
            // certificate already in correct order
            X509Certificate[] cac = (X509Certificate[]) caCertPath.toArray(new X509Certificate[] {});
            java.security.cert.TrustAnchor anchor = new java.security.cert.TrustAnchor(cac[0], null);
            // Set the PKIX parameters
            java.security.cert.PKIXParameters params = new java.security.cert.PKIXParameters(
                    java.util.Collections.singleton(anchor));
            params.setRevocationEnabled(false);
            java.security.cert.CertPathValidator cpv = java.security.cert.CertPathValidator.getInstance("PKIX",
                    "BC");
            java.security.cert.PKIXCertPathValidatorResult result = (java.security.cert.PKIXCertPathValidatorResult) cpv
                    .validate(cp, params);
            if (log.isDebugEnabled()) {
                log.debug("Certificate verify result: " + result.toString());
            }
        } catch (java.security.cert.CertPathValidatorException cpve) {
            throw new Exception(
                    "Invalid certificate or certificate not issued by specified CA: " + cpve.getMessage());
        } catch (Exception e) {
            throw new Exception("Error checking certificate chain: " + e.getMessage());
        }
        return true;
    }

    /**
     * Checks that the given date is within the certificate's validity period. 
     * In other words, this determines whether the certificate would be valid at the given date/time.
     * 
     * This utility class is only a helper to get the same behavior as the standard java.security.cert API regardless if using X.509 or CV Certificate.
     *
     * @param cert certificate to verify, if null the method returns immediately, null does not have a validity to check.
     * @param date the Date to check against to see if this certificate is valid at that date/time.
     * @throws NoSuchFieldException 
     * @throws CertificateExpiredException - if the certificate has expired with respect to the date supplied. 
      * @throws CertificateNotYetValidException - if the certificate is not yet valid with respect to the date supplied.
      * @see java.security.cert.X509Certificate#checkValidity(Date)
     */
    public static void checkValidity(Certificate cert, Date date)
            throws CertificateExpiredException, CertificateNotYetValidException {
        if (cert != null) {
            if (cert instanceof X509Certificate) {
                X509Certificate xcert = (X509Certificate) cert;
                xcert.checkValidity(date);
            } else if (StringUtils.equals(cert.getType(), "CVC")) {
                CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
                try {
                    Date start = cvccert.getCVCertificate().getCertificateBody().getValidFrom();
                    Date end = cvccert.getCVCertificate().getCertificateBody().getValidTo();
                    if (start.after(date)) {
                        String msg = "Certificate startDate '" + start + "' is after check date '" + date + "'";
                        if (log.isTraceEnabled()) {
                            log.trace(msg);
                        }
                        throw new CertificateNotYetValidException(msg);
                    }
                    if (end.before(date)) {
                        String msg = "Certificate endDate '" + end + "' is before check date '" + date + "'";
                        if (log.isTraceEnabled()) {
                            log.trace(msg);
                        }
                        throw new CertificateExpiredException(msg);
                    }
                } catch (NoSuchFieldException e) {
                    log.error("NoSuchFieldException: ", e);
                }
            }
        }
    }

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

    /** Returns OCSP URL that is inside AuthorithInformationAccess extension, or null.
     * 
     * @param cert is the certificate to parse
     * @throws CertificateParsingException
     */
    public static String getAuthorityInformationAccessOcspUrl(Certificate cert) throws CertificateParsingException {
        String ret = null;
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            try {
                DERObject obj = getExtensionValue(x509cert, X509Extensions.AuthorityInfoAccess.getId());
                if (obj == null) {
                    return null;
                }
                AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(obj);
                AccessDescription[] ad = aia.getAccessDescriptions();
                if ((ad != null) && (ad.length > 0)) {
                    for (int i = 0; i < ad.length; i++) {
                        if (ad[i].getAccessMethod().equals(X509ObjectIdentifiers.ocspAccessMethod)) {
                            GeneralName gn = ad[i].getAccessLocation();
                            if (gn.getTagNo() == 6) {
                                DERIA5String str = DERIA5String.getInstance(gn.getDERObject());
                                ret = str.getString();
                                break; // no need to go on any further, we got a value
                            }
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Error parsing AuthorityInformationAccess", e);
                throw new CertificateParsingException(e.toString());
            }
        }
        return ret;
    }

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

    /** Gets a URI string from a GeneralNames structure. 
     * 
     * @param names DER GeneralNames object, that is a sequence of DERTaggedObject
     * @return String with URI if tagNo is 6 (uniformResourceIdentifier), null otherwise
     */
    private static String getStringFromGeneralNames(DERObject names) {
        ASN1Sequence namesSequence = ASN1Sequence.getInstance((ASN1TaggedObject) names, false);
        if (namesSequence.size() == 0) {
            return null;
        }
        DERTaggedObject taggedObject = (DERTaggedObject) namesSequence.getObjectAt(0);
        if (taggedObject.getTagNo() != 6) { // uniformResourceIdentifier  [6]  IA5String,
            return null;
        }
        return new String(ASN1OctetString.getInstance(taggedObject, false).getOctets());
    } //getStringFromGeneralNames

    /**
     * Generate SHA1 fingerprint of certificate in string representation.
     *
     * @param cert Certificate.
     *
     * @return String containing hex format of SHA1 fingerprint, or null if input is null.
     */
    public static String getFingerprintAsString(Certificate cert) {
        if (cert == null) {
            return null;
        }
        try {
            byte[] res = generateSHA1Fingerprint(cert.getEncoded());

            return new String(Hex.encode(res));
        } catch (CertificateEncodingException cee) {
            log.error("Error encoding 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 new String(Hex.encode(res));
        } catch (CRLException ce) {
            log.error("Error encoding CRL.", ce);
        }

        return null;
    }

    /**
     * Generate SHA1 fingerprint of byte array in string representation.
     *
     * @param in byte array to fingerprint.
     *
     * @return String containing hex format of SHA1 fingerprint.
     */
    public static String getFingerprintAsString(byte[] in) {
        byte[] res = generateSHA1Fingerprint(in);
        return new String(Hex.encode(res));
    }

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

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

    /**
     * Generate a MD5 fingerprint from a byte array containing a certificate
     *
     * @param ba Byte array containing DER encoded Certificate.
     *
     * @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

    /** Converts Sun Key usage bits to Bouncy castle key usage kits
     * 
     * @param sku key usage bit fields according to java.security.cert.X509Certificate#getKeyUsage, must be a boolean aray of size 9.
     * @return key usage int according to org.bouncycastle.jce.X509KeyUsage#X509KeyUsage, or -1 if input is null.
     * @see java.security.cert.X509Certificate#getKeyUsage
     * @see org.bouncycastle.jce.X509KeyUsage#X509KeyUsage
     */
    public static int sunKeyUsageToBC(boolean[] sku) {
        if (sku == null) {
            return -1;
        }
        int bcku = 0;
        if (sku[0]) {
            bcku = bcku | X509KeyUsage.digitalSignature;
        }
        if (sku[1]) {
            bcku = bcku | X509KeyUsage.nonRepudiation;
        }
        if (sku[2]) {
            bcku = bcku | X509KeyUsage.keyEncipherment;
        }
        if (sku[3]) {
            bcku = bcku | X509KeyUsage.dataEncipherment;
        }
        if (sku[4]) {
            bcku = bcku | X509KeyUsage.keyAgreement;
        }
        if (sku[5]) {
            bcku = bcku | X509KeyUsage.keyCertSign;
        }
        if (sku[6]) {
            bcku = bcku | X509KeyUsage.cRLSign;
        }
        if (sku[7]) {
            bcku = bcku | X509KeyUsage.encipherOnly;
        }
        if (sku[8]) {
            bcku = bcku | X509KeyUsage.decipherOnly;
        }
        return bcku;
    }

    /** Converts DERBitString ResonFlags to a RevokedCertInfo constant 
     * 
     * @param reasonFlags DERBITString received from org.bouncycastle.asn1.x509.ReasonFlags.
     * @return int according to org.ejbca.core.model.ca.crl.RevokedCertInfo
     */
    public static int bitStringToRevokedCertInfo(DERBitString reasonFlags) {
        int ret = RevokedCertInfo.REVOCATION_REASON_UNSPECIFIED;
        if (reasonFlags == null) {
            return ret;
        }
        int val = reasonFlags.intValue();
        if (log.isDebugEnabled()) {
            log.debug("Int value of bitString revocation reason: " + val);
        }
        if ((val & ReasonFlags.aACompromise) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_AACOMPROMISE;
        }
        if ((val & ReasonFlags.affiliationChanged) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_AFFILIATIONCHANGED;
        }
        if ((val & ReasonFlags.cACompromise) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_CACOMPROMISE;
        }
        if ((val & ReasonFlags.certificateHold) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_CERTIFICATEHOLD;
        }
        if ((val & ReasonFlags.cessationOfOperation) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_CESSATIONOFOPERATION;
        }
        if ((val & ReasonFlags.keyCompromise) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_KEYCOMPROMISE;
        }
        if ((val & ReasonFlags.privilegeWithdrawn) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_PRIVILEGESWITHDRAWN;
        }
        if ((val & ReasonFlags.superseded) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_SUPERSEDED;
        }
        if ((val & ReasonFlags.unused) != 0) {
            ret = RevokedCertInfo.REVOCATION_REASON_UNSPECIFIED;
        }
        return ret;
    }

    /**
     * Method used to insert a CN postfix into DN by extracting the first found CN appending cnpostfix and then replacing the original CN 
     * with the new one in DN.
     * 
     * If no CN could be found in DN then should the given DN be returned untouched
     * 
     * @param dn the DN to manipulate, cannot be null
     * @param cnpostfix the postfix to insert, cannot be null
     * @return the new DN
     */
    public static String insertCNPostfix(String dn, String cnpostfix) {
        String newdn = null;

        if ((dn != null) && (cnpostfix != null)) {
            String o;
            X509NameTokenizer xt = new X509NameTokenizer(dn);
            boolean alreadyreplaced = false;
            while (xt.hasMoreTokens()) {
                o = xt.nextToken();
                if (!alreadyreplaced && (o.length() > 3) && o.substring(0, 3).equalsIgnoreCase("cn=")) {
                    o += cnpostfix;
                    alreadyreplaced = true;
                }
                if (newdn == null) {
                    newdn = o;
                } else {
                    newdn += "," + o;
                }
            }
        }

        return newdn;
    } // insertCNPostfix

    /** Simple methods that returns the signature algorithm value from the certificate. Not usable for setting
     * signature algorithms names in EJBCA, only for human presentation.
     * 
     * @return Signature algorithm from the certificate as a human readable string, for example SHA1WithRSA.
     */
    public static String getCertSignatureAlgorithmAsString(Certificate cert) {
        String certSignatureAlgorithm = null;
        if (cert instanceof X509Certificate) {
            X509Certificate x509cert = (X509Certificate) cert;
            certSignatureAlgorithm = x509cert.getSigAlgName();
            if (log.isDebugEnabled()) {
                log.debug("certSignatureAlgorithm is: " + certSignatureAlgorithm);
            }
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            CVCPublicKey cvcpk;
            try {
                cvcpk = cvccert.getCVCertificate().getCertificateBody().getPublicKey();
                OIDField oid = cvcpk.getObjectIdentifier();
                certSignatureAlgorithm = AlgorithmUtil.getAlgorithmName(oid);
            } catch (NoSuchFieldException e) {
                log.error("NoSuchFieldException: ", e);
            }
        }
        // Try to make it easier to display some signature algorithms that cert.getSigAlgName() does not have a good string for.
        if (certSignatureAlgorithm.equalsIgnoreCase("1.2.840.113549.1.1.10")) {
            certSignatureAlgorithm = AlgorithmConstants.SIGALG_SHA256_WITH_RSA_AND_MGF1;
        }
        // SHA256WithECDSA does not work to be translated in JDK5.
        if (certSignatureAlgorithm.equalsIgnoreCase("1.2.840.10045.4.3.2")) {
            certSignatureAlgorithm = AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA;
        }
        return certSignatureAlgorithm;
    }

    /** Simple method that looks at the certificate and determines, from EJBCA's standpoint, which signature algorithm it is
     * 
     * @param cert the cert to examine
     * @return Signature algorithm from AlgorithmConstants.SIGALG_SHA1_WITH_RSA etc.
     */
    public static String getSignatureAlgorithm(Certificate cert) {
        String signatureAlgorithm = null;
        String certSignatureAlgorithm = getCertSignatureAlgorithmAsString(cert);

        // The signature strign returned from the certificate is often not usable as the signature algorithm we must
        // specify for a CA in EJBCA, for example SHA1WithECDSA is returned as only ECDSA, so we need some magic to fix it up.
        PublicKey publickey = cert.getPublicKey();
        if (publickey instanceof RSAPublicKey) {
            boolean isMgf = true;
            if (certSignatureAlgorithm.indexOf("MGF") == -1) {
                isMgf = false;
            }
            if (certSignatureAlgorithm.indexOf("256") == -1) {
                boolean md5 = true;
                if (certSignatureAlgorithm.indexOf("MD5") == -1) {
                    md5 = false;
                }
                if (isMgf) {
                    signatureAlgorithm = AlgorithmConstants.SIGALG_SHA1_WITH_RSA_AND_MGF1;
                } else {
                    if (md5) {
                        signatureAlgorithm = AlgorithmConstants.SIGALG_MD5_WITH_RSA;
                    } else {
                        signatureAlgorithm = AlgorithmConstants.SIGALG_SHA1_WITH_RSA;
                    }
                }
            } else {
                if (isMgf) {
                    signatureAlgorithm = AlgorithmConstants.SIGALG_SHA256_WITH_RSA_AND_MGF1;
                } else {
                    signatureAlgorithm = AlgorithmConstants.SIGALG_SHA256_WITH_RSA;
                }
            }
        } else if (publickey instanceof DSAPublicKey) {
            signatureAlgorithm = AlgorithmConstants.SIGALG_SHA1_WITH_DSA;
        } else {
            if (certSignatureAlgorithm.indexOf("256") != -1) {
                signatureAlgorithm = AlgorithmConstants.SIGALG_SHA256_WITH_ECDSA;
            } else if (certSignatureAlgorithm.indexOf("224") != -1) {
                signatureAlgorithm = AlgorithmConstants.SIGALG_SHA224_WITH_ECDSA;
            } else {
                signatureAlgorithm = AlgorithmConstants.SIGALG_SHA1_WITH_ECDSA;
            }
        }

        log.debug("getSignatureAlgorithm: " + signatureAlgorithm);
        return signatureAlgorithm;
    } // getSignatureAlgorithm

    /**
     * class for breaking up an X500 Name into it's component tokens, ala
     * java.util.StringTokenizer. Taken from BouncyCastle, but does NOT
     * use or consider escaped characters. Used for reversing DNs without unescaping.
     */
    private static class BasicX509NameTokenizer {
        final private String oid;
        private int index = -1;
        /* Since this class isn't thread safe anyway, we can use the slightly faster StringBuilder instead of StringBuffer */
        final private StringBuilder buf = new StringBuilder();

        public BasicX509NameTokenizer(final String oid) {
            this.oid = oid;
        }

        public boolean hasMoreTokens() {
            return (index != oid.length());
        }

        public String nextToken() {
            if (index == oid.length()) {
                return null;
            }
            int end = index + 1;
            boolean quoted = false;
            boolean escaped = false;
            buf.setLength(0);
            while (end != oid.length()) {
                char c = oid.charAt(end);
                if (c == '"') {
                    if (!escaped) {
                        buf.append(c);
                        quoted ^= true; // Faster than "quoted = !quoted;"
                    } else {
                        buf.append(c);
                    }
                    escaped = false;
                } else {
                    if (escaped || quoted) {
                        buf.append(c);
                        escaped = false;
                    } else if (c == '\\') {
                        buf.append(c);
                        escaped = true;
                    } else if ((c == ',') && (!escaped)) {
                        break;
                    } else {
                        buf.append(c);
                    }
                }
                end++;
            }
            index = end;
            return buf.toString().trim();
        }
    } // BasicX509NameTokenizer

    /**
     * Obtains a List with the DERObjectIdentifiers for 
     * dNObjects names, in the specified order
     * 
     * @param ldaporder if true the returned order are as defined in LDAP RFC (CN=foo,O=bar,C=SE), otherwise the order is a defined in X.500 (C=SE,O=bar,CN=foo).
     * @return List with DERObjectIdentifiers defining the known order we require
     * @see org.ejbca.util.dn.DnComponents#getDnObjects(boolean)
     */
    private static List<DERObjectIdentifier> getX509FieldOrder(final boolean ldaporder) {
        final String[] dNObjects = DnComponents.getDnObjects(ldaporder);
        final List<DERObjectIdentifier> fieldOrder = new ArrayList<DERObjectIdentifier>(dNObjects.length);
        for (final String dNObject : dNObjects) {
            fieldOrder.add(DnComponents.getOid(dNObject));
        }
        return fieldOrder;
    }

    /**
     * Obtain a X509Name reordered, if some fields from original X509Name 
     * doesn't appear in "ordering" parameter, they will be added at end 
     * in the original order.
     *   
     * @param x509Name the X509Name that is unordered 
     * @param ldaporder true if LDAP ordering of DN should be used (default in EJBCA), false for X.500 order, ldap order is CN=A,OU=B,O=C,C=SE, x.500 order is the reverse
     * @return X509Name with ordered conmponents according to the orcering vector
     */
    private static X509Name getOrderedX509Name(final X509Name x509Name, final boolean ldaporder,
            final X509NameEntryConverter converter) {
        //-- Null prevent
        // Guess order of the input name
        final boolean isLdapOrder = !isDNReversed(x509Name.toString());
        //-- New order for the X509 Fields
        final List<DERObjectIdentifier> newOrdering = new ArrayList<DERObjectIdentifier>();
        final List<Object> newValues = new ArrayList<Object>();
        //-- Add ordered fields
        @SuppressWarnings("unchecked")
        final Vector<DERObjectIdentifier> allOids = x509Name.getOIDs();
        // If we think the DN is in LDAP order, first order it as a LDAP DN, if we don't think it's LDAP order
        // order it as a X.500 DN
        final List<DERObjectIdentifier> ordering = getX509FieldOrder(isLdapOrder);
        final HashSet<DERObjectIdentifier> hs = new HashSet<DERObjectIdentifier>(allOids.size() + ordering.size());
        for (final DERObjectIdentifier oid : ordering) {
            if (!hs.contains(oid)) {
                hs.add(oid);
                @SuppressWarnings("unchecked")
                final Vector<Object> valueList = x509Name.getValues(oid);
                //-- Only add the OID if has not null value
                for (final Object value : valueList) {
                    newOrdering.add(oid);
                    newValues.add(value);
                }
            }
        }
        //-- Add unexpected fields to the end
        for (final DERObjectIdentifier oid : allOids) {
            if (!hs.contains(oid)) {
                hs.add(oid);
                @SuppressWarnings("unchecked")
                final Vector<Object> valueList = x509Name.getValues(oid);
                //-- Only add the OID if has not null value
                for (final Object value : valueList) {
                    newOrdering.add(oid);
                    newValues.add(value);
                    if (log.isDebugEnabled()) {
                        log.debug("added --> " + oid + " val: " + value);
                    }
                }
            }
        }
        // If the requested ordering was the reverse of the ordering the input string was in (by our guess in the beginning)
        // we have to reverse the vectors
        if (ldaporder != isLdapOrder) {
            if (log.isDebugEnabled()) {
                log.debug("Reversing order of DN, ldaporder=" + ldaporder + ", isLdapOrder=" + isLdapOrder);
            }
            Collections.reverse(newOrdering);
            Collections.reverse(newValues);
        }
        //-- Return X509Name with the ordered fields
        return new X509Name(new Vector<DERObjectIdentifier>(newOrdering), new Vector<Object>(newValues), converter);
    }

    /**
     * Obtain the directory string for the directoryName generation
     * form the Subject Alternative Name String.
     * 
     * @param altName
     * @return
     */
    private static String getDirectoryStringFromAltName(String altName) {
        String directoryName = CertTools.getPartFromDN(altName, CertTools.DIRECTORYNAME);
        //DNFieldExtractor dnfe = new DNFieldExtractor(altName, DNFieldExtractor.TYPE_SUBJECTALTNAME);
        //String directoryName = dnfe.getField(DNFieldExtractor.DIRECTORYNAME, 0);
        /** TODO: Validate or restrict the directoryName Fields? */
        return ("".equals(directoryName) ? null : directoryName);
    } // getDirectoryStringFromAltName

    /**
     * Method to create certificate path and to check it's validity from a list of certificates.
     * The list of certificates should only contain one root certificate.
     *
     * @param certlist
     * @return the certificatepath with the root CA at the end, either collection of Certificate or byte[] (der encoded certs)
     * @throws CertPathValidatorException if the certificate chain can not be constructed
     * @throws InvalidAlgorithmParameterException 
     * @throws NoSuchProviderException 
     * @throws NoSuchAlgorithmException 
     * @throws CertificateException 
     */
    public static Collection<Certificate> createCertChain(Collection<?> certlistin)
            throws CertPathValidatorException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, CertificateException {
        ArrayList<Certificate> returnval = new ArrayList<Certificate>();

        Collection<Certificate> certlist = orderCertificateChain(certlistin);

        // set certificate chain
        Certificate rootcert = null;
        ArrayList<Certificate> calist = new ArrayList<Certificate>();
        Iterator<Certificate> iter = certlist.iterator();
        while (iter.hasNext()) {
            Certificate next = iter.next();
            if (CertTools.isSelfSigned(next)) {
                rootcert = next;
            } else {
                calist.add(next);
            }
        }

        if (calist.isEmpty()) {
            // only one root cert, no certchain
            returnval.add(rootcert);
        } else {
            // We need a bit special handling for CV certificates because those can not be handled using a PKIX CertPathValidator
            Certificate test = calist.get(0);
            if (test.getType().equals("CVC")) {
                if (calist.size() == 1) {
                    returnval.add(test);
                    returnval.add(rootcert);
                } else {
                    throw new CertPathValidatorException(
                            "CVC certificate chain can not be of length longer than two.");
                }
            } else {
                // Normal X509 certificates
                HashSet<TrustAnchor> trustancors = new HashSet<TrustAnchor>();
                TrustAnchor trustanchor = null;
                trustanchor = new TrustAnchor((X509Certificate) rootcert, null);
                trustancors.add(trustanchor);

                // Create the parameters for the validator
                PKIXParameters params = new PKIXParameters(trustancors);

                // Disable CRL checking since we are not supplying any CRLs
                params.setRevocationEnabled(false);
                params.setDate(new Date());

                // Create the validator and validate the path
                CertPathValidator certPathValidator = CertPathValidator
                        .getInstance(CertPathValidator.getDefaultType(), "BC");
                CertificateFactory fact = CertTools.getCertificateFactory();
                CertPath certpath = fact.generateCertPath(calist);

                CertPathValidatorResult result = certPathValidator.validate(certpath, params);

                // Get the certificates validate in the path
                PKIXCertPathValidatorResult pkixResult = (PKIXCertPathValidatorResult) result;
                returnval.addAll(certpath.getCertificates());

                // Get the CA used to validate this path
                TrustAnchor ta = pkixResult.getTrustAnchor();
                X509Certificate cert = ta.getTrustedCert();
                returnval.add(cert);
            }
        }
        return returnval;
    } // createCertChain

    /**
     * Method ordering a list of certificate into a certificate path with the root CA at the end.
     * Does not check validity or verification of any kind, just ordering by issuerdn.
     * @param certlist list of certificates to order can be collection of Certificate or byte[] (der encoded certs).
     * @return Collection with certificatechain.
     */
    private static Collection<Certificate> orderCertificateChain(Collection<?> certlist)
            throws CertPathValidatorException {
        ArrayList<Certificate> returnval = new ArrayList<Certificate>();
        Certificate rootca = null;
        HashMap<String, Certificate> cacertmap = new HashMap<String, Certificate>();
        Iterator<?> iter = certlist.iterator();
        while (iter.hasNext()) {
            Certificate cert = null;
            Object o = iter.next();
            try {
                cert = (Certificate) o;
            } catch (ClassCastException e) {
                // This was not a certificate, is it byte encoded?
                byte[] certBytes = (byte[]) o;
                try {
                    cert = CertTools.getCertfromByteArray(certBytes);
                } catch (CertificateException e1) {
                    throw new CertPathValidatorException(e1);
                }
            }
            if (CertTools.isSelfSigned(cert)) {
                rootca = cert;
            } else {
                log.debug("Adding to cacertmap with index '" + CertTools.getIssuerDN(cert) + "'");
                cacertmap.put(CertTools.getIssuerDN(cert), cert);
            }
        }

        if (rootca == null) {
            throw new CertPathValidatorException("No root CA certificate found in certificatelist");
        }
        returnval.add(0, rootca);
        Certificate currentcert = rootca;
        int i = 0;
        while (certlist.size() != returnval.size() && i <= certlist.size()) {
            log.debug("Looking in cacertmap for '" + CertTools.getSubjectDN(currentcert) + "'");
            Certificate nextcert = (Certificate) cacertmap.get(CertTools.getSubjectDN(currentcert));
            if (nextcert == null) {
                throw new CertPathValidatorException("Error building certificate path");
            }
            returnval.add(0, nextcert);
            currentcert = nextcert;
            i++;
        }

        if (i > certlist.size()) {
            throw new CertPathValidatorException("Error building certificate path");
        }

        return returnval;
    } // orderCertificateChain

    /**
     * @return true if the chains are nonempty, contain the same certificates in the same order
     */
    public static boolean compareCertificateChains(Certificate[] chainA, Certificate[] chainB) {
        if (chainA == null || chainB == null) {
            return false;
        }
        if (chainA.length != chainB.length) {
            return false;
        }
        for (int i = 0; i < chainA.length; i++) {
            if (chainA[i] == null || !chainA[i].equals(chainB[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Dumps a certificate (cvc or x.509) to string format, suitable for manual inspection/debugging.
     *
     * @param cert Certificate
     *
     * @return String with cvc or asn.1 dump.
     */
    public static String dumpCertificateAsString(final Certificate cert) {
        String ret = null;
        if (cert instanceof X509Certificate) {
            try {
                final Certificate c = getCertfromByteArray(cert.getEncoded());
                ret = c.toString();
                //             ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(cert.getEncoded()));
                //             DERObject obj = ais.readObject();
                //             ret = ASN1Dump.dumpAsString(obj);
            } catch (CertificateException e) {
                ret = e.getMessage();
            }
        } else if (StringUtils.equals(cert.getType(), "CVC")) {
            final CardVerifiableCertificate cvccert = (CardVerifiableCertificate) cert;
            final CVCObject obj = cvccert.getCVCertificate();
            ret = obj.getAsText("");
        } else {
            throw new IllegalArgumentException(
                    "dumpCertificateAsString: Certificate of type " + cert.getType() + " is not implemented");
        }
        return ret;
    }

} // CertTools