mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-08 22:29:33 +00:00
Merge pull request #7828 from gdicristofaro/CT-7161-fileUpload
CT-7161 file upload
This commit is contained in:
commit
56e7958ba6
@ -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.LicenseRequest;
|
||||
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 java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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 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_UPLOAD_FILE_METADATA_PATH = "/_ah/api/reputation/v1/upload/meta";
|
||||
|
||||
private static final String AUTOPSY_PRODUCT = "AUTOPSY";
|
||||
|
||||
private static final CTApiDAO instance = new CTApiDAO();
|
||||
@ -74,15 +78,27 @@ public class CTApiDAO {
|
||||
}
|
||||
|
||||
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException {
|
||||
return getAuthToken(decrypted, false);
|
||||
}
|
||||
|
||||
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, boolean fileUpload) throws CTCloudException {
|
||||
AuthTokenRequest authTokenRequest = new AuthTokenRequest()
|
||||
.setAutopsyVersion(getAppVersion())
|
||||
.setRequestFileUpload(false)
|
||||
.setRequestFileUpload(fileUpload)
|
||||
.setBoostLicenseId(decrypted.getBoostLicenseId())
|
||||
.setHostId(decrypted.getLicenseHostId());
|
||||
|
||||
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) {
|
||||
return new HashMap<String, String>() {
|
||||
{
|
||||
|
@ -76,8 +76,7 @@ public class CTCloudException extends Exception{
|
||||
|
||||
public String getErrorDetails() {
|
||||
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. "
|
||||
+ "\nPlease contact Basis support at %s for help if the problem presists.",
|
||||
return String.format("An API error %s occurred. Please try again, and contact Basis support at %s for help if the problem persists.",
|
||||
StringUtils.isNotBlank(getCause().getLocalizedMessage()) ? "("+getCause().getLocalizedMessage()+")": "(Unknown)",
|
||||
Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM );
|
||||
}else {
|
||||
|
@ -21,42 +21,37 @@ package com.basistech.df.cybertriage.autopsy.ctapi;
|
||||
import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.io.InputStream;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.NTCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.AuthSchemes;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
@ -64,86 +59,54 @@ import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
|
||||
import org.apache.http.impl.client.WinHttpClients;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 String HOST_URL = Version.getBuildType() == Version.Type.RELEASE ? Constants.CT_CLOUD_SERVER : Constants.CT_CLOUD_DEV_SERVER;
|
||||
|
||||
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 String NB_PROXY_SELECTOR_NAME = "org.netbeans.core.NbProxySelector";
|
||||
|
||||
private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; // milli sec
|
||||
|
||||
private static final CTCloudHttpClient instance = new CTCloudHttpClient();
|
||||
|
||||
public static CTCloudHttpClient getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
|
||||
private final SSLContext sslContext;
|
||||
private String hostName = null;
|
||||
private final ProxySelector proxySelector;
|
||||
|
||||
private CTCloudHttpClient() {
|
||||
// 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() {
|
||||
if (StringUtils.isBlank(hostName)) {
|
||||
try {
|
||||
hostName = InetAddress.getLocalHost().getCanonicalHostName();
|
||||
} catch (UnknownHostException ex) {
|
||||
LOGGER.log(Level.WARNING, "An error occurred while fetching the hostname", ex);
|
||||
}
|
||||
}
|
||||
|
||||
int proxyPort = 0;
|
||||
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)) {
|
||||
private static URI getUri(String host, String path, Map<String, String> urlReqParams) throws URISyntaxException {
|
||||
String url = host + path;
|
||||
URIBuilder builder = new URIBuilder(url);
|
||||
|
||||
if (!MapUtils.isEmpty(urlReqParams)) {
|
||||
@ -156,9 +119,22 @@ public class CTCloudHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
URI postURI = builder.build();
|
||||
HttpPost postRequest = new HttpPost(postURI);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
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);
|
||||
postRequest.setHeader("Content-type", "application/json");
|
||||
@ -177,10 +153,14 @@ public class CTCloudHttpClient {
|
||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
|
||||
LOGGER.log(Level.INFO, "Response Received. - Status OK");
|
||||
// Parse Response
|
||||
if (classType != null) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
String entityStr = EntityUtils.toString(entity);
|
||||
O respObj = mapper.readValue(entityStr, classType);
|
||||
return respObj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine());
|
||||
handleNonOKResponse(response, "");
|
||||
@ -191,16 +171,64 @@ public class CTCloudHttpClient {
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
} 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);
|
||||
}
|
||||
|
||||
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
|
||||
* 200 OK.
|
||||
@ -262,148 +290,141 @@ public class CTCloudHttpClient {
|
||||
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.
|
||||
* @param proxySettings The network proxy settings.
|
||||
*
|
||||
* @param proxySelector The proxy selector.
|
||||
* @param sslContext The ssl context or null.
|
||||
* @return The connection to CT Cloud.
|
||||
*/
|
||||
private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) {
|
||||
HttpClientBuilder builder = getHttpClientBuilder(proxySettings);
|
||||
private static CloseableHttpClient createConnection(ProxySelector proxySelector, SSLContext sslContext) throws SSLInitializationException {
|
||||
HttpClientBuilder builder;
|
||||
|
||||
if (sslContext != null) {
|
||||
builder.setSSLContext(sslContext);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
|
||||
&& StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
|
||||
&& ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
|
||||
&& 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.useSystemProperties();
|
||||
LOGGER.log(Level.WARNING, "Using Win HTTP Client");
|
||||
} else {
|
||||
builder = HttpClients.custom();
|
||||
builder.setDefaultRequestConfig(config);
|
||||
if (Objects.nonNull(proxyCredsProvider)) { // make sure non null proxycreds before setting it
|
||||
builder.setDefaultCredentialsProvider(proxyCredsProvider);
|
||||
}
|
||||
// builder.setDefaultRequestConfig(config);
|
||||
LOGGER.log(Level.WARNING, "Using default http client");
|
||||
}
|
||||
if (Objects.nonNull(proxyHost)) {
|
||||
builder.setProxy(proxyHost);
|
||||
LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost));
|
||||
|
||||
if (sslContext != null) {
|
||||
builder.setSSLContext(sslContext);
|
||||
}
|
||||
|
||||
return builder;
|
||||
} else {
|
||||
return HttpClients.custom();
|
||||
}
|
||||
if (proxySelector != null) {
|
||||
builder.setRoutePlanner(new SystemDefaultRoutePlanner(proxySelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 builder.build();
|
||||
}
|
||||
|
||||
return proxyCredsProvider;
|
||||
private static class LoggingProxySelector extends ProxySelector {
|
||||
|
||||
private final ProxySelector delegate;
|
||||
|
||||
public LoggingProxySelector(ProxySelector delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
private static class ProxySettingArgs {
|
||||
|
||||
private final boolean systemOrManualProxy;
|
||||
private final String hostName;
|
||||
private final String proxyHostname;
|
||||
private final int proxyPort;
|
||||
private final String proxyUserId;
|
||||
private final char[] proxyPassword;
|
||||
private final String authScheme;
|
||||
|
||||
ProxySettingArgs(boolean systemOrManualProxy, String hostName, String proxyHostname, int proxyPort, String proxyUserId, char[] proxyPassword, String authScheme) {
|
||||
this.systemOrManualProxy = systemOrManualProxy;
|
||||
this.hostName = hostName;
|
||||
this.proxyHostname = proxyHostname;
|
||||
this.proxyPort = proxyPort;
|
||||
this.proxyUserId = proxyUserId;
|
||||
this.proxyPassword = proxyPassword;
|
||||
this.authScheme = authScheme;
|
||||
@Override
|
||||
public List<Proxy> select(URI uri) {
|
||||
List<Proxy> selectedProxies = delegate.select(uri);
|
||||
LOGGER.log(Level.INFO, MessageFormat.format("Proxy selected for {0} are {1}", uri, selectedProxies));
|
||||
return selectedProxies;
|
||||
}
|
||||
|
||||
boolean isSystemOrManualProxy() {
|
||||
return systemOrManualProxy;
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import java.net.URI;
|
||||
/**
|
||||
* Constants regarding connections to cyber triage cloud.
|
||||
*/
|
||||
final public class Constants {
|
||||
final class Constants {
|
||||
|
||||
public static final String CYBER_TRIAGE = "CyberTriage";
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ public class DecryptedLicenseResponse {
|
||||
private final Long fileUploads;
|
||||
private final Instant activationTime;
|
||||
private final String product;
|
||||
private final String limitType;
|
||||
private final LicenseLimitType limitType;
|
||||
private final String timezone;
|
||||
private final String customerEmail;
|
||||
private final String customerName;
|
||||
@ -54,7 +54,7 @@ public class DecryptedLicenseResponse {
|
||||
@JsonDeserialize(using = InstantEpochMillisDeserializer.class)
|
||||
@JsonProperty("activationTime") Instant activationTime,
|
||||
@JsonProperty("product") String product,
|
||||
@JsonProperty("limitType") String limitType,
|
||||
@JsonProperty("limitType") LicenseLimitType limitType,
|
||||
@JsonProperty("timezone") String timezone,
|
||||
@JsonProperty("customerEmail") String customerEmail,
|
||||
@JsonProperty("customerName") String customerName
|
||||
@ -96,7 +96,7 @@ public class DecryptedLicenseResponse {
|
||||
return product;
|
||||
}
|
||||
|
||||
public String getLimitType() {
|
||||
public LicenseLimitType getLimitType() {
|
||||
return limitType;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
|
||||
OptionsCategory_Name_CyberTriage=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.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html>
|
||||
LicenseDisclaimerPanel.border.title=Disclaimer
|
||||
LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from
|
||||
|
@ -3,7 +3,7 @@
|
||||
# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
|
||||
OptionsCategory_Name_CyberTriage=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.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html>
|
||||
LicenseDisclaimerPanel.border.title=Disclaimer
|
||||
LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from
|
||||
|
@ -6,17 +6,18 @@
|
||||
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
|
||||
<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, "{key}")"/>
|
||||
<Color PropertyName="color" blue="0" green="0" red="ff" type="rgb"/>
|
||||
</TitledBorder>
|
||||
</Border>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[2147483647, 90]"/>
|
||||
<Dimension value="[2147483647, 108]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[562, 90]"/>
|
||||
<Dimension value="[562, 108]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[400, 90]"/>
|
||||
<Dimension value="[400, 108]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
@ -29,7 +30,7 @@
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<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>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
@ -47,45 +48,7 @@
|
||||
</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="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"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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"/>
|
||||
<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>
|
||||
@ -96,7 +59,7 @@
|
||||
</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="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>
|
||||
</Constraints>
|
||||
|
||||
@ -113,5 +76,111 @@
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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>
|
||||
</Form>
|
||||
|
@ -32,7 +32,12 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
|
||||
|
||||
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
|
||||
@ -52,14 +57,18 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
|
||||
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 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
|
||||
setMaximumSize(new java.awt.Dimension(2147483647, 90));
|
||||
setMinimumSize(new java.awt.Dimension(562, 90));
|
||||
setPreferredSize(new java.awt.Dimension(400, 90));
|
||||
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, 108));
|
||||
setMinimumSize(new java.awt.Dimension(562, 108));
|
||||
setPreferredSize(new java.awt.Dimension(400, 108));
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
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.gridx = 0;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.gridwidth = 2;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
|
||||
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);
|
||||
spacer.setLayout(spacerLayout);
|
||||
spacerLayout.setHorizontalGroup(
|
||||
@ -108,24 +95,94 @@ public class LicenseDisclaimerPanel extends javax.swing.JPanel {
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 2;
|
||||
gridBagConstraints.gridy = 3;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
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
|
||||
|
||||
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()) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(new URI(CHECKOUT_PAGE_URL));
|
||||
Desktop.getDesktop().browse(new URI(url));
|
||||
} 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 {
|
||||
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
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -24,3 +24,5 @@ CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
|
||||
EULADialog.cancelButton.text=Cancel
|
||||
EULADialog.acceptButton.text=Accept
|
||||
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
|
||||
|
@ -22,6 +22,10 @@ CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text=
|
||||
CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text=
|
||||
CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.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_title=Add a License...
|
||||
CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc=The license number has already been entered
|
||||
@ -45,9 +49,11 @@ CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining=File uploads rema
|
||||
# {0} - hashLookupsRemaining
|
||||
CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining=Hash lookups remaining: {0}
|
||||
# {0} - maxDailyFileLookups
|
||||
CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day
|
||||
# {1} - resetSuffix
|
||||
CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}{1}
|
||||
# {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_localErr_desc=A general error occurred while fetching malware scans information. Please try again later.
|
||||
CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_title=General Error
|
||||
@ -56,3 +62,5 @@ CTOPtionsPanel_loadMalwareScansInfo_loading=Loading...
|
||||
EULADialog.cancelButton.text=Cancel
|
||||
EULADialog.acceptButton.text=Accept
|
||||
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
|
||||
|
@ -27,7 +27,7 @@ import org.openide.util.NbBundle.Messages;
|
||||
/**
|
||||
* 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 String licenseString = null;
|
||||
|
@ -26,7 +26,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
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_LICENSE_FILENAME = "CyberTriageLicense.json";
|
||||
private static final String MALWARE_INGEST_SETTINGS_FILENAME = "MalwareIngestSettings.json";
|
||||
|
||||
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() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,54 @@
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<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>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
|
@ -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.DecryptedLicenseResponse;
|
||||
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.util.LicenseDecryptorUtil;
|
||||
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.format.DateTimeFormatter;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@ -112,6 +114,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
@Override
|
||||
public synchronized void saveSettings() {
|
||||
ctPersistence.saveLicenseResponse(getLicenseInfo());
|
||||
ctPersistence.saveMalwareSettings(getIngestSettings());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,12 +131,27 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
if (licenseInfo != null) {
|
||||
loadMalwareScansInfo(licenseInfo);
|
||||
}
|
||||
|
||||
MalwareIngestSettings ingestSettings = ctPersistence.loadMalwareIngestSettings();
|
||||
setIngestSettings(ingestSettings);
|
||||
}
|
||||
|
||||
private synchronized LicenseResponse getLicenseInfo() {
|
||||
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) {
|
||||
this.licenseInfo = licenseInfo;
|
||||
this.licenseInfoMessage = licenseMessage;
|
||||
@ -202,6 +220,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
private void initComponents() {
|
||||
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();
|
||||
licenseInfoMessageLabel = new javax.swing.JLabel();
|
||||
licenseInfoUserLabel = new javax.swing.JLabel();
|
||||
@ -218,6 +238,35 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
|
||||
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.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
@ -378,6 +427,10 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
}
|
||||
}//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({
|
||||
"# {0} - userName",
|
||||
"# {1} - email",
|
||||
@ -387,9 +440,11 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
"# {0} - idNumber",
|
||||
"CTMalwareScannerOptionsPanel_licenseInfo_id=ID: {0}",
|
||||
"# {0} - maxDailyLookups",
|
||||
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}/day",
|
||||
"# {1} - resetSuffix",
|
||||
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}{1}",
|
||||
"# {0} - maxDailyFileLookups",
|
||||
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day",
|
||||
"# {1} - resetSuffix",
|
||||
"CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}{1}",
|
||||
"# {0} - countersResetDate",
|
||||
"CTMalwareScannerOptionsPanel_malwareScans_countersReset=Counters reset: {0}",
|
||||
"# {0} - hashLookupsRemaining",
|
||||
@ -425,7 +480,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
this.malwareScansMessageLabel.setVisible(StringUtils.isNotBlank(this.authTokenMessage));
|
||||
this.malwareScansMessageLabel.setText(this.authTokenMessage);
|
||||
|
||||
if (authTokenResponse == null) {
|
||||
if (authTokenResponse == null || this.licenseInfo == null) {
|
||||
this.maxHashLookupsLabel.setVisible(false);
|
||||
this.maxFileUploadsLabel.setVisible(false);
|
||||
this.countersResetLabel.setVisible(false);
|
||||
@ -433,15 +488,62 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
this.fileUploadsRemainingLabel.setVisible(false);
|
||||
} else {
|
||||
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.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.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.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.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 {
|
||||
LicenseResponse licenseResponse = get();
|
||||
SwingUtilities.invokeLater(() -> acceptEula(licenseResponse));
|
||||
} catch (InterruptedException ex) {
|
||||
} catch (InterruptedException | CancellationException ex) {
|
||||
// ignore cancellation; just load current license
|
||||
setLicenseDisplay(licenseInfo, null);
|
||||
loadMalwareScansInfo(licenseInfo);
|
||||
@ -557,7 +659,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
|
||||
AuthTokenResponse authTokenResponse = null;
|
||||
try {
|
||||
authTokenResponse = get();
|
||||
} catch (InterruptedException ex) {
|
||||
} catch (InterruptedException | CancellationException ex) {
|
||||
// ignore cancellation
|
||||
} catch (ExecutionException ex) {
|
||||
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
|
||||
private javax.swing.JLabel countersResetLabel;
|
||||
private javax.swing.JCheckBox fileUploadCheckbox;
|
||||
private javax.swing.JLabel fileUploadsRemainingLabel;
|
||||
private javax.swing.JLabel hashLookupsRemainingLabel;
|
||||
private javax.swing.JButton licenseInfoAddButton;
|
||||
|
@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
/**
|
||||
* 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 String EULA_RESOURCE = "EULA.htm";
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -21,14 +21,10 @@ package com.basistech.df.cybertriage.autopsy.malwarescan;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@ -36,7 +32,7 @@ import java.util.function.Consumer;
|
||||
* blocks (and subsequently add and flush operations) until previous batch
|
||||
* finishes.
|
||||
*/
|
||||
public class BatchProcessor<T> {
|
||||
class BatchProcessor<T> {
|
||||
|
||||
private ExecutorService processingExecutorService = Executors.newSingleThreadExecutor();
|
||||
|
||||
|
@ -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
|
||||
# {0} - errorResponse
|
||||
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_exhaustedHashLookups_desc=The remaining hash lookups for this license have been 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_title=Processing Timeout
|
||||
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_title=Batch Processing Timeout
|
||||
# {0} - remainingLookups
|
||||
MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc=This license only has {0} lookups remaining
|
||||
MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low
|
||||
MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc=This license only has {0} lookups remaining.
|
||||
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_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_noRemaining_title=No remaining lookups
|
||||
MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc=There are no more remaining hash lookups for this license at this time. Malware scanning will be disabled.
|
||||
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_displayName=Cyber Triage Malware Scanner
|
||||
MalwareScanIngestModuleFactory_version=1.0.0
|
||||
|
@ -19,15 +19,21 @@
|
||||
package com.basistech.df.cybertriage.autopsy.malwarescan;
|
||||
|
||||
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.AuthenticatedRequestData;
|
||||
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.MalwareResultBean.Status;
|
||||
import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
|
||||
import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -36,7 +42,9 @@ import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.curator.shaded.com.google.common.collect.Lists;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -49,6 +57,7 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.AnalysisResult;
|
||||
import org.sleuthkit.datamodel.Blackboard;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.HashUtility;
|
||||
import org.sleuthkit.datamodel.HashUtility.HashResult;
|
||||
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
|
||||
*/
|
||||
public class MalwareScanIngestModule implements FileIngestModule {
|
||||
class MalwareScanIngestModule implements FileIngestModule {
|
||||
|
||||
private static final SharedProcessing sharedProcessing = new SharedProcessing();
|
||||
|
||||
@ -93,6 +102,14 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
//minimum lookups left before issuing warning
|
||||
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(
|
||||
"application/x-bat",//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 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 CTApiDAO ctApiDAO = CTApiDAO.getInstance();
|
||||
|
||||
private RunState runState = 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;
|
||||
private IngestJobState ingestJobState = null;
|
||||
|
||||
@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_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_noRemaining_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_title=No remaining lookups",
|
||||
"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 {
|
||||
// only run this code once per startup
|
||||
if (runState == RunState.STARTED_UP || runState == RunState.DISABLED) {
|
||||
if (ingestJobState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
Optional<LicenseInfo> licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo();
|
||||
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_desc(),
|
||||
null);
|
||||
runState = RunState.DISABLED;
|
||||
return;
|
||||
|
||||
return IngestJobState.DISABLED;
|
||||
}
|
||||
|
||||
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense());
|
||||
@ -160,80 +194,177 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
|
||||
if (lookupsRemaining <= 0) {
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_title(),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_desc(),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_title(),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc(),
|
||||
null);
|
||||
runState = RunState.DISABLED;
|
||||
return;
|
||||
|
||||
return IngestJobState.DISABLED;
|
||||
} else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) {
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title(),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc(lookupsRemaining),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_title(),
|
||||
Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc(lookupsRemaining),
|
||||
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
|
||||
tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
malwareType = tskCase.getBlackboard().getOrAddArtifactType(
|
||||
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
BlackboardArtifact.Type malwareType = tskCase.getBlackboard().getOrAddArtifactType(
|
||||
MALWARE_TYPE_NAME,
|
||||
Bundle.MalwareScanIngestModule_malwareTypeDisplayName(),
|
||||
BlackboardArtifact.Category.ANALYSIS_RESULT);
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
dsId = context.getDataSource().getId();
|
||||
ingestJobId = context.getJobId();
|
||||
licenseInfo = licenseInfoOpt.get();
|
||||
|
||||
// set run state to initialized
|
||||
runState = RunState.STARTED_UP;
|
||||
} catch (Exception ex) {
|
||||
runState = RunState.DISABLED;
|
||||
throw new IngestModuleException("An exception occurred on MalwareScanIngestModule startup", ex);
|
||||
}
|
||||
return new IngestJobState(
|
||||
context,
|
||||
tskCase,
|
||||
new PathNormalizer(tskCase),
|
||||
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) {
|
||||
limit = limit == null ? 0 : limit;
|
||||
used = used == null ? 0 : 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())) {
|
||||
return af.getMd5Hash();
|
||||
}
|
||||
break;
|
||||
case SHA256:
|
||||
if (StringUtils.isNotBlank(af.getSha256Hash())) {
|
||||
return af.getSha256Hash();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(HashType.MD5));
|
||||
List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(hashType));
|
||||
if (CollectionUtils.isNotEmpty(hashResults)) {
|
||||
for (HashResult hashResult : hashResults) {
|
||||
if (hashResult.getType() == HashType.MD5) {
|
||||
if (hashResult.getType() == hashType) {
|
||||
return hashResult.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
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.getId()),
|
||||
af.getId(),
|
||||
hashType.name()),
|
||||
ex);
|
||||
}
|
||||
|
||||
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({
|
||||
"MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout",
|
||||
"MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out"
|
||||
})
|
||||
IngestModule.ProcessResult process(AbstractFile af) {
|
||||
try {
|
||||
if (runState == RunState.STARTED_UP
|
||||
if (ingestJobState != null
|
||||
&& ingestJobState.isDoFileLookups()
|
||||
&& !ingestJobState.getIngestJobContext().fileIngestIsCancelled()
|
||||
&& af.getKnown() != TskData.FileKnown.KNOWN
|
||||
&& EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(fileTypeDetector.getMIMEType(af)).trim().toLowerCase())
|
||||
&& CollectionUtils.isEmpty(af.getAnalysisResults(malwareType))) {
|
||||
&& EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(ingestJobState.getFileTypeDetector().getMIMEType(af)).trim().toLowerCase())
|
||||
&& CollectionUtils.isEmpty(af.getAnalysisResults(ingestJobState.getMalwareType()))) {
|
||||
|
||||
String md5 = getOrCalcHash(af);
|
||||
String md5 = getOrCalcMd5(af);
|
||||
if (StringUtils.isNotBlank(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({
|
||||
"MalwareScanIngestModule_SharedProcessing_authTokenResponseError_title=Authentication API error",
|
||||
"# {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_generalProcessingError_title=Hash Lookup Error",
|
||||
"MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results",})
|
||||
private void handleBatch(List<FileRecord> fileRecords) {
|
||||
if (runState != RunState.STARTED_UP || fileRecords == null || fileRecords.isEmpty()) {
|
||||
private void handleBatch(IngestJobState ingestJobState, List<FileRecord> fileRecords) {
|
||||
if (ingestJobState == null
|
||||
|| !ingestJobState.isDoFileLookups()
|
||||
|| ingestJobState.getIngestJobContext().fileIngestIsCancelled()
|
||||
|| fileRecords == null
|
||||
|| fileRecords.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -278,11 +420,10 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
continue;
|
||||
}
|
||||
|
||||
String sanitizedMd5 = sanitizedMd5(fr.getMd5hash());
|
||||
String sanitizedMd5 = normalizedMd5(fr.getMd5hash());
|
||||
md5ToObjId
|
||||
.computeIfAbsent(sanitizedMd5, (k) -> new ArrayList<>())
|
||||
.add(fr.getObjId());
|
||||
|
||||
}
|
||||
|
||||
List<String> md5Hashes = new ArrayList<>(md5ToObjId.keySet());
|
||||
@ -292,40 +433,347 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
}
|
||||
|
||||
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
|
||||
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense());
|
||||
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(ingestJobState.getLicenseInfo().getDecryptedLicense());
|
||||
|
||||
// make sure we are in bounds for the remaining scans
|
||||
long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
|
||||
if (remainingScans <= 0) {
|
||||
runState = RunState.DISABLED;
|
||||
ingestJobState.disableDoFileLookups();
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
|
||||
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
|
||||
List<CTCloudBean> repResult = ctApiDAO.getReputationResults(
|
||||
new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse),
|
||||
return ctApiDAO.getReputationResults(
|
||||
new AuthenticatedRequestData(ingestJobState.getLicenseInfo().getDecryptedLicense(), authTokenResponse),
|
||||
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<>();
|
||||
if (!CollectionUtils.isEmpty(repResult)) {
|
||||
SleuthkitCase.CaseDbTransaction trans = null;
|
||||
try {
|
||||
trans = tskCase.beginTransaction();
|
||||
trans = ingestJobState.getTskCase().beginTransaction();
|
||||
for (CTCloudBean result : repResult) {
|
||||
String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue());
|
||||
String sanitizedMd5 = normalizedMd5(result.getMd5HashValue());
|
||||
List<Long> objIds = md5ToObjId.remove(sanitizedMd5);
|
||||
if (objIds == null || objIds.isEmpty()) {
|
||||
if (CollectionUtils.isEmpty(objIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Long objId : objIds) {
|
||||
AnalysisResult res = createAnalysisResult(objId, result, trans);
|
||||
AnalysisResult res = createAnalysisResult(ingestJobState, trans, result, objId);
|
||||
if (res != null) {
|
||||
createdArtifacts.add(res);
|
||||
}
|
||||
@ -343,27 +791,40 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(createdArtifacts)) {
|
||||
tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
|
||||
ex);
|
||||
}
|
||||
ingestJobState.getTskCase().getBlackboard().postArtifacts(
|
||||
createdArtifacts,
|
||||
Bundle.MalwareScanIngestModuleFactory_displayName(),
|
||||
ingestJobState.getIngestJobId()
|
||||
);
|
||||
}
|
||||
|
||||
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({
|
||||
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES",
|
||||
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO"
|
||||
})
|
||||
private AnalysisResult createAnalysisResult(Long objId, CTCloudBean cloudBean, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException {
|
||||
if (objId == null || cloudBean == null || cloudBean.getMalwareResult() == null) {
|
||||
private AnalysisResult createAnalysisResult(IngestJobState ingestJobState, SleuthkitCase.CaseDbTransaction trans, CTCloudBean cloudBean, Long objId) throws Blackboard.BlackboardException {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -377,10 +838,10 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
|
||||
String justification = cloudBean.getMalwareResult().getStatusDescription();
|
||||
|
||||
return tskCase.getBlackboard().newAnalysisResult(
|
||||
malwareType,
|
||||
return ingestJobState.getTskCase().getBlackboard().newAnalysisResult(
|
||||
ingestJobState.getMalwareType(),
|
||||
objId,
|
||||
dsId,
|
||||
ingestJobState.getDsId(),
|
||||
score,
|
||||
conclusion,
|
||||
MALWARE_CONFIG,
|
||||
@ -389,39 +850,52 @@ public class MalwareScanIngestModule implements FileIngestModule {
|
||||
trans).getAnalysisResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when ingest should shut down.
|
||||
*/
|
||||
@Messages({
|
||||
"MalwareScanIngestModule_SharedProcessing_flushTimeout_title=Processing Timeout",
|
||||
"MalwareScanIngestModule_SharedProcessing_flushTimeout_desc=A timeout occurred while finishing processing"
|
||||
})
|
||||
synchronized void shutDown() {
|
||||
// if already shut down, return
|
||||
if (runState == RunState.SHUT_DOWN) {
|
||||
if (ingestJobState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flush any remaining items
|
||||
try {
|
||||
batchProcessor.flushAndReset();
|
||||
longPollForNotFound(ingestJobState);
|
||||
} catch (InterruptedException ex) {
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_title(),
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_desc(),
|
||||
ex);
|
||||
} catch (Exception ex) {
|
||||
notifyWarning(
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
|
||||
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
|
||||
ex);
|
||||
} finally {
|
||||
// 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);
|
||||
logger.log(Level.WARNING, message, ex);
|
||||
}
|
||||
|
||||
private enum RunState {
|
||||
STARTED_UP, DISABLED, SHUT_DOWN
|
||||
}
|
||||
|
||||
class FileRecord {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
BIN
docs/doxygen-user/images/ct_malware_license_agreement.png
Normal file
BIN
docs/doxygen-user/images/ct_malware_license_agreement.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
docs/doxygen-user/images/ct_malware_scanner_options_panel.png
Normal file
BIN
docs/doxygen-user/images/ct_malware_scanner_options_panel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
docs/doxygen-user/images/ct_upload_file.png
Normal file
BIN
docs/doxygen-user/images/ct_upload_file.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
Loading…
x
Reference in New Issue
Block a user