com.mps.deepviolet.api.CipherSuiteUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of DeepViolet Show documentation
Show all versions of DeepViolet Show documentation
TLS/SSL security introspection API
The newest version!
package com.mps.deepviolet.api;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERApplicationSpecific;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.DERVisibleString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.mps.deepviolet.api.IDVSession.CIPHER_NAME_CONVENTION;
import com.mps.deepviolet.util.FileUtils;
//import sun.security.provider.certpath.OCSP;
//import sun.security.provider.certpath.OCSP.RevocationStatus;
/**
* Utility class to handle cryptographic functions. Significant contributions around
* ciphersuite handling adapted from code examples by Thomas Pornin.
* For more information see, The Transport Layer Security (TLS) Protocol Version 1.2,
* Transport Layer Security (TLS) Parameters,
* TestSSLServer
* @author Milton Smith
*/
class CipherSuiteUtil {
// Handshake protocol version legend
// SSL v1
// SSL v2
// SSL v3
// SSL v3.1 = TLS v1.0
// SSL v3.2 = TLS v1.1
// SSL v3.3 = TLS v1.2
private static final Logger logger = LoggerFactory.getLogger("com.mps.deepviolet.suite.CipherSuiteUtil");
// Common OIDs to Extension Mappings
private static final HashMap OIDMAP = new HashMap();
private static final SSLSocketFactory dsc = HttpsURLConnection.getDefaultSSLSocketFactory();
private static final HostnameVerifier dhv = HttpsURLConnection.getDefaultHostnameVerifier();
static final int MAX_RECORD_LEN = 16384;
static final int CHANGE_CIPHER_SPEC = 20;
static final int ALERT = 21;
static final int HANDSHAKE = 22;
static final int APPLICATION = 23;
private static final SecureRandom RNG = new SecureRandom();
private static final byte[] SSL2_CLIENT_HELLO = {
(byte)0x80, (byte)0x2E, // header (record length)
(byte)0x01, // message type (CLIENT HELLO)
(byte)0x00, (byte)0x02, // version (0x0002)
(byte)0x00, (byte)0x15, // cipher specs list length
(byte)0x00, (byte)0x00, // session ID length
(byte)0x00, (byte)0x10, // challenge length
0x01, 0x00, (byte)0x80, // SSL_CK_RC4_128_WITH_MD5
0x02, 0x00, (byte)0x80, // SSL_CK_RC4_128_EXPORT40_WITH_MD5
0x03, 0x00, (byte)0x80, // SSL_CK_RC2_128_CBC_WITH_MD5
0x04, 0x00, (byte)0x80, // SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5
0x05, 0x00, (byte)0x80, // SSL_CK_IDEA_128_CBC_WITH_MD5
0x06, 0x00, (byte)0x40, // SSL_CK_DES_64_CBC_WITH_MD5
0x07, 0x00, (byte)0xC0, // SSL_CK_DES_192_EDE3_CBC_WITH_MD5
0x54, 0x54, 0x54, 0x54, // challenge data (16 bytes)
0x54, 0x54, 0x54, 0x54,
0x54, 0x54, 0x54, 0x54,
0x54, 0x54, 0x54, 0x54
};
private static final byte[] HEARTBEAT = {
(byte)0x018, // message type (HEARTBEAT)
(byte)0x03, (byte)0x01, // tls version
(byte)0x00, (byte)0x03, // length
(byte)0x01, // type (request)
(byte)0x40, (byte)0x00 // payload length
};
static final int UNASSIGNED = -1; // no evaluation in json mapping file
static final int CLEAR = 0; // no encryption
static final int WEAK = 1; // weak encryption: 40-bit key
static final int MEDIUM = 2; // medium encryption: 56-bit key
static final int STRONG = 3; // strong encryption
public static final String NO_CIPHERS = "No Ciphers";
static Map CIPHER_SUITES =
new TreeMap();
static boolean bCiphersInitialized = false;
static {
// Generate cipher map dynamically based upon Mozilla json data.
//initCipherMap();
}
static {
// Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
Security.addProvider( new BouncyCastleProvider() );
OIDMAP.put( "2.5.29.14","SubjectKeyIdentifier");
OIDMAP.put( "2.5.29.15","KeyUsage");
OIDMAP.put( "2.5.29.16","PrivateKeyUsage");
OIDMAP.put( "2.5.29.17","SubjectAlternativeName");
OIDMAP.put( "2.5.29.18","IssuerAlternativeName");
OIDMAP.put( "2.5.29.19","BasicConstraints");
OIDMAP.put( "2.5.29.30","NameConstraints");
OIDMAP.put( "2.5.29.33","PolicyMappings");
OIDMAP.put( "2.5.29.35","AuthorityKeyIdentifier");
OIDMAP.put( "2.5.29.36","PolicyConstraints");
OIDMAP.put( "1.3.6.1.5.5.7.48.1","ocsp");
OIDMAP.put( "1.3.6.1.5.5.7.48.2","caIssuers");
OIDMAP.put( "1.2.840.113549.1.1.1","SubjectPublicKeyInfo");
OIDMAP.put( "1.3.6.1.5.5.7.48.1.1","BasicOCSPResponse");
OIDMAP.put( "1.2.840.113549.1.1.5","SignatureAlgorithm");
OIDMAP.put( "1.3.6.1.5.5.7.1.1","AuthorityInfoAccess");
OIDMAP.put("1.3.6.1.4.1.11129.2.4.2", "SignedCertificateTimestampList");
OIDMAP.put("1.3.6.1.5.5.7.2.2", "CPSUserNotice");
OIDMAP.put( "2.5.29.31","CRLDistributionPoints");
OIDMAP.put( "2.5.29.32","CertificatePolicies");
OIDMAP.put( "1.3.6.1.4.1.6449.1.2.1.5.1","CertificatePolicyId");
OIDMAP.put( "2.5.29.32","CertificatePolicies");
OIDMAP.put( "1.3.6.1.5.5.7.2.1","qualifierID");
OIDMAP.put( "2.5.29.37","ExtendedKeyUsages");
OIDMAP.put( "2.5.29.15","KeyUsage");
OIDMAP.put( "2.5.29.14","SubjectKeyIdentifier");
}
// TODO This needs to go bye bye, abad idea. I'm thinking a better way to do this is eventually
// get to a MVC type architecture. This would better address the ways deepviolet can be used
static ServerMetadata getServerMetadataInstance( URL url, IDVSession.CIPHER_NAME_CONVENTION cipher_name_convention, MutableDVSession session, DVBackgroundTask dvtask ) throws Exception {
HostData hostdata = new HostData(url);;
Boolean compress = false;
// Generate cipher map dynamically based upon Mozilla json data.
dvtask.setStatusBarMessage("Initializing DV ciphersuite maps.");
if( !bCiphersInitialized) initCipherMap();
dvtask.setStatusBarMessage("Reviewing server protocols.");
String name = url.getHost();
int port = ( url.getPort() > 0 ) ? url.getPort() : 443;
InetSocketAddress isa = new InetSocketAddress(name, port);
Set sv = new TreeSet();
for (int v = 0x0300; v <= 0x0303; v ++) {
CipherSuiteUtilServerHello sh = connect(isa,
v, CIPHER_SUITES.keySet());
if (sh == null) {
continue;
}
sv.add(sh.protoVersion);
dvtask.setStatusBarMessage("Analysing TLS version "+sh.protoVersion);
if (sh.compression == 1) {
compress = true;
session.setSessionPropertyValue(IDVSession.SESSION_PROPERTIES.DEFLATE_COMPRESSION, "true");
logger.warn("Server vulnerable to CRIME attack. Compression enabled.");
}else{
session.setSessionPropertyValue(IDVSession.SESSION_PROPERTIES.DEFLATE_COMPRESSION, "false");
}
}
dvtask.setStatusBarMessage("Sending TLS server Hello SSLv2");
ServerHelloSSLv2 sh2 = connectV2(isa);
if (sh2 != null) {
sv.add(0x0200);
}
if (sv.size() == 0) {
dvtask.setStatusBarMessage("Server may not be SSL/TLS enabled. host=" + isa);
logger.error("Server may not be SSL/TLS enabled. host=" + isa);
return null;
}
dvtask.setStatusBarMessage("Normalizing ciphersuite names to "+cipher_name_convention+" specification");
Set lastSuppCS = null;
Map> suppCS = new TreeMap>();
Set certID = new TreeSet();
boolean vulnFREAK = false;
if (sh2 != null) {
ArrayList listv2 = new ArrayList();
String[] tmp = new String[0];
Set vc2 = new TreeSet();
for (int c : sh2.cipherSuites) {
vc2.add(c);
}
for (int c : vc2) {
String suitename = cipherSuiteStringV2(c, cipher_name_convention);
if( !vulnFREAK ) {
if( suitename == null || suitename.length()==0 ) {
logger.info("Freak vulnerability analysis skipped due to null ciphersuite name. id=0x" + c );
} else {
vulnFREAK = cipherSuiteStringV2(c, cipher_name_convention).indexOf("RSA_EXPORT") > -1;
if (vulnFREAK ) {
logger.warn("Following ciphersuite vulnerable to FREAK attack, "+suitename);
}
}
}
listv2.add( suitename+"(0x"+Integer.toHexString(c)+")" );
}
suppCS.put(0x0200, vc2);
if (sh2.serverCertName != null) {
hostdata.setScalarValue("getServerMetadataInstance",sh2.serverCertHash, sh2.serverCertName);
}
hostdata.setVectorValue( "getServerMetadataInstance",versionString(0x0200), listv2.toArray(tmp));
}
dvtask.setStatusBarMessage("Starting ciphersuite analysis.");
int agMaxStrength = STRONG;
int agMinStrength = STRONG;
boolean vulnBEAST = false;
//boolean vulnROBOT = false;
for (int v : sv) {
if (v == 0x0200) {
continue;
}
Set vsc = supportedSuites(isa, v, certID);
suppCS.put(v, vsc);
ArrayList listv = new ArrayList();
String[] tmp = new String[0];
for (int c : vsc) {
String suitename = cipherSuiteString(c, cipher_name_convention);
if( suitename == null || suitename.length()==0 ) {
logger.info("Freak vulnerability analysis skipped due to null ciphersuite name. id=0x" + c );
} else {
if( !vulnFREAK ) {
vulnFREAK = cipherSuiteString(c, cipher_name_convention).indexOf("RSA_EXPORT") > -1;
if( vulnFREAK ) {
logger.warn("Following ciphersuite vulnerable to FREAK attack, "+suitename);
}
}
}
//Set vsc = suppCS.get(c);
agMaxStrength = Math.min(
maxStrength(vsc), agMaxStrength);
agMinStrength = Math.min(
minStrength(vsc), agMinStrength);
if (!vulnBEAST) {
vulnBEAST = testBEAST(isa, c, vsc);
if( vulnBEAST ) {
logger.warn("Following ciphersuite vulnerable to BEAST attack, "+suitename);
}
}
// if (!vulnROBOT) {
// vulnROBOT = suitename.startsWith("TLS_RSA");
// if( vulnROBOT ) {
// logger.warn("Following ciphersuite vulnerable to ROBOT attack, "+suitename);
// }
// }
listv.add( suitename+"(0x"+Integer.toHexString(c)+")" );
}
hostdata.setVectorValue( "getServerMetadataInstance",versionString(v), listv.toArray(tmp));
}
// // Iterate over supported ciphersuites.
// int agMaxStrength = STRONG;
// int agMinStrength = STRONG;
// boolean vulnBEAST = false;
// boolean vulnROBOT = false;
// for (int v : sv) {
// Set vsc = suppCS.get(v);
// agMaxStrength = Math.min(
// maxStrength(vsc), agMaxStrength);
// agMinStrength = Math.min(
// minStrength(vsc), agMinStrength);
// if (!vulnBEAST) {
// vulnBEAST = testBEAST(isa, v, vsc);
// }
// if (!vulnROBOT) {
// vulnROBOT = cipherSuiteString(v, cipher_name_convention).startsWith("TLS_RSA");
// }
// }
//TODO: NEEDS TO BE CHECKED AND TESTED.
session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.MINIMAL_ENCRYPTION_STRENGTH, strengthString(agMinStrength));
session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.ACHIEVABLE_ENCRYPTION_STRENGTH, strengthString(agMaxStrength));
session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.BEAST_VULNERABLE, Boolean.toString(vulnBEAST)); // Notes: Check TLS version <=1.0 and CBC enabled. https://blog.qualys.com/ssllabs/2013/09/10/is-beast-still-a-threat
session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.CRIME_VULNERABLE, Boolean.toString(compress)); // Notes: Check TLS compression enabled, https://en.wikipedia.org/wiki/CRIME
session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.FREAK_VULNERABLE, Boolean.toString(vulnFREAK)); // Notes: Check ciphers containing, RSA_EXPORT https://censys.io/blog/freak
//session.setVulnerabilityAssessmentValue(IDVSession.VULNERABILITY_ASSESSMENTS.ROBOT_VULNERABLE, Boolean.toString(vulnROBOT)); // Notes: Check ciphers starting, TLS_RSA https://robotattack.org/
dvtask.setStatusBarMessage("Ciphersuite analysis complete.");
return hostdata;
}
/*
* Original Mozilla JSON file
* server-side-tls-conf-4.0.json = https://statics.tls.security.mozilla.org/server-side-tls-conf-4.0.json
* ciphermap.json = https://github.com/april/tls-table/blob/master/tls-table.py
*/
private static void initCipherMap() {
String ciphermap = FileUtils.getJsonResourceAsString("ciphermap.json");
Object document = Configuration.defaultConfiguration().jsonProvider().parse(ciphermap);
List> ciphermetalist = JsonPath.read(document, "$[?(@.*)]");
// Note (milton): A point to consider about the Mozilla json evaluation data and a source
// of confusion for me is that all ciphers listed in modern are also listed in
// intermediate. All ciphers in intermediate are also listed in old. To separate
// the evals out properly for DV purposes we add ciphers to a map in the following
// order, modern, intermediate, and then old. Further once we set the cipher
// evaluation we don't allow the evaluation to be reassigned to a lower level.
HashMap strengtheval = new HashMap();
String cipherevaluation = FileUtils.getJsonResourceAsString("server-side-tls-conf-4.0.json");
Object d2 = Configuration.defaultConfiguration().jsonProvider().parse(cipherevaluation);
List mc1 = JsonPath.read(d2, "$.configurations.modern.ciphersuites.*");
for( String ciph: mc1) {
strengtheval.put(ciph, new Integer(STRONG));
}
//TODO note this does not return just intermediate.ciphersuites.* need to fix
Object d3 = Configuration.defaultConfiguration().jsonProvider().parse(cipherevaluation);
List mc2 = JsonPath.read(d3, "$.configurations.intermediate.ciphersuites.*");
for( String ciph: mc2) {
if( !strengtheval.containsKey(ciph) ){ // don't remove, see note.
strengtheval.put(ciph,new Integer(MEDIUM));
}
}
//TODO note this does not return just intermediate.ciphersuites.* need to fix
Object d4 = Configuration.defaultConfiguration().jsonProvider().parse(cipherevaluation);
List mc3 = JsonPath.read(d4, "$.configurations.old.ciphersuites.*");
for( String ciph: mc3) {
if( !strengtheval.containsKey(ciph) ){ // don't remove, see note.
strengtheval.put(ciph,new Integer(WEAK));
}
}
Iterator> i = ciphermetalist.iterator();
while( i.hasNext() ) {
Map, ?> ci = (Map, ?>)i.next();
//System.out.println("ci="+ci.getClass().getName());
Iterator> keys = ci.keySet().iterator();
while ( keys.hasNext() ) {
Object obj = keys.next();
//System.out.println("obj="+obj.getClass().getName()+" val="+obj.toString());
Map, ?> ch1 = (Map, ?>)ci.get(obj.toString());
//System.out.println("ch1="+ch1.getClass().getName()+" val="+ch1.toString());
List ciphercode = Arrays.asList(obj.toString().split(","));
String ho = ciphercode.get(0);
List lo = Arrays.asList(ciphercode.get(1).split("x"));
String sho = ho;
String slo = lo.get(1).replaceFirst("^0+(?!$)", "");
sho = ( sho.equals("0") ) ? "" : sho;
Integer cc1 = Integer.decode(sho+slo);
Iterator> cns = ch1.values().iterator();
int istrengtheval = -1; //unknown strength
while( cns.hasNext() ) {
String key = (String)cns.next();
if( strengtheval.containsKey(key) ) {
istrengtheval = ((Integer)strengtheval.get(key)).intValue();
break;
}
}
logger.debug("Cached Mozilla ciphers, "+obj.toString()+" "+ch1.toString()+" strengtheval="+istrengtheval);
makeCS(cc1.intValue(),ch1, istrengtheval);
}
}
}
static String versionString(int version) {
if (version == 0x0200) {
return "SSLv2";
} else if (version == 0x0300) {
return "SSLv3";
} else if ((version >>> 8) == 0x03) {
return "TLSv1." + ((version & 0xFF) - 1);
} else {
return String.format("UNKNOWN_VERSION:0x%04X", version);
}
}
/*
* Enumerate server cipher suites. This is accomplished by
* repeatedly contacting the server, each time removing from our
* list of supported suites returned by the server. The cipher
* suites remaining at the end of this operation are unsupported
* by the server.
*/
static Set supportedSuites(InetSocketAddress isa, int version,
Set serverCertID)
{
// Notes: the problem with using the past approach, CIPHER_SUITES.keySet(),
// is that some servers use ciphers outside those included with the
// Mozilla cipher mapings. As a result DV was missing some ciphers.
// The new approach is more comprensive but takes longer.
//Set cs = new TreeSet(CIPHER_SUITES.keySet());
Set rs = new TreeSet();
int BLK_SIZE = 6000;
int CIPHERMAPSZ = 0xFFFF;
int i2=0; int i3=1;
Set scanblk = null;
for ( int i=1; i();
while( i2< BLK_SIZE*i3 ) {
scanblk.add(i2);
i2++;
}
i3++;
// // Take it easy on server, delay between a little between block checks
// try {
// Thread.currentThread().sleep((int)(Math.random()*750));
// } catch (InterruptedException e) {}
for (;;) {
//TODO could make this multi-threaded to speed up scanning.
// although need to be kind to servers. Don't want too
// many connections and create performance problems.
CipherSuiteUtilServerHello sh = connect(isa, version, scanblk);
if (sh == null) {
break;
}
if (!scanblk.contains(sh.cipherSuite)) {
//TODO need a better way to communicate this in the future
String ciphersuite = Integer.toHexString(sh.cipherSuite);
logger.warn("Server wants to use"
+ " cipher suite "+ciphersuite+" which client"
+ " did not announce.");
break;
}
scanblk.remove(sh.cipherSuite);
rs.add(sh.cipherSuite);
if (sh.serverCertName != null) {
serverCertID.add(sh.serverCertHash
+ ": " + sh.serverCertName);
}
}
}
return rs;
}
static int minStrength(Set supp)
{
int m = STRONG;
for (int suite : supp) {
CipherSuite cs = CIPHER_SUITES.get(suite);
if (cs == null) {
continue;
}
if (cs.strength < m) {
m = cs.strength;
}
}
return m;
}
static int maxStrength(Set supp)
{
int m = CLEAR;
for (int suite : supp) {
CipherSuite cs = CIPHER_SUITES.get(suite);
if (cs == null) {
continue;
}
if (cs.strength > m) {
m = cs.strength;
}
}
return m;
}
/**
* Utility method to convert an javax.security.cert.X509Certificate
to
* java.security.cert.X509Certificate
. No doubt, a bit of legacy.
* @param cert X509 certificate to convert.
* @return java.security.cert.X509Certificate Converted object
* @see http://exampledepot.8waytrips.com/egs/javax.security.cert/ConvertCert.html
*/
// public static java.security.cert.X509Certificate convert(javax.security.cert.X509Certificate cert) {
// try {
// byte[] encoded = cert.getEncoded();
// ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
// java.security.cert.CertificateFactory cf
// = java.security.cert.CertificateFactory.getInstance("X.509");
// return (java.security.cert.X509Certificate)cf.generateCertificate(bis);
// } catch (javax.security.cert.CertificateEncodingException e) {
// logger.error(e.getMessage(),e);
// } catch (java.security.cert.CertificateException e) {
// logger.error(e.getMessage(),e);
// }
// return null;
// }
/**
* Analysis to determine ciphersuite strength.
* @param protocol Ciphersuite protocol to test.
* @return String indicating strength, CLEAR(no encryption), WEAK, MEDIUM, STRONG.
*/
static final String getStrength(String protocol) {
String clear = "CLEAR{no encryption}";
String weak = "WEAK";
String medium = "MEDIUM";
String strong = "STRONG";
String unknown = "UNKNOWN";
String result = unknown;
if (protocol.contains("_NULL_") ) {
result = clear;
} else {
Collection suites = CIPHER_SUITES.values();
CipherSuite c = null;
Iterator i = suites.iterator();
while( i.hasNext() ) {
c = (CipherSuite)i.next();
if ( c.names.containsValue(protocol) ) {
switch ( c.strength ) {
case STRONG:
result = strong;
break;
case MEDIUM:
result = medium;
break;
case WEAK:
result = weak;
break;
}
}
}
}
return result;
}
/**
* Retrieve a server certificate based upon URL.
* @param url Target URL
* @return X509Certificate Server certificate.
* @throws Exception Thrown on problems.
*/
static final X509Certificate getServerCertificate(URL url) throws Exception {
X509Certificate[] certs = getServerCertificateChain(url);
return certs[0];
}
/**
* Return server responses
* @param url Target URL
* @return Map HTTPS response headers
* @throws Exception Thrown on problems.
*/
static final Map> getHttpResponseHeaders(URL url) throws Exception {
HttpsURLConnection conn = null;
Map> result = new HashMap>();
try {
enableTLSChainTesting(false);
conn = (HttpsURLConnection)url.openConnection();
conn.connect();
result = conn.getHeaderFields();
} finally {
enableTLSChainTesting(true);
}
return result;
}
// public static final synchronized ServerMetadata getHttpResponseHeaders(URL url) throws Exception {
//
// HostData hostdata = null;
//
// // return cached instance of server TLS data if available and not expired.
// if ( hostcache.containsKey( url ) ) {
// hostdata = hostcache.get(url);
// if( !hostdata.isExpired() )
// return hostdata;
// }
//
// // No cached instance of TLS data so create some
// hostdata = new HostData(url);
// hostcache.put(url, hostdata);
//
// HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
// conn.connect();
// Map> headers = conn.getHeaderFields();
//
// Set k = headers.keySet();
// Iterator keys = k.iterator();
//
// while( keys.hasNext() ) {
//
// String key = keys.next();
// hostdata.setVectorValue(key,(String[])headers.get(key).toArray() );
//
// }
//
// return hostdata;
//
// }
/**
* Enable default testing for TLS certificate trust chains.
* @param value true, chain will be tested. false, chain will not be tested.
* @throws Exception Thrown on error
*/
static final void enableTLSChainTesting( boolean value ) throws Exception {
if( value ) {
HttpsURLConnection.setDefaultSSLSocketFactory(dsc);
HttpsURLConnection.setDefaultHostnameVerifier(dhv);
} else {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new TrustAllX509TrustManager() }, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
}
}
/**
* Retrieve a certificate chain based upon URL. Note this API will return
* certificates with unvalidated and possibly bad trust chains.
* @param url Target URL
* @return X509Certificate Certificate chain
* @throws Exception Thrown on problems.
* @see java-overriding-function-to-disable-ssl-certificate-check
*/
static final X509Certificate[] getServerCertificateChain(URL url) throws Exception {
ArrayList list = new ArrayList();
try {
enableTLSChainTesting(false);
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.connect();
Certificate[] certs = conn.getServerCertificates();
for (Certificate cert : certs) {
if(cert instanceof X509Certificate) {
list.add( (X509Certificate)cert );
} else {
logger.info("Unsupported certificate type. type="+cert.getClass().getName());
}
}
} finally {
enableTLSChainTesting(true);
}
return list.toArray(new X509Certificate[0]);
}
/**
* Get a list of the Java root certificates.
* For more information, How can I get a list of trusted root certificates in Java?
* @return An array of X509Certificates root certificates from the Java trust store
* @throws Exception Thrown on problems.
*/
static final X509Certificate[] getJavaRootCertificates() throws Exception {
//TODO: Maybe be good to consider caching this at some point (at least for a few seconds)
// Load the JDK's cacerts keystore file
String filename = System.getProperty("java.home") + "/lib/security/cacerts".replace('/', File.separatorChar);
logger.debug("CACERTS file, "+filename);
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit"; //default password
keystore.load(is, password.toCharArray());
// This class retrieves the most-trusted CAs from the keystore
PKIXParameters params = new PKIXParameters(keystore);
// Get the set of trust anchors, which contain the most-trusted CA certificates,
// and return the java root certs.
Iterator it = params.getTrustAnchors().iterator();
ArrayList result = new ArrayList();
while( it.hasNext() ) {
TrustAnchor ta = it.next();
result.add(ta.getTrustedCert());
}
return result.toArray(new X509Certificate[0]);
}
// /**
// * Test to see if a particular SHA1 hash is a root in the Java system keystore.
// * @param sha1hash
// * @return true, SHA1 hash belongs to a Java root. false, no Java root found.
// */
// public static final boolean isJavaRootCertificateSHA1(String sha1hash) throws Exception {
//
// boolean result = false;
//
// for( X509Certificate cert : getJavaRootCertificates() ) {
//
// String fingerprint = sha1Fingerprint(cert.getEncoded());
//
// if( fingerprint.equals(sha1hash) ) {
//
// result = true; break;
// }
// }
//
// return result;
// }
/**
* Test to see if a particular IssuerDN is a root in the Java system keystore.
* @param IssuerDN Issuing authority
* @return true, IssuerDN matches a Java root. false, no matching IssuerDN found.
*/
static final boolean isJavaRootCertificateDN(String IssuerDN) throws Exception {
boolean result = false;
for( X509Certificate cert : getJavaRootCertificates() ) {
if ( cert.getIssuerDN().getName().equals(IssuerDN) ) {
result = true; break;
}
}
return result;
}
/**
* Check trust status of each certificate in the chain.
* @param certs Chain of X509Certificates to test.
* @param url Server URL to test against.
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws UnknownHostException
* @throws IOException
* @return True, the certificate chain is trusted. False, the chain is not trusted.
*/
static final boolean checkTrustedCertificate( X509Certificate[] certs, URL url) throws KeyStoreException,
NoSuchAlgorithmException, UnknownHostException, IOException {
boolean valid = false;
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket)factory.createSocket(url.getHost(), url.getDefaultPort());
SSLSession session = socket.getSession();
String keyexchalgo = getKeyExchangeAlgorithm(session);
try { socket.close(); } catch( IOException e ) {}
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
// you could use a non-default KeyStore as your truststore too, instead of null.
for (TrustManager trustManager: trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
X509TrustManager x509TrustManager = (X509TrustManager)trustManager;
try {
x509TrustManager.checkServerTrusted(certs,keyexchalgo);
valid = true;
} catch( CertificateException e ) {
// Eat the stacktrace
logger.error( "url="+url.toString() );
}
}
}
return valid;
}
// check OCSP
// public static final String checkOCSPStatus(X509Certificate cert, X509Certificate issuer) {
//
// // init PKIX parameters
// PKIXParameters params = null;
// params = new PKIXParameters(trustedCertsSet);
// params.addCertStore(store);
//
// // enable OCSP
// Security.setProperty("ocsp.enable", "true");
// if (ocspServer != null) {
// Security.setProperty("ocsp.responderURL", args[1]);
// Security.setProperty("ocsp.responderCertSubjectName",
// ocspCert.getSubjectX500Principal().getName());
// }
//
// // perform validation
// CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
// PKIXCertPathValidatorResult cpv_result =
// (PKIXCertPathValidatorResult) cpv.validate(cp, params);
// X509Certificate trustedCert = (X509Certificate)
// cpv_result.getTrustAnchor().getTrustedCert();
//
// if (trustedCert == null) {
// System.out.println("Trsuted Cert = NULL");
// } else {
// System.out.println("Trusted CA DN = " +
// trustedCert.getSubjectDN());
// }
//
// return buff.toString();
//
// }
/**
* Parse out the Cipher's Key Exchange Algorithm
* @param session Target SSLSession
* @return String TLS key exchange algorithm.
*/
private static final String getKeyExchangeAlgorithm( SSLSession session ) {
String cipher = session.getCipherSuite().toString();
int i1 = cipher.indexOf('_')+1;
int i2 = cipher.indexOf("_WITH");
String keyexch = cipher.substring(i1, i2);
return keyexch;
}
/**
* Is this test certificate a self-signed certificate.
* @param cert Target certificate to test.
* @return boolean True, certificate is self-signed. False, certificate is not self-signed.
*/
static final boolean isSelfSignedCertificate( X509Certificate cert ) {
boolean result = false;
if (cert != null ) {
if ( cert.getIssuerDN().equals(cert.getSubjectDN()) )
result = true;
}
return result;
}
/**
* Generate signer fingerprint from certificate bytes
* @param der Certificate in bytes
* @param signatureAlgorithm Signing algorithm for the certificate, ex: SHA256
* @return String Signer fingerprint in hex.
* @throws NoSuchAlgorithmException
*/
public static final String signerFingerprint( byte[] der, String signatureAlgorithm ) throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance(signatureAlgorithm);
sha1.update( der );
StringBuffer buff = new StringBuffer();
buff.append(byteArrayToHex(sha1.digest()));
return buff.toString();
}
// /**
// * Generate SHA1 fingerprint from certificate bytes
// * @param der Certificate in bytes
// * @return String SHA1 fingerprint in hex.
// * @throws NoSuchAlgorithmException
// */
// static final String sha1Fingerprint( byte[] der ) throws NoSuchAlgorithmException {
//
// MessageDigest sha1 = MessageDigest.getInstance("SHA1");
// sha1.update( der );
//
// StringBuffer buff = new StringBuffer();
// buff.append(byteArrayToHex(sha1.digest()));
//
// return buff.toString();
//
// }
// /**
// * Generate MD5 fingerprint from certificate bytes
// * @param der Certificate in bytes
// * @return String MD5 fingerprint in hex.
// * @throws NoSuchAlgorithmException
// */
// static final String md5Fingerprint( byte[] der ) throws NoSuchAlgorithmException {
//
// MessageDigest sha1 = MessageDigest.getInstance("MD5");
// sha1.update( der );
//
// StringBuffer buff = new StringBuffer();
// buff.append(byteArrayToHex(sha1.digest()));
//
// return buff.toString();
//
// }
/**
* Convert an array of bytes to a String based hex representation
* @param a Target byte array to convert.
* @return String String based hex representation of target byte array.
*/
static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a) {
sb.append(String.format("%02x", b & 0xff));
sb.append(':');
}
sb.setLength(sb.length()-1);
return sb.toString().toUpperCase();
}
/**
* Returns human readable OID name for the OID number.
* @param oidkey OID number sequence. Ex: 2.5.29.15
* @return Human readable String representation of the OID number sequence. Ex: keyusage
*/
static String getOIDKeyName(String oidkey) {
// TODO: Need to figure out a better way to do this.
return (OIDMAP.get(oidkey)!=null) ? OIDMAP.get(oidkey) : oidkey;
}
/**
* Convert der
encoded data to ASN1Primitive
.
* For more information,
* (StackOverflow: How do I decode a DER encoded string in Java?)
* @param data byte[] of der
encoded data
* @return ASN1Primitive
representation of der
encoded data
* @throws IOException
*/
static final ASN1Primitive toDERObject(byte[] data) throws IOException {
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
ASN1InputStream asnInputStream = new ASN1InputStream(inStream);
ASN1Primitive p = asnInputStream.readObject();
asnInputStream.close();
return p;
}
/**
* Reentrant method to decode ASN1Primiatives. ASN1Primiatives types handled: DEROctetString
,
* DLSequence
, DERSequence
, DERIA5String
, DERBitString
,
* ASN1Boolean
, ASN1Integer
, DERTaggedObject
,
* ASN1ObjectIdentifier
. NOTE: this does not decode all OIDs. For supported
* OIDs please refer to the code. This is mostly a trial and error process as follows, 1) review logs
* for unsupported OIDs, 2) include code to support new OIDs, 3) explore more web sites, 4) goto step 1
* @param primitive
* @param buff
* @throws IOException
*/
static final void walkASN1Sequence( ASN1Primitive primitive, StringBuffer buff ) throws IOException {
if (primitive instanceof DEROctetString) {
byte[] bytes = ((DEROctetString) primitive).getOctets();
ASN1Primitive p = null;
try {
p = toDERObject(bytes);
walkASN1Sequence(p, buff);
} catch (IOException e ) {
buff.append( byteArrayToHex(bytes));
}
} else if( primitive instanceof DLSequence ) {
DLSequence dl = (DLSequence)primitive;
for (int i=0; i < dl.size() ; i++ ) {
ASN1Primitive p = dl.getObjectAt(i).toASN1Primitive();
walkASN1Sequence( p, buff );
}
} else if( primitive instanceof DERSequence ) {
DERSequence ds = (DERSequence)primitive;
for (int i=0; i < ds.size() ; i++ ) {
ASN1Primitive p = ds.getObjectAt(i).toASN1Primitive();
walkASN1Sequence( p, buff );
}
} else if( primitive instanceof DERApplicationSpecific ) {
//TODO: May be useful to parse differently depending upon tag type in future.
DERApplicationSpecific app = (DERApplicationSpecific)primitive;
int tag = app.getApplicationTag();
StringBuffer buff2 = new StringBuffer();
buff2.append( "tag="+tag+" ");
String hex = CipherSuiteUtil.byteArrayToHex(app.getContents());
buff2.append( hex );
buff.append(buff2.toString());
// Assistance by https://svn.cesecore.eu/svn/ejbca/branches/Branch_3_11/ejbca/conf/extendedkeyusage.properties
} else if (primitive instanceof ASN1ObjectIdentifier ) {
ASN1ObjectIdentifier i = (ASN1ObjectIdentifier)primitive;
String kn = CipherSuiteUtil.getOIDKeyName(i.toString());
if ( kn.equals("2.5.29.37.0") ) {
buff.append( "anyextendedkeyusage ");
} else if (kn.equals("1.3.6.1.5.5.7.3.1") ) {
buff.append( "serverauth ");
} else if (kn.equals("1.3.6.1.5.5.7.3.2") ) {
buff.append( "clientauth ");
} else if (kn.equals("1.3.6.1.5.5.7.3.3") ) {
buff.append( "codesigning ");
} else if (kn.equals("1.3.6.1.5.5.7.3.4") ) {
buff.append( "emailprotection ");
} else if (kn.equals("1.3.6.1.5.5.7.3.8") ) {
buff.append( "timestamping ");
} else if (kn.equals("1.3.6.1.5.5.7.3.9") ) {
buff.append( "ocspsigner ");
} else if (kn.equals("1.3.6.1.5.5.7.3.15") ) {
buff.append( "scvpserver ");
} else if (kn.equals("1.3.6.1.5.5.7.3.16") ) {
buff.append( "scvpclient ");
} else if (kn.equals("1.3.6.1.5.5.7.3.22") ) {
buff.append( "eku_pkix_sshserver ");
} else if (kn.equals("1.3.6.1.5.5.7.3.21") ) {
buff.append( "eku_pkix_sshclient ");
} else {
buff.append( CipherSuiteUtil.getOIDKeyName(i.toString()) );
buff.append( "=" );
}
} else if (primitive instanceof DERVisibleString ) {
DERVisibleString vstring = (DERVisibleString)primitive;
buff.append( vstring.getString() );
//buff.append( ' ' );
} else if (primitive instanceof DERIA5String ) {
DERIA5String ia5string = (DERIA5String)primitive;
buff.append( ia5string.getString() );
//buff.append( ' ' );
} else if (primitive instanceof DERUTF8String ) {
DERUTF8String utf8string = (DERUTF8String)primitive;
buff.append( utf8string.toString() );
//buff.append( ' ' );
} else if (primitive instanceof DERBitString ) {
DERBitString bitstring = (DERBitString)primitive;
int v = bitstring.intValue();
if( (v & 0x1) == 1 )
buff.append( "digitial_signature ");
if ((v & 0x2) == 2)
buff.append( "nonrepudiation ");
if ((v & 0x4) == 4)
buff.append( "keyencipherment ");
if ((v & 0x8) == 8)
buff.append( "dataencipherment ");
if ((v & 0x16) == 16)
buff.append( "keyagreement ");
if ((v & 0x32) == 32)
buff.append( "keycertsign ");
if ((v & 0x64) == 64)
buff.append( "crlsign ");
if ((v & 0x128) == 128)
buff.append( "encipheronly ");
if ((v & 0x256) == 256)
buff.append( "decipherOnly ");
} else if (primitive instanceof ASN1Boolean ) {
ASN1Boolean ans1boolean = (ASN1Boolean)primitive;
buff.append( ans1boolean.isTrue() ? "TRUE" : "FALSE" );
//buff.append( ' ' );
} else if (primitive instanceof ASN1Integer ) {
ASN1Integer ans1int = (ASN1Integer)primitive;
buff.append( ans1int.toString() );
//buff.append( ' ' );
} else if (primitive instanceof DERSet ) {
DERSet derset = (DERSet)primitive;
buff.append( derset.toString() );
//buff.append( ' ' );
// Assistance fm http://stackoverflow.com/questions/16058889/java-bouncy-castle-ocsp-url
} else if (primitive instanceof DERTaggedObject ) {
DERTaggedObject t = (DERTaggedObject)primitive;
byte[] b = t.getEncoded();
int length = b[1];
if( t.getTagNo() == 6 ) { // Several
buff.append( new String(b, 2, length) );
buff.append( " | ");
} else if( t.getTagNo() == 2 ) { // SubjectAlternativeName
buff.append( new String(b, 2, length) );
buff.append( " | ");
} else if( t.getTagNo() == 1 ) { // NameContraints
ASN1Primitive p = t.getObject();
walkASN1Sequence( p, buff );
} else if( t.getTagNo() == 0 ) { // CRLDistributionPoints
ASN1Primitive p = t.getObject();
walkASN1Sequence( p, buff );
} else if( t.getTagNo() == 4 ) { // AuthorityKeyIdentifier
ASN1Primitive p = t.getObject();
walkASN1Sequence( p, buff );
} else {
StringBuffer buff2 = new StringBuffer();
buff2.append( "type="+t.getTagNo()+" ");
String hex = CipherSuiteUtil.byteArrayToHex(b);
buff2.append( hex );
buff2.append( " | ");
buff.append(buff2.toString());
logger.info("Unhandled DERTaggedObject type. RAW="+buff2.toString() );
}
} else {
buff.append( "Unhandled type, see log" );
buff.append( " | ");
logger.error("Unhandled primitive data type, type="+primitive.getClass().getName() );
}
}
/**
* Return the value of the OID associated with the X509Certificate. For more information,
* How do I decode a DER encoded string in Java?
* @param X509Certificate Certificate to test.
* @param oid OID to retrieve.
* @return String String value for the specified OID parameter.
* @throws IOException
*/
static final String getExtensionValue(X509Certificate X509Certificate, String oid) throws IOException {
StringBuffer buff = new StringBuffer();
buff.append('[');
byte[] extensionValue = X509Certificate.getExtensionValue(oid);
if (extensionValue == null) return null;
walkASN1Sequence( toDERObject(extensionValue), buff);
if( buff.toString().endsWith(" | ")) {
buff.setLength(buff.length()-3);
} else if( buff.toString().endsWith("| ")) {
buff.setLength(buff.length()-2);
} else if( buff.toString().endsWith(" ")) {
buff.setLength(buff.length()-1);
}
buff.append(']');
return buff.toString();
}
//************************************************************
//************************************************************
//************************************************************
//************************************************************
//************************************************************
/*
* Connect to the server, send a ClientHello, and decode the
* response (ServerHello). On error, null is returned.
*/
static CipherSuiteUtilServerHello connect(InetSocketAddress isa,
int version, Collection cipherSuites)
{
Socket s = null;
try {
s = new Socket();
try {
s.connect(isa);
} catch (IOException ioe) {
logger.error("could not connect to "
+ isa + ": " + ioe.toString());
return null;
}
byte[] ch = makeClientHello(version, cipherSuites);
OutputRecord orec = new OutputRecord(
s.getOutputStream());
orec.setType(HANDSHAKE);
orec.setVersion(version);
orec.write(ch);
orec.flush();
return new CipherSuiteUtilServerHello(s.getInputStream());
} catch (IOException ioe) {
// ignored
} finally {
try {
s.close();
} catch (IOException ioe) {
// ignored
}
}
return null;
}
/*
* Connect to the server, send a SSLv2 CLIENT HELLO, and decode
* the response (SERVER HELLO). On error, null is returned.
*/
static ServerHelloSSLv2 connectV2(InetSocketAddress isa)
{
Socket s = null;
try {
s = new Socket();
try {
s.connect(isa);
} catch (IOException ioe) {
logger.error("could not connect to "
+ isa + ": " + ioe.toString());
return null;
}
s.getOutputStream().write(SSL2_CLIENT_HELLO);
return new ServerHelloSSLv2(s.getInputStream());
} catch (IOException ioe) {
// ignored
} finally {
try {
s.close();
} catch (IOException ioe) {
// ignored
}
}
return null;
}
static void readFully(InputStream in, byte[] buf)
throws IOException
{
readFully(in, buf, 0, buf.length);
}
static void readFully(InputStream in, byte[] buf, int off, int len)
throws IOException
{
while (len > 0) {
int rlen = in.read(buf, off, len);
if (rlen < 0) {
throw new EOFException();
}
off += rlen;
len -= rlen;
}
}
/*
* A custom stream which encodes data bytes into SSL/TLS records
* (no encryption).
*/
static class OutputRecord extends OutputStream {
private OutputStream out;
private byte[] buffer = new byte[MAX_RECORD_LEN + 5];
private int ptr;
private int version;
private int type;
OutputRecord(OutputStream out)
{
this.out = out;
ptr = 5;
}
void setType(int type)
{
this.type = type;
}
void setVersion(int version)
{
this.version = version;
}
public void flush()
throws IOException
{
buffer[0] = (byte)type;
enc16be(version, buffer, 1);
enc16be(ptr - 5, buffer, 3);
out.write(buffer, 0, ptr);
out.flush();
ptr = 5;
}
public void write(int b)
throws IOException
{
buffer[ptr ++] = (byte)b;
if (ptr == buffer.length) {
flush();
}
}
public void write(byte[] buf, int off, int len)
throws IOException
{
while (len > 0) {
int clen = Math.min(buffer.length - ptr, len);
System.arraycopy(buf, off, buffer, ptr, clen);
ptr += clen;
off += clen;
len -= clen;
if (ptr == buffer.length) {
flush();
}
}
}
}
static final void enc16be(int val, byte[] buf, int off)
{
buf[off] = (byte)(val >>> 8);
buf[off + 1] = (byte)val;
}
static final void enc24be(int val, byte[] buf, int off)
{
buf[off] = (byte)(val >>> 16);
buf[off + 1] = (byte)(val >>> 8);
buf[off + 2] = (byte)val;
}
static final void enc32be(int val, byte[] buf, int off)
{
buf[off] = (byte)(val >>> 24);
buf[off + 1] = (byte)(val >>> 16);
buf[off + 2] = (byte)(val >>> 8);
buf[off + 3] = (byte)val;
}
static final int dec16be(byte[] buf, int off)
{
return ((buf[off] & 0xFF) << 8)
| (buf[off + 1] & 0xFF);
}
static final int dec24be(byte[] buf, int off)
{
return ((buf[off] & 0xFF) << 16)
| ((buf[off + 1] & 0xFF) << 8)
| (buf[off + 2] & 0xFF);
}
static final int dec32be(byte[] buf, int off)
{
return ((buf[off] & 0xFF) << 24)
| ((buf[off + 1] & 0xFF) << 16)
| ((buf[off + 2] & 0xFF) << 8)
| (buf[off + 3] & 0xFF);
}
/*
* Compute the SHA-1 hash of some bytes, returning the hash
* value in hexadecimal.
*/
static String doSHA1(byte[] buf)
{
return doSHA1(buf, 0, buf.length);
}
static String doSHA1(byte[] buf, int off, int len)
{
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(buf, off, len);
byte[] hv = md.digest();
Formatter f = new Formatter();
for (byte b : hv) {
f.format("%02x", b & 0xFF);
}
return f.toString();
} catch (NoSuchAlgorithmException nsae) {
throw new Error(nsae);
}
}
/*
* Build a ClientHello message, with the specified maximum
* supported version, and list of cipher suites.
*/
static byte[] makeClientHello(int version,
Collection cipherSuites)
{
try {
return makeClientHello0(version, cipherSuites);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
static byte[] makeClientHello0(int version,
Collection cipherSuites)
throws IOException
{
ByteArrayOutputStream b = new ByteArrayOutputStream();
/*
* Message header:
* message type: one byte (1 = "ClientHello")
* message length: three bytes (this will be adjusted
* at the end of this method).
*/
b.write(1);
b.write(0);
b.write(0);
b.write(0);
/*
* The maximum version that we intend to support.
*/
b.write(version >>> 8);
b.write(version);
/*
* The client random has length 32 bytes, but begins with
* the client's notion of the current time, over 32 bits
* (seconds since 1970/01/01 00:00:00 UTC, not counting
* leap seconds).
*/
byte[] rand = new byte[32];
RNG.nextBytes(rand);
enc32be((int)(System.currentTimeMillis() / 1000), rand, 0);
b.write(rand);
/*
* We send an empty session ID.
*/
b.write(0);
/*
* The list of cipher suites (list of 16-bit values; the
* list length in bytes is written first).
*/
int num = cipherSuites.size();
byte[] cs = new byte[2 + num * 2];
enc16be(num * 2, cs, 0);
int j = 2;
for (int s : cipherSuites) {
enc16be(s, cs, j);
j += 2;
}
b.write(cs);
/*
* Compression methods: we claim to support Deflate (1)
* and the standard no-compression (0), with Deflate
* being preferred.
*/
b.write(2);
b.write(1);
b.write(0);
/*
* If we had extensions to add, they would go here.
*/
/*
* We now get the message as a blob. The message length
* must be adjusted in the header.
*/
byte[] msg = b.toByteArray();
enc24be(msg.length - 4, msg, 1);
return msg;
}
static final String strengthString(int strength)
{
switch (strength) {
case UNASSIGNED: return "unassigned evaluation";
case CLEAR: return "no encryption";
case WEAK: return "weak encryption (40-bit)";
case MEDIUM: return "medium encryption (56-bit)";
case STRONG: return "strong encryption (96-bit or more)";
default:
throw new Error("Unknown strength evalution: " + strength);
}
}
static final String cipherSuiteString(int suite, IDVSession.CIPHER_NAME_CONVENTION cipher_name_convention)
{
CipherSuite cs = CIPHER_SUITES.get(suite);
String ciphername = String.format("UNKNOWN_SUITE:%04X", suite);
//Need to map Enum name to String name of the ciphersuite since that's way it's stored in JSON.
if( cs != null ) {
ciphername = (String)cs.names.get(cipher_name_convention.toString());
}
return ciphername;
}
static final String cipherSuiteStringV2(int suite, IDVSession.CIPHER_NAME_CONVENTION cipher_name_convention)
{
CipherSuite cs = CIPHER_SUITES.get(suite);
String ciphername = String.format("UNKNOWN_SUITE:%02X,%02X,%02X",
suite >> 16, (suite >> 8) & 0xFF, suite & 0XFF);
//Need to map Enum name to String name of the ciphersuite since that's way it's stored in JSON.
if (cs != null) {
ciphername = (String)cs.names.get(cipher_name_convention.toString());
}
return ciphername;
}
//*****************************************************************
//*****************************************************************
//todo need to fix this asap
//*****************************************************************
//*****************************************************************
private static final void makeCS(int suite, Map names, int strength)
{
CipherSuite cs = new CipherSuite();
cs.suite = suite;
cs.names = names;
// cs.isCBC = isCBC;
cs.strength = strength;
CIPHER_SUITES.put(suite, cs);
// /*
// * Consistency test: the strength and CBC status can normally
// * be inferred from the name itself.
// */
// boolean inferredCBC = name.contains("_CBC_");
// int inferredStrength;
// if (name.contains("_NULL_")) {
// inferredStrength = CLEAR;
// } else if (name.contains("DES40") || name.contains("_40_")
// || name.contains("EXPORT40"))
// {
// inferredStrength = WEAK;
// } else if ((name.contains("_DES_") || name.contains("DES_64"))
// && !name.contains("DES_192"))
// {
// inferredStrength = MEDIUM;
// } else {
// inferredStrength = STRONG;
// }
// if (inferredStrength != strength || inferredCBC != isCBC) {
// throw new RuntimeException(
// "wrong classification: " + name);
// }
}
// private static final void N(int suite, String name)
// {
// makeCS(suite, name, false, CLEAR);
// }
//
// private static final void S4(int suite, String name)
// {
// makeCS(suite, name, false, WEAK);
// }
//
// private static final void S8(int suite, String name)
// {
// makeCS(suite, name, false, STRONG);
// }
//
// private static final void B4(int suite, String name)
// {
// makeCS(suite, name, true, WEAK);
// }
//
// private static final void B5(int suite, String name)
// {
// makeCS(suite, name, true, MEDIUM);
// }
//
// private static final void B8(int suite, String name)
// {
// makeCS(suite, name, true, STRONG);
// }
static boolean testBEAST(InetSocketAddress isa,
int version, Set supp)
{
/*
* TLS 1.1+ is not vulnerable to BEAST.
* We do not test SSLv2 either.
*/
if (version < 0x0300 || version > 0x0301) {
return false;
}
/*
* BEAST attack works if the server allows the client to
* use a CBC cipher. Existing clients also supports RC4,
* so we consider that a server protects the clients if
* it chooses RC4 over CBC streams when given the choice.
* We only consider strong cipher suites here.
*/
List strongCBC = new ArrayList();
List strongStream = new ArrayList();
for (int suite : supp) {
CipherSuite cs = CIPHER_SUITES.get(suite);
if (cs == null) {
continue;
}
if (cs.strength < STRONG) {
continue;
}
// if (cs.isCBC) {
// strongCBC.add(suite);
// } else {
// strongStream.add(suite);
// }
}
if (strongCBC.size() == 0) {
return false;
}
if (strongStream.size() == 0) {
return true;
}
List ns = new ArrayList(strongCBC);
ns.addAll(strongStream);
CipherSuiteUtilServerHello sh = connect(isa, version, ns);
return !strongStream.contains(sh.cipherSuite);
}
}