All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mps.deepviolet.api.CipherSuiteUtil Maven / Gradle / Ivy

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);
		}

	
}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy