/*************************************************************************
* *
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.bind.DatatypeConverter;
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.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CRLException;
import java.security.cert.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.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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.commons.lang.BooleanUtils;
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.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERGeneralString;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.ASN1Object;
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.Extension;
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.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.jcajce.provider.config.ConfigurableProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.ejbca.core.model.ca.catoken.CATokenInfo;
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.CVCProvider;
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 8363 2009-11-27 15:52:10Z anatom $
*/
public class CertTools {
private static final Logger log = Logger.getLogger(CertTools.class);
// Initialize dnComponents
static {
DnComponents.getDnObjects();
}
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");
/** Parameters used when generating or verifying ECDSA keys/certs using the "implicitlyCA" key encoding.
* The curve parameters is then defined outside of the key and configured in the BC provider.
*/
private static String IMPLICITLYCA_Q = "@ecdsa.implicitlyca.q@";
private static String IMPLICITLYCA_A = "@ecdsa.implicitlyca.a@";
private static String IMPLICITLYCA_B = "@ecdsa.implicitlyca.b@";
private static String IMPLICITLYCA_G = "@ecdsa.implicitlyca.g@";
private static String IMPLICITLYCA_N = "@ecdsa.implicitlyca.n@";
/** System provider used to circumvent a bug in Glassfish. Should only be used by
* X509CAInfo, OCSPCAService, XKMSCAService, CMSCAService.
* Defaults to SUN but can be changed to IBM by the installBCProvider method.
*/
public static String SYSTEM_SECURITY_PROVIDER = "SUN";
/** Flag indicating if the BC provider should be removed before installing it again. When developing and re-deploying alot
* this is needed so you don't have to restart JBoss all the time.
* In production it may cause failures because the BC provider may get removed just when another thread wants to use it.
* Therefore the default value is false.
*/
private static final boolean developmentProviderInstallation = BooleanUtils.toBoolean("@development.provider.installation@");
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-----";
/**
* inhibits creation of new CertTools
*/
protected CertTools() {
}
/** See stringToBcX500Name(String, X509NameEntryConverter), this method uses the default BC converter (X509DefaultEntryConverter)
* @see #stringToBcX500Name(String, X509NameEntryConverter)
* @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);
}
/**
* Creates a (Bouncycastle) X500Name object from a string with a DN. Known OID
* (with order) are:
* EmailAddress, UID, CN, SN (SerialNumber), GivenName, Initials, SurName, T, OU,
* O, L, ST, DC, C
* 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
* @return X509Name or null if input is null
*/
private static X509Name stringToBcX509Name(String dn, X509NameEntryConverter converter) {
return stringToBcX509Name(dn, converter, true);
}
// Remove extra '+' character escaping
private static String getUnescapedPlus(final String value) {
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();
}
public static X509Name stringToBcX509Name(final String dn, final X509NameEntryConverter converter, final boolean ldaporder) {
if (dn == null) {
return null;
}
final Vector defaultOrdering = new Vector();
final Vector values = new Vector();
final X509NameTokenizer x509NameTokenizer = new X509NameTokenizer(dn);
while (x509NameTokenizer.hasMoreTokens()) {
// This is a pair key=val (CN=xx)
final String pair = x509NameTokenizer.nextToken(); // Will escape '+' and initial '#' chars
final int index = pair.indexOf('=');
if (index != -1) {
final 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);
}
try {
// -- First search the OID by name in declared OID's
ASN1ObjectIdentifier oid = DnComponents.getOid(key);
// -- If isn't declared, we try to create it
if (oid == null) {
oid = new ASN1ObjectIdentifier(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("Huh, what's this? DN: " + dn + " PAIR: " + pair);
}
}
final X509Name x509Name = new X509Name(defaultOrdering, values, converter);
// -- Reorder fields
final X509Name orderedX509Name = getOrderedX509Name(x509Name, ldaporder, converter);
// log.trace("stringToBcDNString: "+dn);
}
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("getEmailFromDN(" + dn + ")");
}
ArrayList ret = new ArrayList();
for (int i = 0; i < EMAILIDS.length ; i++) {
ArrayList emails = getPartsFromDN(dn, EMAILIDS[i]);
if (emails.size() > 0) {
ret.addAll(emails);
}
}
if (log.isTraceEnabled()) {
log.trace(" 0) {
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;
BasicX509NameTokenizer xt = new BasicX509NameTokenizer(dn);
StringBuffer buf = new StringBuffer();
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("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();
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("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("getPartsFromDN: dn:'" + dn + "', dnpart=" + dnpart);
}
ArrayList parts = new ArrayList();
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("getCustomOids: dn:'" + dn);
}
ArrayList parts = new ArrayList();
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);
new DERObjectIdentifier(oid);
parts.add(oid);
}
} catch (IllegalArgumentException e) {
// Not a valid oid
}
}
}
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();
}
//System.out.println("[CertTool] DN= ", dn);
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(" 0) {
ret = NumberUtils.createBigInteger(buf.toString());
} else {
log.debug("getSerialNumber: Sequence does not contain a numeric string, 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: "+sequence);
ret = BigInteger.valueOf(0);
}
} 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 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("=1.36 to be the same
// as the behavior in BC 1.35, it changed from SN to SERIALNUMBER in BC 1.36
// We must be backwards compatible
// TODO: should this be removed?
X509Name.DefaultSymbols.put(X509Name.SN, "SN");
// We hard specify the system security provider in a few cases (see SYSTEM_SECURITY_PROVIDER).
// If the SUN provider does not exist, we will always use BC.
Provider p = Security.getProvider(CertTools.SYSTEM_SECURITY_PROVIDER);
if (p == null) {
log.debug("SUN security provider does not exist, using BC as system default provider.");
SYSTEM_SECURITY_PROVIDER = "BC";
}
}
/** Check if parameters have been set correctly during pre-process, otherwise log an error and
* set default values. Mostly used to be able to do JUnit testing
*/
private static void checkImplicitParams() {
if (StringUtils.contains(IMPLICITLYCA_Q, "ecdsa.implicitlyca.q")) {
log.info("IMPLICITLYCA_Q not set, using default.");
IMPLICITLYCA_Q = "883423532389192164791648750360308885314476597252960362792450860609699839";
}
if (StringUtils.contains(IMPLICITLYCA_A, "ecdsa.implicitlyca.a")) {
log.info("IMPLICITLYCA_A not set, using default.");
IMPLICITLYCA_A = "7fffffffffffffffffffffff7fffffffffff8000000000007ffffffffffc";
}
if (StringUtils.contains(IMPLICITLYCA_B, "ecdsa.implicitlyca.b")) {
log.info("IMPLICITLYCA_B not set, using default.");
IMPLICITLYCA_B = "6b016c3bdcf18941d0d654921475ca71a9db2fb27d1d37796185c2942c0a";
}
if (StringUtils.contains(IMPLICITLYCA_G, "ecdsa.implicitlyca.g")) {
log.info("IMPLICITLYCA_G not set, using default.");
IMPLICITLYCA_G = "020ffa963cdca8816ccc33b8642bedf905c3d358573d3f27fbbd3b3cb9aaaf";
}
if (StringUtils.contains(IMPLICITLYCA_N, "ecdsa.implicitlyca.n")) {
log.info("IMPLICITLYCA_N not set, using default.");
IMPLICITLYCA_N = "883423532389192164791648750360308884807550341691627752275345424702807307";
}
}
/**
* Reads a certificate in PEM-format from a file. The file may contain other things,
* the first certificate in the file is read.
*
* @param certFile the file containing the certificate in PEM-format
* @return Ordered Collection of X509Certificate, first certificate first, or empty Collection
* @exception IOException if the filen cannot be read.
* @exception CertificateException if the filen does not contain a correct certificate.
*/
public static Collection getCertsFromPEM(String certFile) throws IOException, CertificateException {
if (log.isTraceEnabled()) {
log.trace(">getCertfromPEM: certFile=" + certFile);
}
InputStream inStrm = null;
Collection certs;
try {
inStrm = new FileInputStream(certFile);
certs = getCertsFromPEM(inStrm);
} finally {
if (inStrm != null) {
inStrm.close();
}
}
if (log.isTraceEnabled()) {
log.trace("getCertfromPEM");
ArrayList ret = new ArrayList();
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.size() == 0) {
// 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("getCertCollectionFromArray: "+provider);
}
ArrayList ret = new ArrayList();
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("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("Certificate exception 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.info("Certificate exception trying to read CVCCertificate: ", e);
throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
} catch (ConstructionException e) {
log.info("Certificate exception trying to read CVCCertificate: ", e);
throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
} catch (IllegalArgumentException e) {
log.info("Certificate exception trying to read CVCCertificate: ", e);
throw new CertificateException("Certificate exception trying to read CVCCertificate", e);
}
}
if (ret == null) {
throw new CertificateException("No certificate found");
}
//log.trace("getCRLfromByteArray");
if (crl == null) {
throw new IOException("Cannot read byte[] that is 'null'!");
}
CertificateFactory cf = CertTools.getCertificateFactory();
X509CRL x509crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(crl));
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("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(",
* dNSName=, uniformResourceIdentifier=,
* iPAddress=, guid=, directoryName=
*
* 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;
java.util.Collection altNames = x509cert.getSubjectAlternativeNames();
if (altNames == null) {
return null;
}
Iterator iter = altNames.iterator();
String append = "";
while (iter.hasNext()) {
java.util.List item = (java.util.List)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 emails = CertTools.getEmailFromDN(altName);
if (!emails.isEmpty()) {
Iterator iter = emails.iterator();
while (iter.hasNext()) {
GeneralName gn = new GeneralName(1, new DERIA5String((String)iter.next()));
vec.add(gn);
}
}
ArrayList dns = CertTools.getPartsFromDN(altName, CertTools.DNS);
if (!dns.isEmpty()) {
Iterator 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 uri = CertTools.getPartsFromDN(altName, CertTools.URI);
if (!uri.isEmpty()) {
Iterator 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 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 iter = uri.iterator();
while (iter.hasNext()) {
GeneralName gn = new GeneralName(6, new DERIA5String((String)iter.next()));
vec.add(gn);
}
}
ArrayList ipstr = CertTools.getPartsFromDN(altName, CertTools.IPADDR);
if (!ipstr.isEmpty()) {
Iterator 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 upn = CertTools.getPartsFromDN(altName, CertTools.UPN);
if (!upn.isEmpty()) {
Iterator 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 guid = CertTools.getPartsFromDN(altName, CertTools.GUID);
if (!guid.isEmpty()) {
Iterator 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 krb5principalname = CertTools.getPartsFromDN(altName, CertTools.KRB5PRINCIPAL);
if (!krb5principalname.isEmpty()) {
Iterator 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 principalarr = new ArrayList();
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 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 customoids = CertTools.getCustomOids(altName);
if (!customoids.isEmpty()) {
Iterator iter = customoids.iterator();
while (iter.hasNext()) {
String oid = (String)iter.next();
ArrayList oidval = CertTools.getPartsFromDN(altName, oid);
if (!oidval.isEmpty()) {
Iterator 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= or uri= etc
* @throws IOException
* @see #getSubjectAlternativeName
*/
public static String getGeneralNameString(int tag, ASN1Encodable value) throws IOException {
String ret = null;
switch (tag) {
case 0: ASN1Sequence seq = getAltnameSequence(value.toASN1Primitive().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 caCertPath) throws Exception {
try {
ArrayList certlist = new ArrayList();
// 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.
*
* @param certificate cert to verify
* @param date the Date to check against to see if this certificate is valid at that date/time.
* @throws CertificateNotYetValidException
* @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 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+"'";
log.error(msg);
throw new CertificateNotYetValidException(msg);
}
if (end.before(date)) {
String msg = "Certificate endDate '"+end+"' is before check date '"+date+"'";
log.error(msg);
throw new CertificateExpiredException(msg);
}
} catch (NoSuchFieldException e) {
log.error("NoSuchFieldException: ", e);
}
}
}
/**
* Return the CRL distribution point URL form a certificate.
*/
public static URL getCrlDistributionPoint(Certificate certificate)
throws CertificateParsingException {
if (certificate instanceof X509Certificate) {
X509Certificate x509cert = (X509Certificate) certificate;
try {
ASN1Object obj = getExtensionValue(x509cert, Extension.cRLDistributionPoints);
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
* @return
* @throws CertificateParsingException
*/
public static String getAuthorityInformationAccessOcspUrl(Certificate cert)
throws CertificateParsingException {
String ret = null;
if (cert instanceof X509Certificate) {
X509Certificate x509cert = (X509Certificate) cert;
try {
ASN1Object obj = getExtensionValue(x509cert, Extension.authorityInfoAccess);
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) {
// TODO: is this correct?
DERIA5String str = DERIA5String.getInstance(gn.getName());
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 ASN1Object getExtensionValue(X509Certificate cert, ASN1ObjectIdentifier oid)
throws IOException {
if (cert == null) {
return null;
}
byte[] bytes = cert.getExtensionValue(oid.getId());
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
private static String getStringFromGeneralNames(ASN1Object names) {
ASN1Sequence namesSequence = ASN1Sequence.getInstance((ASN1TaggedObject)names, false);
if (namesSequence.size() == 0) {
return null;
}
DERTaggedObject taggedObject
= (DERTaggedObject)namesSequence.getObjectAt(0);
return new String(ASN1OctetString.getInstance(taggedObject, false).getOctets());
} //getStringFromGeneralNames
/**
* Generate SHA1 fingerprint in string representation.
*
* @param ba Byte array containing DER encoded Certificate.
*
* @return String containing hex format of SHA1 fingerprint.
*/
public static String getCertFingerprintAsString(byte[] ba) {
try {
Certificate cert = getCertfromByteArray(ba);
byte[] res = generateSHA1Fingerprint(cert.getEncoded());
return new String(Hex.encode(res));
} catch (CertificateEncodingException cee) {
log.error("Error encoding X509 certificate.", cee);
} catch (CertificateException cee) {
log.error("Error decoding X509 certificate.", cee);
}
return null;
}
/**
* 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 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(" 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 = CATokenInfo.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 = CATokenInfo.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 CATokenInfo.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 = CATokenInfo.SIGALG_SHA1_WITH_RSA_AND_MGF1;
} else {
if (md5) {
signatureAlgorithm = CATokenInfo.SIGALG_MD5_WITH_RSA;
} else {
signatureAlgorithm = CATokenInfo.SIGALG_SHA1_WITH_RSA;
}
}
} else {
if (isMgf) {
signatureAlgorithm = CATokenInfo.SIGALG_SHA256_WITH_RSA_AND_MGF1;
} else {
signatureAlgorithm = CATokenInfo.SIGALG_SHA256_WITH_RSA;
}
}
} else if ( publickey instanceof DSAPublicKey ) {
signatureAlgorithm = CATokenInfo.SIGALG_SHA1_WITH_DSA;
} else {
if (certSignatureAlgorithm.indexOf("256") != -1) {
signatureAlgorithm = CATokenInfo.SIGALG_SHA256_WITH_ECDSA;
} else if (certSignatureAlgorithm.indexOf("224") != -1) {
signatureAlgorithm = CATokenInfo.SIGALG_SHA224_WITH_ECDSA;
} else {
signatureAlgorithm = CATokenInfo.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.
*/
public static class BasicX509NameTokenizer
{
private String oid;
private int index;
private StringBuffer buf = new StringBuffer();
public BasicX509NameTokenizer(
String oid)
{
this.oid = oid;
this.index = -1;
}
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 = !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 Vector with the DERObjectIdentifiers for
* dNObjects names.
*
* @return Vector with DERObjectIdentifiers defining the known order we require
*/
private static Vector getDefaultX509FieldOrder(){
Vector fieldOrder = new Vector();
String[] dNObjects = DnComponents.getDnObjects();
for (int i = 0; i < dNObjects.length; i++) {
fieldOrder.add(DnComponents.getOid(dNObjects[i]));
}
return fieldOrder;
}
/**
* Obtains a Vector 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 Vector with DERObjectIdentifiers defining the known order we require
*/
public static Vector getX509FieldOrder(boolean ldaporder){
Vector fieldOrder = new Vector();
String[] dNObjects = DnComponents.getDnObjects(ldaporder);
for (int i = 0; i < dNObjects.length; i++) {
fieldOrder.add(DnComponents.getOid(dNObjects[i]));
}
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, 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 newOrdering = new ArrayList();
final List