Merge pull request #7828 from gdicristofaro/CT-7161-fileUpload

CT-7161 file upload
This commit is contained in:
Mark McKinnon 2023-08-15 19:52:38 -04:00 committed by GitHub
commit 56e7958ba6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1778 additions and 895 deletions

View File

@ -27,7 +27,9 @@ import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest; import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest; 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.json.LicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil; import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil;
import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -45,6 +47,8 @@ public class CTApiDAO {
private static final String LICENSE_REQUEST_PATH = "/_ah/api/license/v1/activate"; 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 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 String CTCLOUD_SERVER_HASH_PATH = "/_ah/api/reputation/v1/query/file/hash/md5?query_types=CORRELATION,MALWARE";
private static final String CTCLOUD_UPLOAD_FILE_METADATA_PATH = "/_ah/api/reputation/v1/upload/meta";
private static final String AUTOPSY_PRODUCT = "AUTOPSY"; private static final String AUTOPSY_PRODUCT = "AUTOPSY";
private static final CTApiDAO instance = new CTApiDAO(); private static final CTApiDAO instance = new CTApiDAO();
@ -74,15 +78,27 @@ public class CTApiDAO {
} }
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException { public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException {
return getAuthToken(decrypted, false);
}
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, boolean fileUpload) throws CTCloudException {
AuthTokenRequest authTokenRequest = new AuthTokenRequest() AuthTokenRequest authTokenRequest = new AuthTokenRequest()
.setAutopsyVersion(getAppVersion()) .setAutopsyVersion(getAppVersion())
.setRequestFileUpload(false) .setRequestFileUpload(fileUpload)
.setBoostLicenseId(decrypted.getBoostLicenseId()) .setBoostLicenseId(decrypted.getBoostLicenseId())
.setHostId(decrypted.getLicenseHostId()); .setHostId(decrypted.getLicenseHostId());
return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class); return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
} }
public void uploadFile(String url, String fileName, InputStream fileIs) throws CTCloudException {
httpClient.doFileUploadPost(url, fileName, fileIs);
}
public void uploadMeta(AuthenticatedRequestData authenticatedRequestData, MetadataUploadRequest metaRequest) throws CTCloudException {
httpClient.doPost(CTCLOUD_UPLOAD_FILE_METADATA_PATH, getAuthParams(authenticatedRequestData), metaRequest, null);
}
private static Map<String, String> getAuthParams(AuthenticatedRequestData authenticatedRequestData) { private static Map<String, String> getAuthParams(AuthenticatedRequestData authenticatedRequestData) {
return new HashMap<String, String>() { return new HashMap<String, String>() {
{ {

View File

@ -76,8 +76,7 @@ public class CTCloudException extends Exception{
public String getErrorDetails() { public String getErrorDetails() {
if(getErrorCode() == CTCloudException.ErrorCode.UNKNOWN && Objects.nonNull(getCause())){ if(getErrorCode() == CTCloudException.ErrorCode.UNKNOWN && Objects.nonNull(getCause())){
return String.format("Malware scan error %s occurred. Please try \"Re Scan\" from the dashboard to attempt Malware scaning again. " return String.format("An API error %s occurred. Please try again, and contact Basis support at %s for help if the problem persists.",
+ "\nPlease contact Basis support at %s for help if the problem presists.",
StringUtils.isNotBlank(getCause().getLocalizedMessage()) ? "("+getCause().getLocalizedMessage()+")": "(Unknown)", StringUtils.isNotBlank(getCause().getLocalizedMessage()) ? "("+getCause().getLocalizedMessage()+")": "(Unknown)",
Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM ); Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM );
}else { }else {

View File

@ -21,42 +21,37 @@ package com.basistech.df.cybertriage.autopsy.ctapi;
import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil; import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException; import java.io.IOException;
import java.net.Authenticator; import java.io.InputStream;
import java.net.InetAddress; import java.net.Proxy;
import java.net.PasswordAuthentication; import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.Collection;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Stream;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus; 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.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
@ -64,86 +59,54 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.apache.http.client.utils.URIBuilder; import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
import org.apache.http.impl.client.WinHttpClients; import org.apache.http.impl.client.WinHttpClients;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.apache.http.ssl.SSLInitializationException;
import org.netbeans.core.ProxySettings;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.coreutils.Version;
/** /**
* Makes the http requests to CT cloud. * Makes the http requests to CT cloud.
*
* NOTE: regarding proxy settings, the host and port are handled by the
* NbProxySelector. Any proxy authentication is handled by NbAuthenticator which
* is installed at startup (i.e. NbAuthenticator.install). See
* GeneralOptionsModel.testHttpConnection to see how the general options panel
* tests the connection.
*/ */
public class CTCloudHttpClient { class CTCloudHttpClient {
private static final CTCloudHttpClient instance = new CTCloudHttpClient();
private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName()); private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName());
private static final String HOST_URL = Version.getBuildType() == Version.Type.RELEASE ? Constants.CT_CLOUD_SERVER : Constants.CT_CLOUD_DEV_SERVER; private static final String HOST_URL = Version.getBuildType() == Version.Type.RELEASE ? Constants.CT_CLOUD_SERVER : Constants.CT_CLOUD_DEV_SERVER;
private static final String NB_PROXY_SELECTOR_NAME = "org.netbeans.core.NbProxySelector";
private static final List<String> 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 private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; // milli sec
private static final CTCloudHttpClient instance = new CTCloudHttpClient();
public static CTCloudHttpClient getInstance() { public static CTCloudHttpClient getInstance() {
return instance; return instance;
} }
private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper(); private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
private final SSLContext sslContext; private final SSLContext sslContext;
private String hostName = null; private final ProxySelector proxySelector;
private CTCloudHttpClient() { private CTCloudHttpClient() {
// leave as null for now unless we want to customize this at a later date // leave as null for now unless we want to customize this at a later date
this.sslContext = null; this.sslContext = createSSLContext();
this.proxySelector = getProxySelector();
} }
private ProxySettingArgs getProxySettings() { private static URI getUri(String host, String path, Map<String, String> urlReqParams) throws URISyntaxException {
if (StringUtils.isBlank(hostName)) { String url = host + path;
try {
hostName = InetAddress.getLocalHost().getCanonicalHostName();
} catch (UnknownHostException ex) {
LOGGER.log(Level.WARNING, "An error occurred while fetching the hostname", ex);
}
}
int proxyPort = 0;
if (StringUtils.isNotBlank(ProxySettings.getHttpPort())) {
try {
proxyPort = Integer.parseInt(ProxySettings.getHttpsPort());
} catch (NumberFormatException ex) {
LOGGER.log(Level.WARNING, "Unable to convert port to integer");
}
}
return new ProxySettingArgs(
ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION,
hostName,
ProxySettings.getHttpsHost(),
proxyPort,
ProxySettings.getAuthenticationUsername(),
ProxySettings.getAuthenticationPassword(),
null
);
}
public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) throws CTCloudException {
return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
}
public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) throws CTCloudException {
String url = HOST_URL + urlPath;
try {
LOGGER.log(Level.INFO, "initiating http connection to ctcloud server");
try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) {
URIBuilder builder = new URIBuilder(url); URIBuilder builder = new URIBuilder(url);
if (!MapUtils.isEmpty(urlReqParams)) { if (!MapUtils.isEmpty(urlReqParams)) {
@ -156,9 +119,22 @@ public class CTCloudHttpClient {
} }
} }
URI postURI = builder.build(); return builder.build();
HttpPost postRequest = new HttpPost(postURI); }
public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) throws CTCloudException {
return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
}
public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) throws CTCloudException {
URI postURI = null;
try {
postURI = getUri(HOST_URL, urlPath, urlReqParams);
LOGGER.log(Level.INFO, "initiating http connection to ctcloud server");
try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
HttpPost postRequest = new HttpPost(postURI);
configureRequestTimeout(postRequest); configureRequestTimeout(postRequest);
postRequest.setHeader("Content-type", "application/json"); postRequest.setHeader("Content-type", "application/json");
@ -177,10 +153,14 @@ public class CTCloudHttpClient {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
LOGGER.log(Level.INFO, "Response Received. - Status OK"); LOGGER.log(Level.INFO, "Response Received. - Status OK");
// Parse Response // Parse Response
if (classType != null) {
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
String entityStr = EntityUtils.toString(entity); String entityStr = EntityUtils.toString(entity);
O respObj = mapper.readValue(entityStr, classType); O respObj = mapper.readValue(entityStr, classType);
return respObj; return respObj;
} else {
return null;
}
} else { } else {
LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine()); LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine());
handleNonOKResponse(response, ""); handleNonOKResponse(response, "");
@ -191,16 +171,64 @@ public class CTCloudHttpClient {
} }
} }
} catch (IOException ex) { } catch (IOException ex) {
LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + url, ex); LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + postURI, ex);
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
} catch (SSLInitializationException ex) {
LOGGER.log(Level.WARNING, "No such algorithm exception raised when creating SSL connection for CT Cloud using " + postURI, ex);
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex); throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + url, ex); LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + postURI, ex);
throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex); throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
} }
return null; return null;
} }
public void doFileUploadPost(String fullUrlPath, String fileName, InputStream fileIs) throws CTCloudException {
URI postUri;
try {
postUri = new URI(fullUrlPath);
} catch (URISyntaxException ex) {
LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + fullUrlPath, ex);
throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
}
try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + fullUrlPath);
HttpPost post = new HttpPost(postUri);
configureRequestTimeout(post);
post.addHeader("Connection", "keep-alive");
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody(
"file",
fileIs,
ContentType.APPLICATION_OCTET_STREAM,
fileName
);
HttpEntity multipart = builder.build();
post.setEntity(multipart);
try (CloseableHttpResponse response = httpclient.execute(post)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
LOGGER.log(Level.INFO, "Response Received. - Status OK");
} else {
LOGGER.log(Level.WARNING, MessageFormat.format("Response Received. - Status Error {0}", response.getStatusLine()));
handleNonOKResponse(response, fileName);
}
}
} catch (SSLInitializationException ex) {
LOGGER.log(Level.WARNING, "SSL exception raised when connecting to Reversing Labs for file content upload ", ex);
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "IO Exception raised when connecting to Reversing Labs for file content upload ", ex);
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex);
}
}
/** /**
* A generic way to handle the HTTP response - when the response code is NOT * A generic way to handle the HTTP response - when the response code is NOT
* 200 OK. * 200 OK.
@ -262,148 +290,141 @@ public class CTCloudHttpClient {
request.setConfig(config); request.setConfig(config);
} }
/**
* Get ProxySelector present (favoring NbProxySelector if present).
*
* @return The found ProxySelector or null.
*/
private static ProxySelector getProxySelector() {
Collection<? extends ProxySelector> selectors = Lookup.getDefault().lookupAll(ProxySelector.class);
return (selectors != null ? selectors.stream() : Stream.empty())
.filter(s -> s != null)
.map(s -> (ProxySelector) s)
.sorted((a, b) -> {
String aName = a.getClass().getCanonicalName();
String bName = b.getClass().getCanonicalName();
boolean aIsNb = aName.equalsIgnoreCase(NB_PROXY_SELECTOR_NAME);
boolean bIsNb = bName.equalsIgnoreCase(NB_PROXY_SELECTOR_NAME);
if (aIsNb == bIsNb) {
return StringUtils.compareIgnoreCase(aName, bName);
} else {
return aIsNb ? -1 : 1;
}
})
.findFirst()
// TODO take this out to remove proxy selector logging
.map(s -> new LoggingProxySelector(s))
.orElse(null);
}
/**
* Create an SSLContext object using our in-memory keystore.
*
* @return
*/
private static SSLContext createSSLContext() {
LOGGER.log(Level.INFO, "Creating custom SSL context");
try {
// I'm not sure how much of this is really necessary to set up, but it works
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
KeyManager[] keyManagers = getKeyManagers();
TrustManager[] trustManagers = getTrustManagers();
sslContext.init(keyManagers, trustManagers, new SecureRandom());
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException ex) {
LOGGER.log(Level.SEVERE, "Error creating SSL context", ex);
return null;
}
}
// jvm default key manager
// based in part on this: https://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm/16229909
private static KeyManager[] getKeyManagers() {
LOGGER.log(Level.INFO, "Using default algorithm to create trust store: " + KeyManagerFactory.getDefaultAlgorithm());
try {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(null, null);
return kmf.getKeyManagers();
} catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException ex) {
LOGGER.log(Level.SEVERE, "Error getting KeyManagers", ex);
return new KeyManager[0];
}
}
// jvm default trust store
// based in part on this: https://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm/16229909
private static TrustManager[] getTrustManagers() {
try {
LOGGER.log(Level.INFO, "Using default algorithm to create trust store: " + TrustManagerFactory.getDefaultAlgorithm());
TrustManagerFactory tmf
= TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509TrustManager tm = (X509TrustManager) tmf.getTrustManagers()[0];
return new TrustManager[]{tm};
} catch (KeyStoreException | NoSuchAlgorithmException ex) {
LOGGER.log(Level.SEVERE, "Error getting TrustManager", ex);
return new TrustManager[0];
}
}
/** /**
* Creates a connection to CT Cloud with the given arguments. * Creates a connection to CT Cloud with the given arguments.
* @param proxySettings The network proxy settings. *
* @param proxySelector The proxy selector.
* @param sslContext The ssl context or null. * @param sslContext The ssl context or null.
* @return The connection to CT Cloud. * @return The connection to CT Cloud.
*/ */
private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) { private static CloseableHttpClient createConnection(ProxySelector proxySelector, SSLContext sslContext) throws SSLInitializationException {
HttpClientBuilder builder = getHttpClientBuilder(proxySettings); HttpClientBuilder builder;
if (sslContext != null) { if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
builder.setSSLContext(sslContext); && StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
} && ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
return builder.build(); && WinHttpClients.isWinAuthAvailable()) {
}
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 = WinHttpClients.custom();
builder.useSystemProperties(); builder.useSystemProperties();
LOGGER.log(Level.WARNING, "Using Win HTTP Client"); LOGGER.log(Level.WARNING, "Using Win HTTP Client");
} else { } else {
builder = HttpClients.custom(); builder = HttpClients.custom();
builder.setDefaultRequestConfig(config); // 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"); LOGGER.log(Level.WARNING, "Using default http client");
} }
if (Objects.nonNull(proxyHost)) {
builder.setProxy(proxyHost); if (sslContext != null) {
LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost)); builder.setSSLContext(sslContext);
} }
return builder; if (proxySelector != null) {
} else { builder.setRoutePlanner(new SystemDefaultRoutePlanner(proxySelector));
return HttpClients.custom();
}
} }
/** return builder.build();
* 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 LoggingProxySelector extends ProxySelector {
private final ProxySelector delegate;
public LoggingProxySelector(ProxySelector delegate) {
this.delegate = delegate;
} }
private static class ProxySettingArgs { @Override
public List<Proxy> select(URI uri) {
private final boolean systemOrManualProxy; List<Proxy> selectedProxies = delegate.select(uri);
private final String hostName; LOGGER.log(Level.INFO, MessageFormat.format("Proxy selected for {0} are {1}", uri, selectedProxies));
private final String proxyHostname; return selectedProxies;
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() { @Override
return systemOrManualProxy; public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
LOGGER.log(Level.WARNING, MessageFormat.format("Connection failed connecting to {0} socket address {1}", uri, sa), ioe);
delegate.connectFailed(uri, sa, ioe);
} }
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;
}
} }
} }

View File

@ -23,7 +23,7 @@ import java.net.URI;
/** /**
* Constants regarding connections to cyber triage cloud. * Constants regarding connections to cyber triage cloud.
*/ */
final public class Constants { final class Constants {
public static final String CYBER_TRIAGE = "CyberTriage"; public static final String CYBER_TRIAGE = "CyberTriage";

View File

@ -1,446 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.basistech.df.cybertriage.autopsy.ctapi;
import java.net.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import org.netbeans.api.keyring.Keyring;
import org.openide.util.*;
import org.openide.util.lookup.ServiceProvider;
/**
* Taken from https://raw.githubusercontent.com/apache/netbeans/master/platform/o.n.core/src/org/netbeans/core/ProxySettings.java
* @author Jiri Rechtacek
*/
public class ProxySettings {
public static final String PROXY_HTTP_HOST = "proxyHttpHost"; // NOI18N
public static final String PROXY_HTTP_PORT = "proxyHttpPort"; // NOI18N
public static final String PROXY_HTTPS_HOST = "proxyHttpsHost"; // NOI18N
public static final String PROXY_HTTPS_PORT = "proxyHttpsPort"; // NOI18N
public static final String PROXY_SOCKS_HOST = "proxySocksHost"; // NOI18N
public static final String PROXY_SOCKS_PORT = "proxySocksPort"; // NOI18N
public static final String NOT_PROXY_HOSTS = "proxyNonProxyHosts"; // NOI18N
public static final String PROXY_TYPE = "proxyType"; // NOI18N
public static final String USE_PROXY_AUTHENTICATION = "useProxyAuthentication"; // NOI18N
public static final String PROXY_AUTHENTICATION_USERNAME = "proxyAuthenticationUsername"; // NOI18N
public static final String PROXY_AUTHENTICATION_PASSWORD = "proxyAuthenticationPassword"; // NOI18N
public static final String USE_PROXY_ALL_PROTOCOLS = "useProxyAllProtocols"; // NOI18N
public static final String DIRECT = "DIRECT"; // NOI18N
public static final String PAC = "PAC"; // NOI18N
public static final String SYSTEM_PROXY_HTTP_HOST = "systemProxyHttpHost"; // NOI18N
public static final String SYSTEM_PROXY_HTTP_PORT = "systemProxyHttpPort"; // NOI18N
public static final String SYSTEM_PROXY_HTTPS_HOST = "systemProxyHttpsHost"; // NOI18N
public static final String SYSTEM_PROXY_HTTPS_PORT = "systemProxyHttpsPort"; // NOI18N
public static final String SYSTEM_PROXY_SOCKS_HOST = "systemProxySocksHost"; // NOI18N
public static final String SYSTEM_PROXY_SOCKS_PORT = "systemProxySocksPort"; // NOI18N
public static final String SYSTEM_NON_PROXY_HOSTS = "systemProxyNonProxyHosts"; // NOI18N
public static final String SYSTEM_PAC = "systemPAC"; // NOI18N
// Only for testing purpose (Test connection in General options panel)
public static final String TEST_SYSTEM_PROXY_HTTP_HOST = "testSystemProxyHttpHost"; // NOI18N
public static final String TEST_SYSTEM_PROXY_HTTP_PORT = "testSystemProxyHttpPort"; // NOI18N
public static final String HTTP_CONNECTION_TEST_URL = "https://netbeans.apache.org";// NOI18N
private static String presetNonProxyHosts;
/** No proxy is used to connect. */
public static final int DIRECT_CONNECTION = 0;
/** Proxy setting is automatically detect in OS. */
public static final int AUTO_DETECT_PROXY = 1; // as default
/** Manually set proxy host and port. */
public static final int MANUAL_SET_PROXY = 2;
/** Proxy PAC file automatically detect in OS. */
public static final int AUTO_DETECT_PAC = 3;
/** Proxy PAC file manually set. */
public static final int MANUAL_SET_PAC = 4;
private static final Logger LOGGER = Logger.getLogger(ProxySettings.class.getName());
private static Preferences getPreferences() {
return NbPreferences.forModule (ProxySettings.class);
}
public static String getHttpHost () {
return normalizeProxyHost (getPreferences ().get (PROXY_HTTP_HOST, ""));
}
public static String getHttpPort () {
return getPreferences ().get (PROXY_HTTP_PORT, "");
}
public static String getHttpsHost () {
if (useProxyAllProtocols ()) {
return getHttpHost ();
} else {
return getPreferences ().get (PROXY_HTTPS_HOST, "");
}
}
public static String getHttpsPort () {
if (useProxyAllProtocols ()) {
return getHttpPort ();
} else {
return getPreferences ().get (PROXY_HTTPS_PORT, "");
}
}
public static String getSocksHost () {
if (useProxyAllProtocols ()) {
return getHttpHost ();
} else {
return getPreferences ().get (PROXY_SOCKS_HOST, "");
}
}
public static String getSocksPort () {
if (useProxyAllProtocols ()) {
return getHttpPort ();
} else {
return getPreferences ().get (PROXY_SOCKS_PORT, "");
}
}
public static String getNonProxyHosts () {
String hosts = getPreferences ().get (NOT_PROXY_HOSTS, getDefaultUserNonProxyHosts ());
return compactNonProxyHosts(hosts);
}
public static int getProxyType () {
int type = getPreferences ().getInt (PROXY_TYPE, AUTO_DETECT_PROXY);
if (AUTO_DETECT_PROXY == type) {
type = ProxySettings.getSystemPac() != null ? AUTO_DETECT_PAC : AUTO_DETECT_PROXY;
}
return type;
}
public static String getSystemHttpHost() {
return getPreferences().get(SYSTEM_PROXY_HTTP_HOST, "");
}
public static String getSystemHttpPort() {
return getPreferences().get(SYSTEM_PROXY_HTTP_PORT, "");
}
public static String getSystemHttpsHost() {
return getPreferences().get(SYSTEM_PROXY_HTTPS_HOST, "");
}
public static String getSystemHttpsPort() {
return getPreferences().get(SYSTEM_PROXY_HTTPS_PORT, "");
}
public static String getSystemSocksHost() {
return getPreferences().get(SYSTEM_PROXY_SOCKS_HOST, "");
}
public static String getSystemSocksPort() {
return getPreferences().get(SYSTEM_PROXY_SOCKS_PORT, "");
}
public static String getSystemNonProxyHosts() {
return getPreferences().get(SYSTEM_NON_PROXY_HOSTS, getModifiedNonProxyHosts(""));
}
public static String getSystemPac() {
return getPreferences().get(SYSTEM_PAC, null);
}
public static String getTestSystemHttpHost() {
return getPreferences().get(TEST_SYSTEM_PROXY_HTTP_HOST, "");
}
public static String getTestSystemHttpPort() {
return getPreferences().get(TEST_SYSTEM_PROXY_HTTP_PORT, "");
}
public static boolean useAuthentication () {
return getPreferences ().getBoolean (USE_PROXY_AUTHENTICATION, false);
}
public static boolean useProxyAllProtocols () {
return getPreferences ().getBoolean (USE_PROXY_ALL_PROTOCOLS, false);
}
public static String getAuthenticationUsername () {
return getPreferences ().get (PROXY_AUTHENTICATION_USERNAME, "");
}
public static char[] getAuthenticationPassword () {
String old = getPreferences().get(PROXY_AUTHENTICATION_PASSWORD, null);
if (old != null) {
getPreferences().remove(PROXY_AUTHENTICATION_PASSWORD);
setAuthenticationPassword(old.toCharArray());
}
char[] pwd = Keyring.read(PROXY_AUTHENTICATION_PASSWORD);
return pwd != null ? pwd : new char[0];
}
public static void setAuthenticationPassword(char[] password) {
Keyring.save(ProxySettings.PROXY_AUTHENTICATION_PASSWORD, password,
// XXX consider including getHttpHost and/or getHttpsHost
NbBundle.getMessage(ProxySettings.class, "ProxySettings.password.description")); // NOI18N
}
public static void addPreferenceChangeListener (PreferenceChangeListener l) {
getPreferences ().addPreferenceChangeListener (l);
}
public static void removePreferenceChangeListener (PreferenceChangeListener l) {
getPreferences ().removePreferenceChangeListener (l);
}
private static String getPresetNonProxyHosts () {
if (presetNonProxyHosts == null) {
presetNonProxyHosts = System.getProperty ("http.nonProxyHosts", ""); // NOI18N
}
return presetNonProxyHosts;
}
private static String getDefaultUserNonProxyHosts () {
return getModifiedNonProxyHosts (getSystemNonProxyHosts ());
}
private static String concatProxies(String... proxies) {
StringBuilder sb = new StringBuilder();
for (String n : proxies) {
if (n == null) {
continue;
}
n = n.trim();
if (n.isEmpty()) {
continue;
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '|') { // NOI18N
if (!n.startsWith("|")) { // NOI18N
sb.append('|'); // NOI18N
}
}
sb.append(n);
}
return sb.toString();
}
private static String getModifiedNonProxyHosts (String systemPreset) {
String fromSystem = systemPreset.replace (";", "|").replace (",", "|"); //NOI18N
String fromUser = getPresetNonProxyHosts () == null ? "" : getPresetNonProxyHosts ().replace (";", "|").replace (",", "|"); //NOI18N
if (Utilities.isWindows ()) {
fromSystem = addReguralToNonProxyHosts (fromSystem);
}
final String staticNonProxyHosts = NbBundle.getMessage(ProxySettings.class, "StaticNonProxyHosts"); // NOI18N
String nonProxy = concatProxies(fromUser, fromSystem, staticNonProxyHosts); // NOI18N
String localhost;
try {
localhost = InetAddress.getLocalHost().getHostName();
if (!"localhost".equals(localhost)) { // NOI18N
nonProxy = nonProxy + "|" + localhost; // NOI18N
} else {
// Avoid this error when hostname == localhost:
// Error in http.nonProxyHosts system property: sun.misc.REException: localhost is a duplicate
}
}
catch (UnknownHostException e) {
// OK. Sometimes a hostname is assigned by DNS, but a computer
// is later pulled off the network. It may then produce a bogus
// name for itself which can't actually be resolved. Normally
// "localhost" is aliased to 127.0.0.1 anyway.
}
/* per Milan's agreement it's removed. See issue #89868
try {
String localhost2 = InetAddress.getLocalHost().getCanonicalHostName();
if (!"localhost".equals(localhost2) && !localhost2.equals(localhost)) { // NOI18N
nonProxy = nonProxy + "|" + localhost2; // NOI18N
} else {
// Avoid this error when hostname == localhost:
// Error in http.nonProxyHosts system property: sun.misc.REException: localhost is a duplicate
}
}
catch (UnknownHostException e) {
// OK. Sometimes a hostname is assigned by DNS, but a computer
// is later pulled off the network. It may then produce a bogus
// name for itself which can't actually be resolved. Normally
// "localhost" is aliased to 127.0.0.1 anyway.
}
*/
return compactNonProxyHosts (nonProxy);
}
// avoid duplicate hosts
private static String compactNonProxyHosts (String hosts) {
StringTokenizer st = new StringTokenizer(hosts, ","); //NOI18N
StringBuilder nonProxyHosts = new StringBuilder();
while (st.hasMoreTokens()) {
String h = st.nextToken().trim();
if (h.length() == 0) {
continue;
}
if (nonProxyHosts.length() > 0) {
nonProxyHosts.append("|"); // NOI18N
}
nonProxyHosts.append(h);
}
st = new StringTokenizer (nonProxyHosts.toString(), "|"); //NOI18N
Set<String> set = new HashSet<String> ();
StringBuilder compactedProxyHosts = new StringBuilder();
while (st.hasMoreTokens ()) {
String t = st.nextToken ();
if (set.add (t.toLowerCase (Locale.US))) {
if (compactedProxyHosts.length() > 0) {
compactedProxyHosts.append('|'); // NOI18N
}
compactedProxyHosts.append(t);
}
}
return compactedProxyHosts.toString();
}
private static String addReguralToNonProxyHosts (String nonProxyHost) {
StringTokenizer st = new StringTokenizer (nonProxyHost, "|"); // NOI18N
StringBuilder reguralProxyHosts = new StringBuilder();
while (st.hasMoreTokens ()) {
String t = st.nextToken ();
if (t.indexOf ('*') == -1) { //NOI18N
t = t + '*'; //NOI18N
}
if (reguralProxyHosts.length() > 0)
reguralProxyHosts.append('|'); // NOI18N
reguralProxyHosts.append(t);
}
return reguralProxyHosts.toString();
}
public static String normalizeProxyHost (String proxyHost) {
if (proxyHost.toLowerCase (Locale.US).startsWith ("http://")) { // NOI18N
return proxyHost.substring (7, proxyHost.length ());
} else {
return proxyHost;
}
}
private static InetSocketAddress analyzeProxy(URI uri) {
Parameters.notNull("uri", uri); // NOI18N
List<Proxy> proxies = ProxySelector.getDefault().select(uri);
assert proxies != null : "ProxySelector cannot return null for " + uri; // NOI18N
assert !proxies.isEmpty() : "ProxySelector cannot return empty list for " + uri; // NOI18N
String protocol = uri.getScheme();
Proxy p = proxies.get(0);
if (Proxy.Type.DIRECT == p.type()) {
// return null for DIRECT proxy
return null;
}
if (protocol == null
|| ((protocol.startsWith("http") || protocol.equals("ftp")) && Proxy.Type.HTTP == p.type()) // NOI18N
|| !(protocol.startsWith("http") || protocol.equals("ftp"))) { // NOI18N
if (p.address() instanceof InetSocketAddress) {
// check is
//assert ! ((InetSocketAddress) p.address()).isUnresolved() : p.address() + " must be resolved address.";
return (InetSocketAddress) p.address();
} else {
LOGGER.log(Level.INFO, p.address() + " is not instanceof InetSocketAddress but " + p.address().getClass()); // NOI18N
return null;
}
} else {
return null;
}
}
public static void reload() {
Reloader reloader = Lookup.getDefault().lookup(Reloader.class);
reloader.reload();
}
@ServiceProvider(service = NetworkSettings.ProxyCredentialsProvider.class, position = 1000)
public static class NbProxyCredentialsProvider extends NetworkSettings.ProxyCredentialsProvider {
@Override
public String getProxyHost(URI u) {
if (getPreferences() == null) {
return null;
}
InetSocketAddress sa = analyzeProxy(u);
return sa == null ? null : sa.getHostName();
}
@Override
public String getProxyPort(URI u) {
if (getPreferences() == null) {
return null;
}
InetSocketAddress sa = analyzeProxy(u);
return sa == null ? null : Integer.toString(sa.getPort());
}
@Override
protected String getProxyUserName(URI u) {
if (getPreferences() == null) {
return null;
}
return ProxySettings.getAuthenticationUsername();
}
@Override
protected char[] getProxyPassword(URI u) {
if (getPreferences() == null) {
return null;
}
return ProxySettings.getAuthenticationPassword();
}
@Override
protected boolean isProxyAuthentication(URI u) {
if (getPreferences() == null) {
return false;
}
return getPreferences().getBoolean(USE_PROXY_AUTHENTICATION, false);
}
}
/** A bridge between <code>o.n.core</code> and <code>core.network</code>.
* An implementation of this class brings a facility to reload Network Proxy Settings
* from underlying OS.
* The module <code>core.network</code> provides a implementation which may be accessible
* via <code>Lookup.getDefault()</code>. It's not guaranteed any implementation is found on all distribution.
*
* @since 3.40
*/
public abstract static class Reloader {
/** Reloads Network Proxy Settings from underlying system.
*
*/
public abstract void reload();
}
}

View File

@ -38,7 +38,7 @@ public class DecryptedLicenseResponse {
private final Long fileUploads; private final Long fileUploads;
private final Instant activationTime; private final Instant activationTime;
private final String product; private final String product;
private final String limitType; private final LicenseLimitType limitType;
private final String timezone; private final String timezone;
private final String customerEmail; private final String customerEmail;
private final String customerName; private final String customerName;
@ -54,7 +54,7 @@ public class DecryptedLicenseResponse {
@JsonDeserialize(using = InstantEpochMillisDeserializer.class) @JsonDeserialize(using = InstantEpochMillisDeserializer.class)
@JsonProperty("activationTime") Instant activationTime, @JsonProperty("activationTime") Instant activationTime,
@JsonProperty("product") String product, @JsonProperty("product") String product,
@JsonProperty("limitType") String limitType, @JsonProperty("limitType") LicenseLimitType limitType,
@JsonProperty("timezone") String timezone, @JsonProperty("timezone") String timezone,
@JsonProperty("customerEmail") String customerEmail, @JsonProperty("customerEmail") String customerEmail,
@JsonProperty("customerName") String customerName @JsonProperty("customerName") String customerName
@ -96,7 +96,7 @@ public class DecryptedLicenseResponse {
return product; return product;
} }
public String getLimitType() { public LicenseLimitType getLimitType() {
return limitType; return limitType;
} }

View File

@ -0,0 +1,30 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.basistech.df.cybertriage.autopsy.ctapi.json;
/**
* The limit type (and reset) for the license.
*/
public enum LicenseLimitType {
HOURLY,
DAILY,
WEEKLY,
MONTHLY,
NO_RESET;
}

View File

@ -0,0 +1,109 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.basistech.df.cybertriage.autopsy.ctapi.json;
import com.fasterxml.jackson.annotation.JsonProperty;
public class MetadataUploadRequest {
@JsonProperty("file_upload_url")
private String fileUploadUrl;
@JsonProperty("sha1")
private String sha1;
@JsonProperty("sha256")
private String sha256;
@JsonProperty("md5")
private String md5;
@JsonProperty("filePath")
private String filePath;
@JsonProperty("fileSize")
private Long fileSizeBytes;
@JsonProperty("createdDate")
private Long createdDate;
public String getFileUploadUrl() {
return fileUploadUrl;
}
public MetadataUploadRequest setFileUploadUrl(String fileUploadUrl) {
this.fileUploadUrl = fileUploadUrl;
return this;
}
public String getSha1() {
return sha1;
}
public MetadataUploadRequest setSha1(String sha1) {
this.sha1 = sha1;
return this;
}
public String getSha256() {
return sha256;
}
public MetadataUploadRequest setSha256(String sha256) {
this.sha256 = sha256;
return this;
}
public String getMd5() {
return md5;
}
public MetadataUploadRequest setMd5(String md5) {
this.md5 = md5;
return this;
}
public String getFilePath() {
return filePath;
}
public MetadataUploadRequest setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public Long getFileSizeBytes() {
return fileSizeBytes;
}
public MetadataUploadRequest setFileSizeBytes(Long fileSizeBytes) {
this.fileSizeBytes = fileSizeBytes;
return this;
}
public Long getCreatedDate() {
return createdDate;
}
public MetadataUploadRequest setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
return this;
}
}

View File

@ -3,7 +3,7 @@
# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template # Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
OptionsCategory_Name_CyberTriage=Cyber Triage OptionsCategory_Name_CyberTriage=Cyber Triage
OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage
LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a non-free license to use.</html> LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html>
LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from
LicenseDisclaimerPanel.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html>
LicenseDisclaimerPanel.border.title=Disclaimer LicenseDisclaimerPanel.border.title=Disclaimer
LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from

View File

@ -3,7 +3,7 @@
# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template # Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
OptionsCategory_Name_CyberTriage=Cyber Triage OptionsCategory_Name_CyberTriage=Cyber Triage
OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage
LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a non-free license to use.</html> LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html>
LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from
LicenseDisclaimerPanel.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html>
LicenseDisclaimerPanel.border.title=Disclaimer LicenseDisclaimerPanel.border.title=Disclaimer
LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from

View File

@ -6,17 +6,18 @@
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Disclaimer"> <TitledBorder title="Disclaimer">
<ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<Color PropertyName="color" blue="0" green="0" red="ff" type="rgb"/>
</TitledBorder> </TitledBorder>
</Border> </Border>
</Property> </Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[2147483647, 90]"/> <Dimension value="[2147483647, 108]"/>
</Property> </Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[562, 90]"/> <Dimension value="[562, 108]"/>
</Property> </Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[400, 90]"/> <Dimension value="[400, 108]"/>
</Property> </Property>
</Properties> </Properties>
<AuxValues> <AuxValues>
@ -29,7 +30,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,90,0,0,2,50"/> <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,108,0,0,2,50"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
@ -47,45 +48,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> <GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="purchaseFromLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.purchaseFromLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="3" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="link">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.link.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Hand Cursor"/>
</Property>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="linkMouseClicked"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
</Component> </Component>
@ -96,7 +59,7 @@
</AuxValues> </AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/> <GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/>
</Constraint> </Constraint>
</Constraints> </Constraints>
@ -113,5 +76,111 @@
</DimensionLayout> </DimensionLayout>
</Layout> </Layout>
</Container> </Container>
<Container class="javax.swing.JPanel" name="trialPanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="trialLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.trialLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="trialLink">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="getHtmlLink(TRIAL_URL)" type="code"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Hand Cursor"/>
</Property>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="trialLinkMouseClicked"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="purchasePanel">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="10" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="purchaseFromLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.purchaseFromLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="purchaseLink">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="getHtmlLink(PURCHASE_URL)" type="code"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Hand Cursor"/>
</Property>
</Properties>
<Events>
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="purchaseLinkMouseClicked"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="3" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -32,7 +32,12 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
private static final Logger LOGGER = Logger.getLogger(LicenseDisclaimerPanel.class.getName()); private static final Logger LOGGER = Logger.getLogger(LicenseDisclaimerPanel.class.getName());
private static final String CHECKOUT_PAGE_URL = "https://cybertriage.com/autopsy-checkout"; private static final String TRIAL_URL = "https://cybertriage.com/autopsy-trial";
private static final String PURCHASE_URL = "https://cybertriage.com/autopsy-checkout";
private static String getHtmlLink(String url) {
return "<html><span style=\"color: blue; text-decoration: underline\">" + url + "</span></html>";
}
/** /**
* Creates new form LicenseDisclaimerPanel * Creates new form LicenseDisclaimerPanel
@ -52,14 +57,18 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
java.awt.GridBagConstraints gridBagConstraints; java.awt.GridBagConstraints gridBagConstraints;
javax.swing.JLabel disclaimer = new javax.swing.JLabel(); javax.swing.JLabel disclaimer = new javax.swing.JLabel();
javax.swing.JLabel purchaseFromLabel = new javax.swing.JLabel();
javax.swing.JLabel link = new javax.swing.JLabel();
javax.swing.JPanel spacer = new javax.swing.JPanel(); javax.swing.JPanel spacer = new javax.swing.JPanel();
javax.swing.JPanel trialPanel = new javax.swing.JPanel();
javax.swing.JLabel trialLabel = new javax.swing.JLabel();
javax.swing.JLabel trialLink = new javax.swing.JLabel();
javax.swing.JPanel purchasePanel = new javax.swing.JPanel();
javax.swing.JLabel purchaseFromLabel = new javax.swing.JLabel();
javax.swing.JLabel purchaseLink = new javax.swing.JLabel();
setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.border.title"))); // NOI18N setBorder(javax.swing.BorderFactory.createTitledBorder(null, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.border.title"), javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Segoe UI", 0, 12), new java.awt.Color(255, 0, 0))); // NOI18N
setMaximumSize(new java.awt.Dimension(2147483647, 90)); setMaximumSize(new java.awt.Dimension(2147483647, 108));
setMinimumSize(new java.awt.Dimension(562, 90)); setMinimumSize(new java.awt.Dimension(562, 108));
setPreferredSize(new java.awt.Dimension(400, 90)); setPreferredSize(new java.awt.Dimension(400, 108));
setLayout(new java.awt.GridBagLayout()); setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(disclaimer, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.disclaimer.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(disclaimer, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.disclaimer.text")); // NOI18N
@ -67,34 +76,12 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0; gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0; gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
add(disclaimer, gridBagConstraints); add(disclaimer, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(purchaseFromLabel, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.purchaseFromLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 3);
add(purchaseFromLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(link, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.link.text")); // NOI18N
link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
link.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
linkMouseClicked(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5);
add(link, gridBagConstraints);
javax.swing.GroupLayout spacerLayout = new javax.swing.GroupLayout(spacer); javax.swing.GroupLayout spacerLayout = new javax.swing.GroupLayout(spacer);
spacer.setLayout(spacerLayout); spacer.setLayout(spacerLayout);
spacerLayout.setHorizontalGroup( spacerLayout.setHorizontalGroup(
@ -108,24 +95,94 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0; gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2; gridBagConstraints.gridy = 3;
gridBagConstraints.weighty = 1.0; gridBagConstraints.weighty = 1.0;
add(spacer, gridBagConstraints); add(spacer, gridBagConstraints);
trialPanel.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(trialLabel, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.trialLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
trialPanel.add(trialLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(trialLink, getHtmlLink(TRIAL_URL));
trialLink.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
trialLink.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
trialLinkMouseClicked(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
trialPanel.add(trialLink, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
add(trialPanel, gridBagConstraints);
purchasePanel.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(purchaseFromLabel, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.purchaseFromLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
purchasePanel.add(purchaseFromLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(purchaseLink, getHtmlLink(PURCHASE_URL));
purchaseLink.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
purchaseLink.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
purchaseLinkMouseClicked(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 0);
purchasePanel.add(purchaseLink, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
add(purchasePanel, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
private void linkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_linkMouseClicked private void purchaseLinkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_purchaseLinkMouseClicked
gotoLink(PURCHASE_URL);
}//GEN-LAST:event_purchaseLinkMouseClicked
private void trialLinkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_trialLinkMouseClicked
gotoLink(TRIAL_URL);
}//GEN-LAST:event_trialLinkMouseClicked
private void gotoLink(String url) {
if (Desktop.isDesktopSupported()) { if (Desktop.isDesktopSupported()) {
try { try {
Desktop.getDesktop().browse(new URI(CHECKOUT_PAGE_URL)); Desktop.getDesktop().browse(new URI(url));
} catch (IOException | URISyntaxException e) { } catch (IOException | URISyntaxException e) {
LOGGER.log(Level.SEVERE, "Error opening link to: " + CHECKOUT_PAGE_URL, e); LOGGER.log(Level.SEVERE, "Error opening link to: " + url, e);
} }
} else { } else {
LOGGER.log(Level.WARNING, "Desktop API is not supported. Link cannot be opened."); LOGGER.log(Level.WARNING, "Desktop API is not supported. Link cannot be opened.");
} }
}//GEN-LAST:event_linkMouseClicked }
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -24,3 +24,5 @@ CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
EULADialog.cancelButton.text=Cancel EULADialog.cancelButton.text=Cancel
EULADialog.acceptButton.text=Accept EULADialog.acceptButton.text=Accept
EULADialog.title=Cyber Triage End User License Agreement EULADialog.title=Cyber Triage End User License Agreement
CTMalwareScannerOptionsPanel.fileUploadCheckbox.text=Upload file if hash lookup produces no results
CTMalwareScannerOptionsPanel.fileUploadPanel.border.title=File Upload

View File

@ -22,6 +22,10 @@ CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text=
CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text= CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text=
CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text= CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text=
CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text= CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
CTMalwareScannerOptionsPanel_getResetSuffix_daily=/day
CTMalwareScannerOptionsPanel_getResetSuffix_hourly=/hour
CTMalwareScannerOptionsPanel_getResetSuffix_monthly=/month
CTMalwareScannerOptionsPanel_getResetSuffix_weekly=/week
CTMalwareScannerOptionsPanel_licenseAddDialog_desc=License Number: CTMalwareScannerOptionsPanel_licenseAddDialog_desc=License Number:
CTMalwareScannerOptionsPanel_licenseAddDialog_title=Add a License... CTMalwareScannerOptionsPanel_licenseAddDialog_title=Add a License...
CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc=The license number has already been entered CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc=The license number has already been entered
@ -45,9 +49,11 @@ CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining=File uploads rema
# {0} - hashLookupsRemaining # {0} - hashLookupsRemaining
CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining=Hash lookups remaining: {0} CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining=Hash lookups remaining: {0}
# {0} - maxDailyFileLookups # {0} - maxDailyFileLookups
CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day # {1} - resetSuffix
CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}{1}
# {0} - maxDailyLookups # {0} - maxDailyLookups
CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}/day # {1} - resetSuffix
CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}{1}
CTMalwareScannerOptionsPanel_MalwareScansFetcher_apiErr_title=Server Error CTMalwareScannerOptionsPanel_MalwareScansFetcher_apiErr_title=Server Error
CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_desc=A general error occurred while fetching malware scans information. Please try again later. CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_desc=A general error occurred while fetching malware scans information. Please try again later.
CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_title=General Error CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_title=General Error
@ -56,3 +62,5 @@ CTOPtionsPanel_loadMalwareScansInfo_loading=Loading...
EULADialog.cancelButton.text=Cancel EULADialog.cancelButton.text=Cancel
EULADialog.acceptButton.text=Accept EULADialog.acceptButton.text=Accept
EULADialog.title=Cyber Triage End User License Agreement EULADialog.title=Cyber Triage End User License Agreement
CTMalwareScannerOptionsPanel.fileUploadCheckbox.text=Upload file if hash lookup produces no results
CTMalwareScannerOptionsPanel.fileUploadPanel.border.title=File Upload

View File

@ -27,7 +27,7 @@ import org.openide.util.NbBundle.Messages;
/** /**
* License dialog * License dialog
*/ */
public class CTLicenseDialog extends javax.swing.JDialog { class CTLicenseDialog extends javax.swing.JDialog {
private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9\\-]+?\\s*$"); private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9\\-]+?\\s*$");
private String licenseString = null; private String licenseString = null;

View File

@ -26,7 +26,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
@ -40,6 +39,7 @@ public class CTLicensePersistence {
private static final String CT_SETTINGS_DIR = "CyberTriage"; private static final String CT_SETTINGS_DIR = "CyberTriage";
private static final String CT_LICENSE_FILENAME = "CyberTriageLicense.json"; private static final String CT_LICENSE_FILENAME = "CyberTriageLicense.json";
private static final String MALWARE_INGEST_SETTINGS_FILENAME = "MalwareIngestSettings.json";
private static final Logger logger = Logger.getLogger(CTLicensePersistence.class.getName()); private static final Logger logger = Logger.getLogger(CTLicensePersistence.class.getName());
@ -91,7 +91,44 @@ public class CTLicensePersistence {
}); });
} }
public synchronized boolean saveMalwareSettings(MalwareIngestSettings malwareIngestSettings) {
if (malwareIngestSettings != null) {
File settingsFile = getMalwareIngestFile();
try {
settingsFile.getParentFile().mkdirs();
objectMapper.writeValue(settingsFile, malwareIngestSettings);
return true;
} catch (IOException ex) {
logger.log(Level.WARNING, "There was an error writing malware ingest settings to file: " + settingsFile.getAbsolutePath(), ex);
}
}
return false;
}
public synchronized MalwareIngestSettings loadMalwareIngestSettings() {
MalwareIngestSettings settings = null;
File settingsFile = getMalwareIngestFile();
if (settingsFile.exists() && settingsFile.isFile()) {
try {
settings = objectMapper.readValue(settingsFile, MalwareIngestSettings.class);
} catch (IOException ex) {
logger.log(Level.WARNING, "There was an error reading malware ingest settings from file: " + settingsFile.getAbsolutePath(), ex);
}
}
if (settings == null) {
settings = new MalwareIngestSettings();
}
return settings;
}
private File getCTLicenseFile() { private File getCTLicenseFile() {
return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, CT_LICENSE_FILENAME).toFile(); return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, CT_LICENSE_FILENAME).toFile();
} }
private File getMalwareIngestFile() {
return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, MALWARE_INGEST_SETTINGS_FILENAME).toFile();
}
} }

View File

@ -11,11 +11,54 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-109,0,0,1,-29"/> <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-69,0,0,1,-29"/>
</AuxValues> </AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents> <SubComponents>
<Container class="javax.swing.JPanel" name="fileUploadPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="File Upload">
<ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.fileUploadPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="fileUploadCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.fileUploadCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileUploadCheckboxActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="licenseInfoPanel"> <Container class="javax.swing.JPanel" name="licenseInfoPanel">
<Properties> <Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">

View File

@ -24,6 +24,7 @@ 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.AuthTokenResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseLimitType;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil; import com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil;
import com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException; import com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException;
@ -33,6 +34,7 @@ import java.io.IOException;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -112,6 +114,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
@Override @Override
public synchronized void saveSettings() { public synchronized void saveSettings() {
ctPersistence.saveLicenseResponse(getLicenseInfo()); ctPersistence.saveLicenseResponse(getLicenseInfo());
ctPersistence.saveMalwareSettings(getIngestSettings());
} }
@Override @Override
@ -128,12 +131,27 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
if (licenseInfo != null) { if (licenseInfo != null) {
loadMalwareScansInfo(licenseInfo); loadMalwareScansInfo(licenseInfo);
} }
MalwareIngestSettings ingestSettings = ctPersistence.loadMalwareIngestSettings();
setIngestSettings(ingestSettings);
} }
private synchronized LicenseResponse getLicenseInfo() { private synchronized LicenseResponse getLicenseInfo() {
return this.licenseInfo == null ? null : this.licenseInfo.getLicenseResponse(); return this.licenseInfo == null ? null : this.licenseInfo.getLicenseResponse();
} }
private MalwareIngestSettings getIngestSettings() {
return new MalwareIngestSettings()
.setUploadFiles(this.fileUploadCheckbox.isSelected());
}
private void setIngestSettings(MalwareIngestSettings ingestSettings) {
if (ingestSettings == null) {
ingestSettings = new MalwareIngestSettings();
}
this.fileUploadCheckbox.setSelected(ingestSettings.isUploadFiles());
}
private synchronized void setLicenseDisplay(LicenseInfo licenseInfo, String licenseMessage) { private synchronized void setLicenseDisplay(LicenseInfo licenseInfo, String licenseMessage) {
this.licenseInfo = licenseInfo; this.licenseInfo = licenseInfo;
this.licenseInfoMessage = licenseMessage; this.licenseInfoMessage = licenseMessage;
@ -202,6 +220,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
private void initComponents() { private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints; java.awt.GridBagConstraints gridBagConstraints;
javax.swing.JPanel fileUploadPanel = new javax.swing.JPanel();
fileUploadCheckbox = new javax.swing.JCheckBox();
javax.swing.JPanel licenseInfoPanel = new javax.swing.JPanel(); javax.swing.JPanel licenseInfoPanel = new javax.swing.JPanel();
licenseInfoMessageLabel = new javax.swing.JLabel(); licenseInfoMessageLabel = new javax.swing.JLabel();
licenseInfoUserLabel = new javax.swing.JLabel(); licenseInfoUserLabel = new javax.swing.JLabel();
@ -218,6 +238,35 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
setLayout(new java.awt.GridBagLayout()); setLayout(new java.awt.GridBagLayout());
fileUploadPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.fileUploadPanel.border.title"))); // NOI18N
fileUploadPanel.setLayout(new java.awt.GridBagLayout());
fileUploadCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(fileUploadCheckbox, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.fileUploadCheckbox.text")); // NOI18N
fileUploadCheckbox.setMaximumSize(new java.awt.Dimension(32767, 20));
fileUploadCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
fileUploadCheckboxActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
fileUploadPanel.add(fileUploadCheckbox, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
add(fileUploadPanel, gridBagConstraints);
licenseInfoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title"))); // NOI18N licenseInfoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title"))); // NOI18N
licenseInfoPanel.setLayout(new java.awt.GridBagLayout()); licenseInfoPanel.setLayout(new java.awt.GridBagLayout());
@ -378,6 +427,10 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
} }
}//GEN-LAST:event_licenseInfoAddButtonActionPerformed }//GEN-LAST:event_licenseInfoAddButtonActionPerformed
private void fileUploadCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileUploadCheckboxActionPerformed
this.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
}//GEN-LAST:event_fileUploadCheckboxActionPerformed
@NbBundle.Messages({ @NbBundle.Messages({
"# {0} - userName", "# {0} - userName",
"# {1} - email", "# {1} - email",
@ -387,9 +440,11 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
"# {0} - idNumber", "# {0} - idNumber",
"CTMalwareScannerOptionsPanel_licenseInfo_id=ID: {0}", "CTMalwareScannerOptionsPanel_licenseInfo_id=ID: {0}",
"# {0} - maxDailyLookups", "# {0} - maxDailyLookups",
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}/day", "# {1} - resetSuffix",
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}{1}",
"# {0} - maxDailyFileLookups", "# {0} - maxDailyFileLookups",
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day", "# {1} - resetSuffix",
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}{1}",
"# {0} - countersResetDate", "# {0} - countersResetDate",
"CTMalwareScannerOptionsPanel_malwareScans_countersReset=Counters reset: {0}", "CTMalwareScannerOptionsPanel_malwareScans_countersReset=Counters reset: {0}",
"# {0} - hashLookupsRemaining", "# {0} - hashLookupsRemaining",
@ -425,7 +480,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
this.malwareScansMessageLabel.setVisible(StringUtils.isNotBlank(this.authTokenMessage)); this.malwareScansMessageLabel.setVisible(StringUtils.isNotBlank(this.authTokenMessage));
this.malwareScansMessageLabel.setText(this.authTokenMessage); this.malwareScansMessageLabel.setText(this.authTokenMessage);
if (authTokenResponse == null) { if (authTokenResponse == null || this.licenseInfo == null) {
this.maxHashLookupsLabel.setVisible(false); this.maxHashLookupsLabel.setVisible(false);
this.maxFileUploadsLabel.setVisible(false); this.maxFileUploadsLabel.setVisible(false);
this.countersResetLabel.setVisible(false); this.countersResetLabel.setVisible(false);
@ -433,15 +488,62 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
this.fileUploadsRemainingLabel.setVisible(false); this.fileUploadsRemainingLabel.setVisible(false);
} else { } else {
this.maxHashLookupsLabel.setVisible(true); this.maxHashLookupsLabel.setVisible(true);
this.maxHashLookupsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups(this.authTokenResponse.getHashLookupLimit())); this.maxHashLookupsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups(
this.authTokenResponse.getHashLookupLimit(),
getResetSuffix(this.licenseInfo.getDecryptedLicense().getLimitType())));
this.maxFileUploadsLabel.setVisible(true); this.maxFileUploadsLabel.setVisible(true);
this.maxFileUploadsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups(this.authTokenResponse.getFileUploadLimit())); this.maxFileUploadsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups(
this.authTokenResponse.getFileUploadLimit(),
getResetSuffix(this.licenseInfo.getDecryptedLicense().getLimitType())));
this.countersResetLabel.setVisible(true); this.countersResetLabel.setVisible(true);
this.countersResetLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_countersReset(this.authTokenResponse.getResetDate() == null ? "" : MALWARE_SCANS_RESET_FORMAT.format(this.authTokenResponse.getResetDate()))); this.countersResetLabel.setText(getCountersResetText(this.licenseInfo.getDecryptedLicense().getLimitType(), this.authTokenResponse));
this.hashLookupsRemainingLabel.setVisible(true); this.hashLookupsRemainingLabel.setVisible(true);
this.hashLookupsRemainingLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining(remaining(this.authTokenResponse.getHashLookupLimit(), this.authTokenResponse.getHashLookupCount()))); this.hashLookupsRemainingLabel.setText(
Bundle.CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining(
remaining(this.authTokenResponse.getHashLookupLimit(), this.authTokenResponse.getHashLookupCount())));
this.fileUploadsRemainingLabel.setVisible(true); this.fileUploadsRemainingLabel.setVisible(true);
this.fileUploadsRemainingLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining(remaining(this.authTokenResponse.getFileUploadLimit(), this.authTokenResponse.getFileUploadCount()))); this.fileUploadsRemainingLabel.setText(
Bundle.CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining(
remaining(this.authTokenResponse.getFileUploadLimit(), this.authTokenResponse.getFileUploadCount())));
}
}
private static String getCountersResetText(LicenseLimitType limitType, AuthTokenResponse authTokenResponse) {
if (limitType == null || limitType == LicenseLimitType.NO_RESET) {
return "";
} else {
return Bundle.CTMalwareScannerOptionsPanel_malwareScans_countersReset(
MALWARE_SCANS_RESET_FORMAT.format(authTokenResponse.getResetDate()));
}
}
@Messages({
"CTMalwareScannerOptionsPanel_getResetSuffix_hourly=/hour",
"CTMalwareScannerOptionsPanel_getResetSuffix_daily=/day",
"CTMalwareScannerOptionsPanel_getResetSuffix_weekly=/week",
"CTMalwareScannerOptionsPanel_getResetSuffix_monthly=/month"
})
private String getResetSuffix(LicenseLimitType limitType) {
if (limitType == null) {
return "";
}
switch (limitType) {
case HOURLY:
return Bundle.CTMalwareScannerOptionsPanel_getResetSuffix_hourly();
case DAILY:
return Bundle.CTMalwareScannerOptionsPanel_getResetSuffix_daily();
case WEEKLY:
return Bundle.CTMalwareScannerOptionsPanel_getResetSuffix_weekly();
case MONTHLY:
return Bundle.CTMalwareScannerOptionsPanel_getResetSuffix_monthly();
case NO_RESET:
default:
return "";
} }
} }
@ -501,7 +603,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
try { try {
LicenseResponse licenseResponse = get(); LicenseResponse licenseResponse = get();
SwingUtilities.invokeLater(() -> acceptEula(licenseResponse)); SwingUtilities.invokeLater(() -> acceptEula(licenseResponse));
} catch (InterruptedException ex) { } catch (InterruptedException | CancellationException ex) {
// ignore cancellation; just load current license // ignore cancellation; just load current license
setLicenseDisplay(licenseInfo, null); setLicenseDisplay(licenseInfo, null);
loadMalwareScansInfo(licenseInfo); loadMalwareScansInfo(licenseInfo);
@ -557,7 +659,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
AuthTokenResponse authTokenResponse = null; AuthTokenResponse authTokenResponse = null;
try { try {
authTokenResponse = get(); authTokenResponse = get();
} catch (InterruptedException ex) { } catch (InterruptedException | CancellationException ex) {
// ignore cancellation // ignore cancellation
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
if (ex.getCause() != null && ex.getCause() instanceof CTCloudException cloudEx) { if (ex.getCause() != null && ex.getCause() instanceof CTCloudException cloudEx) {
@ -589,6 +691,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel countersResetLabel; private javax.swing.JLabel countersResetLabel;
private javax.swing.JCheckBox fileUploadCheckbox;
private javax.swing.JLabel fileUploadsRemainingLabel; private javax.swing.JLabel fileUploadsRemainingLabel;
private javax.swing.JLabel hashLookupsRemainingLabel; private javax.swing.JLabel hashLookupsRemainingLabel;
private javax.swing.JButton licenseInfoAddButton; private javax.swing.JButton licenseInfoAddButton;

View File

@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* Dialog for displaying the Cyber Triage EULA before the license is saved. * Dialog for displaying the Cyber Triage EULA before the license is saved.
*/ */
public class EULADialog extends javax.swing.JDialog { class EULADialog extends javax.swing.JDialog {
private static final Logger LOGGER = Logger.getLogger(EULADialog.class.getName()); private static final Logger LOGGER = Logger.getLogger(EULADialog.class.getName());
private static final String EULA_RESOURCE = "EULA.htm"; private static final String EULA_RESOURCE = "EULA.htm";

View File

@ -0,0 +1,37 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
/**
* Settings for the malware ingest module.
*/
public class MalwareIngestSettings {
private boolean uploadFiles = true;
public boolean isUploadFiles() {
return uploadFiles;
}
public MalwareIngestSettings setUploadFiles(boolean uploadFiles) {
this.uploadFiles = uploadFiles;
return this;
}
}

View File

@ -21,14 +21,10 @@ package com.basistech.df.cybertriage.autopsy.malwarescan;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -36,7 +32,7 @@ import java.util.function.Consumer;
* blocks (and subsequently add and flush operations) until previous batch * blocks (and subsequently add and flush operations) until previous batch
* finishes. * finishes.
*/ */
public class BatchProcessor<T> { class BatchProcessor<T> {
private ExecutorService processingExecutorService = Executors.newSingleThreadExecutor(); private ExecutorService processingExecutorService = Executors.newSingleThreadExecutor();

View File

@ -1,3 +1,7 @@
MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_desc=Waiting for all uploaded files to complete scanning.
MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_title=Waiting for File Upload Results
MalwareScanIngestModule_longPollForNotFound_timeout_desc=There was a timeout while waiting for file uploads to be processed. Please try again later.
MalwareScanIngestModule_longPollForNotFound_timeout_title=File Upload Results Timeout
MalwareScanIngestModule_malwareTypeDisplayName=Malware MalwareScanIngestModule_malwareTypeDisplayName=Malware
# {0} - errorResponse # {0} - errorResponse
MalwareScanIngestModule_SharedProcessing_authTokenResponseError_desc=Received error: ''{0}'' when fetching the API authentication token for the license MalwareScanIngestModule_SharedProcessing_authTokenResponseError_desc=Received error: ''{0}'' when fetching the API authentication token for the license
@ -6,6 +10,8 @@ MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO
MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES
MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc=The remaining hash lookups for this license have been exhausted MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc=The remaining hash lookups for this license have been exhausted
MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title=Hash Lookups Exhausted MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title=Hash Lookups Exhausted
MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_desc=Not all files were processed because hash lookup limits were exceeded. Please try again when your limits reset.
MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_title=Lookup Limits Exceeded
MalwareScanIngestModule_SharedProcessing_flushTimeout_desc=A timeout occurred while finishing processing MalwareScanIngestModule_SharedProcessing_flushTimeout_desc=A timeout occurred while finishing processing
MalwareScanIngestModule_SharedProcessing_flushTimeout_title=Processing Timeout MalwareScanIngestModule_SharedProcessing_flushTimeout_title=Processing Timeout
MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results
@ -16,12 +22,22 @@ MalwareScanIngestModule_SharedProcessing_repServicenResponseError_title=Lookup A
MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out
MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout
# {0} - remainingLookups # {0} - remainingLookups
MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc=This license only has {0} lookups remaining MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc=This license only has {0} lookups remaining.
MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_title=Hash Lookups Low
# {0} - remainingUploads
MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_desc=This license only has {0} file uploads remaining.
MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_title=File Uploads Limit Low
MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled. MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled.
MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License
MalwareScanIngestModule_ShareProcessing_noRemaining_desc=There are no more remaining hash lookups for this license at this time. Cyber Triage processing will be disabled. MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc=There are no more remaining hash lookups for this license at this time. Malware scanning will be disabled.
MalwareScanIngestModule_ShareProcessing_noRemaining_title=No remaining lookups MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_title=No remaining lookups
MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_desc=There are no more remaining file uploads for this license at this time. File uploading will be disabled.
MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_title=No remaining file uploads
MalwareScanIngestModule_uploadFile_noRemainingFileUploads_desc=There are no more file uploads on this license at this time. File uploads will be disabled for remaining uploads.
MalwareScanIngestModule_uploadFile_noRemainingFileUploads_title=No Remaining File Uploads
# {0} - objectId
MalwareScanIngestModule_uploadFile_notUploadable_desc=A file did not meet requirements for upload (object id: {0}).
MalwareScanIngestModule_uploadFile_notUploadable_title=Not Able to Upload
MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables. MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables.
MalwareScanIngestModuleFactory_displayName=Cyber Triage Malware Scanner MalwareScanIngestModuleFactory_displayName=Cyber Triage Malware Scanner
MalwareScanIngestModuleFactory_version=1.0.0 MalwareScanIngestModuleFactory_version=1.0.0

View File

@ -19,15 +19,21 @@
package com.basistech.df.cybertriage.autopsy.malwarescan; 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.CTCloudException;
import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; 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.AuthenticatedRequestData;
import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean; import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean.Status;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence; import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HexFormat;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -36,7 +42,9 @@ import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.curator.shaded.com.google.common.collect.Lists;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
@ -49,6 +57,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.AnalysisResult; import org.sleuthkit.datamodel.AnalysisResult;
import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.HashUtility; import org.sleuthkit.datamodel.HashUtility;
import org.sleuthkit.datamodel.HashUtility.HashResult; import org.sleuthkit.datamodel.HashUtility.HashResult;
import org.sleuthkit.datamodel.HashUtility.HashType; import org.sleuthkit.datamodel.HashUtility.HashType;
@ -60,7 +69,7 @@ import org.sleuthkit.datamodel.TskData;
/** /**
* Uses CT cloud API to determine if file is malware * Uses CT cloud API to determine if file is malware
*/ */
public class MalwareScanIngestModule implements FileIngestModule { class MalwareScanIngestModule implements FileIngestModule {
private static final SharedProcessing sharedProcessing = new SharedProcessing(); private static final SharedProcessing sharedProcessing = new SharedProcessing();
@ -93,6 +102,14 @@ public class MalwareScanIngestModule implements FileIngestModule {
//minimum lookups left before issuing warning //minimum lookups left before issuing warning
private static final long LOW_LOOKUPS_REMAINING = 250; private static final long LOW_LOOKUPS_REMAINING = 250;
//minimum file uploads left before issuing warning
private static final long LOW_UPLOADS_REMAINING = 25;
private static final long MIN_UPLOAD_SIZE = 1;
private static final long MAX_UPLOAD_SIZE = 1_000_000_000;
private static final int NUM_FILE_UPLOAD_RETRIES = 7;
private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000;
private static final Set<String> EXECUTABLE_MIME_TYPES = Stream.of( private static final Set<String> EXECUTABLE_MIME_TYPES = Stream.of(
"application/x-bat",//NON-NLS "application/x-bat",//NON-NLS
"application/x-dosexec",//NON-NLS "application/x-dosexec",//NON-NLS
@ -111,37 +128,54 @@ public class MalwareScanIngestModule implements FileIngestModule {
private static final String MALWARE_CONFIG = "Cyber Triage Cloud"; private static final String MALWARE_CONFIG = "Cyber Triage Cloud";
private static final Logger logger = Logger.getLogger(MalwareScanIngestModule.class.getName()); private static final Logger logger = Logger.getLogger(MalwareScanIngestModule.class.getName());
private final BatchProcessor<FileRecord> batchProcessor = new BatchProcessor<FileRecord>(BATCH_SIZE, FLUSH_SECS_TIMEOUT, this::handleBatch);
private final BatchProcessor<FileRecord> batchProcessor = new BatchProcessor<FileRecord>(
BATCH_SIZE,
FLUSH_SECS_TIMEOUT,
(lst) -> SharedProcessing.this.handleBatch(SharedProcessing.this.ingestJobState, lst));
private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance(); private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance();
private final CTApiDAO ctApiDAO = CTApiDAO.getInstance(); private final CTApiDAO ctApiDAO = CTApiDAO.getInstance();
private RunState runState = null; private IngestJobState ingestJobState = null;
private SleuthkitCase tskCase = null;
private FileTypeDetector fileTypeDetector = null;
private LicenseInfo licenseInfo = null;
private BlackboardArtifact.Type malwareType = null;
private long dsId = 0;
private long ingestJobId = 0;
@Messages({ @Messages({
"MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low",
"# {0} - remainingLookups",
"MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc=This license only has {0} lookups remaining",
"MalwareScanIngestModule_malwareTypeDisplayName=Malware", "MalwareScanIngestModule_malwareTypeDisplayName=Malware",
"MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License", "MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License",
"MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled.", "MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled.",
"MalwareScanIngestModule_ShareProcessing_noRemaining_title=No remaining lookups", "MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_title=No remaining lookups",
"MalwareScanIngestModule_ShareProcessing_noRemaining_desc=There are no more remaining hash lookups for this license at this time. Cyber Triage processing will be disabled." "MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc=There are no more remaining hash lookups for this license at this time. Malware scanning will be disabled.",
}) "MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_title=Hash Lookups Low",
"# {0} - remainingLookups",
"MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc=This license only has {0} lookups remaining.",
"MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_title=No remaining file uploads",
"MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_desc=There are no more remaining file uploads for this license at this time. File uploading will be disabled.",
"MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_title=File Uploads Limit Low",
"# {0} - remainingUploads",
"MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_desc=This license only has {0} file uploads remaining.",})
synchronized void startUp(IngestJobContext context) throws IngestModuleException { synchronized void startUp(IngestJobContext context) throws IngestModuleException {
// only run this code once per startup // only run this code once per startup
if (runState == RunState.STARTED_UP || runState == RunState.DISABLED) { if (ingestJobState != null) {
return; return;
} }
try { try {
ingestJobState = getNewJobState(context);
} catch (Exception ex) {
ingestJobState = IngestJobState.DISABLED;
throw new IngestModuleException("An exception occurred on MalwareScanIngestModule startup", ex);
}
}
/**
* Sets up the state necessary for a new ingest job.
*
* @param context The ingest job context.
* @return A pair of the runtime state (i.e. started up, disabled) and
* parameters required for the job.
* @throws Exception
*/
private IngestJobState getNewJobState(IngestJobContext context) throws Exception {
// get saved license // get saved license
Optional<LicenseInfo> licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo(); Optional<LicenseInfo> licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo();
if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) { if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) {
@ -149,8 +183,8 @@ public class MalwareScanIngestModule implements FileIngestModule {
Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_title(), Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_title(),
Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_desc(), Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_desc(),
null); null);
runState = RunState.DISABLED;
return; return IngestJobState.DISABLED;
} }
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense()); AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense());
@ -160,80 +194,177 @@ public class MalwareScanIngestModule implements FileIngestModule {
long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount()); long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
if (lookupsRemaining <= 0) { if (lookupsRemaining <= 0) {
notifyWarning( notifyWarning(
Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_title(), Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_title(),
Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_desc(), Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc(),
null); null);
runState = RunState.DISABLED;
return; return IngestJobState.DISABLED;
} else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) { } else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) {
notifyWarning( notifyWarning(
Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title(), Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_title(),
Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc(lookupsRemaining), Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc(lookupsRemaining),
null); null);
} }
// determine lookups remaining
boolean uploadFiles = ctSettingsPersistence.loadMalwareIngestSettings().isUploadFiles();
if (uploadFiles) {
long uploadsRemaining = remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount());
if (uploadsRemaining <= 0) {
notifyWarning(
Bundle.MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_title(),
Bundle.MalwareScanIngestModule_ShareProcessing_noUploadsRemaining_desc(),
null);
uploadFiles = false;
} else if (lookupsRemaining < LOW_UPLOADS_REMAINING) {
notifyWarning(
Bundle.MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_title(),
Bundle.MalwareScanIngestModule_ShareProcessing_lowUploadsLimitWarning_desc(lookupsRemaining),
null);
}
}
// setup necessary variables for processing // setup necessary variables for processing
tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
malwareType = tskCase.getBlackboard().getOrAddArtifactType( BlackboardArtifact.Type malwareType = tskCase.getBlackboard().getOrAddArtifactType(
MALWARE_TYPE_NAME, MALWARE_TYPE_NAME,
Bundle.MalwareScanIngestModule_malwareTypeDisplayName(), Bundle.MalwareScanIngestModule_malwareTypeDisplayName(),
BlackboardArtifact.Category.ANALYSIS_RESULT); BlackboardArtifact.Category.ANALYSIS_RESULT);
fileTypeDetector = new FileTypeDetector();
dsId = context.getDataSource().getId();
ingestJobId = context.getJobId();
licenseInfo = licenseInfoOpt.get();
// set run state to initialized return new IngestJobState(
runState = RunState.STARTED_UP; context,
} catch (Exception ex) { tskCase,
runState = RunState.DISABLED; new PathNormalizer(tskCase),
throw new IngestModuleException("An exception occurred on MalwareScanIngestModule startup", ex); new FileTypeDetector(),
} licenseInfoOpt.get(),
malwareType,
uploadFiles,
true
);
} }
/**
* Determines remaining given a possibly null limit and used count.
*
* @param limit The limit (can be null).
* @param used The number used (can be null).
* @return The remaining amount.
*/
private static long remaining(Long limit, Long used) { private static long remaining(Long limit, Long used) {
limit = limit == null ? 0 : limit; limit = limit == null ? 0 : limit;
used = used == null ? 0 : used; used = used == null ? 0 : used;
return limit - used; return limit - used;
} }
private String getOrCalcHash(AbstractFile af) { /**
* Gets the md5 hash from the abstract file or calculates it.
*
* @param af The abstract file.
* @return The md5 hash (or null if could not be determined).
*/
private static String getOrCalcHash(AbstractFile af, HashType hashType) {
switch (hashType) {
case MD5:
if (StringUtils.isNotBlank(af.getMd5Hash())) { if (StringUtils.isNotBlank(af.getMd5Hash())) {
return af.getMd5Hash(); return af.getMd5Hash();
} }
break;
case SHA256:
if (StringUtils.isNotBlank(af.getSha256Hash())) {
return af.getSha256Hash();
}
}
try { try {
List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(HashType.MD5)); List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(hashType));
if (CollectionUtils.isNotEmpty(hashResults)) { if (CollectionUtils.isNotEmpty(hashResults)) {
for (HashResult hashResult : hashResults) { for (HashResult hashResult : hashResults) {
if (hashResult.getType() == HashType.MD5) { if (hashResult.getType() == hashType) {
return hashResult.getValue(); return hashResult.getValue();
} }
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.WARNING, logger.log(Level.WARNING,
MessageFormat.format("An error occurred while processing file name: {0} and obj id: {1}.", MessageFormat.format("An error occurred while processing hash for file name: {0} and obj id: {1} and hash type {2}.",
af.getName(), af.getName(),
af.getId()), af.getId(),
hashType.name()),
ex); ex);
} }
return null; return null;
} }
/**
* Gets or calculates the md5 for a file.
*
* @param af The file.
* @return The hash.
*/
private static String getOrCalcMd5(AbstractFile af) {
return getOrCalcHash(af, HashType.MD5);
}
/**
* Gets or calculates the sha256 for a file.
*
* @param af The file.
* @return The hash.
*/
private static String getOrCalcSha256(AbstractFile af) {
return getOrCalcHash(af, HashType.SHA256);
}
/**
* Gets or calculates the sha1 for a file.
*
* @param af The file.
* @return The hash.
*/
private static String getOrCalcSha1(AbstractFile af) throws NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
if (StringUtils.isNotBlank(af.getSha1Hash())) {
return af.getSha1Hash();
}
// taken from https://stackoverflow.com/questions/6293713/java-how-to-create-sha-1-for-a-file
MessageDigest digest = MessageDigest.getInstance("SHA-1");
ReadContentInputStream afStream = new ReadContentInputStream(af);
int n = 0;
byte[] buffer = new byte[8192];
while (n != -1) {
n = afStream.read(buffer);
if (n > 0) {
digest.update(buffer, 0, n);
}
}
byte[] hashBytes = digest.digest();
String hashString = HexFormat.of().formatHex(hashBytes);
return hashString;
}
/**
* Processes a file. The file goes through the lookup process if the
* file meets acceptable criteria: 1) not FileKnown.KNOWN 2) is
* executable 3) does not have any pre-existing TSK_MALWARE results 4)
* file lookup has not been disabled.
*
* @param af The file.
* @return OK or ERROR.
*/
@Messages({ @Messages({
"MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout", "MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout",
"MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out" "MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out"
}) })
IngestModule.ProcessResult process(AbstractFile af) { IngestModule.ProcessResult process(AbstractFile af) {
try { try {
if (runState == RunState.STARTED_UP if (ingestJobState != null
&& ingestJobState.isDoFileLookups()
&& !ingestJobState.getIngestJobContext().fileIngestIsCancelled()
&& af.getKnown() != TskData.FileKnown.KNOWN && af.getKnown() != TskData.FileKnown.KNOWN
&& EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(fileTypeDetector.getMIMEType(af)).trim().toLowerCase()) && EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(ingestJobState.getFileTypeDetector().getMIMEType(af)).trim().toLowerCase())
&& CollectionUtils.isEmpty(af.getAnalysisResults(malwareType))) { && CollectionUtils.isEmpty(af.getAnalysisResults(ingestJobState.getMalwareType()))) {
String md5 = getOrCalcHash(af); String md5 = getOrCalcMd5(af);
if (StringUtils.isNotBlank(md5)) { if (StringUtils.isNotBlank(md5)) {
batchProcessor.add(new FileRecord(af.getId(), md5)); batchProcessor.add(new FileRecord(af.getId(), md5));
} }
@ -254,6 +385,13 @@ public class MalwareScanIngestModule implements FileIngestModule {
} }
} }
/**
* Handles a batch of files to be sent to CT file lookup for results.
*
* @param ingestJobState The current state of operation for the ingest
* job.
* @param fileRecords The file records to be uploaded.
*/
@Messages({ @Messages({
"MalwareScanIngestModule_SharedProcessing_authTokenResponseError_title=Authentication API error", "MalwareScanIngestModule_SharedProcessing_authTokenResponseError_title=Authentication API error",
"# {0} - errorResponse", "# {0} - errorResponse",
@ -265,8 +403,12 @@ public class MalwareScanIngestModule implements FileIngestModule {
"MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc=The remaining hash lookups for this license have been exhausted", "MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc=The remaining hash lookups for this license have been exhausted",
"MalwareScanIngestModule_SharedProcessing_generalProcessingError_title=Hash Lookup Error", "MalwareScanIngestModule_SharedProcessing_generalProcessingError_title=Hash Lookup Error",
"MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results",}) "MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results",})
private void handleBatch(List<FileRecord> fileRecords) { private void handleBatch(IngestJobState ingestJobState, List<FileRecord> fileRecords) {
if (runState != RunState.STARTED_UP || fileRecords == null || fileRecords.isEmpty()) { if (ingestJobState == null
|| !ingestJobState.isDoFileLookups()
|| ingestJobState.getIngestJobContext().fileIngestIsCancelled()
|| fileRecords == null
|| fileRecords.isEmpty()) {
return; return;
} }
@ -278,11 +420,10 @@ public class MalwareScanIngestModule implements FileIngestModule {
continue; continue;
} }
String sanitizedMd5 = sanitizedMd5(fr.getMd5hash()); String sanitizedMd5 = normalizedMd5(fr.getMd5hash());
md5ToObjId md5ToObjId
.computeIfAbsent(sanitizedMd5, (k) -> new ArrayList<>()) .computeIfAbsent(sanitizedMd5, (k) -> new ArrayList<>())
.add(fr.getObjId()); .add(fr.getObjId());
} }
List<String> md5Hashes = new ArrayList<>(md5ToObjId.keySet()); List<String> md5Hashes = new ArrayList<>(md5ToObjId.keySet());
@ -292,40 +433,347 @@ public class MalwareScanIngestModule implements FileIngestModule {
} }
try { try {
List<CTCloudBean> repResult = getHashLookupResults(ingestJobState, md5Hashes);
handleLookupResults(ingestJobState, md5ToObjId, repResult);
} catch (Exception ex) {
notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
ex);
}
}
/**
* Handles results received from CT Cloud.
*
* @param ingestJobState The current state of operations of the ingest
* module.
* @param md5ToObjId The mapping of md5 to a list of object ids.
* @param repResult The ct cloud results.
* @throws org.sleuthkit.datamodel.Blackboard.BlackboardException
* @throws TskCoreException
*/
@Messages({
"MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_title=Lookup Limits Exceeded",
"MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_desc=Not all files were processed because hash lookup limits were exceeded. Please try again when your limits reset.",})
private void handleLookupResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> repResult) throws Blackboard.BlackboardException, TskCoreException, TskCoreException, CTCloudException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
if (CollectionUtils.isEmpty(repResult)) {
return;
}
Map<Status, List<CTCloudBean>> statusGroupings = repResult.stream()
.filter(bean -> bean.getMalwareResult() != null)
.collect(Collectors.groupingBy(bean -> bean.getMalwareResult().getStatus()));
// for all found items, create analysis results
List<CTCloudBean> found = statusGroupings.get(Status.FOUND);
createAnalysisResults(ingestJobState, found, md5ToObjId);
// if being scanned, check list to run later
handleNonFoundResults(ingestJobState, md5ToObjId, statusGroupings.get(Status.BEING_SCANNED), false);
// if not found, try upload
handleNonFoundResults(ingestJobState, md5ToObjId, statusGroupings.get(Status.NOT_FOUND), true);
// indicate a general error if some result in an error
if (CollectionUtils.isNotEmpty(statusGroupings.get(Status.ERROR))) {
notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
null);
}
// indicate some results were not processed if limits exceeded in results
if (CollectionUtils.isNotEmpty(statusGroupings.get(Status.LIMITS_EXCEEDED))) {
notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedResultsHashLookups_desc(),
null);
}
}
/**
* Handles a CT cloud response objects that have a status that isn't
* FOUND but still are queryable (i.e. NOT_FOUND, BEING_SCANNED).
*
* @param ingestJobState The current state of operations of the ingest
* module.
* @param md5ToObjId The mapping of md5 to a list of object ids.
* @param results The ct cloud results.
* @param performFileUpload True if the class of results warrants file
* upload (i.e. NOT_FOUND)
*/
private void handleNonFoundResults(IngestJobState ingestJobState, Map<String, List<Long>> md5ToObjId, List<CTCloudBean> results, boolean performFileUpload) throws CTCloudException, TskCoreException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
if (CollectionUtils.isNotEmpty(results)
&& ingestJobState.isDoFileLookups()
&& ((performFileUpload && ingestJobState.isUploadUnknownFiles()) || (!performFileUpload && ingestJobState.isQueryForMissing()))) {
for (CTCloudBean beingScanned : CollectionUtils.emptyIfNull(results)) {
String sanitizedMd5 = normalizedMd5(beingScanned.getMd5HashValue());
if (StringUtils.isBlank(sanitizedMd5)) {
continue;
}
List<Long> correspondingObjIds = md5ToObjId.get(sanitizedMd5);
if (CollectionUtils.isEmpty(correspondingObjIds)) {
continue;
}
if (performFileUpload) {
uploadFile(ingestJobState, sanitizedMd5, correspondingObjIds.get(0));
}
ingestJobState.getUnidentifiedHashes().put(sanitizedMd5, correspondingObjIds);
}
}
}
/**
* Makes CT Cloud REST API query for results regarding the status of a
* list of md5 hashes for executables.
*
* @param ingestJobState The current state of operations of the ingest
* module.
* @param md5Hashes The md5 hashes to check.
* @return The results from CT Cloud.
* @throws CTCloudException
*/
private List<CTCloudBean> getHashLookupResults(IngestJobState ingestJobState, List<String> md5Hashes) throws CTCloudException {
if (ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return Collections.emptyList();
}
// get an auth token with the license // get an auth token with the license
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense()); AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(ingestJobState.getLicenseInfo().getDecryptedLicense());
// make sure we are in bounds for the remaining scans // make sure we are in bounds for the remaining scans
long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount()); long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
if (remainingScans <= 0) { if (remainingScans <= 0) {
runState = RunState.DISABLED; ingestJobState.disableDoFileLookups();
notifyWarning( notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(), Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(), Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
null); null);
return; return Collections.emptyList();
} else if (ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return Collections.emptyList();
}
// while we have a valid auth token, also check file uploads.
if (ingestJobState.isUploadUnknownFiles()) {
long remainingUploads = remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount());
if (remainingUploads <= 0) {
ingestJobState.disableUploadUnknownFiles();
notifyWarning(
Bundle.MalwareScanIngestModule_uploadFile_noRemainingFileUploads_title(),
Bundle.MalwareScanIngestModule_uploadFile_noRemainingFileUploads_desc(),
null);
}
} }
// using auth token, get results // using auth token, get results
List<CTCloudBean> repResult = ctApiDAO.getReputationResults( return ctApiDAO.getReputationResults(
new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse), new AuthenticatedRequestData(ingestJobState.getLicenseInfo().getDecryptedLicense(), authTokenResponse),
md5Hashes md5Hashes
); );
}
/**
* Normalizes an md5 string for the purposes of lookup in a map.
*
* @param orig The original value.
* @return The normalized value
*/
private static String normalizedMd5(String orig) {
return StringUtils.defaultString(orig).trim().toLowerCase();
}
/**
* Whether or not an abstract file meets the requirements to be
* uploaded.
*
* @param af The abstract file.
* @return True if can be uploaded.
*/
private static boolean isUploadable(AbstractFile af) {
long size = af.getSize();
return size >= MIN_UPLOAD_SIZE && size <= MAX_UPLOAD_SIZE;
}
/**
* Uploads a file to CT Cloud if the file is valid for upload.
*
* @param ingestJobState The current state of the ingest job.
* @param objId The object id of the file to upload to CT cloud.
* @return True if successfully uploaded.
* @throws CTCloudException
* @throws TskCoreException
*/
@Messages({
"MalwareScanIngestModule_uploadFile_notUploadable_title=Not Able to Upload",
"# {0} - objectId",
"MalwareScanIngestModule_uploadFile_notUploadable_desc=A file did not meet requirements for upload (object id: {0}).",
"MalwareScanIngestModule_uploadFile_noRemainingFileUploads_title=No Remaining File Uploads",
"MalwareScanIngestModule_uploadFile_noRemainingFileUploads_desc=There are no more file uploads on this license at this time. File uploads will be disabled for remaining uploads.",})
private boolean uploadFile(IngestJobState ingestJobState, String md5, long objId) throws CTCloudException, TskCoreException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
if (!ingestJobState.isUploadUnknownFiles() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return false;
}
AbstractFile af = ingestJobState.getTskCase().getAbstractFileById(objId);
if (af == null) {
return false;
}
if (!isUploadable(af)) {
notifyWarning(
Bundle.MalwareScanIngestModule_uploadFile_notUploadable_title(),
Bundle.MalwareScanIngestModule_uploadFile_notUploadable_desc(objId),
null);
return false;
}
// get auth token / file upload url
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(ingestJobState.getLicenseInfo().getDecryptedLicense(), true);
if (StringUtils.isBlank(authTokenResponse.getFileUploadUrl())) {
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR);
} else if (remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount()) <= 0) {
// don't proceed with upload if reached limit
ingestJobState.disableUploadUnknownFiles();
notifyWarning(
Bundle.MalwareScanIngestModule_uploadFile_noRemainingFileUploads_title(),
Bundle.MalwareScanIngestModule_uploadFile_noRemainingFileUploads_desc(),
null);
return false;
} else if (ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return false;
}
// upload bytes
ReadContentInputStream fileInputStream = new ReadContentInputStream(af);
ctApiDAO.uploadFile(authTokenResponse.getFileUploadUrl(), af.getName(), fileInputStream);
// upload metadata
MetadataUploadRequest metaRequest = new MetadataUploadRequest()
.setCreatedDate(af.getCrtime() == 0 ? null : af.getCrtime())
.setFilePath(ingestJobState.getPathNormalizer().normalizePath(af.getUniquePath()))
.setFileSizeBytes(af.getSize())
.setFileUploadUrl(authTokenResponse.getFileUploadUrl())
.setMd5(md5)
.setSha1(getOrCalcSha1(af))
.setSha256(getOrCalcSha256(af));
ctApiDAO.uploadMeta(new AuthenticatedRequestData(ingestJobState.getLicenseInfo().getDecryptedLicense(), authTokenResponse), metaRequest);
return true;
}
/**
* Does long polling for any pending results.
*
* @param ingestJobState The state of the ingest job.
* @throws InterruptedException
* @throws CTCloudException
* @throws org.sleuthkit.datamodel.Blackboard.BlackboardException
* @throws TskCoreException
*/
@Messages({
"MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_title=Waiting for File Upload Results",
"MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_desc=Waiting for all uploaded files to complete scanning.",
"MalwareScanIngestModule_longPollForNotFound_timeout_title=File Upload Results Timeout",
"MalwareScanIngestModule_longPollForNotFound_timeout_desc=There was a timeout while waiting for file uploads to be processed. Please try again later.",})
private void longPollForNotFound(IngestJobState ingestJobState) throws InterruptedException, CTCloudException, Blackboard.BlackboardException, TskCoreException {
if (!ingestJobState.isDoFileLookups()
|| !ingestJobState.isQueryForMissing()
|| MapUtils.isEmpty(ingestJobState.getUnidentifiedHashes())
|| ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return;
}
MessageNotifyUtil.Notify.info(
Bundle.MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_title(),
Bundle.MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_desc()
);
Map<String, List<Long>> remaining = new HashMap<>(ingestJobState.getUnidentifiedHashes());
for (int retry = 0; retry < NUM_FILE_UPLOAD_RETRIES; retry++) {
List<List<String>> md5Batches = Lists.partition(new ArrayList<>(remaining.keySet()), BATCH_SIZE);
for (List<String> batch : md5Batches) {
// if we have exceeded limits or cancelled, then we're done.
if (!ingestJobState.isDoFileLookups() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return;
}
List<CTCloudBean> repResult = getHashLookupResults(ingestJobState, batch);
Map<Status, List<CTCloudBean>> statusGroupings = repResult.stream()
.filter(bean -> bean.getMalwareResult() != null)
.collect(Collectors.groupingBy(bean -> bean.getMalwareResult().getStatus()));
// for all found items, create analysis results
List<CTCloudBean> found = statusGroupings.get(Status.FOUND);
createAnalysisResults(ingestJobState, found, remaining);
// remove any found items from the list of items to long poll for
for (CTCloudBean foundItem : CollectionUtils.emptyIfNull(found)) {
String normalizedMd5 = normalizedMd5(foundItem.getMd5HashValue());
remaining.remove(normalizedMd5);
}
}
if (remaining.isEmpty()) {
return;
}
// exponential backoff before trying again
long waitMultiplier = ((long) Math.pow(2, retry));
for (int i = 0; i < waitMultiplier; i++) {
if (!ingestJobState.isDoFileLookups() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return;
}
Thread.sleep(FILE_UPLOAD_RETRY_SLEEP_MILLIS);
}
}
notifyWarning(
Bundle.MalwareScanIngestModule_longPollForNotFound_timeout_title(),
Bundle.MalwareScanIngestModule_longPollForNotFound_timeout_desc(),
null
);
}
/**
* Creates TSK_MALWARE analysis results based on a list of cloud beans
* received from the CT cloud api.
*
* @param ingestJobState The ingest job state.
* @param repResult The list of cloud beans. Only cloud beans with a
* malware status
* @param md5ToObjId The mapping of md5
* @throws org.sleuthkit.datamodel.Blackboard.BlackboardException
* @throws TskCoreException
*/
private void createAnalysisResults(IngestJobState ingestJobState, List<CTCloudBean> repResult, Map<String, List<Long>> md5ToObjId) throws Blackboard.BlackboardException, TskCoreException {
if (CollectionUtils.isEmpty(repResult)) {
return;
}
List<BlackboardArtifact> createdArtifacts = new ArrayList<>(); List<BlackboardArtifact> createdArtifacts = new ArrayList<>();
if (!CollectionUtils.isEmpty(repResult)) {
SleuthkitCase.CaseDbTransaction trans = null; SleuthkitCase.CaseDbTransaction trans = null;
try { try {
trans = tskCase.beginTransaction(); trans = ingestJobState.getTskCase().beginTransaction();
for (CTCloudBean result : repResult) { for (CTCloudBean result : repResult) {
String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue()); String sanitizedMd5 = normalizedMd5(result.getMd5HashValue());
List<Long> objIds = md5ToObjId.remove(sanitizedMd5); List<Long> objIds = md5ToObjId.remove(sanitizedMd5);
if (objIds == null || objIds.isEmpty()) { if (CollectionUtils.isEmpty(objIds)) {
continue; continue;
} }
for (Long objId : objIds) { for (Long objId : objIds) {
AnalysisResult res = createAnalysisResult(objId, result, trans); AnalysisResult res = createAnalysisResult(ingestJobState, trans, result, objId);
if (res != null) { if (res != null) {
createdArtifacts.add(res); createdArtifacts.add(res);
} }
@ -343,27 +791,40 @@ public class MalwareScanIngestModule implements FileIngestModule {
} }
if (!CollectionUtils.isEmpty(createdArtifacts)) { if (!CollectionUtils.isEmpty(createdArtifacts)) {
tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId); ingestJobState.getTskCase().getBlackboard().postArtifacts(
} createdArtifacts,
} Bundle.MalwareScanIngestModuleFactory_displayName(),
} catch (Exception ex) { ingestJobState.getIngestJobId()
notifyWarning( );
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
ex);
}
} }
private String sanitizedMd5(String orig) {
return StringUtils.defaultString(orig).trim().toLowerCase();
} }
/**
* Creates an analysis result for the given information.
*
* @param ingestJobState The state of the ingest job.
* @param trans The case database transaction to use.
* @param cloudBean The bean indicating the malware result.
* @param objId The object id of the corresponding file that will
* receive the analysis result.
* @return The created analysis result or null if none created.
* @throws org.sleuthkit.datamodel.Blackboard.BlackboardException
*/
@Messages({ @Messages({
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES", "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES",
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO" "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO"
}) })
private AnalysisResult createAnalysisResult(Long objId, CTCloudBean cloudBean, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException { private AnalysisResult createAnalysisResult(IngestJobState ingestJobState, SleuthkitCase.CaseDbTransaction trans, CTCloudBean cloudBean, Long objId) throws Blackboard.BlackboardException {
if (objId == null || cloudBean == null || cloudBean.getMalwareResult() == null) { if (objId == null || cloudBean == null || cloudBean.getMalwareResult() == null || cloudBean.getMalwareResult().getStatus() != Status.FOUND) {
logger.log(Level.WARNING, MessageFormat.format("Attempting to create analysis result with invalid parameters [objId: {0}, cloud bean status: {1}]",
objId == null
? "<null>"
: objId,
(cloudBean == null || cloudBean.getMalwareResult() == null || cloudBean.getMalwareResult().getStatus() == null)
? "<null>"
: cloudBean.getMalwareResult().getStatus().name()
));
return null; return null;
} }
@ -377,10 +838,10 @@ public class MalwareScanIngestModule implements FileIngestModule {
String justification = cloudBean.getMalwareResult().getStatusDescription(); String justification = cloudBean.getMalwareResult().getStatusDescription();
return tskCase.getBlackboard().newAnalysisResult( return ingestJobState.getTskCase().getBlackboard().newAnalysisResult(
malwareType, ingestJobState.getMalwareType(),
objId, objId,
dsId, ingestJobState.getDsId(),
score, score,
conclusion, conclusion,
MALWARE_CONFIG, MALWARE_CONFIG,
@ -389,39 +850,52 @@ public class MalwareScanIngestModule implements FileIngestModule {
trans).getAnalysisResult(); trans).getAnalysisResult();
} }
/**
* Called when ingest should shut down.
*/
@Messages({ @Messages({
"MalwareScanIngestModule_SharedProcessing_flushTimeout_title=Processing Timeout", "MalwareScanIngestModule_SharedProcessing_flushTimeout_title=Processing Timeout",
"MalwareScanIngestModule_SharedProcessing_flushTimeout_desc=A timeout occurred while finishing processing" "MalwareScanIngestModule_SharedProcessing_flushTimeout_desc=A timeout occurred while finishing processing"
}) })
synchronized void shutDown() { synchronized void shutDown() {
// if already shut down, return // if already shut down, return
if (runState == RunState.SHUT_DOWN) { if (ingestJobState == null) {
return; return;
} }
// flush any remaining items // flush any remaining items
try { try {
batchProcessor.flushAndReset(); batchProcessor.flushAndReset();
longPollForNotFound(ingestJobState);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
notifyWarning( notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_title(), Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_desc(), Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_desc(),
ex); ex);
} catch (Exception ex) {
notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
ex);
} finally { } finally {
// set state to shut down and clear any remaining // set state to shut down and clear any remaining
runState = RunState.SHUT_DOWN; ingestJobState = null;
} }
} }
private void notifyWarning(String title, String message, Exception ex) { /**
* Creates a warning notification to display in the lower right corner
* and a corresponding log message.
*
* @param title The title of the warning.
* @param message The message of the warning.
* @param ex The corresponding exception (or null if none).
*/
private static void notifyWarning(String title, String message, Exception ex) {
MessageNotifyUtil.Notify.warn(title, message); MessageNotifyUtil.Notify.warn(title, message);
logger.log(Level.WARNING, message, ex); logger.log(Level.WARNING, message, ex);
} }
private enum RunState {
STARTED_UP, DISABLED, SHUT_DOWN
}
class FileRecord { class FileRecord {
private final long objId; private final long objId;
@ -441,5 +915,116 @@ public class MalwareScanIngestModule implements FileIngestModule {
} }
} }
/**
* Represents the state of the current ingest job.
*
* NOTE: if doFileLookups is false, most variables will likely be null
* (TSK case, file type detector, etc.) and should not be used. The
* contract for this class should be that if doFileLookups is true or
* uploadUnknownFiles is true, the remaining variables should be non
* null, if doFileLookups is false and uploadUnknownFiles is false, no
* other access to this class can be made reliably.
*/
static class IngestJobState {
static final IngestJobState DISABLED = new IngestJobState(
null,
null,
null,
null,
null,
null,
false,
false
);
private final SleuthkitCase tskCase;
private final FileTypeDetector fileTypeDetector;
private final LicenseInfo licenseInfo;
private final BlackboardArtifact.Type malwareType;
private final long dsId;
private final long ingestJobId;
private final boolean queryForMissing;
private final Map<String, List<Long>> unidentifiedHashes = new HashMap<>();
// this can change mid run
private boolean uploadUnknownFiles;
private boolean doFileLookups;
private final IngestJobContext ingestJobContext;
private final PathNormalizer pathNormalizer;
IngestJobState(IngestJobContext ingestJobContext, SleuthkitCase tskCase, PathNormalizer pathNormalizer, FileTypeDetector fileTypeDetector, LicenseInfo licenseInfo, BlackboardArtifact.Type malwareType, boolean uploadUnknownFiles, boolean doFileLookups) {
this.tskCase = tskCase;
this.fileTypeDetector = fileTypeDetector;
this.pathNormalizer = pathNormalizer;
this.licenseInfo = licenseInfo;
this.malwareType = malwareType;
this.dsId = ingestJobContext == null ? 0L : ingestJobContext.getDataSource().getId();
this.ingestJobId = ingestJobContext == null ? 0L : ingestJobContext.getJobId();
this.ingestJobContext = ingestJobContext;
// for now, querying for any missing files will be tied to whether initially we should upload files and do lookups at all
this.queryForMissing = uploadUnknownFiles && doFileLookups;
this.uploadUnknownFiles = uploadUnknownFiles;
this.doFileLookups = doFileLookups;
}
SleuthkitCase getTskCase() {
return tskCase;
}
IngestJobContext getIngestJobContext() {
return ingestJobContext;
}
FileTypeDetector getFileTypeDetector() {
return fileTypeDetector;
}
LicenseInfo getLicenseInfo() {
return licenseInfo;
}
BlackboardArtifact.Type getMalwareType() {
return malwareType;
}
long getDsId() {
return dsId;
}
long getIngestJobId() {
return ingestJobId;
}
Map<String, List<Long>> getUnidentifiedHashes() {
return unidentifiedHashes;
}
boolean isQueryForMissing() {
return queryForMissing;
}
boolean isUploadUnknownFiles() {
return uploadUnknownFiles;
}
void disableUploadUnknownFiles() {
this.uploadUnknownFiles = false;
}
boolean isDoFileLookups() {
return doFileLookups;
}
void disableDoFileLookups() {
this.doFileLookups = false;
}
public PathNormalizer getPathNormalizer() {
return pathNormalizer;
}
}
} }
} }

View File

@ -0,0 +1,201 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2023 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.basistech.df.cybertriage.autopsy.malwarescan;
import com.google.common.net.InetAddresses;
import java.net.InetAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility class to anonymize paths.
*/
class PathNormalizer {
private static final Logger LOGGER = Logger.getLogger(PathNormalizer.class.getName());
private static final String ANONYMIZED_USERNAME = "<user>";
private static final String ANONYMIZED_IP = "<private_ip>";
private static final String ANONYMIZED_HOSTNAME = "<hostname>";
private static final String FORWARD_SLASH = "/";
private static final String BACK_SLASH = "\\";
private static final Pattern USER_PATH_FORWARD_SLASH_REGEX = Pattern.compile("(?<!all )([/]{0,1}\\Qusers\\E/)(?!(public|Default|defaultAccount|All Users))([^/]+)(/){0,1}", Pattern.CASE_INSENSITIVE);
private static final Pattern USER_PATH_BACK_SLASH_REGEX = Pattern.compile("(?<!all )([\\\\]{0,1}\\Qusers\\E\\\\)(?!(public|Default|defaultAccount|All Users))([^\\\\]+)([\\\\]){0,1}", Pattern.CASE_INSENSITIVE);
private static final Pattern USER_PATH_FORWARD_SLASH_REGEX_XP = Pattern.compile("([/]{0,1}\\Qdocuments and settings\\E/)(?!(Default User|All Users))([^/]+)(/){0,1}", Pattern.CASE_INSENSITIVE);
private static final Pattern USER_PATH_BACK_SLASH_REGEX_XP = Pattern.compile("([\\\\]{0,1}\\Qdocuments and settings\\E\\\\)(?!(Default User|All Users))([^\\\\]+)(\\\\){0,1}", Pattern.CASE_INSENSITIVE);
private static final Pattern UNC_PATH_FORWARD_SLASH_PATTERN = Pattern.compile("(//)([^/]+)(/){0,1}");
private static final Pattern UNC_PATH_BACK_SLASH_PATTERN = Pattern.compile("(\\\\\\\\)([^\\\\]+)(\\\\){0,1}");
private static final String USERNAME_REGEX_REPLACEMENT = "$1" + ANONYMIZED_USERNAME + "$4";
private final SleuthkitCase skCase;
PathNormalizer(SleuthkitCase skCase) {
this.skCase = skCase;
}
protected List<String> getUsernames() {
try {
return this.skCase.getOsAccountManager().getOsAccounts().stream()
.filter(acct -> acct != null)
.map(acct -> acct.getLoginName().orElse(null))
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "There was an error getting current os accounts", ex);
return Collections.emptyList();
}
}
public String normalizePath(String inputString) {
if (StringUtils.isBlank(inputString)) {
return "";
}
String anonymousString = anonymizeUserFromPathsWithForwardSlashes(inputString);
anonymousString = anonymizeUserFromPathsWithBackSlashes(anonymousString);
anonymousString = anonymizeServerFromUNCPath(anonymousString);
return anonymousString;
}
private String anonymizeUserFromPathsWithForwardSlashes(String stringWithUsername) {
String anonymousString = stringWithUsername;
anonymousString = regexReplace(anonymousString, USER_PATH_FORWARD_SLASH_REGEX_XP, USERNAME_REGEX_REPLACEMENT);
anonymousString = regexReplace(anonymousString, USER_PATH_FORWARD_SLASH_REGEX, USERNAME_REGEX_REPLACEMENT);
anonymousString = replaceFolder(anonymousString, getUsernames(), ANONYMIZED_USERNAME, FORWARD_SLASH);
return anonymousString;
}
// Most paths in CyberTriage are normalized with forward slashes
// but there can still be strings containing paths that are not normalized such paths contained in arguments or event log payloads
private String anonymizeUserFromPathsWithBackSlashes(String stringWithUsername) {
String anonymousString = stringWithUsername;
anonymousString = regexReplace(anonymousString, USER_PATH_BACK_SLASH_REGEX_XP, USERNAME_REGEX_REPLACEMENT);
anonymousString = regexReplace(anonymousString, USER_PATH_BACK_SLASH_REGEX, USERNAME_REGEX_REPLACEMENT);
anonymousString = replaceFolder(anonymousString, getUsernames(), ANONYMIZED_USERNAME, BACK_SLASH);
return anonymousString;
}
private String anonymizeServerFromUNCPath(String inputString) {
Set<String> serverNames = new HashSet<>();
String anonymousString = inputString.toLowerCase(Locale.ENGLISH);
Matcher forwardSlashMatcher = UNC_PATH_FORWARD_SLASH_PATTERN.matcher(anonymousString);
while (forwardSlashMatcher.find()) {
String serverName = forwardSlashMatcher.group(2);
serverNames.add(serverName);
}
Matcher backSlashMatcher = UNC_PATH_BACK_SLASH_PATTERN.matcher(anonymousString);
while (backSlashMatcher.find()) {
String serverName = backSlashMatcher.group(2);
serverNames.add(serverName);
}
for (String serverName : serverNames) {
if (StringUtils.isBlank(serverName)) {
continue;
}
if (InetAddresses.isInetAddress(serverName) && isLocalIP(serverName)) {
anonymousString = replaceFolder(anonymousString, Collections.singletonList(serverName), ANONYMIZED_IP);
} else {
anonymousString = replaceFolder(anonymousString, Collections.singletonList(serverName), ANONYMIZED_HOSTNAME);
}
}
return anonymousString;
}
private static String regexReplace(String orig, Pattern pattern, String regexReplacement) {
Matcher matcher = pattern.matcher(orig);
return matcher.replaceAll(regexReplacement);
}
private static String replaceFolder(String orig, List<String> valuesToReplace, String replacementValue) {
String anonymized = orig;
anonymized = replaceFolder(anonymized, valuesToReplace, replacementValue, FORWARD_SLASH);
anonymized = replaceFolder(anonymized, valuesToReplace, replacementValue, BACK_SLASH);
return anonymized;
}
private static String replaceFolder(String orig, List<String> valuesToReplace, String replacementValue, String folderDelimiter) {
if (orig == null || valuesToReplace == null) {
return orig;
}
String anonymousString = orig;
// ensure non-null
folderDelimiter = StringUtils.defaultString(folderDelimiter);
replacementValue = StringUtils.defaultString(replacementValue);
// replace
for (String valueToReplace : valuesToReplace) {
if (StringUtils.isNotEmpty(valueToReplace)) {
anonymousString = StringUtils.replace(anonymousString,
folderDelimiter + valueToReplace + folderDelimiter,
folderDelimiter + replacementValue + folderDelimiter);
}
}
return anonymousString;
}
/**
* Returns true if IP Address is Any Local / Site Local / Link Local / Loop
* back local. Sample list "0.0.0.0", wildcard addres
* "10.1.1.1","10.10.10.10", site local address "127.0.0.0","127.2.2.2",
* loopback address "169.254.0.0","169.254.10.10", Link local address
* "172.16.0.0","172.31.245.245", site local address
*
* @param ipAddress
* @return
*/
public static boolean isLocalIP(String ipAddress) {
try {
InetAddress a = InetAddresses.forString(ipAddress);
return a.isAnyLocalAddress() || a.isSiteLocalAddress()
|| a.isLoopbackAddress() || a.isLinkLocalAddress();
} catch (IllegalArgumentException ex) {
LOGGER.log(Level.WARNING, "Invalid IP string", ex);
return false;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB