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)