19 package com.basistech.df.cybertriage.autopsy.ctapi;
 
   24 import com.fasterxml.jackson.databind.ObjectMapper;
 
   25 import java.io.IOException;
 
   26 import java.io.InputStream;
 
   27 import java.net.Proxy;
 
   28 import java.net.ProxySelector;
 
   29 import java.net.SocketAddress;
 
   31 import java.net.URISyntaxException;
 
   32 import java.security.KeyManagementException;
 
   33 import java.security.KeyStore;
 
   34 import java.security.KeyStoreException;
 
   35 import java.security.NoSuchAlgorithmException;
 
   36 import java.security.SecureRandom;
 
   37 import java.security.UnrecoverableKeyException;
 
   38 import java.text.MessageFormat;
 
   39 import java.util.Collection;
 
   40 import java.util.Collections;
 
   41 import java.util.List;
 
   43 import java.util.Map.Entry;
 
   44 import java.util.logging.Level;
 
   45 import java.util.stream.Stream;
 
   46 import javax.net.ssl.KeyManager;
 
   47 import javax.net.ssl.KeyManagerFactory;
 
   48 import javax.net.ssl.SSLContext;
 
   49 import javax.net.ssl.TrustManager;
 
   50 import javax.net.ssl.TrustManagerFactory;
 
   51 import javax.net.ssl.X509TrustManager;
 
   52 import org.apache.commons.collections4.MapUtils;
 
   53 import org.apache.commons.lang.ArrayUtils;
 
   54 import org.apache.commons.lang3.StringUtils;
 
   55 import org.apache.http.HttpEntity;
 
   56 import org.apache.http.HttpStatus;
 
   57 import org.apache.http.client.config.RequestConfig;
 
   58 import org.apache.http.client.methods.CloseableHttpResponse;
 
   59 import org.apache.http.client.methods.HttpPost;
 
   60 import org.apache.http.client.methods.HttpPut;
 
   61 import org.apache.http.client.methods.HttpRequestBase;
 
   62 import org.apache.http.impl.client.CloseableHttpClient;
 
   63 import org.apache.http.util.EntityUtils;
 
   64 import org.apache.http.client.utils.URIBuilder;
 
   65 import org.apache.http.entity.ContentType;
 
   66 import org.apache.http.entity.InputStreamEntity;
 
   67 import org.apache.http.entity.StringEntity;
 
   69 import org.apache.http.impl.client.HttpClientBuilder;
 
   70 import org.apache.http.impl.client.HttpClients;
 
   71 import org.apache.http.impl.client.WinHttpClients;
 
   72 import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
 
   73 import org.apache.http.ssl.SSLInitializationException;
 
   74 import org.netbeans.core.ProxySettings;
 
   75 import org.openide.util.Lookup;
 
   87 class CTCloudHttpClient {
 
   89     private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName());
 
   90     private static final String HOST_URL = Version.
getBuildType() == Version.
Type.
RELEASE ? Constants.CT_CLOUD_SERVER : Constants.CT_CLOUD_DEV_SERVER;
 
   91     private static final String NB_PROXY_SELECTOR_NAME = 
"org.netbeans.core.NbProxySelector";
 
   93     private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; 
 
   95     private static final CTCloudHttpClient instance = 
new CTCloudHttpClient();
 
   97     public static CTCloudHttpClient getInstance() {
 
  101     private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
 
  102     private final SSLContext sslContext;
 
  103     private final ProxySelector proxySelector;
 
  105     private CTCloudHttpClient() {
 
  107         this.sslContext = createSSLContext();
 
  108         this.proxySelector = getProxySelector();
 
  111     private static URI getUri(String host, String path, Map<String, String> urlReqParams) 
throws URISyntaxException {
 
  112         String url = host + path;
 
  113         URIBuilder builder = 
new URIBuilder(url);
 
  115         if (!MapUtils.isEmpty(urlReqParams)) {
 
  116             for (Entry<String, String> e : urlReqParams.entrySet()) {
 
  117                 String key = e.getKey();
 
  118                 String value = e.getValue();
 
  119                 if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
 
  120                     builder.addParameter(key, value);
 
  125         return builder.build();
 
  128     public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) 
throws CTCloudException {
 
  129         return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
 
  132     public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) 
throws CTCloudException {
 
  136             postURI = getUri(HOST_URL, urlPath, urlReqParams);
 
  137             LOGGER.log(Level.INFO, 
"initiating http connection to ctcloud server");
 
  138             try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
 
  140                 HttpPost postRequest = 
new HttpPost(postURI);
 
  142                 configureRequestTimeout(postRequest);
 
  143                 postRequest.setHeader(
"Content-type", 
"application/json");
 
  145                 if (jsonBody != null) {
 
  146                     String requestBody = mapper.writeValueAsString(jsonBody);
 
  147                     if (StringUtils.isNotBlank(requestBody)) {
 
  148                         HttpEntity entity = 
new StringEntity(requestBody, 
"UTF-8");
 
  149                         postRequest.setEntity(entity);
 
  153                 LOGGER.log(Level.INFO, 
"initiating http post request to ctcloud server " + postRequest.getURI());
 
  154                 try (CloseableHttpResponse response = httpclient.execute(postRequest)) {
 
  156                     if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
 
  157                         LOGGER.log(Level.INFO, 
"Response Received. - Status OK");
 
  159                         if (classType != null) {
 
  160                             HttpEntity entity = response.getEntity();
 
  161                             if (entity != null) {
 
  162                                 String entityStr = EntityUtils.toString(entity);
 
  163                                 if (StringUtils.isNotBlank(entityStr)) {
 
  164                                     O respObj = mapper.readValue(entityStr, classType);
 
  172                         LOGGER.log(Level.WARNING, 
"Response Received. - Status Error {}", response.getStatusLine());
 
  173                         handleNonOKResponse(response, 
"");
 
  176                 } 
catch (CTCloudException ex) {
 
  178                 } 
catch (Exception ex) {
 
  179                     LOGGER.log(Level.WARNING, 
"Error when parsing response from CyberTriage Cloud", ex);
 
  180                     throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex);
 
  183         } 
catch (IOException ex) {
 
  184             LOGGER.log(Level.WARNING, 
"IO Exception raised when connecting to  CT Cloud using " + postURI, ex);
 
  185             throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
 
  186         } 
catch (SSLInitializationException ex) {
 
  187             LOGGER.log(Level.WARNING, 
"No such algorithm exception raised when creating SSL connection for  CT Cloud using " + postURI, ex);
 
  188             throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
 
  189         } 
catch (URISyntaxException ex) {
 
  190             LOGGER.log(Level.WARNING, 
"Wrong URL syntax for CT Cloud " + postURI, ex);
 
  191             throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
 
  197     public void doFileUploadPut(FileUploadRequest fileUploadRequest) 
throws CTCloudException {
 
  198         if (fileUploadRequest == null) {
 
  199             throw new CTCloudException(ErrorCode.BAD_REQUEST, 
new IllegalArgumentException(
"fileUploadRequest cannot be null"));
 
  202         String fullUrlPath = fileUploadRequest.getFullUrlPath();
 
  203         String fileName = fileUploadRequest.getFileName();
 
  204         InputStream fileInputStream = fileUploadRequest.getFileInputStream();
 
  205         Long contentLength = fileUploadRequest.getContentLength();
 
  207         if (StringUtils.isBlank(fullUrlPath) || fileInputStream == null || contentLength == null || contentLength <= 0) {
 
  208             throw new CTCloudException(ErrorCode.BAD_REQUEST, 
new IllegalArgumentException(
"fullUrlPath, fileInputStream, contentLength must not be empty, null or less than 0"));
 
  213             putUri = 
new URI(fullUrlPath);
 
  214         } 
catch (URISyntaxException ex) {
 
  215             LOGGER.log(Level.WARNING, 
"Wrong URL syntax for CT Cloud " + fullUrlPath, ex);
 
  216             throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
 
  219         try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
 
  220             LOGGER.log(Level.INFO, 
"initiating http post request to ctcloud server " + fullUrlPath);
 
  221             HttpPut put = 
new HttpPut(putUri);
 
  222             configureRequestTimeout(put);
 
  224             put.addHeader(
"Connection", 
"keep-alive");
 
  225             put.setEntity(
new InputStreamEntity(fileInputStream, contentLength, ContentType.APPLICATION_OCTET_STREAM));
 
  227             try (CloseableHttpResponse response = httpclient.execute(put)) {
 
  228                 int statusCode = response.getStatusLine().getStatusCode();
 
  229                 if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
 
  230                     LOGGER.log(Level.INFO, 
"Response Received. - Status OK");
 
  232                     LOGGER.log(Level.WARNING, MessageFormat.format(
"Response Received. - Status Error {0}", response.getStatusLine()));
 
  233                     handleNonOKResponse(response, fileName);
 
  236         } 
catch (SSLInitializationException ex) {
 
  237             LOGGER.log(Level.WARNING, 
"SSL exception raised when connecting to Reversing Labs for file content upload ", ex);
 
  238             throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
 
  239         } 
catch (IOException ex) {
 
  240             LOGGER.log(Level.WARNING, 
"IO Exception raised when connecting to Reversing Labs for file content upload ", ex);
 
  241             throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
 
  254     private void handleNonOKResponse(CloseableHttpResponse response, String fileName) 
throws CTCloudException, IOException {
 
  255         LOGGER.log(Level.WARNING, MessageFormat.format(
 
  256                 "Response code {0}. Message Body {1}",
 
  257                 response.getStatusLine().getStatusCode(),
 
  258                 EntityUtils.toString(response.getEntity())));
 
  260         switch (response.getStatusLine().getStatusCode()) {
 
  262             case HttpStatus.SC_BAD_REQUEST:
 
  264                 throw new CTCloudException(CTCloudException.ErrorCode.BAD_REQUEST);
 
  265             case HttpStatus.SC_UNAUTHORIZED:
 
  267                 throw new CTCloudException(CTCloudException.ErrorCode.INVALID_KEY);
 
  268             case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
 
  270                 throw new CTCloudException(CTCloudException.ErrorCode.PROXY_UNAUTHORIZED);
 
  271             case HttpStatus.SC_FORBIDDEN:
 
  272                 throw new CTCloudException(CTCloudException.ErrorCode.UN_AUTHORIZED);
 
  273             case HttpStatus.SC_INTERNAL_SERVER_ERROR:
 
  275                 throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE);
 
  276             case HttpStatus.SC_SERVICE_UNAVAILABLE:
 
  279                 throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE);
 
  280             case HttpStatus.SC_GATEWAY_TIMEOUT:
 
  281                 throw new CTCloudException(CTCloudException.ErrorCode.GATEWAY_TIMEOUT);
 
  283                 String returnData = EntityUtils.toString(response.getEntity());
 
  284                 LOGGER.log(Level.WARNING, MessageFormat.format(
"upload response content for {0}:\n {1}", fileName, returnData));
 
  285                 throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR);
 
  297     private void configureRequestTimeout(HttpRequestBase request) {
 
  298         RequestConfig config = RequestConfig.custom()
 
  299                 .setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS)
 
  300                 .setConnectTimeout(CONNECTION_TIMEOUT_MS)
 
  301                 .setSocketTimeout(CONNECTION_TIMEOUT_MS)
 
  303         request.setConfig(config);
 
  311     private static ProxySelector getProxySelector() {
 
  312         Collection<? extends ProxySelector> selectors = Lookup.getDefault().lookupAll(ProxySelector.class);
 
  313         return (selectors != null ? selectors.stream() : Stream.empty())
 
  314                 .filter(s -> s != null)
 
  315                 .map(s -> (ProxySelector) s)
 
  317                     String aName = a.getClass().getCanonicalName();
 
  318                     String bName = b.getClass().getCanonicalName();
 
  319                     boolean aIsNb = aName.equalsIgnoreCase(NB_PROXY_SELECTOR_NAME);
 
  320                     boolean bIsNb = bName.equalsIgnoreCase(NB_PROXY_SELECTOR_NAME);
 
  321                     if (aIsNb == bIsNb) {
 
  322                         return StringUtils.compareIgnoreCase(aName, bName);
 
  324                         return aIsNb ? -1 : 1;
 
  329                 .map(s -> 
new LoggingProxySelector(s))
 
  338     private static SSLContext createSSLContext() {
 
  339         LOGGER.log(Level.INFO, 
"Creating custom SSL context");
 
  343             SSLContext sslContext = SSLContext.getInstance(
"TLSv1.2");
 
  344             KeyManager[] keyManagers = getKeyManagers();
 
  345             TrustManager[] trustManagers = getTrustManagers();
 
  346             sslContext.init(keyManagers, trustManagers, 
new SecureRandom());
 
  348         } 
catch (NoSuchAlgorithmException | KeyManagementException ex) {
 
  349             LOGGER.log(Level.SEVERE, 
"Error creating SSL context", ex);
 
  356     private static KeyManager[] getKeyManagers() {
 
  357         LOGGER.log(Level.INFO, 
"Using default algorithm to create trust store: " + KeyManagerFactory.getDefaultAlgorithm());
 
  359             KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
 
  360             kmf.init(null, null);
 
  361             return kmf.getKeyManagers();
 
  362         } 
catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException ex) {
 
  363             LOGGER.log(Level.SEVERE, 
"Error getting KeyManagers", ex);
 
  364             return new KeyManager[0];
 
  371     private static TrustManager[] getTrustManagers() {
 
  373             LOGGER.log(Level.INFO, 
"Using default algorithm to create trust store: " + TrustManagerFactory.getDefaultAlgorithm());
 
  374             TrustManagerFactory tmf
 
  375                     = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
 
  376             tmf.init((KeyStore) null);
 
  377             X509TrustManager tm = (X509TrustManager) tmf.getTrustManagers()[0];
 
  379             return new TrustManager[]{tm};
 
  380         } 
catch (KeyStoreException | NoSuchAlgorithmException ex) {
 
  381             LOGGER.log(Level.SEVERE, 
"Error getting TrustManager", ex);
 
  382             return new TrustManager[0];
 
  393     private static CloseableHttpClient createConnection(ProxySelector proxySelector, SSLContext sslContext) 
throws SSLInitializationException {
 
  394         HttpClientBuilder builder;
 
  396         if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
 
  397                 && StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
 
  398                 && ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
 
  399                 && WinHttpClients.isWinAuthAvailable()) {
 
  401             builder = WinHttpClients.custom();
 
  402             builder.useSystemProperties();
 
  403             LOGGER.log(Level.WARNING, 
"Using Win HTTP Client");
 
  405             builder = HttpClients.custom();
 
  407             LOGGER.log(Level.WARNING, 
"Using default http client");
 
  410         if (sslContext != null) {
 
  411             builder.setSSLContext(sslContext);
 
  414         if (proxySelector != null) {
 
  415             builder.setRoutePlanner(
new SystemDefaultRoutePlanner(proxySelector));
 
  418         return builder.build();
 
  431             List<Proxy> selectedProxies = delegate.select(uri);
 
  432             LOGGER.log(Level.INFO, MessageFormat.format(
"Proxy selected for {0} are {1}", uri, selectedProxies));
 
  433             return selectedProxies;
 
  438             LOGGER.log(Level.WARNING, MessageFormat.format(
"Connection failed connecting to {0} socket address {1}", uri, sa), ioe);
 
  439             delegate.connectFailed(uri, sa, ioe);
 
static Version.Type getBuildType()
List< Proxy > select(URI uri)
final ProxySelector delegate
void connectFailed(URI uri, SocketAddress sa, IOException ioe)
LoggingProxySelector(ProxySelector delegate)