Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CTCloudHttpClient.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2023 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package com.basistech.df.cybertriage.autopsy.ctapi;
20 
22 import com.fasterxml.jackson.databind.ObjectMapper;
23 import java.io.IOException;
24 import java.net.Authenticator;
25 import java.net.InetAddress;
26 import java.net.PasswordAuthentication;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.net.UnknownHostException;
30 import java.security.KeyManagementException;
31 import java.security.KeyStoreException;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.SecureRandom;
34 import java.security.UnrecoverableKeyException;
35 import java.text.MessageFormat;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42 import java.util.Objects;
43 import java.util.logging.Level;
44 import javax.net.ssl.KeyManager;
45 import javax.net.ssl.KeyManagerFactory;
46 import javax.net.ssl.SSLContext;
47 import javax.net.ssl.TrustManager;
48 import javax.net.ssl.TrustManagerFactory;
49 import javax.net.ssl.X509TrustManager;
50 import org.apache.commons.collections.CollectionUtils;
51 import org.apache.commons.collections4.MapUtils;
52 import org.apache.commons.lang3.StringUtils;
53 import org.apache.http.HttpEntity;
54 import org.apache.http.HttpHost;
55 import org.apache.http.HttpStatus;
56 import org.apache.http.auth.AuthScope;
57 import org.apache.http.auth.NTCredentials;
58 import org.apache.http.client.CredentialsProvider;
59 import org.apache.http.client.config.AuthSchemes;
60 import org.apache.http.client.config.RequestConfig;
61 import org.apache.http.client.methods.CloseableHttpResponse;
62 import org.apache.http.client.methods.HttpPost;
63 import org.apache.http.client.methods.HttpRequestBase;
64 import org.apache.http.impl.client.CloseableHttpClient;
65 import org.apache.http.util.EntityUtils;
66 import org.apache.http.client.utils.URIBuilder;
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.SystemDefaultCredentialsProvider;
72 import org.apache.http.impl.client.WinHttpClients;
74 
78 public class CTCloudHttpClient {
79 
80  private static final CTCloudHttpClient instance = new CTCloudHttpClient();
81  private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName());
83 
84  private static final List<String> DEFAULT_SCHEME_PRIORITY
85  = new ArrayList<>(Arrays.asList(
86  AuthSchemes.SPNEGO,
87  AuthSchemes.KERBEROS,
88  AuthSchemes.NTLM,
89  AuthSchemes.CREDSSP,
90  AuthSchemes.DIGEST,
91  AuthSchemes.BASIC));
92 
93  private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; // milli sec
94 
95  public static CTCloudHttpClient getInstance() {
96  return instance;
97  }
98 
99  private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
100  private final SSLContext sslContext;
101  private String hostName = null;
102 
103  private CTCloudHttpClient() {
104  // leave as null for now unless we want to customize this at a later date
105  this.sslContext = null;
106  }
107 
109  if (StringUtils.isBlank(hostName)) {
110  try {
111  hostName = InetAddress.getLocalHost().getCanonicalHostName();
112  } catch (UnknownHostException ex) {
113  LOGGER.log(Level.WARNING, "An error occurred while fetching the hostname", ex);
114  }
115  }
116 
117  int proxyPort = 0;
118  if (StringUtils.isNotBlank(ProxySettings.getHttpPort())) {
119  try {
120  proxyPort = Integer.parseInt(ProxySettings.getHttpsPort());
121  } catch (NumberFormatException ex) {
122  LOGGER.log(Level.WARNING, "Unable to convert port to integer");
123  }
124  }
125 
126  return new ProxySettingArgs(
128  hostName,
130  proxyPort,
133  null
134  );
135  }
136 
137  public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) throws CTCloudException {
138  return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
139  }
140 
141  public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) throws CTCloudException {
142  String url = HOST_URL + urlPath;
143  try {
144 
145  LOGGER.log(Level.INFO, "initiating http connection to ctcloud server");
146  try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) {
147  URIBuilder builder = new URIBuilder(url);
148 
149  if (!MapUtils.isEmpty(urlReqParams)) {
150  for (Entry<String, String> e : urlReqParams.entrySet()) {
151  String key = e.getKey();
152  String value = e.getValue();
153  if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
154  builder.addParameter(key, value);
155  }
156  }
157  }
158 
159  URI postURI = builder.build();
160  HttpPost postRequest = new HttpPost(postURI);
161 
162 
163  configureRequestTimeout(postRequest);
164  postRequest.setHeader("Content-type", "application/json");
165 
166  if (jsonBody != null) {
167  String requestBody = mapper.writeValueAsString(jsonBody);
168  if (StringUtils.isNotBlank(requestBody)) {
169  HttpEntity entity = new StringEntity(requestBody, "UTF-8");
170  postRequest.setEntity(entity);
171  }
172  }
173 
174  LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + postRequest.getURI());
175  try (CloseableHttpResponse response = httpclient.execute(postRequest)) {
176 
177  if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
178  LOGGER.log(Level.INFO, "Response Received. - Status OK");
179  // Parse Response
180  HttpEntity entity = response.getEntity();
181  String entityStr = EntityUtils.toString(entity);
182  O respObj = mapper.readValue(entityStr, classType);
183  return respObj;
184  } else {
185  LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine());
186  handleNonOKResponse(response, "");
187  }
188  } catch (Exception ex) {
189  LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex);
190  throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex);
191  }
192  }
193  } catch (IOException ex) {
194  LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + url, ex);
195  throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
196  } catch (URISyntaxException ex) {
197  LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + url, ex);
198  throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
199  }
200 
201  return null;
202  }
203 
213  private void handleNonOKResponse(CloseableHttpResponse response, String fileName) throws CTCloudException, IOException {
214  LOGGER.log(Level.WARNING, MessageFormat.format(
215  "Response code {0}. Message Body {1}",
216  response.getStatusLine().getStatusCode(),
217  EntityUtils.toString(response.getEntity())));
218 
219  switch (response.getStatusLine().getStatusCode()) {
220 
221  case HttpStatus.SC_BAD_REQUEST:
222  //400: Bad request => Unsupported HTTP method or invalid http request (e.g., empty body).
223  throw new CTCloudException(CTCloudException.ErrorCode.BAD_REQUEST);
224  case HttpStatus.SC_UNAUTHORIZED:
225  //401 Invalid API key => An invalid API key, or no API key, has been provided
226  throw new CTCloudException(CTCloudException.ErrorCode.INVALID_KEY);
227  case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
228  // 407 Proxy server authentication required.
229  throw new CTCloudException(CTCloudException.ErrorCode.PROXY_UNAUTHORIZED);
230  case HttpStatus.SC_FORBIDDEN:
231  throw new CTCloudException(CTCloudException.ErrorCode.UN_AUTHORIZED);
232  case HttpStatus.SC_INTERNAL_SERVER_ERROR:
233  //500 Internal error Server temporarily unavailable; please try again later. If the issue persists, please contact RL.
234  throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE);
235  case HttpStatus.SC_SERVICE_UNAVAILABLE:
236  //503 Server is too busy. Try again later.
237  //503 Failed to request scan. Try again later. The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. If the issue persists, please contact RL.
238  throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE);
239  case HttpStatus.SC_GATEWAY_TIMEOUT:
240  throw new CTCloudException(CTCloudException.ErrorCode.GATEWAY_TIMEOUT);
241  default:
242  String returnData = EntityUtils.toString(response.getEntity());
243  LOGGER.log(Level.WARNING, MessageFormat.format("upload response content for {0}:\n {1}", fileName, returnData));
244  throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR);
245  }
246  }
247 
256  private void configureRequestTimeout(HttpRequestBase request) {
257  RequestConfig config = RequestConfig.custom()
258  .setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS)
259  .setConnectTimeout(CONNECTION_TIMEOUT_MS)
260  .setSocketTimeout(CONNECTION_TIMEOUT_MS)
261  .build();
262  request.setConfig(config);
263  }
264 
271  private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) {
272  HttpClientBuilder builder = getHttpClientBuilder(proxySettings);
273 
274  if (sslContext != null) {
275  builder.setSSLContext(sslContext);
276  }
277  return builder.build();
278  }
279 
280  private static HttpClientBuilder getHttpClientBuilder(ProxySettingArgs proxySettings) {
281 
282  if (proxySettings.isSystemOrManualProxy()) {
283 
284  Authenticator.setDefault(new Authenticator() {
285  @Override
286  protected PasswordAuthentication getPasswordAuthentication() {
287  LOGGER.info("Requesting Password Authentication...");
288  return super.getPasswordAuthentication();
289  }
290  });
291 
292  HttpClientBuilder builder = null;
293  HttpHost proxyHost = null;
294  CredentialsProvider proxyCredsProvider = null;
295  RequestConfig config = null;
296 
297  if (Objects.nonNull(proxySettings.getProxyHostname()) && proxySettings.getProxyPort() > 0) {
298  proxyHost = new HttpHost(proxySettings.getProxyHostname(), proxySettings.getProxyPort());
299 
300  proxyCredsProvider = getProxyCredentialsProvider(proxySettings);
301  if (StringUtils.isNotBlank(proxySettings.getAuthScheme())) {
302  if (!DEFAULT_SCHEME_PRIORITY.get(0).equalsIgnoreCase(proxySettings.getAuthScheme())) {
303  DEFAULT_SCHEME_PRIORITY.removeIf(s -> s.equalsIgnoreCase(proxySettings.getAuthScheme()));
304  DEFAULT_SCHEME_PRIORITY.add(0, proxySettings.getAuthScheme());
305  }
306  }
307  config = RequestConfig.custom().setProxyPreferredAuthSchemes(DEFAULT_SCHEME_PRIORITY).build();
308  }
309 
310  if (Objects.isNull(proxyCredsProvider) && WinHttpClients.isWinAuthAvailable()) {
311  builder = WinHttpClients.custom();
312  builder.useSystemProperties();
313  LOGGER.log(Level.WARNING, "Using Win HTTP Client");
314  } else {
315  builder = HttpClients.custom();
316  builder.setDefaultRequestConfig(config);
317  if (Objects.nonNull(proxyCredsProvider)) { // make sure non null proxycreds before setting it
318  builder.setDefaultCredentialsProvider(proxyCredsProvider);
319  }
320  LOGGER.log(Level.WARNING, "Using default http client");
321  }
322  if (Objects.nonNull(proxyHost)) {
323  builder.setProxy(proxyHost);
324  LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost));
325  }
326 
327  return builder;
328  } else {
329  return HttpClients.custom();
330  }
331  }
332 
339  private static CredentialsProvider getProxyCredentialsProvider(ProxySettingArgs proxySettings) {
340  CredentialsProvider proxyCredsProvider = null;
341  if (proxySettings.isSystemOrManualProxy()) {
342  if (StringUtils.isNotBlank(proxySettings.getProxyUserId())) {
343  if (null != proxySettings.getProxyPassword() && proxySettings.getProxyPassword().length > 0) { // Password will be blank for KERBEROS / NEGOTIATE schemes.
344  proxyCredsProvider = new SystemDefaultCredentialsProvider();
345  String userId = proxySettings.getProxyUserId();
346  String domain = null;
347  if (userId.contains("\\")) {
348  domain = userId.split("\\\\")[0];
349  userId = userId.split("\\\\")[1];
350  }
351  String workStation = proxySettings.getHostName();
352  proxyCredsProvider.setCredentials(new AuthScope(proxySettings.getProxyHostname(), proxySettings.getProxyPort()),
353  new NTCredentials(userId, new String(proxySettings.getProxyPassword()), workStation, domain));
354  }
355  }
356  }
357 
358  return proxyCredsProvider;
359  }
360 
361  private static class ProxySettingArgs {
362 
363  private final boolean systemOrManualProxy;
364  private final String hostName;
365  private final String proxyHostname;
366  private final int proxyPort;
367  private final String proxyUserId;
368  private final char[] proxyPassword;
369  private final String authScheme;
370 
371  ProxySettingArgs(boolean systemOrManualProxy, String hostName, String proxyHostname, int proxyPort, String proxyUserId, char[] proxyPassword, String authScheme) {
372  this.systemOrManualProxy = systemOrManualProxy;
373  this.hostName = hostName;
374  this.proxyHostname = proxyHostname;
375  this.proxyPort = proxyPort;
376  this.proxyUserId = proxyUserId;
377  this.proxyPassword = proxyPassword;
378  this.authScheme = authScheme;
379  }
380 
381  boolean isSystemOrManualProxy() {
382  return systemOrManualProxy;
383  }
384 
385  String getHostName() {
386  return hostName;
387  }
388 
389  String getProxyHostname() {
390  return proxyHostname;
391  }
392 
393  int getProxyPort() {
394  return proxyPort;
395  }
396 
397  String getProxyUserId() {
398  return proxyUserId;
399  }
400 
401  char[] getProxyPassword() {
402  return proxyPassword;
403  }
404 
405  public String getAuthScheme() {
406  return authScheme;
407  }
408  }
409 }
static Version.Type getBuildType()
Definition: Version.java:87
static CredentialsProvider getProxyCredentialsProvider(ProxySettingArgs proxySettings)
static HttpClientBuilder getHttpClientBuilder(ProxySettingArgs proxySettings)
static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext)
void handleNonOKResponse(CloseableHttpResponse response, String fileName)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.