/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.vcentercontroller; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.security.MessageDigest; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.HttpParams; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.cloud.http.ssl.SSLHelper; /** * Created with IntelliJ IDEA. * User: alaplante * Date: 9/23/14 * Time: 2:43 PM * To change this template use File | Settings | File Templates. */ public class VcenterHostCertificateGetter { private static VcenterHostCertificateGetter instance = new VcenterHostCertificateGetter(); private final static Logger _log = LoggerFactory.getLogger(VcenterHostCertificateGetter.class); public synchronized static VcenterHostCertificateGetter getInstance() { return instance; } public enum HostConnectionStatus { REACHABLE, UNREACHABLE, UNAUTHORIZED, UNKNOWN } private HttpResponse executeRequest(String host, Integer port, String certificatePath, String username, String password, Integer timeout) throws Exception { if (host == null || host.equals("")) { throw new IllegalArgumentException("Invalid host " + host); } if (port == null) { throw new IllegalArgumentException("Invalid port " + port); } if (username == null) { throw new IllegalArgumentException("Invalid username " + username); } if (password == null) { throw new IllegalArgumentException("Invalid password " + password); } if (timeout == null) { throw new IllegalArgumentException("Invalid timeout " + timeout); } _log.info("Get SSL thumbprint host " + host + ", port " + port + ", certificatePath " + certificatePath + ", timeout " + timeout); HttpResponse response; DefaultHttpClient httpclient = null; HttpParams params = new BasicHttpParams(); if (timeout > 0) { _log.info("Socket timeout set to " + timeout + " seconds"); params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); params.setParameter(CoreConnectionPNames.SO_TIMEOUT, timeout); } try { // basic authentication credentials _log.info("Configure HTTP client for the basic authentication challenge"); httpclient = new DefaultHttpClient(params); SSLHelper.configurePermissiveSSL(httpclient); httpclient.getCredentialsProvider().setCredentials( new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials(username, password)); // request to certificate HttpGet httpget = new HttpGet("https://" + host + ":" + port + certificatePath); // execute request _log.info("Executing request " + httpget.getRequestLine()); response = httpclient.execute(httpget); _log.info("HTTP request executed successfully " + response); } finally { // release connection try { if (httpclient != null) { if (httpclient.getConnectionManager() != null) { httpclient.getConnectionManager().shutdown(); } } } catch (Exception ex) { _log.info("Ignore httpclient.getConnectionManager().shutdown() exception ", ex); } } if (response == null) { _log.error("HTTP response null"); throw new Exception("HTTP response null"); } return response; } HostConnectionStatus getConnectionStatus(String host, Integer port, String certificatePath, String username, String password, Integer timeout) { HttpResponse response = null; // First, make sure can execute the request successfully try { response = executeRequest(host, port, certificatePath, username, password, timeout); } catch (Exception ex) { if (ex instanceof java.net.SocketTimeoutException) { // This exception is usually the case when we're waiting for an ESXi to boot for the first time. // It makes a mess in the logs if we print the stack and ERROR for each attempt we make. // Assumption is if this code is used for other cases, errors further up the chain will indicate // a bigger problem (like an ESXi host being down) _log.warn("Error trying to connect to " + host + ":" + port + ". Connection timed out"); } else { _log.error("Error in executeRequest ", ex); } if (ex instanceof java.net.UnknownHostException) { _log.info("Host " + host + " is unknown"); return HostConnectionStatus.UNKNOWN; } else { _log.info("Return unreachable due to exception"); return HostConnectionStatus.UNREACHABLE; } } // Second, ensure that the response is valid if (response == null || response.getStatusLine() == null) { _log.error("Null response from host " + host); return HostConnectionStatus.UNREACHABLE; } // Third, inspect the response String responseStatus = response.getStatusLine().toString().toLowerCase(); _log.info("Host " + host + " response is " + responseStatus); if (responseStatus.contains("401 unauthorized")) { _log.info("Unauthorized response"); return HostConnectionStatus.UNAUTHORIZED; } else if (responseStatus.contains("200 ok")) { _log.info("Reachable response"); return HostConnectionStatus.REACHABLE; } else { _log.info("Unreachable response"); return HostConnectionStatus.UNREACHABLE; } } String getSSLThumbprint(String host, Integer port, String certificatePath, String username, String password, Integer timeout) throws Exception { HttpResponse response = null; try { response = executeRequest(host, port, certificatePath, username, password, timeout); } catch (Exception ex) { _log.error("Unexepected error executing request https://{}", host + ":" + port + certificatePath, ex); throw new Exception("Unexepected error executing request https://" + host + ":" + port + certificatePath); } String cert = null; try { // extract base64 encoded certificate from response _log.info("Extract base64 encoded certification from response"); _log.info("Response " + response.getStatusLine()); if (response.getEntity() != null) { cert = convertStreamToString(response.getEntity().getContent()); _log.info("Response content length: " + response.getEntity().getContentLength()); _log.info("Content: " + cert); // strip pem certificate of begin and end strings cert = cert.replaceAll("-----BEGIN CERTIFICATE-----", "").replaceAll("-----END CERTIFICATE-----", ""); _log.info("Certificate extracted " + cert); } } catch (Exception ex) { _log.error("Unexepected error extracting content from response {} ", response.getEntity().getContent(), ex); throw new Exception("Unexepected error extracting content from response " + response.getEntity().getContent()); } if (cert == null || cert.equals("")) { _log.error("Certificate base 64 encoded string " + cert); throw new Exception("Certificate base 64 encoded string " + cert); } /** * THEN SHA-1 HASH THE CERT */ ByteArrayInputStream byteArrayInputStream = null; try { // decode _log.info("Decode base64 encoded certificate string"); Base64 base64 = new Base64(); byteArrayInputStream = new ByteArrayInputStream(base64.decode(cert)); _log.info("Base64 decoded"); // DO NOT PRINT THE byte[] since it breaks the next step - Could not parse certificate: // java.io.IOException: DerInputStream.getLength(): lengthTag=127, too big. } catch (Exception ex) { _log.error("Unexepected error decode base64 encoded certificate ", ex); throw new Exception("Unexepected error decode base64 encoded certificate"); } if (byteArrayInputStream == null) { _log.error("Decoded base 64 byte[] null"); throw new Exception("Decoded base 64 byte[] null"); } X509Certificate certificate = null; try { // convert to x509 cert _log.info("Convert decoded base64 byte[] to X509 certificate"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); certificate = (X509Certificate) cf.generateCertificate(byteArrayInputStream); _log.info("X509 certificate created " + certificate); } catch (Exception ex) { _log.error("Unexepected error creating x509 certificate ", ex); throw new Exception("Unexepected error creating x509 certificate"); } if (certificate == null) { _log.error("X509 Certificate null"); throw new Exception("X509 Certificate null"); } byte[] digest; try { // hash to SHA1 _log.info("Hash X509 certificate to SHA-1"); MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] der = certificate.getEncoded(); md.update(der); digest = md.digest(); _log.info("SHA-1 hash created " + digest); } catch (Exception ex) { _log.error("Unexepected error SHA-1 hashing certificate ", ex); throw new Exception("Unexepected error SHA-1 hashing certificate"); } if (digest == null) { _log.error("SHA-1 hash null"); throw new Exception("SHA-1 hash null"); } String thumbprint; try { // convert to hex _log.info("Convert hash to hex"); // BigInteger bigInt = new BigInteger(1,digest) // thumbprint = bigInt.toString(16) thumbprint = getHex(digest); _log.info("Hex created " + thumbprint); } catch (Exception ex) { _log.error("Unexepected error converting SSL thumbprint to hex ", ex); throw new Exception("Unexepected error converting SSL thumbprint to hex"); } if (thumbprint == null || thumbprint.equals("")) { _log.error("thumbprint " + thumbprint); throw new Exception("thumbprint " + thumbprint); } String hash; try { // splice in colons and bring to upper _log.info("Splice colons to hash and bring to upper case"); thumbprint = thumbprint.toUpperCase(); StringBuffer buf = new StringBuffer(thumbprint); int index = 2; while (index < buf.toString().length()) { buf.insert(index, ':'); index += 3; // 2 (for chars) + 1 (for spliced colon) } hash = buf.toString(); _log.info("Hash string formatted " + hash); } catch (Exception ex) { _log.error("Unexepected error splicing colons into SSL thumbprint ", ex); throw new Exception("Unexepected error splicing colons into SSL thumbprint"); } if (hash == null || hash.equals("")) { _log.error("SSL thumbprint is empty"); throw new Exception("SSL thumbprint is empty"); } if (hash.length() != 59) { _log.error("SSL thumbprint does not appear to be a valid"); throw new Exception("SSL thumbprint does not appear to be a valid"); } return hash; } public String convertStreamToString(InputStream is) throws IOException { if (is != null) { Writer writer = new StringWriter(); char[] buffer = new char[1024]; try { Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } } finally { is.close(); } return writer.toString(); } else { return ""; } } public static String getHex(byte[] raw) { if (raw == null) { return null; } final StringBuilder hex = new StringBuilder(2 * raw.length); for (final byte b : raw) { hex.append("0123456789ABCDEF".charAt((b & 0xF0) >> 4)) .append("0123456789ABCDEF".charAt((b & 0x0F))); } return hex.toString(); } }