org.apache.commons.ssl.Certificates Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of not-going-to-be-commons-ssl Show documentation
Show all versions of not-going-to-be-commons-ssl Show documentation
A Java 9+ compliant fork of Not-Yet-Commons-SSL
/*
* $HeadURL: file:///opt/dev/not-yet-commons-ssl-SVN-repo/tags/commons-ssl-0.3.17/src/java/org/apache/commons/ssl/Certificates.java $
* $Revision: 180 $
* $Date: 2014-09-23 11:33:47 -0700 (Tue, 23 Sep 2014) $
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
*/
package org.apache.commons.ssl;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.security.auth.x500.X500Principal;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.lang.reflect.Method;
/**
* @author Credit Union Central of British Columbia
* @author www.cucbc.com
* @author [email protected]
* @since 19-Aug-2005
*/
public class Certificates {
public final static CertificateFactory CF;
public final static String LINE_ENDING = System.getProperty("line.separator");
private final static HashMap crl_cache = new HashMap();
public final static String CRL_EXTENSION = "2.5.29.31";
public final static String OCSP_EXTENSION = "1.3.6.1.5.5.7.1.1";
private final static DateFormat DF = new SimpleDateFormat("yyyy/MMM/dd");
public interface SerializableComparator extends Comparator, Serializable {
}
public final static SerializableComparator COMPARE_BY_EXPIRY =
new SerializableComparator() {
public int compare(Object o1, Object o2) {
X509Certificate c1 = (X509Certificate) o1;
X509Certificate c2 = (X509Certificate) o2;
if (c1 == c2) // this deals with case where both are null
{
return 0;
}
if (c1 == null) // non-null is always bigger than null
{
return -1;
}
if (c2 == null) {
return 1;
}
if (c1.equals(c2)) {
return 0;
}
Date d1 = c1.getNotAfter();
Date d2 = c2.getNotAfter();
int c = d1.compareTo(d2);
if (c == 0) {
String s1 = JavaImpl.getSubjectX500(c1);
String s2 = JavaImpl.getSubjectX500(c2);
c = s1.compareTo(s2);
if (c == 0) {
s1 = JavaImpl.getIssuerX500(c1);
s2 = JavaImpl.getIssuerX500(c2);
c = s1.compareTo(s2);
if (c == 0) {
BigInteger big1 = c1.getSerialNumber();
BigInteger big2 = c2.getSerialNumber();
c = big1.compareTo(big2);
if (c == 0) {
try {
byte[] b1 = c1.getEncoded();
byte[] b2 = c2.getEncoded();
int len1 = b1.length;
int len2 = b2.length;
int i = 0;
for (; i < len1 && i < len2; i++) {
c = ((int) b1[i]) - ((int) b2[i]);
if (c != 0) {
break;
}
}
if (c == 0) {
c = b1.length - b2.length;
}
}
catch (CertificateEncodingException cee) {
// I give up. They can be equal if they
// really want to be this badly.
c = 0;
}
}
}
}
}
return c;
}
};
static {
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X.509");
}
catch (CertificateException ce) {
ce.printStackTrace(System.out);
}
finally {
CF = cf;
}
}
public static String toPEMString(X509Certificate cert)
throws CertificateEncodingException {
return toString(cert.getEncoded());
}
public static String toString(byte[] x509Encoded) {
byte[] encoded = Base64.encodeBase64(x509Encoded);
StringBuffer buf = new StringBuffer(encoded.length + 100);
buf.append("-----BEGIN CERTIFICATE-----\n");
for (int i = 0; i < encoded.length; i += 64) {
if (encoded.length - i >= 64) {
buf.append(new String(encoded, i, 64));
} else {
buf.append(new String(encoded, i, encoded.length - i));
}
buf.append(LINE_ENDING);
}
buf.append("-----END CERTIFICATE-----");
buf.append(LINE_ENDING);
return buf.toString();
}
public static String toString(X509Certificate cert) {
return toString(cert, false);
}
public static String toString(X509Certificate cert, boolean htmlStyle) {
String cn = getCN(cert);
String startStart = DF.format(cert.getNotBefore());
String endDate = DF.format(cert.getNotAfter());
String subject = JavaImpl.getSubjectX500(cert);
String issuer = JavaImpl.getIssuerX500(cert);
Iterator crls = getCRLs(cert).iterator();
if (subject.equals(issuer)) {
issuer = "self-signed";
}
StringBuffer buf = new StringBuffer(128);
if (htmlStyle) {
buf.append("");
}
buf.append(cn);
if (htmlStyle) {
buf.append("");
}
buf.append(LINE_ENDING);
buf.append("Valid: ");
buf.append(startStart);
buf.append(" - ");
buf.append(endDate);
buf.append(LINE_ENDING);
buf.append("s: ");
buf.append(subject);
buf.append(LINE_ENDING);
buf.append("i: ");
buf.append(issuer);
while (crls.hasNext()) {
buf.append(LINE_ENDING);
buf.append("CRL: ");
buf.append((String) crls.next());
}
buf.append(LINE_ENDING);
return buf.toString();
}
public static List getCRLs(X509Extension cert) {
// What follows is a poor man's CRL extractor, for those lacking
// a BouncyCastle "bcprov.jar" in their classpath.
// It's a very basic state-machine: look for a standard URL scheme
// (such as http), and then start looking for a terminator. After
// running hexdump a few times on these things, it looks to me like
// the UTF-8 value "65533" seems to happen near where these things
// terminate. (Of course this stuff is ASN.1 and not UTF-8, but
// I happen to like some of the functions available to the String
// object). - [email protected], May 10th, 2006
byte[] bytes = cert.getExtensionValue(CRL_EXTENSION);
LinkedList httpCRLS = new LinkedList();
LinkedList ftpCRLS = new LinkedList();
LinkedList otherCRLS = new LinkedList();
if (bytes == null) {
// just return empty list
return httpCRLS;
} else {
String s;
try {
s = new String(bytes, "UTF-8");
}
catch (UnsupportedEncodingException uee) {
// We're screwed if this thing has more than one CRL, because
// the "indeOf( (char) 65533 )" below isn't going to work.
s = new String(bytes);
}
int pos = 0;
while (pos >= 0) {
int x = -1, y;
int[] indexes = new int[4];
indexes[0] = s.indexOf("http", pos);
indexes[1] = s.indexOf("ldap", pos);
indexes[2] = s.indexOf("file", pos);
indexes[3] = s.indexOf("ftp", pos);
Arrays.sort(indexes);
for (int i = 0; i < indexes.length; i++) {
if (indexes[i] >= 0) {
x = indexes[i];
break;
}
}
if (x >= 0) {
y = s.indexOf((char) 65533, x);
String crl = y > x ? s.substring(x, y - 1) : s.substring(x);
if (y > x && crl.endsWith("0")) {
crl = crl.substring(0, crl.length() - 1);
}
String crlTest = crl.trim().toLowerCase();
if (crlTest.startsWith("http")) {
httpCRLS.add(crl);
} else if (crlTest.startsWith("ftp")) {
ftpCRLS.add(crl);
} else {
otherCRLS.add(crl);
}
pos = y;
} else {
pos = -1;
}
}
}
httpCRLS.addAll(ftpCRLS);
httpCRLS.addAll(otherCRLS);
return httpCRLS;
}
public static void checkCRL(X509Certificate cert)
throws CertificateException {
// String name = cert.getSubjectX500Principal().toString();
byte[] bytes = cert.getExtensionValue("2.5.29.31");
if (bytes == null) {
// log.warn( "Cert doesn't contain X509v3 CRL Distribution Points (2.5.29.31): " + name );
} else {
List crlList = getCRLs(cert);
Iterator it = crlList.iterator();
while (it.hasNext()) {
String url = (String) it.next();
CRLHolder holder = (CRLHolder) crl_cache.get(url);
if (holder == null) {
holder = new CRLHolder(url);
crl_cache.put(url, holder);
}
// success == false means we couldn't actually load the CRL
// (probably due to an IOException), so let's try the next one in
// our list.
boolean success = holder.checkCRL(cert);
if (success) {
break;
}
}
}
}
public static BigInteger getFingerprint(X509Certificate x509)
throws CertificateEncodingException {
return getFingerprint(x509.getEncoded());
}
public static BigInteger getFingerprint(byte[] x509)
throws CertificateEncodingException {
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException nsae) {
throw JavaImpl.newRuntimeException(nsae);
}
sha1.reset();
byte[] result = sha1.digest(x509);
return new BigInteger(result);
}
private static class CRLHolder {
private final String urlString;
private File tempCRLFile;
private long creationTime;
private Set passedTest = new HashSet();
private Set failedTest = new HashSet();
CRLHolder(String urlString) {
if (urlString == null) {
throw new NullPointerException("urlString can't be null");
}
this.urlString = urlString;
}
public synchronized boolean checkCRL(X509Certificate cert)
throws CertificateException {
CRL crl = null;
long now = System.currentTimeMillis();
if (now - creationTime > 24 * 60 * 60 * 1000) {
// Expire cache every 24 hours
if (tempCRLFile != null && tempCRLFile.exists()) {
tempCRLFile.delete();
}
tempCRLFile = null;
passedTest.clear();
/*
Note: if any certificate ever fails the check, we will
remember that fact.
This breaks with temporary "holds" that CRL's can issue.
Apparently a certificate can have a temporary "hold" on its
validity, but I'm not interested in supporting that. If a "held"
certificate is suddenly "unheld", you're just going to need
to restart your JVM.
*/
// failedTest.clear(); <-- DO NOT UNCOMMENT!
}
BigInteger fingerprint = getFingerprint(cert);
if (failedTest.contains(fingerprint)) {
throw new CertificateException("Revoked by CRL (cached response)");
}
if (passedTest.contains(fingerprint)) {
return true;
}
if (tempCRLFile == null) {
try {
// log.info( "Trying to load CRL [" + urlString + "]" );
// java.net.URL blocks forever by default, so CRL-checking
// is freezing some systems. Below we go to great pains
// to enforce timeouts for CRL-checking (5 seconds).
URL url = new URL(urlString);
URLConnection urlConn = url.openConnection();
if (urlConn instanceof HttpsURLConnection) {
// HTTPS sites will use special CRLSocket.getInstance() SocketFactory
// that is configured to timeout after 5 seconds:
HttpsURLConnection httpsConn = (HttpsURLConnection) urlConn;
httpsConn.setSSLSocketFactory(CRLSocket.getSecureInstance());
} else if (urlConn instanceof HttpURLConnection) {
// HTTP timeouts can only be set on Java 1.5 and up. :-(
// The code required to set it for Java 1.4 and Java 1.3 is just too painful.
HttpURLConnection httpConn = (HttpURLConnection) urlConn;
try {
// Java 1.5 and up support these, so using reflection. UGH!!!
Class c = httpConn.getClass();
Method setConnTimeOut = c.getDeclaredMethod("setConnectTimeout", new Class[]{Integer.TYPE});
Method setReadTimeout = c.getDeclaredMethod("setReadTimeout", new Class[]{Integer.TYPE});
setConnTimeOut.invoke(httpConn, Integer.valueOf(5000));
setReadTimeout.invoke(httpConn, Integer.valueOf(5000));
} catch (NoSuchMethodException nsme) {
// oh well, java 1.4 users can suffer.
} catch (Exception e) {
throw new RuntimeException("can't set timeout", e);
}
}
File tempFile = File.createTempFile("crl", ".tmp");
tempFile.deleteOnExit();
OutputStream out = new FileOutputStream(tempFile);
out = new BufferedOutputStream(out);
InputStream in = new BufferedInputStream(urlConn.getInputStream());
try {
Util.pipeStream(in, out);
}
catch (IOException ioe) {
// better luck next time
tempFile.delete();
throw ioe;
}
this.tempCRLFile = tempFile;
this.creationTime = System.currentTimeMillis();
}
catch (IOException ioe) {
// log.warn( "Cannot check CRL: " + e );
}
}
if (tempCRLFile != null && tempCRLFile.exists()) {
try {
InputStream in = new FileInputStream(tempCRLFile);
in = new BufferedInputStream(in);
synchronized (CF) {
crl = CF.generateCRL(in);
}
in.close();
if (crl.isRevoked(cert)) {
// log.warn( "Revoked by CRL [" + urlString + "]: " + name );
passedTest.remove(fingerprint);
failedTest.add(fingerprint);
throw new CertificateException("Revoked by CRL");
} else {
passedTest.add(fingerprint);
}
}
catch (IOException ioe) {
// couldn't load CRL that's supposed to be stored in Temp file.
// log.warn( );
}
catch (CRLException crle) {
// something is wrong with the CRL
// log.warn( );
}
}
return crl != null;
}
}
public static String getCN(X509Certificate cert) {
String[] cns = getCNs(cert);
boolean foundSomeCNs = cns != null && cns.length >= 1;
return foundSomeCNs ? cns[0] : null;
}
public static String[] getCNs(X509Certificate cert) {
try {
final String subjectPrincipal = cert.getSubjectX500Principal().getName(X500Principal.RFC2253);
final LinkedList cnList = new LinkedList();
final LdapName subjectDN = new LdapName(subjectPrincipal);
for (final Rdn rds : subjectDN.getRdns()) {
final Attributes attributes = rds.toAttributes();
final Attribute cn = attributes.get("cn");
if (cn != null) {
try {
final Object value = cn.get();
if (value != null) {
cnList.add(value.toString());
}
} catch (NoSuchElementException ignore) {
} catch (NamingException ignore) {
}
}
}
if (!cnList.isEmpty()) {
return cnList.toArray(new String[cnList.size()]);
}
} catch (InvalidNameException ignore) {
}
return null;
}
/**
* Extracts the array of SubjectAlt DNS names from an X509Certificate.
* Returns null if there aren't any.
*
* Note: Java doesn't appear able to extract international characters
* from the SubjectAlts. It can only extract international characters
* from the CN field.
*
* (Or maybe the version of OpenSSL I'm using to test isn't storing the
* international characters correctly in the SubjectAlts?).
*
* @param cert X509Certificate
* @return Array of SubjectALT DNS names stored in the certificate.
*/
public static String[] getDNSSubjectAlts(X509Certificate cert) {
LinkedList subjectAltList = new LinkedList();
Collection c = null;
try {
c = cert.getSubjectAlternativeNames();
}
catch (CertificateParsingException cpe) {
// Should probably log.debug() this?
cpe.printStackTrace();
}
if (c != null) {
Iterator it = c.iterator();
while (it.hasNext()) {
List list = (List) it.next();
int type = ((Integer) list.get(0)).intValue();
// If type is 2, then we've got a dNSName
if (type == 2) {
String s = (String) list.get(1);
subjectAltList.add(s);
}
}
}
if (!subjectAltList.isEmpty()) {
String[] subjectAlts = new String[subjectAltList.size()];
subjectAltList.toArray(subjectAlts);
return subjectAlts;
} else {
return null;
}
}
/**
* Trims off any null entries on the array. Returns a shrunk array.
*
* @param chain X509Certificate[] chain to trim
* @return Shrunk array with all trailing null entries removed.
*/
public static Certificate[] trimChain(Certificate[] chain) {
for (int i = 0; i < chain.length; i++) {
if (chain[i] == null) {
X509Certificate[] newChain = new X509Certificate[i];
System.arraycopy(chain, 0, newChain, 0, i);
return newChain;
}
}
return chain;
}
/**
* Returns a chain of type X509Certificate[].
*
* @param chain Certificate[] chain to cast to X509Certificate[]
* @return chain of type X509Certificate[].
*/
public static X509Certificate[] x509ifyChain(Certificate[] chain) {
if (chain instanceof X509Certificate[]) {
return (X509Certificate[]) chain;
} else {
X509Certificate[] x509Chain = new X509Certificate[chain.length];
System.arraycopy(chain, 0, x509Chain, 0, chain.length);
return x509Chain;
}
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
FileInputStream in = new FileInputStream(args[i]);
TrustMaterial tm = new TrustMaterial(in);
Iterator it = tm.getCertificates().iterator();
while (it.hasNext()) {
X509Certificate x509 = (X509Certificate) it.next();
System.out.println(toString(x509));
}
}
}
}