0001: /*************************************************************************
0002: * *
0003: * EJBCA: The OpenSource Certificate Authority *
0004: * *
0005: * This software is free software; you can redistribute it and/or *
0006: * modify it under the terms of the GNU Lesser General Public *
0007: * License as published by the Free Software Foundation; either *
0008: * version 2.1 of the License, or any later version. *
0009: * *
0010: * See terms of license at gnu.org. *
0011: * *
0012: *************************************************************************/package org.ejbca.util;
0013:
0014: import java.io.BufferedReader;
0015: import java.io.ByteArrayInputStream;
0016: import java.io.ByteArrayOutputStream;
0017: import java.io.FileInputStream;
0018: import java.io.IOException;
0019: import java.io.InputStream;
0020: import java.io.InputStreamReader;
0021: import java.io.PrintStream;
0022: import java.math.BigInteger;
0023: import java.net.URL;
0024: import java.security.InvalidKeyException;
0025: import java.security.MessageDigest;
0026: import java.security.NoSuchAlgorithmException;
0027: import java.security.NoSuchProviderException;
0028: import java.security.PrivateKey;
0029: import java.security.Provider;
0030: import java.security.PublicKey;
0031: import java.security.SecureRandom;
0032: import java.security.Security;
0033: import java.security.SignatureException;
0034: import java.security.cert.CRLException;
0035: import java.security.cert.Certificate;
0036: import java.security.cert.CertificateEncodingException;
0037: import java.security.cert.CertificateException;
0038: import java.security.cert.CertificateFactory;
0039: import java.security.cert.CertificateParsingException;
0040: import java.security.cert.X509CRL;
0041: import java.security.cert.X509Certificate;
0042: import java.security.interfaces.RSAPublicKey;
0043: import java.util.ArrayList;
0044: import java.util.Collection;
0045: import java.util.Date;
0046: import java.util.Hashtable;
0047: import java.util.Iterator;
0048: import java.util.List;
0049: import java.util.Vector;
0050:
0051: import org.apache.commons.lang.BooleanUtils;
0052: import org.apache.commons.lang.StringUtils;
0053: import org.apache.log4j.Logger;
0054: import org.bouncycastle.asn1.ASN1EncodableVector;
0055: import org.bouncycastle.asn1.ASN1InputStream;
0056: import org.bouncycastle.asn1.ASN1OctetString;
0057: import org.bouncycastle.asn1.ASN1Sequence;
0058: import org.bouncycastle.asn1.ASN1TaggedObject;
0059: import org.bouncycastle.asn1.DERBitString;
0060: import org.bouncycastle.asn1.DEREncodable;
0061: import org.bouncycastle.asn1.DERIA5String;
0062: import org.bouncycastle.asn1.DERObject;
0063: import org.bouncycastle.asn1.DERObjectIdentifier;
0064: import org.bouncycastle.asn1.DEROctetString;
0065: import org.bouncycastle.asn1.DERSequence;
0066: import org.bouncycastle.asn1.DERTaggedObject;
0067: import org.bouncycastle.asn1.DERUTF8String;
0068: import org.bouncycastle.asn1.x509.AccessDescription;
0069: import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
0070: import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
0071: import org.bouncycastle.asn1.x509.BasicConstraints;
0072: import org.bouncycastle.asn1.x509.GeneralName;
0073: import org.bouncycastle.asn1.x509.GeneralNames;
0074: import org.bouncycastle.asn1.x509.PolicyInformation;
0075: import org.bouncycastle.asn1.x509.ReasonFlags;
0076: import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
0077: import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
0078: import org.bouncycastle.asn1.x509.X509DefaultEntryConverter;
0079: import org.bouncycastle.asn1.x509.X509Extension;
0080: import org.bouncycastle.asn1.x509.X509Extensions;
0081: import org.bouncycastle.asn1.x509.X509Name;
0082: import org.bouncycastle.asn1.x509.X509NameEntryConverter;
0083: import org.bouncycastle.asn1.x509.X509NameTokenizer;
0084: import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
0085: import org.bouncycastle.jce.X509KeyUsage;
0086: import org.bouncycastle.jce.interfaces.ConfigurableProvider;
0087: import org.bouncycastle.jce.provider.BouncyCastleProvider;
0088: import org.bouncycastle.math.ec.ECCurve;
0089: import org.bouncycastle.util.encoders.Hex;
0090: import org.bouncycastle.x509.X509V3CertificateGenerator;
0091: import org.ejbca.core.model.ca.catoken.CATokenInfo;
0092: import org.ejbca.core.model.ca.crl.RevokedCertInfo;
0093: import org.ejbca.util.dn.DNFieldExtractor;
0094: import org.ejbca.util.dn.DnComponents;
0095:
0096: /**
0097: * Tools to handle common certificate operations.
0098: *
0099: * @version $Id: CertTools.java,v 1.54 2008/03/14 16:55:36 anatom Exp $
0100: */
0101: public class CertTools {
0102: private static Logger log = Logger.getLogger(CertTools.class);
0103:
0104: // Initialize dnComponents
0105: static {
0106: DnComponents.getDnObjects();
0107: }
0108: public static final String EMAIL = "rfc822name";
0109: public static final String EMAIL1 = "email";
0110: public static final String EMAIL2 = "EmailAddress";
0111: public static final String EMAIL3 = "E";
0112: public static final String DNS = "dNSName";
0113: public static final String URI = "uniformResourceIdentifier";
0114: public static final String URI1 = "uri";
0115: public static final String URI2 = "uniformResourceId";
0116: public static final String IPADDR = "iPAddress";
0117: public static final String DIRECTORYNAME = "directoryName";
0118:
0119: /** Microsoft altName for windows smart card logon */
0120: public static final String UPN = "upn";
0121: /** ObjectID for upn altName for windows smart card logon */
0122: public static final String UPN_OBJECTID = "1.3.6.1.4.1.311.20.2.3";
0123: /** Microsoft altName for windows domain controller guid */
0124: public static final String GUID = "guid";
0125: /** ObjectID for upn altName for windows domain controller guid */
0126: public static final String GUID_OBJECTID = "1.3.6.1.4.1.311.25.1";
0127: /** ObjectID for Mircosoft Encrypted File System Certificates */
0128: public static final String EFS_OBJECTID = "1.3.6.1.4.1.311.10.3.4";
0129: /** ObjectID for Mircosoft Encrypted File System Recovery Certificates */
0130: public static final String EFSR_OBJECTID = "1.3.6.1.4.1.311.10.3.4.1";
0131: /** Object id id-pkix */
0132: public static final String id_pkix = "1.3.6.1.5.5.7";
0133: /** Object id id-kp */
0134: public static final String id_kp = id_pkix + ".3";
0135: /** Object id id-pda */
0136: public static final String id_pda = id_pkix + ".9";
0137: /** Object id id-pda-dateOfBirth
0138: * DateOfBirth ::= GeneralizedTime
0139: */
0140: public static final String id_pda_dateOfBirth = id_pda + ".1";
0141: /** Object id id-pda-placeOfBirth
0142: * PlaceOfBirth ::= DirectoryString
0143: */
0144: public static final String id_pda_placeOfBirth = id_pda + ".2";
0145: /** Object id id-pda-gender
0146: * Gender ::= PrintableString (SIZE(1))
0147: * -- "M", "F", "m" or "f"
0148: */
0149: public static final String id_pda_gender = id_pda + ".3";
0150: /** Object id id-pda-countryOfCitizenship
0151: * CountryOfCitizenship ::= PrintableString (SIZE (2))
0152: * -- ISO 3166 Country Code
0153: */
0154: public static final String id_pda_countryOfCitizenship = id_pda
0155: + ".4";
0156: /** Object id id-pda-countryOfResidence
0157: * CountryOfResidence ::= PrintableString (SIZE (2))
0158: * -- ISO 3166 Country Code
0159: */
0160: public static final String id_pda_countryOfResidence = id_pda
0161: + ".5";
0162: /** OID used for creating MS Templates certificate extension */
0163: public static final String OID_MSTEMPLATE = "1.3.6.1.4.1.311.20.2";
0164: /** New OID for ipsec (rfc4945), replaces old deprecated id_kp_ipsecEndSystem, id_kp_ipsecTunnel and id_kp_ipsecUser */
0165: public static final String id_kp_ipsecIKE = id_kp + ".17";
0166: /** OIDs for SCVP (rfc5055) */
0167: public static final String id_kp_scvpServer = id_kp + ".15";
0168: public static final String id_kp_scvpClient = id_kp + ".16";
0169:
0170: private static final String[] EMAILIDS = { EMAIL, EMAIL1, EMAIL2,
0171: EMAIL3 };
0172: /** ObjectID for unstructuredName DN attribute */
0173: //public static final DERObjectIdentifier unstructuredName = new DERObjectIdentifier("1.2.840.113549.1.9.2");
0174: /** ObjectID for unstructuredAddress DN attribute */
0175: //public static final DERObjectIdentifier unstructuredAddress = new DERObjectIdentifier("1.2.840.113549.1.9.8");
0176: /** Parameters used when generating or verifying ECDSA keys/certs using the "implicitlyCA" key encoding.
0177: * The curve parameters is then defined outside of the key and configured in the BC provider.
0178: */
0179: private static String IMPLICITLYCA_Q = "@ecdsa.implicitlyca.q@";
0180: private static String IMPLICITLYCA_A = "@ecdsa.implicitlyca.a@";
0181: private static String IMPLICITLYCA_B = "@ecdsa.implicitlyca.b@";
0182: private static String IMPLICITLYCA_G = "@ecdsa.implicitlyca.g@";
0183: private static String IMPLICITLYCA_N = "@ecdsa.implicitlyca.n@";
0184:
0185: /** System provider used to circumvent a bug in Glassfish. Should only be used by
0186: * X509CAInfo, OCSPCAService, XKMSCAService, CMSCAService.
0187: * Defaults to SUN but can be changed to IBM by the installBCProvider method.
0188: */
0189: public static String SYSTEM_SECURITY_PROVIDER = "SUN";
0190:
0191: /** Flag indicating if the BC provider should be removed before installing it again. When developing and re-deploying alot
0192: * this is needed so you don't have to restart JBoss all the time.
0193: * In production it may cause failures because the BC provider may get removed just when another thread wants to use it.
0194: * Therefore the default value is false.
0195: */
0196: private static final boolean developmentProviderInstallation = BooleanUtils
0197: .toBoolean("@development.provider.installation@");
0198:
0199: /**
0200: * inhibits creation of new CertTools
0201: */
0202: protected CertTools() {
0203: }
0204:
0205: /** See stringToBcX509Name(String, X509NameEntryConverter), this method uses the default BC converter (X509DefaultEntryConverter)
0206: * @see #stringToBcX509Name(String, X509NameEntryConverter)
0207: * @param dn String containing DN that will be transformed into X509Name, The
0208: * DN string has the format "CN=zz,OU=yy,O=foo,C=SE". Unknown OIDs in
0209: * the string will be added to the end positions of OID array.
0210: *
0211: * @return X509Name or null if input is null
0212: */
0213: public static X509Name stringToBcX509Name(String dn) {
0214: X509NameEntryConverter converter = new X509DefaultEntryConverter();
0215: return stringToBcX509Name(dn, converter);
0216: }
0217:
0218: /**
0219: * Creates a (Bouncycastle) X509Name object from a string with a DN. Known OID
0220: * (with order) are:
0221: * <code> EmailAddress, UID, CN, SN (SerialNumber), GivenName, Initials, SurName, T, OU,
0222: * O, L, ST, DC, C </code>
0223: * To change order edit 'dnObjects' in this source file. Important NOT to mess
0224: * with the ordering within this class, since cert vierification on some
0225: * clients (IE :-() might depend on order.
0226: *
0227: * @param dn
0228: * String containing DN that will be transformed into X509Name, The
0229: * DN string has the format "CN=zz,OU=yy,O=foo,C=SE". Unknown OIDs in
0230: * the string will be added to the end positions of OID array.
0231: * @param converter BC converter for DirectoryStrings, that determines which encoding is chosen
0232: * @return X509Name or null if input is null
0233: */
0234: private static X509Name stringToBcX509Name(String dn,
0235: X509NameEntryConverter converter) {
0236: return stringToBcX509Name(dn, converter,
0237: getDefaultX509FieldOrder());
0238: }
0239:
0240: public static X509Name stringToBcX509Name(String dn,
0241: X509NameEntryConverter converter, Vector dnOrder) {
0242: //log.debug(">stringToBcX509Name: " + dn);
0243: if (dn == null)
0244: return null;
0245:
0246: Vector defaultOrdering = new Vector();
0247: Vector values = new Vector();
0248: X509NameTokenizer xt = new X509NameTokenizer(dn);
0249:
0250: while (xt.hasMoreTokens()) {
0251: // This is a pair key=val (CN=xx)
0252: String pair = xt.nextToken();
0253: int ix = pair.indexOf("=");
0254:
0255: if (ix != -1) {
0256: String key = pair.substring(0, ix).toLowerCase().trim();
0257: String val = pair.substring(ix + 1);
0258: if (val != null) {
0259: // String whitespace from the beginning of the value, to handle the case
0260: // where someone type CN = Foo Bar
0261: val = StringUtils.stripStart(val, null);
0262: }
0263:
0264: // -- First search the OID by name in declared OID's
0265: DERObjectIdentifier oid = DnComponents.getOid(key);
0266:
0267: try {
0268: // -- If isn't declared, we try to create it
0269: if (oid == null) {
0270: oid = new DERObjectIdentifier(key);
0271: }
0272: defaultOrdering.add(oid);
0273: values.add(val);
0274: } catch (IllegalArgumentException e) {
0275: // If it is not an OID we will ignore it
0276: log
0277: .warn("Unknown DN component ignored and silently dropped: "
0278: + key);
0279: }
0280:
0281: } else {
0282: log.warn("Huh, what's this? DN: " + dn + " PAIR: "
0283: + pair);
0284: }
0285: }
0286:
0287: X509Name x509Name = new X509Name(defaultOrdering, values,
0288: converter);
0289:
0290: //-- Reorder fields
0291: X509Name orderedX509Name = getOrderedX509Name(x509Name,
0292: dnOrder, converter);
0293:
0294: //log.debug("<stringToBcX509Name");
0295: return orderedX509Name;
0296: } // stringToBcX509Name
0297:
0298: /**
0299: * Every DN-string should look the same. Creates a name string ordered and looking like we want
0300: * it...
0301: *
0302: * @param dn String containing DN
0303: *
0304: * @return String containing DN, or null if input is null
0305: */
0306: public static String stringToBCDNString(String dn) {
0307: //log.debug(">stringToBcDNString: "+dn);
0308: if (isDNReversed(dn)) {
0309: dn = reverseDN(dn);
0310: }
0311: String ret = null;
0312: X509Name name = stringToBcX509Name(dn);
0313: if (name != null) {
0314: ret = name.toString();
0315: }
0316: // For some databases (MySQL for instance) the database column holding subjectDN
0317: // is only 250 chars long. There have been strange error reported (clipping DN natuarally)
0318: // that is hard to debug if DN is more than 250 chars and we don't have a good message
0319: if ((ret != null) && (ret.length() > 250)) {
0320: log
0321: .info("Warning! DN is more than 250 characters long. Some databases have only 250 characters in the database for SubjectDN. Clipping may occur! DN ("
0322: + ret.length() + " chars): " + ret);
0323: }
0324: //log.debug("<stringToBcDNString: "+ret);
0325: return ret;
0326: }
0327:
0328: /**
0329: * Convenience method for getting an email addresses from a DN. Uses {@link
0330: * getPartsFromDN(String,String)} internally, and searches for {@link EMAIL}, {@link EMAIL1},
0331: * {@link EMAIL2}, {@link EMAIL3} and returns the first one found.
0332: *
0333: * @param dn the DN
0334: *
0335: * @return ArrayList containing email or empty list if email is not present
0336: * @return the found email address, or <code>null</code> if none is found
0337: */
0338: public static ArrayList getEmailFromDN(String dn) {
0339: log.debug(">getEmailFromDN(" + dn + ")");
0340: ArrayList ret = new ArrayList();
0341: for (int i = 0; i < EMAILIDS.length; i++) {
0342: ArrayList emails = getPartsFromDN(dn, EMAILIDS[i]);
0343: if (emails.size() > 0) {
0344: ret.addAll(emails);
0345: }
0346:
0347: }
0348: log.debug("<getEmailFromDN(" + dn + "): " + ret.size());
0349: return ret;
0350: }
0351:
0352: /**
0353: * Search for e-mail address, first in SubjectAltName (as in PKIX
0354: * recomandation) then in subject DN.
0355: * Original author: Marco Ferrante, (c) 2005 CSITA - University of Genoa (Italy)
0356: *
0357: * @param certificate
0358: * @return subject email or null if not present in certificate
0359: */
0360: public static String getEMailAddress(X509Certificate certificate) {
0361: log.debug("Searching for EMail Address in SubjectAltName");
0362: if (certificate == null) {
0363: return null;
0364: }
0365: try {
0366: if (certificate.getSubjectAlternativeNames() != null) {
0367: java.util.Collection altNames = certificate
0368: .getSubjectAlternativeNames();
0369: Iterator iter = altNames.iterator();
0370: while (iter.hasNext()) {
0371: java.util.List item = (java.util.List) iter.next();
0372: Integer type = (Integer) item.get(0);
0373: if (type.intValue() == 1) {
0374: return (String) item.get(1);
0375: }
0376: }
0377: }
0378: } catch (CertificateParsingException e) {
0379: log.error("Error parsing certificate: ", e);
0380: }
0381: log.debug("Searching for EMail Address in Subject DN");
0382: ArrayList emails = CertTools.getEmailFromDN(certificate
0383: .getSubjectDN().getName());
0384: if (emails.size() > 0) {
0385: return (String) emails.get(0);
0386: }
0387: return null;
0388: }
0389:
0390: /**
0391: * Takes a DN and reverses it completely so the first attribute ends up last.
0392: * C=SE,O=Foo,CN=Bar becomes CN=Bar,O=Foo,C=SE.
0393: *
0394: * @param dn String containing DN to be reversed, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
0395: *
0396: * @return String containing reversed DN
0397: */
0398: public static String reverseDN(String dn) {
0399: log.debug(">reverseDN: dn: " + dn);
0400: String ret = null;
0401: if (dn != null) {
0402: String o;
0403: BasicX509NameTokenizer xt = new BasicX509NameTokenizer(dn);
0404: StringBuffer buf = new StringBuffer();
0405: boolean first = true;
0406: while (xt.hasMoreTokens()) {
0407: o = xt.nextToken();
0408: //log.debug("token: "+o);
0409: if (!first) {
0410: buf.insert(0, ",");
0411: } else {
0412: first = false;
0413: }
0414: buf.insert(0, o);
0415: }
0416: if (buf.length() > 0) {
0417: ret = buf.toString();
0418: }
0419: }
0420:
0421: log.debug("<reverseDN: resulting dn: " + ret);
0422: return ret;
0423: } //reverseDN
0424:
0425: /**
0426: * Tries to determine if a DN is in reversed form. It does this by taking the last attribute
0427: * and the first attribute. If the last attribute comes before the first in the dNObjects array
0428: * the DN is assumed to be in reversed order.
0429: * The check if a DN is revered is relative to the default ordering, so if the default ordering is:
0430: * "C=SE, O=PrimeKey, CN=Tomas" (dNObjectsReverse ordering in EJBCA) a dn or form "CN=Tomas, O=PrimeKey, C=SE" is reversed.
0431: *
0432: * if the default ordering is:
0433: * "CN=Tomas, O=PrimeKey, C=SE" (dNObjectsForward ordering in EJBCA) a dn or form "C=SE, O=PrimeKey, CN=Tomas" is reversed.
0434: *
0435: *
0436: * @param dn String containing DN to be checked, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
0437: *
0438: * @return true if the DN is believed to be in reversed order, false otherwise
0439: */
0440: protected static boolean isDNReversed(String dn) {
0441: //log.debug(">isDNReversed: dn: " + dn);
0442: boolean ret = false;
0443: if (dn != null) {
0444: String first = null;
0445: String last = null;
0446: X509NameTokenizer xt = new X509NameTokenizer(dn);
0447: if (xt.hasMoreTokens()) {
0448: first = xt.nextToken();
0449: }
0450: while (xt.hasMoreTokens()) {
0451: last = xt.nextToken();
0452: }
0453: String[] dNObjects = DnComponents.getDnObjects();
0454: if ((first != null) && (last != null)) {
0455: first = first.substring(0, first.indexOf('='));
0456: last = last.substring(0, last.indexOf('='));
0457: int firsti = 0, lasti = 0;
0458: for (int i = 0; i < dNObjects.length; i++) {
0459: if (first.toLowerCase().equals(dNObjects[i])) {
0460: firsti = i;
0461: }
0462: if (last.toLowerCase().equals(dNObjects[i])) {
0463: lasti = i;
0464: }
0465: }
0466: if (lasti < firsti) {
0467: ret = true;
0468: }
0469:
0470: }
0471: }
0472: //log.debug("<isDNReversed: " + ret);
0473: return ret;
0474: } //isDNReversed
0475:
0476: /**
0477: * Gets a specified part of a DN. Specifically the first occurrence it the DN contains several
0478: * instances of a part (i.e. cn=x, cn=y returns x).
0479: *
0480: * @param dn String containing DN, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
0481: * @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc.
0482: *
0483: * @return String containing dnpart or null if dnpart is not present
0484: */
0485: public static String getPartFromDN(String dn, String dnpart) {
0486: log.debug(">getPartFromDN: dn:'" + dn + "', dnpart=" + dnpart);
0487: String part = null;
0488: if ((dn != null) && (dnpart != null)) {
0489: String o;
0490: dnpart += "="; // we search for 'CN=' etc.
0491: X509NameTokenizer xt = new X509NameTokenizer(dn);
0492: while (xt.hasMoreTokens()) {
0493: o = xt.nextToken();
0494: //log.debug("checking: "+o.substring(0,dnpart.length()));
0495: if ((o.length() > dnpart.length())
0496: && o.substring(0, dnpart.length())
0497: .equalsIgnoreCase(dnpart)) {
0498: part = o.substring(dnpart.length());
0499:
0500: break;
0501: }
0502: }
0503: }
0504: log.debug("<getpartFromDN: resulting DN part=" + part);
0505: return part;
0506: } //getPartFromDN
0507:
0508: /**
0509: * Gets a specified parts of a DN. Returns all occurences as an ArrayList, also works if DN contains several
0510: * instances of a part (i.e. cn=x, cn=y returns {x, y, null}).
0511: *
0512: * @param dn String containing DN, The DN string has the format "C=SE, O=xx, OU=yy, CN=zz".
0513: * @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc.
0514: *
0515: * @return ArrayList containing dnparts or empty list if dnpart is not present
0516: */
0517: public static ArrayList getPartsFromDN(String dn, String dnpart) {
0518: log.debug(">getPartsFromDN: dn:'" + dn + "', dnpart=" + dnpart);
0519: ArrayList parts = new ArrayList();
0520: if ((dn != null) && (dnpart != null)) {
0521: String o;
0522: dnpart += "="; // we search for 'CN=' etc.
0523: X509NameTokenizer xt = new X509NameTokenizer(dn);
0524: while (xt.hasMoreTokens()) {
0525: o = xt.nextToken();
0526: if ((o.length() > dnpart.length())
0527: && o.substring(0, dnpart.length())
0528: .equalsIgnoreCase(dnpart)) {
0529: parts.add(o.substring(dnpart.length()));
0530: }
0531: }
0532: }
0533: log.debug("<getpartsFromDN: resulting DN part="
0534: + parts.toString());
0535: return parts;
0536: } //getPartFromDN
0537:
0538: /**
0539: * 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.
0540: *
0541: * @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.
0542: * @param dnpart String specifying which part of the DN to get, should be "CN" or "OU" etc.
0543: *
0544: * @return ArrayList containing oids or empty list if no custom OIDs are present
0545: */
0546: public static ArrayList getCustomOids(String dn) {
0547: log.debug(">getCustomOids: dn:'" + dn);
0548: ArrayList parts = new ArrayList();
0549: if (dn != null) {
0550: String o;
0551: X509NameTokenizer xt = new X509NameTokenizer(dn);
0552: while (xt.hasMoreTokens()) {
0553: o = xt.nextToken();
0554: // Try to see if it is a valid OID
0555: try {
0556: int i = o.indexOf('=');
0557: // An oid is never shorter than 3 chars and must start with 1.
0558: if ((i > 2) && (o.charAt(1) == '.')) {
0559: String oid = o.substring(0, i);
0560: new DERObjectIdentifier(oid);
0561: parts.add(oid);
0562: }
0563: } catch (IllegalArgumentException e) {
0564: // Not a valid oid
0565: }
0566: }
0567: }
0568: log.debug("<getpartsFromDN: resulting DN part="
0569: + parts.toString());
0570: return parts;
0571: } //getPartFromDN
0572:
0573: /**
0574: * Gets subject DN in the format we are sure about (BouncyCastle),supporting UTF8.
0575: *
0576: * @param cert X509Certificate
0577: *
0578: * @return String containing the subjects DN.
0579: */
0580: public static String getSubjectDN(X509Certificate cert) {
0581: return getDN(cert, 1);
0582: }
0583:
0584: /**
0585: * Gets issuer DN in the format we are sure about (BouncyCastle),supporting UTF8.
0586: *
0587: * @param cert X509Certificate
0588: *
0589: * @return String containing the issuers DN.
0590: */
0591: public static String getIssuerDN(X509Certificate cert) {
0592: return getDN(cert, 2);
0593: }
0594:
0595: /**
0596: * Gets subject or issuer DN in the format we are sure about (BouncyCastle),supporting UTF8.
0597: *
0598: * @param cert X509Certificate
0599: * @param which 1 = subjectDN, anything else = issuerDN
0600: *
0601: * @return String containing the DN.
0602: */
0603: private static String getDN(X509Certificate cert, int which) {
0604: //log.debug(">getDN("+which+")");
0605: String dn = null;
0606: if (cert == null) {
0607: return dn;
0608: }
0609: try {
0610: CertificateFactory cf = CertTools.getCertificateFactory();
0611: X509Certificate x509cert = (X509Certificate) cf
0612: .generateCertificate(new ByteArrayInputStream(cert
0613: .getEncoded()));
0614: //log.debug("Created certificate of class: " + x509cert.getClass().getName());
0615:
0616: if (which == 1) {
0617: dn = x509cert.getSubjectDN().toString();
0618: } else {
0619: dn = x509cert.getIssuerDN().toString();
0620: }
0621: } catch (CertificateException ce) {
0622: log.error("CertificateException: ", ce);
0623: return null;
0624: }
0625: //log.debug("<getDN("+which+"):"+dn);
0626: return stringToBCDNString(dn);
0627: } // getDN
0628:
0629: /**
0630: * Gets issuer DN for CRL in the format we are sure about (BouncyCastle),supporting UTF8.
0631: *
0632: * @param crl X509RL
0633: *
0634: * @return String containing the DN.
0635: */
0636: public static String getIssuerDN(X509CRL crl) {
0637: //log.debug(">getIssuerDN(crl)");
0638: String dn = null;
0639: try {
0640: CertificateFactory cf = CertTools.getCertificateFactory();
0641: X509CRL x509crl = (X509CRL) cf
0642: .generateCRL(new ByteArrayInputStream(crl
0643: .getEncoded()));
0644: //log.debug("Created certificate of class: " + x509crl.getClass().getName());
0645: dn = x509crl.getIssuerDN().toString();
0646: } catch (CRLException ce) {
0647: log.error("CRLException: ", ce);
0648: return null;
0649: }
0650: //log.debug("<getIssuerDN(crl):"+dn);
0651: return stringToBCDNString(dn);
0652: } // getIssuerDN
0653:
0654: public static CertificateFactory getCertificateFactory() {
0655: try {
0656: return CertificateFactory.getInstance("X.509", "BC");
0657: } catch (NoSuchProviderException nspe) {
0658: log.error("NoSuchProvider: ", nspe);
0659: } catch (CertificateException ce) {
0660: log.error("CertificateException: ", ce);
0661: }
0662: return null;
0663: }
0664:
0665: public static synchronized void removeBCProvider() {
0666: Security.removeProvider("BC");
0667: }
0668:
0669: public static synchronized void installBCProvider() {
0670: // A flag that ensures that we install the parameters for implcitlyCA only when we have installed a new provider
0671: boolean installImplicitlyCA = false;
0672: if (Security.addProvider(new BouncyCastleProvider()) < 0) {
0673: // If already installed, remove so we can handle redeploy
0674: // Nope, we ignore re-deploy on this level, because it can happen
0675: // that the BC-provider is uninstalled, in just the second another
0676: // thread tries to use the provider, and then that request will fail.
0677: if (developmentProviderInstallation) {
0678: removeBCProvider();
0679: if (Security.addProvider(new BouncyCastleProvider()) < 0) {
0680: log.error("Cannot even install BC provider again!");
0681: } else {
0682: installImplicitlyCA = true;
0683: }
0684: }
0685: } else {
0686: installImplicitlyCA = true;
0687: }
0688: if (installImplicitlyCA) {
0689: // Install EC parameters for implicitlyCA encoding of EC keys, we have default curve parameters if no new ones have been given.
0690: // The parameters are only used if implicitlyCA is used for generating keys, or verifying certs
0691: checkImplicitParams();
0692: ECCurve curve = new ECCurve.Fp(new BigInteger(
0693: IMPLICITLYCA_Q), // q
0694: new BigInteger(IMPLICITLYCA_A, 16), // a
0695: new BigInteger(IMPLICITLYCA_B, 16)); // b
0696: org.bouncycastle.jce.spec.ECParameterSpec implicitSpec = new org.bouncycastle.jce.spec.ECParameterSpec(
0697: curve, curve
0698: .decodePoint(Hex.decode(IMPLICITLYCA_G)), // G
0699: new BigInteger(IMPLICITLYCA_N)); // n
0700: ConfigurableProvider config = (ConfigurableProvider) Security
0701: .getProvider("BC");
0702: if (config != null) {
0703: config.setParameter(
0704: ConfigurableProvider.EC_IMPLICITLY_CA,
0705: implicitSpec);
0706: } else {
0707: log
0708: .error("Can not get ConfigurableProvider, implicitlyCA EC parameters NOT set!");
0709: }
0710: }
0711:
0712: // 2007-05-25
0713: // Finally we must configure SERIALNUMBER behavior in BC >=1.36 to be the same
0714: // as the behavior in BC 1.35, it changed from SN to SERIALNUMBER in BC 1.36
0715: // We must be backwards compatible
0716: X509Name.DefaultSymbols.put(X509Name.SN, "SN");
0717:
0718: // We hard specify the system security provider in a few cases (see SYSTEM_SECURITY_PROVIDER).
0719: // If the SUN provider does not exist, we will always use BC.
0720: Provider p = Security
0721: .getProvider(CertTools.SYSTEM_SECURITY_PROVIDER);
0722: if (p == null) {
0723: log
0724: .debug("SUN security provider does not exist, using BC as system default provider.");
0725: SYSTEM_SECURITY_PROVIDER = "BC";
0726: }
0727:
0728: }
0729:
0730: /** Check if parameters have been set correctly during pre-process, otherwise log an error and
0731: * set default values. Mostly used to be able to do JUnit testing
0732: */
0733: private static void checkImplicitParams() {
0734: if (StringUtils
0735: .contains(IMPLICITLYCA_Q, "ecdsa.implicitlyca.q")) {
0736: log.info("IMPLICITLYCA_Q not set, using default.");
0737: IMPLICITLYCA_Q = "883423532389192164791648750360308885314476597252960362792450860609699839";
0738: }
0739: if (StringUtils
0740: .contains(IMPLICITLYCA_A, "ecdsa.implicitlyca.a")) {
0741: log.info("IMPLICITLYCA_A not set, using default.");
0742: IMPLICITLYCA_A = "7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc";
0743: }
0744: if (StringUtils
0745: .contains(IMPLICITLYCA_B, "ecdsa.implicitlyca.b")) {
0746: log.info("IMPLICITLYCA_B not set, using default.");
0747: IMPLICITLYCA_B = "6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a";
0748: }
0749: if (StringUtils
0750: .contains(IMPLICITLYCA_G, "ecdsa.implicitlyca.g")) {
0751: log.info("IMPLICITLYCA_G not set, using default.");
0752: IMPLICITLYCA_G = "020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf";
0753: }
0754: if (StringUtils
0755: .contains(IMPLICITLYCA_N, "ecdsa.implicitlyca.n")) {
0756: log.info("IMPLICITLYCA_N not set, using default.");
0757: IMPLICITLYCA_N = "883423532389192164791648750360308884807550341691627752275345424702807307";
0758: }
0759: }
0760:
0761: /**
0762: * Reads a certificate in PEM-format from a file. The file may contain other things,
0763: * the first certificate in the file is read.
0764: *
0765: * @param certFile the file containing the certificate in PEM-format
0766: * @return Ordered Collection of X509Certificate, first certificate first, or empty Collection
0767: * @exception IOException if the filen cannot be read.
0768: * @exception CertificateException if the filen does not contain a correct certificate.
0769: */
0770: public static Collection getCertsFromPEM(String certFile)
0771: throws IOException, CertificateException {
0772: log.debug(">getCertfromPEM: certFile=" + certFile);
0773: InputStream inStrm = null;
0774: Collection certs;
0775: try {
0776: inStrm = new FileInputStream(certFile);
0777: certs = getCertsFromPEM(inStrm);
0778: } finally {
0779: if (inStrm != null)
0780: inStrm.close();
0781: }
0782: log.debug("<getCertfromPEM: certFile=" + certFile);
0783: return certs;
0784: }
0785:
0786: /**
0787: * Reads a certificate in PEM-format from an InputStream. The stream may contain other things,
0788: * the first certificate in the stream is read.
0789: *
0790: * @param certFile the input stream containing the certificate in PEM-format
0791: * @return Ordered Collection of X509Certificate, first certificate first, or empty Collection
0792: * @exception IOException if the stream cannot be read.
0793: * @exception CertificateException if the stream does not contain a correct certificate.
0794: */
0795: public static Collection getCertsFromPEM(InputStream certstream)
0796: throws IOException, CertificateException {
0797: log.debug(">getCertfromPEM:");
0798: ArrayList ret = new ArrayList();
0799: String beginKey = "-----BEGIN CERTIFICATE-----";
0800: String endKey = "-----END CERTIFICATE-----";
0801: BufferedReader bufRdr = null;
0802: ByteArrayOutputStream ostr = null;
0803: PrintStream opstr = null;
0804: try {
0805: bufRdr = new BufferedReader(new InputStreamReader(
0806: certstream));
0807: while (bufRdr.ready()) {
0808: ostr = new ByteArrayOutputStream();
0809: opstr = new PrintStream(ostr);
0810: String temp;
0811: while ((temp = bufRdr.readLine()) != null
0812: && !temp.equals(beginKey))
0813: continue;
0814: if (temp == null)
0815: throw new IOException("Error in "
0816: + certstream.toString() + ", missing "
0817: + beginKey + " boundary");
0818: while ((temp = bufRdr.readLine()) != null
0819: && !temp.equals(endKey))
0820: opstr.print(temp);
0821: if (temp == null)
0822: throw new IOException("Error in "
0823: + certstream.toString() + ", missing "
0824: + endKey + " boundary");
0825: opstr.close();
0826:
0827: byte[] certbuf = Base64.decode(ostr.toByteArray());
0828: ostr.close();
0829: // Phweeew, were done, now decode the cert from file back to X509Certificate object
0830: CertificateFactory cf = CertTools
0831: .getCertificateFactory();
0832: X509Certificate x509cert = (X509Certificate) cf
0833: .generateCertificate(new ByteArrayInputStream(
0834: certbuf));
0835: ret.add(x509cert);
0836: }
0837: } finally {
0838: if (bufRdr != null)
0839: bufRdr.close();
0840: if (opstr != null)
0841: opstr.close();
0842: if (ostr != null)
0843: ostr.close();
0844: }
0845: log.debug("<getcertfromPEM:" + ret.size());
0846: return ret;
0847: } // getCertsFromPEM
0848:
0849: /** Converts a regular array of certificates into an ArrayList, using the provided provided.
0850: *
0851: * @param certs Certificate[] of certificates to convert
0852: * @param provider provider for example "SUN" or "BC", use null for the default provider (BC)
0853: * @return An ArrayList of certificates in the same order as the passed in array
0854: * @throws NoSuchProviderException
0855: * @throws CertificateException
0856: */
0857: public static ArrayList getCertCollectionFromArray(
0858: Certificate[] certs, String provider)
0859: throws CertificateException, NoSuchProviderException {
0860: if (log.isDebugEnabled()) {
0861: log.debug(">getCertCollectionFromArray: " + provider);
0862: }
0863: ArrayList ret = new ArrayList();
0864: String prov = provider;
0865: if (prov == null) {
0866: prov = "BC";
0867: }
0868: for (int i = 0; i < certs.length; i++) {
0869: CertificateFactory cf = CertificateFactory.getInstance(
0870: "X.509", prov);
0871: Certificate cert = certs[i];
0872: X509Certificate x509cert = (X509Certificate) cf
0873: .generateCertificate(new ByteArrayInputStream(cert
0874: .getEncoded()));
0875: ret.add(x509cert);
0876: }
0877: if (log.isDebugEnabled()) {
0878: log.debug("<getCertCollectionFromArray: " + ret.size());
0879: }
0880: return ret;
0881: }
0882:
0883: /**
0884: * Returns a certificate in PEM-format.
0885: *
0886: * @param certs Collection of X509Certificate to convert to PEM
0887: * @return byte array containing PEM certificate
0888: * @exception CertificateException if the stream does not contain a correct certificate.
0889: */
0890: public static byte[] getPEMFromCerts(Collection certs)
0891: throws CertificateException {
0892: String beginKey = "-----BEGIN CERTIFICATE-----";
0893: String endKey = "-----END CERTIFICATE-----";
0894: ByteArrayOutputStream ostr = new ByteArrayOutputStream();
0895: PrintStream opstr = new PrintStream(ostr);
0896: Iterator iter = certs.iterator();
0897: while (iter.hasNext()) {
0898: X509Certificate cert = (X509Certificate) iter.next();
0899: byte[] certbuf = Base64.encode(cert.getEncoded());
0900: opstr.println("Subject: " + cert.getSubjectDN());
0901: opstr.println("Issuer: " + cert.getIssuerDN());
0902: opstr.println(beginKey);
0903: opstr.println(new String(certbuf));
0904: opstr.println(endKey);
0905: }
0906: opstr.close();
0907: byte[] ret = ostr.toByteArray();
0908: return ret;
0909: }
0910:
0911: /**
0912: * Returns a CRL in PEM-format.
0913: *
0914: * @param crlbytes the der encoded crl bytes to convert to PEM
0915: * @return byte array containing PEM CRL
0916: * @exception IOException if the stream cannot be read.
0917: */
0918: public static byte[] getPEMFromCrl(byte[] crlbytes) {
0919: String beginKey = "-----BEGIN X509 CRL-----";
0920: String endKey = "-----END X509 CRL-----";
0921: ByteArrayOutputStream ostr = new ByteArrayOutputStream();
0922: PrintStream opstr = new PrintStream(ostr);
0923: byte[] crlb64 = Base64.encode(crlbytes);
0924: opstr.println(beginKey);
0925: opstr.println(new String(crlb64));
0926: opstr.println(endKey);
0927: opstr.close();
0928: byte[] ret = ostr.toByteArray();
0929: return ret;
0930: }
0931:
0932: /**
0933: * Creates X509Certificate from byte[].
0934: *
0935: * @param cert byte array containing certificate in DER-format
0936: *
0937: * @return X509Certificate
0938: *
0939: * @throws CertificateException if the byte array does not contain a proper certificate.
0940: * @throws IOException if the byte array cannot be read.
0941: */
0942: public static X509Certificate getCertfromByteArray(byte[] cert)
0943: throws CertificateException {
0944: log.debug(">getCertfromByteArray:");
0945: CertificateFactory cf = CertTools.getCertificateFactory();
0946: X509Certificate x509cert = (X509Certificate) cf
0947: .generateCertificate(new ByteArrayInputStream(cert));
0948: log.debug("<getCertfromByteArray:");
0949: return x509cert;
0950: } // getCertfromByteArray
0951:
0952: /**
0953: * Creates X509CRL from byte[].
0954: *
0955: * @param crl byte array containing CRL in DER-format
0956: *
0957: * @return X509CRL
0958: *
0959: * @throws IOException if the byte array can not be read.
0960: * @throws CertificateException if the byte arrayen does not contani a correct CRL.
0961: * @throws CRLException if the byte arrayen does not contani a correct CRL.
0962: */
0963: public static X509CRL getCRLfromByteArray(byte[] crl)
0964: throws IOException, CRLException {
0965: log.debug(">getCRLfromByteArray:");
0966:
0967: if (crl == null) {
0968: throw new IOException("Cannot read byte[] that is 'null'!");
0969: }
0970:
0971: CertificateFactory cf = CertTools.getCertificateFactory();
0972: X509CRL x509crl = (X509CRL) cf
0973: .generateCRL(new ByteArrayInputStream(crl));
0974: log.debug("<getCRLfromByteArray:");
0975:
0976: return x509crl;
0977: } // getCRLfromByteArray
0978:
0979: /**
0980: * Checks if a certificate is self signed by verifying if subject and issuer are the same.
0981: *
0982: * @param cert the certificate that skall be checked.
0983: *
0984: * @return boolean true if the certificate has the same issuer and subject, false otherwise.
0985: */
0986: public static boolean isSelfSigned(X509Certificate cert) {
0987: log.debug(">isSelfSigned: cert: " + CertTools.getIssuerDN(cert)
0988: + "\n" + CertTools.getSubjectDN(cert));
0989:
0990: boolean ret = CertTools.getSubjectDN(cert).equals(
0991: CertTools.getIssuerDN(cert));
0992: log.debug("<isSelfSigned:" + ret);
0993:
0994: return ret;
0995: } // isSelfSigned
0996:
0997: /**
0998: * Generate a selfsigned certiicate.
0999: *
1000: * @param dn subject and issuer DN
1001: * @param validity in days
1002: * @param policyId policy string ('2.5.29.32.0') or null
1003: * @param privKey private key
1004: * @param pubKey public key
1005: * @param sigAlg signature algorithm, you can use one of the contants CATokenInfo.SIGALG_XXX
1006: * @param isCA boolean true or false
1007: *
1008: * @return X509Certificate, self signed
1009: *
1010: * @throws NoSuchAlgorithmException DOCUMENT ME!
1011: * @throws SignatureException DOCUMENT ME!
1012: * @throws InvalidKeyException DOCUMENT ME!
1013: * @throws IllegalStateException
1014: * @throws CertificateEncodingException
1015: */
1016: public static X509Certificate genSelfCert(String dn, long validity,
1017: String policyId, PrivateKey privKey, PublicKey pubKey,
1018: String sigAlg, boolean isCA)
1019: throws NoSuchAlgorithmException, SignatureException,
1020: InvalidKeyException, CertificateEncodingException,
1021: IllegalStateException {
1022:
1023: int keyusage = X509KeyUsage.keyCertSign + X509KeyUsage.cRLSign;
1024: return genSelfCertForPurpose(dn, validity, policyId, privKey,
1025: pubKey, sigAlg, isCA, keyusage);
1026:
1027: } //genselfCert
1028:
1029: /**
1030: * Generate a selfsigned certiicate with possibility to specify key usage.
1031: *
1032: * @param dn subject and issuer DN
1033: * @param validity in days
1034: * @param policyId policy string ('2.5.29.32.0') or null
1035: * @param privKey private key
1036: * @param pubKey public key
1037: * @param sigAlg signature algorithm, you can use one of the contants CATokenInfo.SIGALG_XXX
1038: * @param isCA boolean true or false
1039: * @param keyusage as defined by constants in X509KeyUsage
1040: *
1041: * @return X509Certificate, self signed
1042: *
1043: * @throws NoSuchAlgorithmException DOCUMENT ME!
1044: * @throws SignatureException DOCUMENT ME!
1045: * @throws InvalidKeyException DOCUMENT ME!
1046: * @throws IllegalStateException
1047: * @throws CertificateEncodingException
1048: */
1049: public static X509Certificate genSelfCertForPurpose(String dn,
1050: long validity, String policyId, PrivateKey privKey,
1051: PublicKey pubKey, String sigAlg, boolean isCA, int keyusage)
1052: throws NoSuchAlgorithmException, SignatureException,
1053: InvalidKeyException, CertificateEncodingException,
1054: IllegalStateException {
1055: // Create self signed certificate
1056: Date firstDate = new Date();
1057:
1058: // Set back startdate ten minutes to avoid some problems with wrongly set clocks.
1059: firstDate.setTime(firstDate.getTime() - (10 * 60 * 1000));
1060:
1061: Date lastDate = new Date();
1062:
1063: // validity in days = validity*24*60*60*1000 milliseconds
1064: lastDate.setTime(lastDate.getTime()
1065: + (validity * (24 * 60 * 60 * 1000)));
1066:
1067: X509V3CertificateGenerator certgen = new X509V3CertificateGenerator();
1068:
1069: // Serialnumber is random bits, where random generator is initialized with Date.getTime() when this
1070: // bean is created.
1071: byte[] serno = new byte[8];
1072: SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
1073: random.setSeed((new Date().getTime()));
1074: random.nextBytes(serno);
1075: certgen
1076: .setSerialNumber((new java.math.BigInteger(serno))
1077: .abs());
1078: certgen.setNotBefore(firstDate);
1079: certgen.setNotAfter(lastDate);
1080: certgen.setSignatureAlgorithm(sigAlg);
1081: certgen.setSubjectDN(CertTools.stringToBcX509Name(dn));
1082: certgen.setIssuerDN(CertTools.stringToBcX509Name(dn));
1083: certgen.setPublicKey(pubKey);
1084:
1085: // Basic constranits is always critical and MUST be present at-least in CA-certificates.
1086: BasicConstraints bc = new BasicConstraints(isCA);
1087: certgen.addExtension(X509Extensions.BasicConstraints.getId(),
1088: true, bc);
1089:
1090: // Put critical KeyUsage in CA-certificates
1091: if (isCA == true) {
1092: X509KeyUsage ku = new X509KeyUsage(keyusage);
1093: certgen.addExtension(X509Extensions.KeyUsage.getId(), true,
1094: ku);
1095: }
1096:
1097: // Subject and Authority key identifier is always non-critical and MUST be present for certificates to verify in Mozilla.
1098: try {
1099: if (isCA == true) {
1100: SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(
1101: (ASN1Sequence) new ASN1InputStream(
1102: new ByteArrayInputStream(pubKey
1103: .getEncoded())).readObject());
1104: SubjectKeyIdentifier ski = new SubjectKeyIdentifier(
1105: spki);
1106:
1107: SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo(
1108: (ASN1Sequence) new ASN1InputStream(
1109: new ByteArrayInputStream(pubKey
1110: .getEncoded())).readObject());
1111: AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(
1112: apki);
1113:
1114: certgen.addExtension(
1115: X509Extensions.SubjectKeyIdentifier.getId(),
1116: false, ski);
1117: certgen.addExtension(
1118: X509Extensions.AuthorityKeyIdentifier.getId(),
1119: false, aki);
1120: }
1121: } catch (IOException e) { // do nothing
1122: }
1123:
1124: // CertificatePolicies extension if supplied policy ID, always non-critical
1125: if (policyId != null) {
1126: PolicyInformation pi = new PolicyInformation(
1127: new DERObjectIdentifier(policyId));
1128: DERSequence seq = new DERSequence(pi);
1129: certgen.addExtension(X509Extensions.CertificatePolicies
1130: .getId(), false, seq);
1131: }
1132:
1133: X509Certificate selfcert = certgen.generate(privKey);
1134:
1135: return selfcert;
1136: } //genselfCertForPurpose
1137:
1138: /**
1139: * Get the authority key identifier from a certificate extensions
1140: *
1141: * @param cert certificate containing the extension
1142: * @return byte[] containing the authority key identifier, or null if it does not exist
1143: * @throws IOException if extension can not be parsed
1144: */
1145: public static byte[] getAuthorityKeyId(X509Certificate cert)
1146: throws IOException {
1147: if (cert == null) {
1148: return null;
1149: }
1150: byte[] extvalue = cert.getExtensionValue("2.5.29.35");
1151: if (extvalue == null) {
1152: return null;
1153: }
1154: DEROctetString oct = (DEROctetString) (new ASN1InputStream(
1155: new ByteArrayInputStream(extvalue)).readObject());
1156: AuthorityKeyIdentifier keyId = new AuthorityKeyIdentifier(
1157: (ASN1Sequence) new ASN1InputStream(
1158: new ByteArrayInputStream(oct.getOctets()))
1159: .readObject());
1160: return keyId.getKeyIdentifier();
1161: } // getAuthorityKeyId
1162:
1163: /**
1164: * Get the subject key identifier from a certificate extensions
1165: *
1166: * @param cert certificate containing the extension
1167: * @return byte[] containing the subject key identifier, or null if it does not exist
1168: * @throws IOException if extension can not be parsed
1169: */
1170: public static byte[] getSubjectKeyId(X509Certificate cert)
1171: throws IOException {
1172: if (cert == null) {
1173: return null;
1174: }
1175: byte[] extvalue = cert.getExtensionValue("2.5.29.14");
1176: if (extvalue == null) {
1177: return null;
1178: }
1179: ASN1OctetString str = ASN1OctetString
1180: .getInstance(new ASN1InputStream(
1181: new ByteArrayInputStream(extvalue))
1182: .readObject());
1183: SubjectKeyIdentifier keyId = SubjectKeyIdentifier
1184: .getInstance(new ASN1InputStream(
1185: new ByteArrayInputStream(str.getOctets()))
1186: .readObject());
1187: return keyId.getKeyIdentifier();
1188: } // getSubjectKeyId
1189:
1190: /**
1191: * Get a certificate policy ID from a certificate policies extension
1192: *
1193: * @param cert certificate containing the extension
1194: * @param pos position of the policy id, if several exist, the first is as pos 0
1195: * @return String with the certificate policy OID
1196: * @throws IOException if extension can not be parsed
1197: */
1198: public static String getCertificatePolicyId(X509Certificate cert,
1199: int pos) throws IOException {
1200: byte[] extvalue = cert
1201: .getExtensionValue(X509Extensions.CertificatePolicies
1202: .getId());
1203: if (extvalue == null) {
1204: return null;
1205: }
1206: DEROctetString oct = (DEROctetString) (new ASN1InputStream(
1207: new ByteArrayInputStream(extvalue)).readObject());
1208: ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(
1209: new ByteArrayInputStream(oct.getOctets())).readObject();
1210: // Check the size so we don't ArrayIndexOutOfBounds
1211: if (seq.size() < pos + 1) {
1212: return null;
1213: }
1214: PolicyInformation pol = new PolicyInformation(
1215: (ASN1Sequence) seq.getObjectAt(pos));
1216: String id = pol.getPolicyIdentifier().getId();
1217: return id;
1218: } // getCertificatePolicyId
1219:
1220: /**
1221: * Gets the Microsoft specific UPN altName.
1222: *
1223: * @param cert certificate containing the extension
1224: * @return String with the UPN name or null if the altName does not exist
1225: */
1226: public static String getUPNAltName(X509Certificate cert)
1227: throws IOException, CertificateParsingException {
1228: Collection altNames = cert.getSubjectAlternativeNames();
1229: if (altNames != null) {
1230: Iterator i = altNames.iterator();
1231: while (i.hasNext()) {
1232: ASN1Sequence seq = getAltnameSequence((List) i.next());
1233: String ret = getUPNStringFromSequence(seq);
1234: if (ret != null) {
1235: return ret;
1236: }
1237: }
1238: }
1239: return null;
1240: } // getUPNAltName
1241:
1242: /** Helper method for the above method
1243: */
1244: private static String getUPNStringFromSequence(ASN1Sequence seq) {
1245: if (seq != null) {
1246: // First in sequence is the object identifier, that we must check
1247: DERObjectIdentifier id = DERObjectIdentifier
1248: .getInstance(seq.getObjectAt(0));
1249: if (id.getId().equals(CertTools.UPN_OBJECTID)) {
1250: ASN1TaggedObject obj = (ASN1TaggedObject) seq
1251: .getObjectAt(1);
1252: DERUTF8String str = DERUTF8String.getInstance(obj
1253: .getObject());
1254: return str.getString();
1255: }
1256: }
1257: return null;
1258: }
1259:
1260: /**
1261: * Gets the Microsoft specific GUID altName, that is encoded as an octect string.
1262: *
1263: * @param cert certificate containing the extension
1264: * @return String with the hex-encoded GUID byte array or null if the altName does not exist
1265: */
1266: public static String getGuidAltName(X509Certificate cert)
1267: throws IOException, CertificateParsingException {
1268: Collection altNames = cert.getSubjectAlternativeNames();
1269: if (altNames != null) {
1270: Iterator i = altNames.iterator();
1271: while (i.hasNext()) {
1272: ASN1Sequence seq = getAltnameSequence((List) i.next());
1273: if (seq != null) {
1274: // First in sequence is the object identifier, that we must check
1275: DERObjectIdentifier id = DERObjectIdentifier
1276: .getInstance(seq.getObjectAt(0));
1277: if (id.getId().equals(CertTools.GUID_OBJECTID)) {
1278: ASN1TaggedObject obj = (ASN1TaggedObject) seq
1279: .getObjectAt(1);
1280: ASN1OctetString str = ASN1OctetString
1281: .getInstance(obj.getObject());
1282: return new String(Hex.encode(str.getOctets()));
1283: }
1284: }
1285: }
1286: }
1287: return null;
1288: } // getGuidAltName
1289:
1290: /** Helper for the above methods
1291: */
1292: private static ASN1Sequence getAltnameSequence(List listitem)
1293: throws IOException {
1294: Integer no = (Integer) listitem.get(0);
1295: if (no.intValue() == 0) {
1296: byte[] altName = (byte[]) listitem.get(1);
1297: return getAltnameSequence(altName);
1298: }
1299: return null;
1300: }
1301:
1302: private static ASN1Sequence getAltnameSequence(byte[] value)
1303: throws IOException {
1304: DERObject oct = (new ASN1InputStream(new ByteArrayInputStream(
1305: value)).readObject());
1306: ASN1Sequence seq = ASN1Sequence.getInstance(oct);
1307: return seq;
1308: }
1309:
1310: /** Gets an altName string from an X509Extension
1311: *
1312: * @param ext X509Extension with AlternativeNames
1313: * @return String as defined in method getSubjectAlternativeName
1314: */
1315: public static String getAltNameStringFromExtension(X509Extension ext) {
1316: String altName = null;
1317: //GeneralNames
1318: ASN1OctetString octs = ext.getValue();
1319: if (octs != null) {
1320: ASN1InputStream aIn = new ASN1InputStream(
1321: new ByteArrayInputStream(octs.getOctets()));
1322: DERObject obj;
1323: try {
1324: obj = aIn.readObject();
1325: GeneralNames gan = GeneralNames.getInstance(obj);
1326: GeneralName[] gns = gan.getNames();
1327: for (int i = 0; i < gns.length; i++) {
1328: GeneralName gn = gns[i];
1329: int tag = gn.getTagNo();
1330: DEREncodable name = gn.getName();
1331: String str = CertTools.getGeneralNameString(tag,
1332: name);
1333: if (altName == null) {
1334: altName = str;
1335: } else {
1336: altName += ", " + str;
1337: }
1338: }
1339: } catch (IOException e) {
1340: log.error("IOException parsing altNames: ", e);
1341: return null;
1342: }
1343: }
1344: return altName;
1345: }
1346:
1347: /**
1348: * SubjectAltName ::= GeneralNames
1349: *
1350: * GeneralNames :: = SEQUENCE SIZE (1..MAX) OF GeneralName
1351: *
1352: * GeneralName ::= CHOICE {
1353: * otherName [0] OtherName,
1354: * rfc822Name [1] IA5String,
1355: * dNSName [2] IA5String,
1356: * x400Address [3] ORAddress,
1357: * directoryName [4] Name,
1358: * ediPartyName [5] EDIPartyName,
1359: * uniformResourceIdentifier [6] IA5String,
1360: * iPAddress [7] OCTET STRING,
1361: * registeredID [8] OBJECT IDENTIFIER}
1362: *
1363: * SubjectAltName is of form \"rfc822Name=<email>,
1364: * dNSName=<host name>, uniformResourceIdentifier=<http://host.com/>,
1365: * iPAddress=<address>, guid=<globally unique id>, directoryName=<CN=testDirName|dir|name>
1366: *
1367: * Supported altNames are upn, rfc822Name, uniformResourceIdentifier, dNSName, iPAddress, directoryName
1368: *
1369: * @author Marco Ferrante, (c) 2005 CSITA - University of Genoa (Italy)
1370: * @author Tomas Gustavsson
1371: * @param certificate containing alt names
1372: * @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.
1373: * @throws java.lang.Exception
1374: */
1375: public static String getSubjectAlternativeName(
1376: X509Certificate certificate)
1377: throws CertificateParsingException, IOException {
1378: log.debug("Search for SubjectAltName");
1379: if (certificate.getSubjectAlternativeNames() == null)
1380: return null;
1381:
1382: java.util.Collection altNames = certificate
1383: .getSubjectAlternativeNames();
1384: if (altNames == null) {
1385: return null;
1386: }
1387: Iterator iter = altNames.iterator();
1388: String result = "";
1389: String append = "";
1390: while (iter.hasNext()) {
1391: java.util.List item = (java.util.List) iter.next();
1392: Integer type = (Integer) item.get(0);
1393: Object value = item.get(1);
1394: if (!StringUtils.isEmpty(result)) {
1395: // Result already contains one altname, so we have to add comma if there are more altNames
1396: append = ", ";
1397: }
1398: switch (type.intValue()) {
1399: case 0:
1400: ASN1Sequence seq = getAltnameSequence(item);
1401: String upn = getUPNStringFromSequence(seq);
1402: // OtherName can be something else besides UPN
1403: if (upn != null) {
1404: result += append + CertTools.UPN + "=" + upn;
1405: }
1406: break;
1407: case 1:
1408: result += append + CertTools.EMAIL + "="
1409: + (String) value;
1410: break;
1411: case 2:
1412: result += append + CertTools.DNS + "=" + (String) value;
1413: break;
1414: case 3: // SubjectAltName of type x400Address not supported
1415: break;
1416: case 4:
1417: result += append + CertTools.DIRECTORYNAME + "="
1418: + (String) value;
1419: break;
1420: case 5: // SubjectAltName of type ediPartyName not supported
1421: break;
1422: case 6:
1423: result += append + CertTools.URI + "=" + (String) value;
1424: break;
1425: case 7:
1426: result += append + CertTools.IPADDR + "="
1427: + (String) value;
1428: break;
1429: default: // SubjectAltName of unknown type
1430: break;
1431: }
1432: }
1433: if (StringUtils.isEmpty(result)) {
1434: return null;
1435: }
1436: return result;
1437: }
1438:
1439: /**
1440: * From an altName string as defined in getSubjectAlternativeName
1441: * @param altName
1442: * @return ASN.1 GeneralNames
1443: * @see #getSubjectAlternativeName
1444: */
1445: public static GeneralNames getGeneralNamesFromAltName(String altName) {
1446: log.debug(">getGeneralNamesFromAltName: " + altName);
1447:
1448: ASN1EncodableVector vec = new ASN1EncodableVector();
1449:
1450: ArrayList emails = CertTools.getEmailFromDN(altName);
1451: if (!emails.isEmpty()) {
1452: Iterator iter = emails.iterator();
1453: while (iter.hasNext()) {
1454: GeneralName gn = new GeneralName(1, new DERIA5String(
1455: (String) iter.next()));
1456: vec.add(gn);
1457: }
1458: }
1459:
1460: ArrayList dns = CertTools
1461: .getPartsFromDN(altName, CertTools.DNS);
1462: if (!dns.isEmpty()) {
1463: Iterator iter = dns.iterator();
1464: while (iter.hasNext()) {
1465: GeneralName gn = new GeneralName(2, new DERIA5String(
1466: (String) iter.next()));
1467: vec.add(gn);
1468: }
1469: }
1470:
1471: String directoryName = getDirectoryStringFromAltName(altName);
1472: if (directoryName != null) {
1473: X509Name x509DirectoryName = new X509Name(directoryName);
1474: GeneralName gn = new GeneralName(4, x509DirectoryName);
1475: vec.add(gn);
1476: }
1477:
1478: ArrayList uri = CertTools
1479: .getPartsFromDN(altName, CertTools.URI);
1480: if (!uri.isEmpty()) {
1481: Iterator iter = uri.iterator();
1482: while (iter.hasNext()) {
1483: GeneralName gn = new GeneralName(6, new DERIA5String(
1484: (String) iter.next()));
1485: vec.add(gn);
1486: }
1487: }
1488: uri = CertTools.getPartsFromDN(altName, CertTools.URI1);
1489: if (!uri.isEmpty()) {
1490: Iterator iter = uri.iterator();
1491: while (iter.hasNext()) {
1492: GeneralName gn = new GeneralName(6, new DERIA5String(
1493: (String) iter.next()));
1494: vec.add(gn);
1495: }
1496: }
1497: uri = CertTools.getPartsFromDN(altName, CertTools.URI2);
1498: if (!uri.isEmpty()) {
1499: Iterator iter = uri.iterator();
1500: while (iter.hasNext()) {
1501: GeneralName gn = new GeneralName(6, new DERIA5String(
1502: (String) iter.next()));
1503: vec.add(gn);
1504: }
1505: }
1506:
1507: ArrayList ipstr = CertTools.getPartsFromDN(altName,
1508: CertTools.IPADDR);
1509: if (!ipstr.isEmpty()) {
1510: Iterator iter = ipstr.iterator();
1511: while (iter.hasNext()) {
1512: byte[] ipoctets = StringTools
1513: .ipStringToOctets((String) iter.next());
1514: GeneralName gn = new GeneralName(7, new DEROctetString(
1515: ipoctets));
1516: vec.add(gn);
1517: }
1518: }
1519:
1520: // UPN is an OtherName
1521: ArrayList upn = CertTools
1522: .getPartsFromDN(altName, CertTools.UPN);
1523: if (!upn.isEmpty()) {
1524: Iterator iter = upn.iterator();
1525: while (iter.hasNext()) {
1526: ASN1EncodableVector v = new ASN1EncodableVector();
1527: v.add(new DERObjectIdentifier(CertTools.UPN_OBJECTID));
1528: v.add(new DERTaggedObject(true, 0, new DERUTF8String(
1529: (String) iter.next())));
1530: //GeneralName gn = new GeneralName(new DERSequence(v), 0);
1531: DERObject gn = new DERTaggedObject(false, 0,
1532: new DERSequence(v));
1533: vec.add(gn);
1534: }
1535: }
1536:
1537: ArrayList guid = CertTools.getPartsFromDN(altName,
1538: CertTools.GUID);
1539: if (!guid.isEmpty()) {
1540: Iterator iter = guid.iterator();
1541: while (iter.hasNext()) {
1542: ASN1EncodableVector v = new ASN1EncodableVector();
1543: byte[] guidbytes = Hex.decode((String) iter.next());
1544: if (guidbytes != null) {
1545: v.add(new DERObjectIdentifier(
1546: CertTools.GUID_OBJECTID));
1547: v.add(new DERTaggedObject(true, 0,
1548: new DEROctetString(guidbytes)));
1549: DERObject gn = new DERTaggedObject(false, 0,
1550: new DERSequence(v));
1551: vec.add(gn);
1552: } else {
1553: log
1554: .error("Cannot decode hexadecimal guid: "
1555: + guid);
1556: }
1557: }
1558: }
1559:
1560: // To support custom OIDs in altNames, they must be added as an OtherName
1561: ArrayList customoids = CertTools.getCustomOids(altName);
1562: if (!customoids.isEmpty()) {
1563: Iterator iter = customoids.iterator();
1564: while (iter.hasNext()) {
1565: String oid = (String) iter.next();
1566: ArrayList oidval = CertTools.getPartsFromDN(altName,
1567: oid);
1568: if (!oidval.isEmpty()) {
1569: Iterator valiter = oidval.iterator();
1570: while (valiter.hasNext()) {
1571: ASN1EncodableVector v = new ASN1EncodableVector();
1572: v.add(new DERObjectIdentifier(oid));
1573: v.add(new DERTaggedObject(true, 0,
1574: new DERUTF8String((String) valiter
1575: .next())));
1576: DERObject gn = new DERTaggedObject(false, 0,
1577: new DERSequence(v));
1578: vec.add(gn);
1579: }
1580: }
1581: }
1582: }
1583:
1584: GeneralNames ret = null;
1585: if (vec.size() > 0) {
1586: ret = new GeneralNames(new DERSequence(vec));
1587: }
1588: return ret;
1589: }
1590:
1591: /**
1592: * GeneralName ::= CHOICE {
1593: * otherName [0] OtherName,
1594: * rfc822Name [1] IA5String,
1595: * dNSName [2] IA5String,
1596: * x400Address [3] ORAddress,
1597: * directoryName [4] Name,
1598: * ediPartyName [5] EDIPartyName,
1599: * uniformResourceIdentifier [6] IA5String,
1600: * iPAddress [7] OCTET STRING,
1601: * registeredID [8] OBJECT IDENTIFIER}
1602: *
1603: * @param tag the no tag 0-8
1604: * @param value the DEREncodable value as returned by GeneralName.getName()
1605: * @return String in form rfc822Name=<email> or uri=<uri> etc
1606: * @throws IOException
1607: * @see #getSubjectAlternativeName
1608: */
1609: public static String getGeneralNameString(int tag,
1610: DEREncodable value) throws IOException {
1611: String ret = null;
1612: switch (tag) {
1613: case 0:
1614: ASN1Sequence seq = getAltnameSequence(value.getDERObject()
1615: .getEncoded());
1616: String upn = getUPNStringFromSequence(seq);
1617: // OtherName can be something else besides UPN
1618: if (upn != null) {
1619: ret = CertTools.UPN + "=" + upn;
1620: }
1621: break;
1622: case 1:
1623: ret = CertTools.EMAIL + "="
1624: + DERIA5String.getInstance(value).getString();
1625: break;
1626: case 2:
1627: ret = CertTools.DNS + "="
1628: + DERIA5String.getInstance(value).getString();
1629: break;
1630: case 3: // SubjectAltName of type x400Address not supported
1631: break;
1632: case 4: // SubjectAltName of type directoryName not supported
1633: break;
1634: case 5: // SubjectAltName of type ediPartyName not supported
1635: break;
1636: case 6:
1637: ret = CertTools.URI + "="
1638: + DERIA5String.getInstance(value).getString();
1639: break;
1640: case 7:
1641: ASN1OctetString oct = ASN1OctetString.getInstance(value);
1642: ret = CertTools.IPADDR + "="
1643: + StringTools.ipOctetsToString(oct.getOctets());
1644: break;
1645: default: // SubjectAltName of unknown type
1646: break;
1647: }
1648: return ret;
1649: }
1650:
1651: /**
1652: * Check the certificate with CA certificate.
1653: *
1654: * @param certificate cert to verify
1655: * @param caCertPath collection of X509Certificate
1656: * @return true if verified OK, false if not
1657: */
1658: public static boolean verify(X509Certificate certificate,
1659: Collection caCertPath) throws Exception {
1660: try {
1661: ArrayList certlist = new ArrayList();
1662: // Create CertPath
1663: certlist.add(certificate);
1664: // Add other certs...
1665: CertificateFactory cf = CertificateFactory.getInstance(
1666: "X.509", "BC");
1667: java.security.cert.CertPath cp = cf
1668: .generateCertPath(certlist);
1669: // Create TrustAnchor. Since EJBCA use BouncyCastle provider, we assume
1670: // certificate already in correct order
1671: X509Certificate[] cac = (X509Certificate[]) caCertPath
1672: .toArray(new X509Certificate[] {});
1673: java.security.cert.TrustAnchor anchor = new java.security.cert.TrustAnchor(
1674: cac[0], null);
1675: // Set the PKIX parameters
1676: java.security.cert.PKIXParameters params = new java.security.cert.PKIXParameters(
1677: java.util.Collections.singleton(anchor));
1678: params.setRevocationEnabled(false);
1679: java.security.cert.CertPathValidator cpv = java.security.cert.CertPathValidator
1680: .getInstance("PKIX", "BC");
1681: java.security.cert.PKIXCertPathValidatorResult result = (java.security.cert.PKIXCertPathValidatorResult) cpv
1682: .validate(cp, params);
1683: log
1684: .debug("Certificate verify result: "
1685: + result.toString());
1686: } catch (java.security.cert.CertPathValidatorException cpve) {
1687: throw new Exception(
1688: "Invalid certificate or certificate not issued by specified CA: "
1689: + cpve.getMessage());
1690: } catch (Exception e) {
1691: throw new Exception("Error checking certificate chain: "
1692: + e.getMessage());
1693: }
1694: return true;
1695: }
1696:
1697: /**
1698: * Return the CRL distribution point URL form a certificate.
1699: */
1700: public static URL getCrlDistributionPoint(
1701: X509Certificate certificate)
1702: throws CertificateParsingException {
1703: try {
1704: DERObject obj = getExtensionValue(certificate,
1705: X509Extensions.CRLDistributionPoints.getId());
1706: if (obj == null) {
1707: return null;
1708: }
1709: ASN1Sequence distributionPoints = (ASN1Sequence) obj;
1710: for (int i = 0; i < distributionPoints.size(); i++) {
1711: ASN1Sequence distrPoint = (ASN1Sequence) distributionPoints
1712: .getObjectAt(i);
1713: for (int j = 0; j < distrPoint.size(); j++) {
1714: ASN1TaggedObject tagged = (ASN1TaggedObject) distrPoint
1715: .getObjectAt(j);
1716: if (tagged.getTagNo() == 0) {
1717: String url = getStringFromGeneralNames(tagged
1718: .getObject());
1719: if (url != null) {
1720: return new URL(url);
1721: }
1722: }
1723: }
1724: }
1725: } catch (Exception e) {
1726: log.error("Error parsing CrlDistributionPoint", e);
1727: throw new CertificateParsingException(e.toString());
1728: }
1729: return null;
1730: }
1731:
1732: /** Returns OCSP URL that is inside AuthorithInformationAccess extension, or null.
1733: *
1734: * @param cert
1735: * @return
1736: * @throws CertificateParsingException
1737: */
1738: public static String getAuthorityInformationAccessOcspUrl(
1739: X509Certificate cert) throws CertificateParsingException {
1740: try {
1741: DERObject obj = getExtensionValue(cert,
1742: X509Extensions.AuthorityInfoAccess.getId());
1743: if (obj == null) {
1744: return null;
1745: }
1746: AuthorityInformationAccess aia = AuthorityInformationAccess
1747: .getInstance(obj);
1748: AccessDescription[] ad = aia.getAccessDescriptions();
1749: if ((ad == null) || (ad.length < 1)) {
1750: return null;
1751: }
1752: if (!ad[0].getAccessMethod().equals(
1753: X509ObjectIdentifiers.ocspAccessMethod)) {
1754: return null;
1755: }
1756: GeneralName gn = ad[0].getAccessLocation();
1757: if (gn.getTagNo() != 6) {
1758: return null;
1759: }
1760: DERIA5String str = DERIA5String.getInstance(gn
1761: .getDERObject());
1762: return str.getString();
1763: } catch (Exception e) {
1764: log.error("Error parsing AuthorityInformationAccess", e);
1765: throw new CertificateParsingException(e.toString());
1766: }
1767: }
1768:
1769: /**
1770: * Return an Extension DERObject from a certificate
1771: */
1772: protected static DERObject getExtensionValue(X509Certificate cert,
1773: String oid) throws IOException {
1774: if (cert == null) {
1775: return null;
1776: }
1777: byte[] bytes = cert.getExtensionValue(oid);
1778: if (bytes == null) {
1779: return null;
1780: }
1781: ASN1InputStream aIn = new ASN1InputStream(
1782: new ByteArrayInputStream(bytes));
1783: ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
1784: aIn = new ASN1InputStream(new ByteArrayInputStream(octs
1785: .getOctets()));
1786: return aIn.readObject();
1787: } //getExtensionValue
1788:
1789: private static String getStringFromGeneralNames(DERObject names) {
1790: ASN1Sequence namesSequence = ASN1Sequence.getInstance(
1791: (ASN1TaggedObject) names, false);
1792: if (namesSequence.size() == 0) {
1793: return null;
1794: }
1795: DERTaggedObject taggedObject = (DERTaggedObject) namesSequence
1796: .getObjectAt(0);
1797: return new String(ASN1OctetString.getInstance(taggedObject,
1798: false).getOctets());
1799: } //getStringFromGeneralNames
1800:
1801: /**
1802: * Generate SHA1 fingerprint in string representation.
1803: *
1804: * @param ba Byte array containing DER encoded X509Certificate.
1805: *
1806: * @return String containing hex format of SHA1 fingerprint.
1807: */
1808: public static String getCertFingerprintAsString(byte[] ba) {
1809: try {
1810: X509Certificate cert = getCertfromByteArray(ba);
1811: byte[] res = generateSHA1Fingerprint(cert.getEncoded());
1812:
1813: return new String(Hex.encode(res));
1814: } catch (CertificateEncodingException cee) {
1815: log.error("Error encoding X509 certificate.", cee);
1816: } catch (CertificateException cee) {
1817: log.error("Error decoding X509 certificate.", cee);
1818: }
1819:
1820: return null;
1821: }
1822:
1823: /**
1824: * Generate SHA1 fingerprint of certificate in string representation.
1825: *
1826: * @param cert X509Certificate.
1827: *
1828: * @return String containing hex format of SHA1 fingerprint, or null if input is null.
1829: */
1830: public static String getFingerprintAsString(X509Certificate cert) {
1831: if (cert == null)
1832: return null;
1833: try {
1834: byte[] res = generateSHA1Fingerprint(cert.getEncoded());
1835:
1836: return new String(Hex.encode(res));
1837: } catch (CertificateEncodingException cee) {
1838: log.error("Error encoding X509 certificate.", cee);
1839: }
1840:
1841: return null;
1842: }
1843:
1844: /**
1845: * Generate SHA1 fingerprint of CRL in string representation.
1846: *
1847: * @param crl X509CRL.
1848: *
1849: * @return String containing hex format of SHA1 fingerprint.
1850: */
1851: public static String getFingerprintAsString(X509CRL crl) {
1852: try {
1853: byte[] res = generateSHA1Fingerprint(crl.getEncoded());
1854:
1855: return new String(Hex.encode(res));
1856: } catch (CRLException ce) {
1857: log.error("Error encoding X509 CRL.", ce);
1858: }
1859:
1860: return null;
1861: }
1862:
1863: /**
1864: * Generate SHA1 fingerprint of byte array in string representation.
1865: *
1866: * @param byte array to fingerprint.
1867: *
1868: * @return String containing hex format of SHA1 fingerprint.
1869: */
1870: public static String getFingerprintAsString(byte[] in) {
1871: byte[] res = generateSHA1Fingerprint(in);
1872: return new String(Hex.encode(res));
1873: }
1874:
1875: /**
1876: * Generate a SHA1 fingerprint from a byte array containing a X.509 certificate
1877: *
1878: * @param ba Byte array containing DER encoded X509Certificate.
1879: *
1880: * @return Byte array containing SHA1 hash of DER encoded certificate.
1881: */
1882: public static byte[] generateSHA1Fingerprint(byte[] ba) {
1883: log.debug(">generateSHA1Fingerprint");
1884: try {
1885: MessageDigest md = MessageDigest.getInstance("SHA1");
1886:
1887: return md.digest(ba);
1888: } catch (NoSuchAlgorithmException nsae) {
1889: log.error("SHA1 algorithm not supported", nsae);
1890: }
1891: log.debug("<generateSHA1Fingerprint");
1892: return null;
1893: } // generateSHA1Fingerprint
1894:
1895: /**
1896: * Generate a MD5 fingerprint from a byte array containing a X.509 certificate
1897: *
1898: * @param ba Byte array containing DER encoded X509Certificate.
1899: *
1900: * @return Byte array containing MD5 hash of DER encoded certificate.
1901: */
1902: public static byte[] generateMD5Fingerprint(byte[] ba) {
1903: try {
1904: MessageDigest md = MessageDigest.getInstance("MD5");
1905:
1906: return md.digest(ba);
1907: } catch (NoSuchAlgorithmException nsae) {
1908: log.error("MD5 algorithm not supported", nsae);
1909: }
1910:
1911: return null;
1912: } // generateMD5Fingerprint
1913:
1914: /** Converts Sun Key usage bits to Bouncy castle key usage kits
1915: *
1916: * @param sku key usage bit fields according to java.security.cert.X509Certificate#getKeyUsage, must be a boolean aray of size 9.
1917: * @return key usage int according to org.bouncycastle.jce.X509KeyUsage#X509KeyUsage, or -1 if input is null.
1918: * @see java.security.cert.X509Certificate#getKeyUsage
1919: * @see org.bouncycastle.jce.X509KeyUsage#X509KeyUsage
1920: */
1921: public static int sunKeyUsageToBC(boolean[] sku) {
1922: if (sku == null) {
1923: return -1;
1924: }
1925: int bcku = 0;
1926: if (sku[0] == true)
1927: bcku = bcku | X509KeyUsage.digitalSignature;
1928: if (sku[1] == true)
1929: bcku = bcku | X509KeyUsage.nonRepudiation;
1930: if (sku[2] == true)
1931: bcku = bcku | X509KeyUsage.keyEncipherment;
1932: if (sku[3] == true)
1933: bcku = bcku | X509KeyUsage.dataEncipherment;
1934: if (sku[4] == true)
1935: bcku = bcku | X509KeyUsage.keyAgreement;
1936: if (sku[5] == true)
1937: bcku = bcku | X509KeyUsage.keyCertSign;
1938: if (sku[6] == true)
1939: bcku = bcku | X509KeyUsage.cRLSign;
1940: if (sku[7] == true)
1941: bcku = bcku | X509KeyUsage.encipherOnly;
1942: if (sku[8] == true)
1943: bcku = bcku | X509KeyUsage.decipherOnly;
1944: return bcku;
1945: }
1946:
1947: /** Converts DERBitString ResonFlags to a RevokedCertInfo constant
1948: *
1949: * @param reasonFlags DERBITString received from org.bouncycastle.asn1.x509.ReasonFlags.
1950: * @return int according to org.ejbca.core.model.ca.crl.RevokedCertInfo
1951: */
1952: public static int bitStringToRevokedCertInfo(
1953: DERBitString reasonFlags) {
1954: int ret = RevokedCertInfo.REVOKATION_REASON_UNSPECIFIED;
1955: if (reasonFlags == null) {
1956: return ret;
1957: }
1958: int val = reasonFlags.intValue();
1959: if ((val & ReasonFlags.aACompromise) != 0) {
1960: ret = RevokedCertInfo.REVOKATION_REASON_AACOMPROMISE;
1961: }
1962: if ((val & ReasonFlags.affiliationChanged) != 0) {
1963: ret = RevokedCertInfo.REVOKATION_REASON_AFFILIATIONCHANGED;
1964: }
1965: if ((val & ReasonFlags.cACompromise) != 0) {
1966: ret = RevokedCertInfo.REVOKATION_REASON_CACOMPROMISE;
1967: }
1968: if ((val & ReasonFlags.certificateHold) != 0) {
1969: ret = RevokedCertInfo.REVOKATION_REASON_CERTIFICATEHOLD;
1970: }
1971: if ((val & ReasonFlags.cessationOfOperation) != 0) {
1972: ret = RevokedCertInfo.REVOKATION_REASON_CESSATIONOFOPERATION;
1973: }
1974: if ((val & ReasonFlags.keyCompromise) != 0) {
1975: ret = RevokedCertInfo.REVOKATION_REASON_KEYCOMPROMISE;
1976: }
1977: if ((val & ReasonFlags.privilegeWithdrawn) != 0) {
1978: ret = RevokedCertInfo.REVOKATION_REASON_PRIVILEGESWITHDRAWN;
1979: }
1980: if ((val & ReasonFlags.super seded) != 0) {
1981: ret = RevokedCertInfo.REVOKATION_REASON_SUPERSEDED;
1982: }
1983: if ((val & ReasonFlags.unused) != 0) {
1984: ret = RevokedCertInfo.REVOKATION_REASON_UNSPECIFIED;
1985: }
1986: return ret;
1987: }
1988:
1989: /**
1990: * Method used to insert a CN postfix into DN by extracting the first found CN appending cnpostfix and then replacing the original CN
1991: * with the new one in DN.
1992: *
1993: * If no CN could be found in DN then should the given DN be returned untouched
1994: *
1995: * @param dn the DN to manipulate, cannot be null
1996: * @param cnpostfix the postfix to insert, cannot be null
1997: * @return the new DN
1998: */
1999: public static String insertCNPostfix(String dn, String cnpostfix) {
2000: String newdn = null;
2001:
2002: if ((dn != null) && (cnpostfix != null)) {
2003: String o;
2004: X509NameTokenizer xt = new X509NameTokenizer(dn);
2005: boolean alreadyreplaced = false;
2006: while (xt.hasMoreTokens()) {
2007: o = xt.nextToken();
2008: if (!alreadyreplaced && (o.length() > 3)
2009: && o.substring(0, 3).equalsIgnoreCase("cn=")) {
2010: o += cnpostfix;
2011: alreadyreplaced = true;
2012: }
2013: if (newdn == null) {
2014: newdn = o;
2015: } else {
2016: newdn += "," + o;
2017: }
2018: }
2019: }
2020:
2021: return newdn;
2022: }
2023:
2024: /** Simple method that looks at the certificate and determines, from EJBCA's standpoint, which signature algorithm it is
2025: *
2026: * @param cert the cert to examine
2027: * @return Signature algorithm from CATokenInfo.SIGALG_SHA1_WITH_RSA etc.
2028: */
2029: public static String getSignatureAlgorithm(X509Certificate cert) {
2030: // Assume that the same hash algorithm is used for signing that was used to sign this CA cert
2031: String certSignatureAlgorithm = cert.getSigAlgName();
2032: String signatureAlgorithm = null;
2033: PublicKey publickey = cert.getPublicKey();
2034: if (publickey instanceof RSAPublicKey) {
2035: if (certSignatureAlgorithm.indexOf("256") == -1) {
2036: signatureAlgorithm = CATokenInfo.SIGALG_SHA1_WITH_RSA;
2037: } else {
2038: signatureAlgorithm = CATokenInfo.SIGALG_SHA256_WITH_RSA;
2039: }
2040: } else {
2041: if (certSignatureAlgorithm.indexOf("256") == -1) {
2042: signatureAlgorithm = CATokenInfo.SIGALG_SHA1_WITH_ECDSA;
2043: } else {
2044: signatureAlgorithm = CATokenInfo.SIGALG_SHA256_WITH_ECDSA;
2045: }
2046: }
2047: log.debug("getSignatureAlgorithm: " + signatureAlgorithm);
2048: return signatureAlgorithm;
2049: }
2050:
2051: /**
2052: * class for breaking up an X500 Name into it's component tokens, ala
2053: * java.util.StringTokenizer. Taken from BouncyCastle, but does NOT
2054: * use or consider escaped characters. Used for reversing DNs without unescaping.
2055: */
2056: private static class BasicX509NameTokenizer {
2057: private String oid;
2058: private int index;
2059: private StringBuffer buf = new StringBuffer();
2060:
2061: public BasicX509NameTokenizer(String oid) {
2062: this .oid = oid;
2063: this .index = -1;
2064: }
2065:
2066: public boolean hasMoreTokens() {
2067: return (index != oid.length());
2068: }
2069:
2070: public String nextToken() {
2071: if (index == oid.length()) {
2072: return null;
2073: }
2074:
2075: int end = index + 1;
2076: boolean quoted = false;
2077: boolean escaped = false;
2078:
2079: buf.setLength(0);
2080:
2081: while (end != oid.length()) {
2082: char c = oid.charAt(end);
2083:
2084: if (c == '"') {
2085: if (!escaped) {
2086: buf.append(c);
2087: quoted = !quoted;
2088: } else {
2089: buf.append(c);
2090: }
2091: escaped = false;
2092: } else {
2093: if (escaped || quoted) {
2094: buf.append(c);
2095: escaped = false;
2096: } else if (c == '\\') {
2097: buf.append(c);
2098: escaped = true;
2099: } else if ((c == ',') && (!escaped)) {
2100: break;
2101: } else {
2102: buf.append(c);
2103: }
2104: }
2105: end++;
2106: }
2107:
2108: index = end;
2109: return buf.toString().trim();
2110: }
2111: }
2112:
2113: /**
2114: * Obtains a Vector with the DERObjectIdentifiers for
2115: * dNObjects names.
2116: *
2117: * @return Vector with DERObjectIdentifiers defining the known order we require
2118: */
2119: private static Vector getDefaultX509FieldOrder() {
2120: Vector fieldOrder = new Vector();
2121: String[] dNObjects = DnComponents.getDnObjects();
2122: for (int i = 0; i < dNObjects.length; i++) {
2123: fieldOrder.add(DnComponents.getOid(dNObjects[i]));
2124: }
2125: return fieldOrder;
2126: }
2127:
2128: /**
2129: * Obtains a Vector with the DERObjectIdentifiers for
2130: * dNObjects names, in the specified order
2131: *
2132: * @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).
2133: * @return Vector with DERObjectIdentifiers defining the known order we require
2134: */
2135: public static Vector getX509FieldOrder(boolean ldaporder) {
2136: Vector fieldOrder = new Vector();
2137: String[] dNObjects = DnComponents.getDnObjects(ldaporder);
2138: for (int i = 0; i < dNObjects.length; i++) {
2139: fieldOrder.add(DnComponents.getOid(dNObjects[i]));
2140: }
2141: return fieldOrder;
2142: }
2143:
2144: /**
2145: * Obtain a X509Name reordered, if some fields from original X509Name
2146: * doesn't appear in "ordering" parameter, they will be added at end
2147: * in the original order.
2148: *
2149: * @param x509Name the X509Name that is unordered
2150: * @param ordering Vector of DERObjectIdentifier defining the desired order of components
2151: * @return X509Name with ordered conmponents according to the orcering vector
2152: */
2153: private static X509Name getOrderedX509Name(X509Name x509Name,
2154: Vector ordering, X509NameEntryConverter converter) {
2155:
2156: //-- Null prevent
2157: if (ordering == null) {
2158: ordering = new Vector();
2159: }
2160:
2161: //-- New order for the X509 Fields
2162: Vector newOrdering = new Vector();
2163: Vector newValues = new Vector();
2164:
2165: Hashtable ht = new Hashtable();
2166: Iterator it = ordering.iterator();
2167:
2168: //-- Add ordered fields
2169: while (it.hasNext()) {
2170: DERObjectIdentifier oid = (DERObjectIdentifier) it.next();
2171:
2172: if (!ht.containsKey(oid)) {
2173: Vector valueList = getX509NameFields(x509Name, oid);
2174: //-- Only add the OID if has not null value
2175: if (valueList != null) {
2176: Iterator itVals = valueList.iterator();
2177: while (itVals.hasNext()) {
2178: Object value = itVals.next();
2179: ht.put(oid, value);
2180: newOrdering.add(oid);
2181: newValues.add(value);
2182: }
2183: }
2184: } // if ht.containsKey
2185: } // while it.hasNext
2186:
2187: Vector allOids = x509Name.getOIDs();
2188:
2189: //-- Add unexpected fields to the end
2190: for (int i = 0; i < allOids.size(); i++) {
2191:
2192: DERObjectIdentifier oid = (DERObjectIdentifier) allOids
2193: .get(i);
2194:
2195: if (!ht.containsKey(oid)) {
2196: Vector valueList = getX509NameFields(x509Name, oid);
2197:
2198: //-- Only add the OID if has not null value
2199: if (valueList != null) {
2200: Iterator itVals = valueList.iterator();
2201:
2202: while (itVals.hasNext()) {
2203: Object value = itVals.next();
2204: ht.put(oid, value);
2205: newOrdering.add(oid);
2206: newValues.add(value);
2207: log
2208: .debug("added --> " + oid + " val: "
2209: + value);
2210: }
2211: }
2212: }
2213: }
2214:
2215: //-- Create X509Name with the ordered fields
2216: X509Name orderedName = new X509Name(newOrdering, newValues,
2217: converter);
2218:
2219: return orderedName;
2220: }
2221:
2222: /**
2223: * Obtain the values for a DN field from X509Name, or null in case
2224: * of the field does not exist.
2225: *
2226: * @param name
2227: * @param id
2228: * @return
2229: */
2230: private static Vector getX509NameFields(X509Name name,
2231: DERObjectIdentifier id) {
2232:
2233: Vector oids = name.getOIDs();
2234: Vector values = name.getValues();
2235: Vector vRet = null;
2236:
2237: for (int i = 0; i < oids.size(); i++) {
2238:
2239: if (id.equals(oids.elementAt(i))) {
2240: if (vRet == null) {
2241: vRet = new Vector();
2242: }
2243: vRet.add(values.get(i));
2244: }
2245:
2246: }
2247:
2248: return vRet;
2249:
2250: }
2251:
2252: /**
2253: * Obtain the directory string for the directoryName generation
2254: * form the Subject Alternative Name String.
2255: *
2256: * @param altName
2257: * @return
2258: */
2259: private static String getDirectoryStringFromAltName(String altName) {
2260:
2261: DNFieldExtractor dnfe = new DNFieldExtractor(altName,
2262: DNFieldExtractor.TYPE_SUBJECTALTNAME);
2263: String directoryName = dnfe.getField(
2264: DNFieldExtractor.DIRECTORYNAME, 0);
2265:
2266: /** TODO: Validate or restrict the directoryName Fields? */
2267:
2268: return ("".equals(directoryName) ? null : directoryName);
2269: }
2270:
2271: } // CertTools
|