From 38ad881023f13b69b05c167dbdc6faafeef62d27 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 20 Jul 2023 16:07:59 -0400 Subject: [PATCH] cloud http client with proxy --- .../autopsy/ctapi/CTCloudHttpClient.java | 378 ++++++++++++++++++ .../cybertriage/autopsy/ctapi/CtApiDAO.java | 53 ++- .../autopsy/ctapi/json/AuthTokenResponse.java | 12 +- .../ctapi/json/AuthenticatedRequestData.java | 36 ++ .../ctapi/json/FileReputationRequest.java | 58 +++ .../ctapi/json/FileReputationResponse.java | 38 ++ .../ctapi/util/CTHostIDGenerationUtil.java | 25 +- .../ctcloud/CTMalwareScannerOptionsPanel.java | 4 +- .../malwarescan/MalwareScanIngestModule.java | 6 +- 9 files changed, 565 insertions(+), 45 deletions(-) create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResponse.java diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java new file mode 100644 index 0000000000..8d04c4c6d6 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java @@ -0,0 +1,378 @@ +/** ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2023 Basis Technology Corp. All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ************************************************************************** */ +package com.basistech.df.cybertriage.autopsy.ctapi; + +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.Authenticator; +import java.net.InetAddress; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.security.NoSuchAlgorithmException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import javax.net.ssl.SSLContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.NTCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.SystemDefaultCredentialsProvider; +import org.apache.http.impl.client.WinHttpClients; + +/** + * Actually makes the http requests to CT cloud. + */ +public class CTCloudHttpClient { + + private static final CTCloudHttpClient instance = new CTCloudHttpClient(); + private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName()); + + private static final List DEFAULT_SCHEME_PRIORITY + = new ArrayList<>(Arrays.asList( + AuthSchemes.SPNEGO, + AuthSchemes.KERBEROS, + AuthSchemes.NTLM, + AuthSchemes.CREDSSP, + AuthSchemes.DIGEST, + AuthSchemes.BASIC)); + + private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; // milli sec + + public static CTCloudHttpClient getInstance() { + return instance; + } + + private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper(); + private final SSLContext sslContext; + private String hostName = null; + + private CTCloudHttpClient() { + SSLContext tmpSslContext; + try { + tmpSslContext = SSLContext.getInstance("TLSv1.2"); + } catch (NoSuchAlgorithmException ex) { + LOGGER.log(Level.WARNING, "Unable to setup ssl context instance", ex); + tmpSslContext = null; + } + this.sslContext = tmpSslContext; + } + + private ProxySettingArgs getProxySettings() { + if (StringUtils.isBlank(hostName)) { + try { + hostName = InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException ex) { + LOGGER.log(Level.WARNING, "An error occurred while fetching the hostname", ex); + } + } + + int proxyPort = 0; + try { + proxyPort = Integer.parseInt(ProxySettings.getHttpsPort()); + } catch (NumberFormatException ex) { + LOGGER.log(Level.WARNING, "An exception occurred while converting port number to integer", ex); + } + + return new ProxySettingArgs( + ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION, + hostName, + ProxySettings.getHttpsHost(), + proxyPort, + ProxySettings.getAuthenticationUsername(), + ProxySettings.getAuthenticationPassword(), + null + ); + } + + public O doPost(String urlPath, Object jsonBody, Class classType) throws CTCloudException { + String url = Constants.CT_CLOUD_SERVER + urlPath; + try { + + LOGGER.log(Level.INFO, "initiating http connection to ctcloud server"); + try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) { + URIBuilder builder = new URIBuilder(url); + URI postURI = builder.build(); + HttpPost postRequest = new HttpPost(postURI); + + configureRequestTimeout(postRequest); + postRequest.setHeader("Content-type", "application/json"); + + if (jsonBody != null) { + String requestBody = mapper.writeValueAsString(jsonBody); + if (StringUtils.isNotBlank(requestBody)) { + HttpEntity entity = new StringEntity(requestBody, "UTF-8"); + postRequest.setEntity(entity); + } + } + + LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + postRequest.getURI()); + try (CloseableHttpResponse response = httpclient.execute(postRequest)) { + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + LOGGER.log(Level.INFO, "Response Received. - Status OK"); + // Parse Response + HttpEntity entity = response.getEntity(); + String entityStr = EntityUtils.toString(entity); + O respObj = mapper.readValue(entityStr, classType); + return respObj; + } else { + LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine()); + handleNonOKResponse(response, ""); + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex); + throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex); + } + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + url, ex); + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex); + } catch (URISyntaxException ex) { + LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + url, ex); + throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex); + } + + return null; + } + + /** + * A generic way to handle the HTTP response - when the response code is NOT + * 200 OK. + * + * @param response + * @param fileName - used only for logging. + * @throws MalwareScannerException + * @throws IOException + */ + private void handleNonOKResponse(CloseableHttpResponse response, String fileName) throws CTCloudException, IOException { + LOGGER.log(Level.WARNING, MessageFormat.format( + "Response code {0}. Message Body {1}", + response.getStatusLine().getStatusCode(), + EntityUtils.toString(response.getEntity()))); + + switch (response.getStatusLine().getStatusCode()) { + + case HttpStatus.SC_BAD_REQUEST: + //400: Bad request => Unsupported HTTP method or invalid http request (e.g., empty body). + throw new CTCloudException(CTCloudException.ErrorCode.BAD_REQUEST); + case HttpStatus.SC_UNAUTHORIZED: + //401 Invalid API key => An invalid API key, or no API key, has been provided + throw new CTCloudException(CTCloudException.ErrorCode.INVALID_KEY); + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + // 407 Proxy server authentication required. + throw new CTCloudException(CTCloudException.ErrorCode.PROXY_UNAUTHORIZED); + case HttpStatus.SC_FORBIDDEN: + throw new CTCloudException(CTCloudException.ErrorCode.UN_AUTHORIZED); + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + //500 Internal error Server temporarily unavailable; please try again later. If the issue persists, please contact RL. + throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE); + case HttpStatus.SC_SERVICE_UNAVAILABLE: + //503 Server is too busy. Try again later. + //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. + throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE); + case HttpStatus.SC_GATEWAY_TIMEOUT: + throw new CTCloudException(CTCloudException.ErrorCode.GATEWAY_TIMEOUT); + default: + String returnData = EntityUtils.toString(response.getEntity()); + LOGGER.log(Level.WARNING, MessageFormat.format("upload response content for {0}:\n {1}", fileName, returnData)); + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR); + } + } + + /** + * NOTE That this is not a perfect solution as timeouts set this way does + * not terminate a connection forcefully after a specified interval. so if + * there is data streaming in from the server at a small speed the + * connection will be kept open. + * + * @param request + */ + private void configureRequestTimeout(HttpRequestBase request) { + RequestConfig config = RequestConfig.custom() + .setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS) + .setConnectTimeout(CONNECTION_TIMEOUT_MS) + .setSocketTimeout(CONNECTION_TIMEOUT_MS) + .build(); + request.setConfig(config); + } + + /** + * Creates and returns a CloseableHttpClient SYSTEM and MANUAL looks up from + * runtime proxy config settings. These are updated accordingly from the + * Proxy Config UI. This allows us to keep the CreateConnection call fairly + * simple and not have to deal with the System Proxy settings and such. + * + * @return + */ + private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) { + HttpClientBuilder builder = getHttpClientBuilder(proxySettings); + + if (sslContext != null) { + builder.setSSLContext(sslContext); + } + return builder.build(); + } + + private static HttpClientBuilder getHttpClientBuilder(ProxySettingArgs proxySettings) { + + if (proxySettings.isSystemOrManualProxy()) { + + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + LOGGER.info("Requesting Password Authentication..."); + return super.getPasswordAuthentication(); + } + }); + + HttpClientBuilder builder = null; + HttpHost proxyHost = null; + CredentialsProvider proxyCredsProvider = null; + RequestConfig config = null; + + if (Objects.nonNull(proxySettings.getProxyHostname()) && proxySettings.getProxyPort() > 0) { + proxyHost = new HttpHost(proxySettings.getProxyHostname(), proxySettings.getProxyPort()); + + proxyCredsProvider = getProxyCredentialsProvider(proxySettings); + if (StringUtils.isNotBlank(proxySettings.getAuthScheme())) { + if (!DEFAULT_SCHEME_PRIORITY.get(0).equalsIgnoreCase(proxySettings.getAuthScheme())) { + DEFAULT_SCHEME_PRIORITY.removeIf(s -> s.equalsIgnoreCase(proxySettings.getAuthScheme())); + DEFAULT_SCHEME_PRIORITY.add(0, proxySettings.getAuthScheme()); + } + } + config = RequestConfig.custom().setProxyPreferredAuthSchemes(DEFAULT_SCHEME_PRIORITY).build(); + } + + if (Objects.isNull(proxyCredsProvider) && WinHttpClients.isWinAuthAvailable()) { + builder = WinHttpClients.custom(); + builder.useSystemProperties(); + LOGGER.log(Level.WARNING, "Using Win HTTP Client"); + } else { + builder = HttpClients.custom(); + builder.setDefaultRequestConfig(config); + if (Objects.nonNull(proxyCredsProvider)) { // make sure non null proxycreds before setting it + builder.setDefaultCredentialsProvider(proxyCredsProvider); + } + LOGGER.log(Level.WARNING, "Using default http client"); + } + if (Objects.nonNull(proxyHost)) { + builder.setProxy(proxyHost); + LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost)); + } + + return builder; + } else { + return HttpClients.custom(); + } + } + + /** + * Returns a CredentialsProvider for proxy, if one is configured. + * + * @return CredentialsProvider, if a proxy is configured with credentials, + * null otherwise + */ + private static CredentialsProvider getProxyCredentialsProvider(ProxySettingArgs proxySettings) { + CredentialsProvider proxyCredsProvider = null; + if (proxySettings.isSystemOrManualProxy()) { + if (StringUtils.isNotBlank(proxySettings.getProxyUserId())) { + if (null != proxySettings.getProxyPassword() && proxySettings.getProxyPassword().length > 0) { // Password will be blank for KERBEROS / NEGOTIATE schemes. + proxyCredsProvider = new SystemDefaultCredentialsProvider(); + String userId = proxySettings.getProxyUserId(); + String domain = null; + if (userId.contains("\\")) { + domain = userId.split("\\\\")[0]; + userId = userId.split("\\\\")[1]; + } + String workStation = proxySettings.getHostName(); + proxyCredsProvider.setCredentials(new AuthScope(proxySettings.getProxyHostname(), proxySettings.getProxyPort()), + new NTCredentials(userId, new String(proxySettings.getProxyPassword()), workStation, domain)); + } + } + } + + return proxyCredsProvider; + } + + private static class ProxySettingArgs { + + private final boolean systemOrManualProxy; + private final String hostName; + private final String proxyHostname; + private final int proxyPort; + private final String proxyUserId; + private final char[] proxyPassword; + private final String authScheme; + + ProxySettingArgs(boolean systemOrManualProxy, String hostName, String proxyHostname, int proxyPort, String proxyUserId, char[] proxyPassword, String authScheme) { + this.systemOrManualProxy = systemOrManualProxy; + this.hostName = hostName; + this.proxyHostname = proxyHostname; + this.proxyPort = proxyPort; + this.proxyUserId = proxyUserId; + this.proxyPassword = proxyPassword; + this.authScheme = authScheme; + } + + boolean isSystemOrManualProxy() { + return systemOrManualProxy; + } + + String getHostName() { + return hostName; + } + + String getProxyHostname() { + return proxyHostname; + } + + int getProxyPort() { + return proxyPort; + } + + String getProxyUserId() { + return proxyUserId; + } + + char[] getProxyPassword() { + return proxyPassword; + } + + public String getAuthScheme() { + return authScheme; + } + } +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java index 66fa03b8f1..f7b5e75147 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java @@ -15,50 +15,52 @@ package com.basistech.df.cybertriage.autopsy.ctapi; import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenRequest; import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData; +import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest; +import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationResult; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse; import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil; -import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Collections; import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.sleuthkit.autopsy.coreutils.Version; /** * * Data access layer for handling the CT api. */ -public class CtApiDAO { +public class CTApiDAO { private static final String LICENSE_REQUEST_PATH = "/_ah/api/license/v1/activate"; private static final String AUTH_TOKEN_REQUEST_PATH = "/_ah/api/auth/v2/generate_token"; + private static final String CTCLOUD_SERVER_HASH_PATH = "/_ah/api/reputation/v1/query/file/hash/md5?query_types=CORRELATION,MALWARE"; - private static final CtApiDAO instance = new CtApiDAO(); - private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper(); + private static final CTApiDAO instance = new CTApiDAO(); + - private CtApiDAO() { + private CTApiDAO() { } - public static CtApiDAO getInstance() { + public static CTApiDAO getInstance() { return instance; } - + private static String getAppVersion() { return Version.getName() + " " + Version.getVersion(); } + + private final CTCloudHttpClient httpClient = CTCloudHttpClient.getInstance(); - private T doPost(String urlPath, Object requestBody, Class responseTypeRef) throws CTCloudException { - return null; - // TODO - } public LicenseResponse getLicenseInfo(String licenseString) throws CTCloudException { LicenseRequest licenseRequest = new LicenseRequest() .setBoostLicenseCode(licenseString) .setHostId(CTHostIDGenerationUtil.generateLicenseHostID()) .setProduct(getAppVersion()); - - return doPost(LICENSE_REQUEST_PATH, licenseRequest, LicenseResponse.class); + + return httpClient.doPost(LICENSE_REQUEST_PATH, licenseRequest, LicenseResponse.class); } @@ -67,14 +69,25 @@ public class CtApiDAO { .setAutopsyVersion(getAppVersion()) .setRequestFileUpload(true) .setBoostLicenseId(boostLicenseId); - - return doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class); + + return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class); } - public List getReputationResults(String authToken, List md5Hashes) throws CTCloudException { - // TODO -// return cloudServiceApi.lookupFileResults(md5Hashes, HashTypes.md5); - return null; + public List getReputationResults(AuthenticatedRequestData authenticatedRequestData, List md5Hashes) throws CTCloudException { + if (CollectionUtils.isEmpty(md5Hashes)) { + return Collections.emptyList(); + } + + FileReputationRequest fileRepReq = new FileReputationRequest() + .setApiKey(authenticatedRequestData.getApiKey()) + .setHostId(CTHostIDGenerationUtil.generateLicenseHostID()) + .setToken(authenticatedRequestData.getToken()) + .setHashes(md5Hashes); + + FileReputationResponse resp = httpClient.doPost(CTCLOUD_SERVER_HASH_PATH, fileRepReq, FileReputationResponse.class); + return resp == null || resp.getItems() == null + ? Collections.emptyList() + : resp.getItems(); } public enum ResultType { diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java index a010bbe13c..c936d05dbe 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java @@ -20,9 +20,7 @@ import java.time.ZonedDateTime; /** * POJO for an auth token response. */ -public class AuthTokenResponse { - private final String token; - private final String apiKey; +public class AuthTokenResponse extends AuthenticatedRequestData { private final Long hashLookupCount; private final Long hashLookupLimit; private final Long fileUploadLimit; @@ -51,14 +49,6 @@ public class AuthTokenResponse { this.expiration = expiration; } - public String getToken() { - return token; - } - - public String getApiKey() { - return apiKey; - } - public Long getHashLookupCount() { return hashLookupCount; } diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java new file mode 100644 index 0000000000..34e0a61af7 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java @@ -0,0 +1,36 @@ +/** ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2023 Basis Technology Corp. All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ************************************************************************** */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Data required for an authenticated request. + */ +public abstract class AuthenticatedRequestData { + + @JsonProperty("token") + protected String token; + @JsonProperty("api_key") + protected String apiKey; + + public String getToken() { + return token; + } + + public String getApiKey() { + return apiKey; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java new file mode 100644 index 0000000000..0d256799f9 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java @@ -0,0 +1,58 @@ +/** ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2023 Basis Technology Corp. All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ************************************************************************** */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Request for file reputation results. + */ +public class FileReputationRequest extends AuthenticatedRequestData { + + @JsonProperty("hashes") + private List hashes; + + @JsonProperty("host_id") + private String hostId; + + public List getHashes() { + return hashes; + } + + public FileReputationRequest setHashes(List hashes) { + this.hashes = hashes; + return this; + } + + public FileReputationRequest setToken(String token) { + this.token = token; + return this; + } + + public FileReputationRequest setApiKey(String apiKey) { + this.apiKey = apiKey; + return this; + } + + public String getHostId() { + return hostId; + } + + public FileReputationRequest setHostId(String hostId) { + this.hostId = hostId; + return this; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResponse.java new file mode 100644 index 0000000000..aaff89b1d1 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResponse.java @@ -0,0 +1,38 @@ +/** ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret + ** of, Basis Technology Corp. It is given in confidence by Basis Technology + ** and may only be used as permitted under the license agreement under which + ** it has been distributed, and in no other way. + ** + ** Copyright (c) 2023 Basis Technology Corp. All rights reserved. + ** + ** The technical data and information provided herein are provided with + ** `limited rights', and the computer software provided herein is provided + ** with `restricted rights' as those terms are defined in DAR and ASPR + ** 7-104.9(a). + ************************************************************************** */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Container for file reputation result list response. + */ +public class FileReputationResponse { + + private final List items; + + @JsonCreator + public FileReputationResponse( + @JsonProperty("items") List items + ) { + this.items = items; + } + + public List getItems() { + return items; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java index f7b68f6bea..184e8593f9 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java @@ -29,7 +29,7 @@ public class CTHostIDGenerationUtil { private static final Logger LOGGER = Logger.getLogger(CTHostIDGenerationUtil.class.getName()); private static final String USER_NAME = System.getProperty("user.name"); - private static String HOST_NAME = ""; + private static String cachedId = ""; /** * Host ID Algorithm: Get MAC address from License4J. Get MD5 hash of it and @@ -41,17 +41,24 @@ public class CTHostIDGenerationUtil { * @return */ public static String generateLicenseHostID() { - if (StringUtils.isBlank(HOST_NAME)) { - + if (StringUtils.isBlank(cachedId)) { try { - HOST_NAME = StringUtils.defaultString(InetAddress.getLocalHost().getCanonicalHostName()); + String hostName = StringUtils.defaultString(InetAddress.getLocalHost().getCanonicalHostName()); + String macAddressMd5 = StringUtils.isNotBlank(HardwareID.getHardwareIDFromEthernetAddress()) + ? Md5HashUtil.getMD5MessageDigest(HardwareID.getHardwareIDFromEthernetAddress()).substring(0, 16) + : Md5HashUtil.getMD5MessageDigest(hostName).substring(0, 16); + + String usernameMd5 = StringUtils.isNotBlank(USER_NAME) + ? Md5HashUtil.getMD5MessageDigest(USER_NAME).substring(0, 16) + : Md5HashUtil.getMD5MessageDigest(hostName).substring(0, 16); + + cachedId = macAddressMd5 + "_" + usernameMd5; + } catch (UnknownHostException ex) { - LOGGER.log(Level.WARNING, "UNable to determine host name.", ex); + LOGGER.log(Level.WARNING, "Unable to determine host name.", ex); } } - String macAddressMd5 = StringUtils.isNotBlank(HardwareID.getHardwareIDFromEthernetAddress()) ? Md5HashUtil.getMD5MessageDigest(HardwareID.getHardwareIDFromEthernetAddress()).substring(0, 16) : Md5HashUtil.getMD5MessageDigest(HOST_NAME).substring(0, 16); - String usernameMd5 = StringUtils.isNotBlank(USER_NAME) ? Md5HashUtil.getMD5MessageDigest(USER_NAME).substring(0, 16) : Md5HashUtil.getMD5MessageDigest(HOST_NAME).substring(0, 16); - String md5 = macAddressMd5 + "_" + usernameMd5; - return md5; + + return cachedId; } } diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java index 069d9fb653..6ff5e74edc 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java @@ -15,7 +15,7 @@ package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; import com.basistech.df.cybertriage.autopsy.ctoptions.subpanel.CTOptionsSubPanel; import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException; -import com.basistech.df.cybertriage.autopsy.ctapi.CtApiDAO; +import com.basistech.df.cybertriage.autopsy.ctapi.CTApiDAO; import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse; @@ -46,7 +46,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel { private static final SimpleDateFormat LICENSE_EXPIRES_FORMAT = new SimpleDateFormat("MMMM d, YYYY"); private static final SimpleDateFormat MALWARE_SCANS_RESET_FORMAT = new SimpleDateFormat("MMM d, YYYY' at 'h:mma"); - private final CtApiDAO ctApiDAO = CtApiDAO.getInstance(); + private final CTApiDAO ctApiDAO = CTApiDAO.getInstance(); private final CTLicensePersistence ctPersistence = CTLicensePersistence.getInstance(); private volatile LicenseInfo licenseInfo = null; diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java index 2b049124bf..65a12f59b7 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java @@ -13,7 +13,7 @@ ************************************************************************** */ package com.basistech.df.cybertriage.autopsy.malwarescan; -import com.basistech.df.cybertriage.autopsy.ctapi.CtApiDAO; +import com.basistech.df.cybertriage.autopsy.ctapi.CTApiDAO; import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationResult; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo; @@ -101,7 +101,7 @@ public class MalwareScanIngestModule implements FileIngestModule { private final BatchProcessor batchProcessor = new BatchProcessor(BATCH_SIZE, BATCH_MILLIS_TIMEOUT, this::handleBatch); private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance(); - private final CtApiDAO ctApiDAO = CtApiDAO.getInstance(); + private final CTApiDAO ctApiDAO = CTApiDAO.getInstance(); private FileTypeDetector fileTypeDetector; private RunState runState = null; @@ -256,7 +256,7 @@ public class MalwareScanIngestModule implements FileIngestModule { } // using auth token, get results - List repResult = ctApiDAO.getReputationResults(authTokenResponse.getToken(), md5Hashes); + List repResult = ctApiDAO.getReputationResults(authTokenResponse, md5Hashes); if (repResult != null && !repResult.isEmpty()) { SleuthkitCase.CaseDbTransaction trans = null;