release/modules/ext/sevenzipjbinding.jar
- ext/sleuthkit-4.12.0.jar
- release/modules/ext/sleuthkit-4.12.0.jar
+ ext/sleuthkit-4.12.1.jar
+ release/modules/ext/sleuthkit-4.12.1.jar
- ext/sleuthkit-caseuco-4.12.0.jar
- release/modules/ext/sleuthkit-caseuco-4.12.0.jar
+ ext/sleuthkit-caseuco-4.12.1.jar
+ release/modules/ext/sleuthkit-caseuco-4.12.1.jar
ext/slf4j-api-1.7.36.jar
@@ -742,8 +742,8 @@
release/modules/ext/spotbugs-annotations-4.6.0.jar
- ext/sqlite-jdbc-3.42.0.0.jar
- release/modules/ext/sqlite-jdbc-3.42.0.0.jar
+ ext/sqlite-jdbc-3.42.0.1.jar
+ release/modules/ext/sqlite-jdbc-3.42.0.1.jar
ext/txw2-2.3.3.jar
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties
new file mode 100644
index 0000000000..caab36116d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties
@@ -0,0 +1,9 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+
+CTIntegrationMissingDialog.title=Cyber Triage Importer Module Required
+CTIntegrationMissingDialog.descriptionLabel.text=The Cyber Triage Importer Module is required to open this case.
To open this case:
- Extract the module from the Integrations tab in the Cyber Triage options panel.
- Select the 'Plugins' option from the 'Tools' menu, and go to the 'Downloaded' tab.
- Click 'Add Plugins...' and select the path of the plugin.
- Press 'Install' to finish the installation.
+CTIntegrationMissingDialog.docsLabel.text=For more information, refer to the Cyber Triage Users Guide
+CTIntegrationMissingDialog.okButton.text=OK
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED
new file mode 100644
index 0000000000..caab36116d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/Bundle.properties-MERGED
@@ -0,0 +1,9 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+
+CTIntegrationMissingDialog.title=Cyber Triage Importer Module Required
+CTIntegrationMissingDialog.descriptionLabel.text=The Cyber Triage Importer Module is required to open this case.
To open this case:
- Extract the module from the Integrations tab in the Cyber Triage options panel.
- Select the 'Plugins' option from the 'Tools' menu, and go to the 'Downloaded' tab.
- Click 'Add Plugins...' and select the path of the plugin.
- Press 'Install' to finish the installation.
+CTIntegrationMissingDialog.docsLabel.text=For more information, refer to the Cyber Triage Users Guide
+CTIntegrationMissingDialog.okButton.text=OK
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form
similarity index 61%
rename from Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.form
rename to Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form
index 83e3c8440a..e61f2f6cbd 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.form
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.form
@@ -1,24 +1,18 @@
-
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java
new file mode 100644
index 0000000000..9e534c9833
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/CTIntegrationMissingDialog.java
@@ -0,0 +1,165 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
+import javax.swing.JComponent;
+import org.sleuthkit.autopsy.coreutils.Logger;
+
+/**
+ * Provides directions with how to enable CT integration with Autopsy when
+ * trying to open a CT exported case.
+ */
+public class CTIntegrationMissingDialog extends javax.swing.JDialog {
+
+ private static final String DOCS_PAGE_URL = "https://docs.cybertriage.com/en/latest/chapters/integrations/autopsy.html";
+
+ private static final Logger LOGGER = Logger.getLogger(CTIntegrationMissingDialog.class.getName());
+
+ /**
+ * Creates new form CTIntegrationMissingDialog
+ */
+ public CTIntegrationMissingDialog(java.awt.Frame parent, boolean modal) {
+ super(parent, modal);
+ initComponents();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ javax.swing.JLabel descriptionLabel = new javax.swing.JLabel();
+ javax.swing.JLabel docsLabel = new javax.swing.JLabel();
+ link = new javax.swing.JLabel();
+ javax.swing.JPanel paddingPanel = new javax.swing.JPanel();
+ javax.swing.JButton okButton = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.title")); // NOI18N
+ setAlwaysOnTop(true);
+ setResizable(false);
+ getContentPane().setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.descriptionLabel.text")); // NOI18N
+ descriptionLabel.setMinimumSize(new java.awt.Dimension(483, 116));
+ 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);
+ getContentPane().add(descriptionLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(docsLabel, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.docsLabel.text")); // NOI18N
+ docsLabel.setMinimumSize(new java.awt.Dimension(312, 16));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 0);
+ getContentPane().add(docsLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(link, "" + DOCS_PAGE_URL + "");
+ 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.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(link, gridBagConstraints);
+
+ javax.swing.GroupLayout paddingPanelLayout = new javax.swing.GroupLayout(paddingPanel);
+ paddingPanel.setLayout(paddingPanelLayout);
+ paddingPanelLayout.setHorizontalGroup(
+ paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+ paddingPanelLayout.setVerticalGroup(
+ paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.weighty = 1.0;
+ getContentPane().add(paddingPanel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CTIntegrationMissingDialog.class, "CTIntegrationMissingDialog.okButton.text")); // NOI18N
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
+ gridBagConstraints.insets = new java.awt.Insets(10, 5, 5, 5);
+ getContentPane().add(okButton, gridBagConstraints);
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void linkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_linkMouseClicked
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().browse(new URI(DOCS_PAGE_URL));
+ } catch (IOException | URISyntaxException e) {
+ LOGGER.log(Level.SEVERE, "Error opening link to: " + DOCS_PAGE_URL, e);
+ }
+ } else {
+ LOGGER.log(Level.WARNING, "Desktop API is not supported. Link cannot be opened.");
+ }
+ }//GEN-LAST:event_linkMouseClicked
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ dispose();
+ }//GEN-LAST:event_okButtonActionPerformed
+
+ public void showDialog(JComponent parentComp) {
+ setLocationRelativeTo(parentComp == null ? getParent() : parentComp);
+ pack();
+ setVisible(true);
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel link;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
index 25e264e49a..f21873f565 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java
@@ -25,9 +25,12 @@ import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBeanResponse;
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.FileUploadRequest;
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 +48,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 +79,28 @@ public class CTApiDAO {
}
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException {
+ return getAuthToken(decrypted, null);
+ }
+
+ public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, Long fileUploadSize) throws CTCloudException {
AuthTokenRequest authTokenRequest = new AuthTokenRequest()
.setAutopsyVersion(getAppVersion())
- .setRequestFileUpload(false)
+ .setRequestFileUpload(fileUploadSize != null && fileUploadSize > 0)
+ .setFileUploadSize(fileUploadSize != null && fileUploadSize > 0 ? fileUploadSize : null)
.setBoostLicenseId(decrypted.getBoostLicenseId())
.setHostId(decrypted.getLicenseHostId());
return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
}
+ public void uploadFile(FileUploadRequest fileUploadRequest) throws CTCloudException {
+ httpClient.doFileUploadPut(fileUploadRequest);
+ }
+
+ public void uploadMeta(AuthenticatedRequestData authenticatedRequestData, MetadataUploadRequest metaRequest) throws CTCloudException {
+ httpClient.doPost(CTCLOUD_UPLOAD_FILE_METADATA_PATH, getAuthParams(authenticatedRequestData), metaRequest, null);
+ }
+
private static Map getAuthParams(AuthenticatedRequestData authenticatedRequestData) {
return new HashMap() {
{
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java
index 52d586ee53..95605c4bce 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java
@@ -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 {
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
index 9d4b189ee9..cf93d58307 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java
@@ -18,148 +18,127 @@
*/
package com.basistech.df.cybertriage.autopsy.ctapi;
+import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException.ErrorCode;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
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;
+import org.apache.http.client.methods.HttpPut;
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.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.client.SystemDefaultCredentialsProvider;
import org.apache.http.impl.client.WinHttpClients;
+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 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);
+ private static URI getUri(String host, String path, Map urlReqParams) throws URISyntaxException {
+ String url = host + path;
+ URIBuilder builder = new URIBuilder(url);
+
+ if (!MapUtils.isEmpty(urlReqParams)) {
+ for (Entry e : urlReqParams.entrySet()) {
+ String key = e.getKey();
+ String value = e.getValue();
+ if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
+ builder.addParameter(key, value);
+ }
}
}
- 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
- );
+ return builder.build();
}
-
+
public O doPost(String urlPath, Object jsonBody, Class classType) throws CTCloudException {
return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
}
public O doPost(String urlPath, Map urlReqParams, Object jsonBody, Class classType) throws CTCloudException {
- String url = HOST_URL + urlPath;
+
+ URI postURI = null;
try {
-
+ postURI = getUri(HOST_URL, urlPath, urlReqParams);
LOGGER.log(Level.INFO, "initiating http connection to ctcloud server");
- try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) {
- URIBuilder builder = new URIBuilder(url);
-
- if (!MapUtils.isEmpty(urlReqParams)) {
- for (Entry e : urlReqParams.entrySet()) {
- String key = e.getKey();
- String value = e.getValue();
- if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
- builder.addParameter(key, value);
- }
- }
- }
+ try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
- URI postURI = builder.build();
HttpPost postRequest = new HttpPost(postURI);
-
configureRequestTimeout(postRequest);
postRequest.setHeader("Content-type", "application/json");
@@ -177,30 +156,92 @@ public class CTCloudHttpClient {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
LOGGER.log(Level.INFO, "Response Received. - Status OK");
// Parse Response
- HttpEntity entity = response.getEntity();
- String entityStr = EntityUtils.toString(entity);
- O respObj = mapper.readValue(entityStr, classType);
- return respObj;
+ if (classType != null) {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ String entityStr = EntityUtils.toString(entity);
+ if (StringUtils.isNotBlank(entityStr)) {
+ O respObj = mapper.readValue(entityStr, classType);
+ return respObj;
+ }
+ }
+ }
+
+ return null;
} else {
LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine());
handleNonOKResponse(response, "");
}
+ // transform all non-CTCloudException's into a CTCloudException
+ } catch (CTCloudException ex) {
+ throw ex;
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex);
throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex);
}
}
} catch (IOException ex) {
- LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + url, ex);
+ 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 doFileUploadPut(FileUploadRequest fileUploadRequest) throws CTCloudException {
+ if (fileUploadRequest == null) {
+ throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fileUploadRequest cannot be null"));
+ }
+
+ String fullUrlPath = fileUploadRequest.getFullUrlPath();
+ String fileName = fileUploadRequest.getFileName();
+ InputStream fileInputStream = fileUploadRequest.getFileInputStream();
+ Long contentLength = fileUploadRequest.getContentLength();
+
+ if (StringUtils.isBlank(fullUrlPath) || fileInputStream == null || contentLength == null || contentLength <= 0) {
+ throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fullUrlPath, fileInputStream, contentLength must not be empty, null or less than 0"));
+ }
+
+ URI putUri;
+ try {
+ putUri = 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);
+ HttpPut put = new HttpPut(putUri);
+ configureRequestTimeout(put);
+
+ put.addHeader("Connection", "keep-alive");
+ put.setEntity(new InputStreamEntity(fileInputStream, contentLength, ContentType.APPLICATION_OCTET_STREAM));
+
+ try (CloseableHttpResponse response = httpclient.execute(put)) {
+ 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.
@@ -263,147 +304,140 @@ public class CTCloudHttpClient {
}
/**
- * Creates a connection to CT Cloud with the given arguments.
- * @param proxySettings The network proxy settings.
- * @param sslContext The ssl context or null.
- * @return The connection to CT Cloud.
+ * Get ProxySelector present (favoring NbProxySelector if present).
+ *
+ * @return The found ProxySelector or null.
*/
- private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) {
- HttpClientBuilder builder = getHttpClientBuilder(proxySettings);
-
- if (sslContext != null) {
- builder.setSSLContext(sslContext);
- }
- return builder.build();
+ 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);
}
- private static HttpClientBuilder getHttpClientBuilder(ProxySettingArgs proxySettings) {
+ /**
+ * Create an SSLContext object using our in-memory keystore.
+ *
+ * @return
+ */
+ private static SSLContext createSSLContext() {
+ LOGGER.log(Level.INFO, "Creating custom SSL context");
+ try {
- if (proxySettings.isSystemOrManualProxy()) {
+ // 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;
+ }
+ }
- Authenticator.setDefault(new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- LOGGER.info("Requesting Password Authentication...");
- return super.getPasswordAuthentication();
- }
- });
+ // 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];
+ }
- 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());
+ // 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];
- proxyCredsProvider = getProxyCredentialsProvider(proxySettings);
- if (StringUtils.isNotBlank(proxySettings.getAuthScheme())) {
- if (!DEFAULT_SCHEME_PRIORITY.get(0).equalsIgnoreCase(proxySettings.getAuthScheme())) {
- DEFAULT_SCHEME_PRIORITY.removeIf(s -> s.equalsIgnoreCase(proxySettings.getAuthScheme()));
- DEFAULT_SCHEME_PRIORITY.add(0, proxySettings.getAuthScheme());
- }
- }
- config = RequestConfig.custom().setProxyPreferredAuthSchemes(DEFAULT_SCHEME_PRIORITY).build();
- }
-
- if (Objects.isNull(proxyCredsProvider) && WinHttpClients.isWinAuthAvailable()) {
- builder = WinHttpClients.custom();
- builder.useSystemProperties();
- LOGGER.log(Level.WARNING, "Using Win HTTP Client");
- } else {
- builder = HttpClients.custom();
- builder.setDefaultRequestConfig(config);
- if (Objects.nonNull(proxyCredsProvider)) { // make sure non null proxycreds before setting it
- builder.setDefaultCredentialsProvider(proxyCredsProvider);
- }
- LOGGER.log(Level.WARNING, "Using default http client");
- }
- if (Objects.nonNull(proxyHost)) {
- builder.setProxy(proxyHost);
- LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost));
- }
-
- return builder;
- } else {
- return HttpClients.custom();
+ return new TrustManager[]{tm};
+ } catch (KeyStoreException | NoSuchAlgorithmException ex) {
+ LOGGER.log(Level.SEVERE, "Error getting TrustManager", ex);
+ return new TrustManager[0];
}
}
/**
- * Returns a CredentialsProvider for proxy, if one is configured.
+ * Creates a connection to CT Cloud with the given arguments.
*
- * @return CredentialsProvider, if a proxy is configured with credentials,
- * null otherwise
+ * @param proxySelector The proxy selector.
+ * @param sslContext The ssl context or null.
+ * @return The connection to CT Cloud.
*/
- 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));
- }
- }
+ private static CloseableHttpClient createConnection(ProxySelector proxySelector, SSLContext sslContext) throws SSLInitializationException {
+ HttpClientBuilder builder;
+
+ if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
+ && StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
+ && ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
+ && WinHttpClients.isWinAuthAvailable()) {
+
+ builder = WinHttpClients.custom();
+ builder.useSystemProperties();
+ LOGGER.log(Level.WARNING, "Using Win HTTP Client");
+ } else {
+ builder = HttpClients.custom();
+ // builder.setDefaultRequestConfig(config);
+ LOGGER.log(Level.WARNING, "Using default http client");
}
- return proxyCredsProvider;
+ if (sslContext != null) {
+ builder.setSSLContext(sslContext);
+ }
+
+ if (proxySelector != null) {
+ builder.setRoutePlanner(new SystemDefaultRoutePlanner(proxySelector));
+ }
+
+ return builder.build();
}
- private static class ProxySettingArgs {
+ private static class LoggingProxySelector extends ProxySelector {
- 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;
+ private final ProxySelector delegate;
- 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;
+ public LoggingProxySelector(ProxySelector delegate) {
+ this.delegate = delegate;
}
- boolean isSystemOrManualProxy() {
- return systemOrManualProxy;
+ @Override
+ public List select(URI uri) {
+ List selectedProxies = delegate.select(uri);
+ LOGGER.log(Level.INFO, MessageFormat.format("Proxy selected for {0} are {1}", uri, selectedProxies));
+ return selectedProxies;
}
- String getHostName() {
- return hostName;
+ @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 getProxyHostname() {
- return proxyHostname;
- }
-
- int getProxyPort() {
- return proxyPort;
- }
-
- String getProxyUserId() {
- return proxyUserId;
- }
-
- char[] getProxyPassword() {
- return proxyPassword;
- }
-
- public String getAuthScheme() {
- return authScheme;
- }
}
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java
index 9587b3cd44..8290d6621d 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java
@@ -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";
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java
deleted file mode 100644
index f710a6ab1b..0000000000
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2023 Basis Technology Corp.
- * Contact: carrier sleuthkit 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 set = new HashSet ();
- 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 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 o.n.core
and core.network
.
- * An implementation of this class brings a facility to reload Network Proxy Settings
- * from underlying OS.
- * The module core.network
provides a implementation which may be accessible
- * via Lookup.getDefault()
. 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();
- }
-}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java
index 6a093fee8e..f137818346 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java
@@ -34,6 +34,9 @@ public class AuthTokenRequest {
@JsonProperty("requestFileUpload")
private boolean requestFileUpload;
+ @JsonProperty("fileUploadSize")
+ private Long fileUploadSize;
+
@JsonProperty("host_id")
private String hostId;
@@ -64,6 +67,16 @@ public class AuthTokenRequest {
return this;
}
+ public Long getFileUploadSize() {
+ return fileUploadSize;
+ }
+
+ public AuthTokenRequest setFileUploadSize(Long fileUploadSize) {
+ this.fileUploadSize = fileUploadSize;
+ return this;
+ }
+
+
public String getHostId() {
return hostId;
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java
index c6f91721ef..d939d6ed75 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java
@@ -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;
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java
new file mode 100644
index 0000000000..48d8dc77b4
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileUploadRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 java.io.InputStream;
+
+/**
+ * Data for a file upload request.
+ */
+public class FileUploadRequest {
+
+ private String fullUrlPath;
+ private String fileName;
+ private InputStream fileInputStream;
+ private Long contentLength;
+
+ public String getFullUrlPath() {
+ return fullUrlPath;
+ }
+
+ public FileUploadRequest setFullUrlPath(String fullUrlPath) {
+ this.fullUrlPath = fullUrlPath;
+ return this;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public FileUploadRequest setFileName(String fileName) {
+ this.fileName = fileName;
+ return this;
+ }
+
+ public InputStream getFileInputStream() {
+ return fileInputStream;
+ }
+
+ public FileUploadRequest setFileInputStream(InputStream fileInputStream) {
+ this.fileInputStream = fileInputStream;
+ return this;
+ }
+
+ public Long getContentLength() {
+ return contentLength;
+ }
+
+ public FileUploadRequest setContentLength(Long contentLength) {
+ this.contentLength = contentLength;
+ return this;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseLimitType.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseLimitType.java
new file mode 100644
index 0000000000..7d185f86ae
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseLimitType.java
@@ -0,0 +1,30 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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;
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java
index a3a8247884..5a85778b60 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java
@@ -32,18 +32,21 @@ public class LicenseResponse {
private final Boolean hostChanged;
private final Long hostChangesRemaining;
private final BoostLicenseResponse boostLicense;
+ private final String errorMsg;
@JsonCreator
public LicenseResponse(
@JsonProperty("success") Boolean success,
@JsonProperty("hostChanged") Boolean hostChanged,
@JsonProperty("hostChangesRemaining") Long hostChangesRemaining,
- @JsonProperty("boostLicense") BoostLicenseResponse boostLicense
+ @JsonProperty("boostLicense") BoostLicenseResponse boostLicense,
+ @JsonProperty("errorMsg") String errorMsg
) {
this.success = success;
this.hostChanged = hostChanged;
this.hostChangesRemaining = hostChangesRemaining;
this.boostLicense = boostLicense;
+ this.errorMsg = errorMsg;
}
public Boolean isSuccess() {
@@ -61,4 +64,8 @@ public class LicenseResponse {
public BoostLicenseResponse getBoostLicense() {
return boostLicense;
}
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java
new file mode 100644
index 0000000000..7d7e1e7f64
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java
@@ -0,0 +1,109 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java
index f62b57d795..26ebe793a4 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java
@@ -34,6 +34,7 @@ import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
+import java.text.MessageFormat;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -42,6 +43,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.lang3.ObjectUtils;
/**
* Decrypts the payload of boost license.
@@ -58,12 +60,12 @@ public class LicenseDecryptorUtil {
private LicenseDecryptorUtil() {
}
-
+
public LicenseInfo createLicenseInfo(LicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException {
- if (licenseResponse == null || licenseResponse.getBoostLicense() == null) {
- throw new InvalidLicenseException("License or boost license are null");
+ if (licenseResponse == null) {
+ throw new InvalidLicenseException("License is null");
}
-
+
DecryptedLicenseResponse decrypted = parseLicenseJSON(licenseResponse.getBoostLicense());
return new LicenseInfo(licenseResponse, decrypted);
}
@@ -78,6 +80,9 @@ public class LicenseDecryptorUtil {
* com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException
*/
public DecryptedLicenseResponse parseLicenseJSON(BoostLicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException {
+ if (licenseResponse == null) {
+ throw new InvalidLicenseException("Boost license is null");
+ }
String decryptedJsonResponse;
try {
@@ -101,6 +106,12 @@ public class LicenseDecryptorUtil {
}
private String decryptLicenseString(String encryptedJson, String ivBase64, String encryptedKey, String version) throws IOException, GeneralSecurityException, InvalidLicenseException {
+ if (ObjectUtils.anyNull(encryptedJson, ivBase64, encryptedKey, version)) {
+ throw new InvalidLicenseException(MessageFormat.format(
+ "encryptedJson: {0}, iv: {1}, encryptedKey: {2}, version: {3} must all be non-null",
+ encryptedJson, ivBase64, encryptedKey, version));
+ }
+
if (!"1.0".equals(version)) {
throw new InvalidLicenseException("Unexpected file version: " + version);
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties
index 46f62fdf7a..840725f5d7 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties
@@ -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=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.
+LicenseDisclaimerPanel.disclaimer.text=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.
LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from
-LicenseDisclaimerPanel.link.text=https://cybertriage.com/autopsy-checkout
LicenseDisclaimerPanel.border.title=Disclaimer
+LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED
index 46f62fdf7a..840725f5d7 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED
@@ -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=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.
+LicenseDisclaimerPanel.disclaimer.text=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.
LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from
-LicenseDisclaimerPanel.link.text=https://cybertriage.com/autopsy-checkout
LicenseDisclaimerPanel.border.title=Disclaimer
+LicenseDisclaimerPanel.trialLabel.text=You can try a free 7-day trial from
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java
index 745aa1d03d..2b6cffe789 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java
@@ -26,12 +26,12 @@ import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
-import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JPanel;
import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.Lookup;
+import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
/**
@@ -64,12 +64,12 @@ public class CTOptionsPanel extends IngestModuleGlobalSettingsPanel {
}
})
.filter(item -> item != null)
- .sorted(Comparator.comparing(p -> p.getClass().getSimpleName().toUpperCase()))
+ .sorted(Comparator.comparing(p -> p.getClass().getSimpleName().toUpperCase()).reversed())
.collect(Collectors.toList());
- addSubOptionsPanels(new LicenseDisclaimerPanel(), this.subPanels);
+ addSubOptionsPanels(this.subPanels);
}
- private void addSubOptionsPanels(JPanel disclaimerPanel, List subPanels) {
+ private void addSubOptionsPanels(List subPanels) {
GridBagConstraints disclaimerConstraints = new GridBagConstraints();
disclaimerConstraints.gridx = 0;
disclaimerConstraints.gridy = 0;
@@ -79,8 +79,6 @@ public class CTOptionsPanel extends IngestModuleGlobalSettingsPanel {
disclaimerConstraints.weighty = 0;
disclaimerConstraints.weightx = 0;
- contentPane.add(disclaimerPanel, disclaimerConstraints);
-
for (int i = 0; i < subPanels.size(); i++) {
CTOptionsSubPanel subPanel = subPanels.get(i);
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java
deleted file mode 100644
index 93c857004b..0000000000
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Autopsy Forensic Browser
- *
- * Copyright 2023 Basis Technology Corp.
- * Contact: carrier sleuthkit 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;
-
-import org.sleuthkit.autopsy.coreutils.Desktop;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.logging.Level;
-import org.sleuthkit.autopsy.coreutils.Logger;
-
-/**
- * Disclaimer for license and place to purchase CT license.
- */
-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";
-
- /**
- * Creates new form LicenseDisclaimerPanel
- */
- public LicenseDisclaimerPanel() {
- initComponents();
- }
-
- /**
- * This method is called from within the constructor to initialize the form.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
- */
- @SuppressWarnings("unchecked")
- // //GEN-BEGIN:initComponents
- private void initComponents() {
- 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();
-
- 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));
- setLayout(new java.awt.GridBagLayout());
-
- org.openide.awt.Mnemonics.setLocalizedText(disclaimer, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.disclaimer.text")); // NOI18N
- disclaimer.setVerticalAlignment(javax.swing.SwingConstants.TOP);
- 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(
- spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 0, Short.MAX_VALUE)
- );
- spacerLayout.setVerticalGroup(
- spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGap(0, 0, Short.MAX_VALUE)
- );
-
- gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 2;
- gridBagConstraints.weighty = 1.0;
- add(spacer, gridBagConstraints);
- }// //GEN-END:initComponents
-
- private void linkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_linkMouseClicked
- if (Desktop.isDesktopSupported()) {
- try {
- Desktop.getDesktop().browse(new URI(CHECKOUT_PAGE_URL));
- } catch (IOException | URISyntaxException e) {
- LOGGER.log(Level.SEVERE, "Error opening link to: " + CHECKOUT_PAGE_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
-}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties
index bd06716288..44e71a4513 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties
@@ -4,18 +4,16 @@
CTLicenseDialog.title=Add a License...
CTLicenseDialog.licenseNumberLabel.text=License Number:
-CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+CTLicenseDialog.licenseNumberTextField.text=
CTLicenseDialog.cancelButton.text=Cancel
CTLicenseDialog.okButton.text=Ok
CTLicenseDialog.warningLabel.text=
CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text=
-CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
CTMalwareScannerOptionsPanel.countersResetLabel.text=
-CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title=License Info
CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text=
CTMalwareScannerOptionsPanel.maxHashLookupsLabel.text=
CTMalwareScannerOptionsPanel.malwareScansMessageLabel.text=
-CTMalwareScannerOptionsPanel.malwareScansPanel.border.title=Malware Scans
+CTMalwareScannerOptionsPanel.malwareScansPanel.border.title=Malware Scanner
CTMalwareScannerOptionsPanel.licenseInfoAddButton.text=Add License
CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text=
CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text=
@@ -24,3 +22,7 @@ CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
EULADialog.cancelButton.text=Cancel
EULADialog.acceptButton.text=Accept
EULADialog.title=Cyber Triage End User License Agreement
+CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
+CTMalwareScannerOptionsPanel.disclaimer.text=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.
+CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit
+CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED
index 58a1befb1f..d9876a8ed7 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED
@@ -4,24 +4,26 @@
CTLicenseDialog.title=Add a License...
CTLicenseDialog.licenseNumberLabel.text=License Number:
-CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+CTLicenseDialog.licenseNumberTextField.text=
CTLicenseDialog.cancelButton.text=Cancel
CTLicenseDialog.okButton.text=Ok
CTLicenseDialog.warningLabel.text=
-CTLicenseDialog_verifyInput_licenseNumberError=Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
+CTLicenseDialog_verifyInput_licenseNumberError=Please enter a license number
CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text=
-CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
CTMalwareScannerOptionsPanel.countersResetLabel.text=
-CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title=License Info
CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text=
CTMalwareScannerOptionsPanel.maxHashLookupsLabel.text=
CTMalwareScannerOptionsPanel.malwareScansMessageLabel.text=
-CTMalwareScannerOptionsPanel.malwareScansPanel.border.title=Malware Scans
+CTMalwareScannerOptionsPanel.malwareScansPanel.border.title=Malware Scanner
CTMalwareScannerOptionsPanel.licenseInfoAddButton.text=Add License
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
@@ -29,6 +31,8 @@ CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title=License Number Alr
CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_desc=Please verify that license number is of format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_title=Invalid License Number
CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error
+# {0} - licenseCode
+CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0}
CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.
CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error
# {0} - expiresDate
@@ -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,7 @@ CTOPtionsPanel_loadMalwareScansInfo_loading=Loading...
EULADialog.cancelButton.text=Cancel
EULADialog.acceptButton.text=Accept
EULADialog.title=Cyber Triage End User License Agreement
+CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
+CTMalwareScannerOptionsPanel.disclaimer.text=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.
+CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit
+CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form
index e7cd2743a0..2ea57d43a8 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form
@@ -127,6 +127,9 @@
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java
index 608ea63040..99fc749dd7 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java
@@ -18,18 +18,20 @@
*/
package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
+import java.awt.Color;
import java.util.regex.Pattern;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.corecomponents.TextPrompt;
/**
* License dialog
*/
-public class CTLicenseDialog extends javax.swing.JDialog {
+class CTLicenseDialog extends javax.swing.JDialog {
- private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9\\-]+?\\s*$");
+ private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9-_]+?\\s*$");
private String licenseString = null;
/**
@@ -38,6 +40,7 @@ public class CTLicenseDialog extends javax.swing.JDialog {
public CTLicenseDialog(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
+ configureHintText();
this.licenseNumberTextField.getDocument().putProperty("filterNewlines", Boolean.TRUE);
this.licenseNumberTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
@@ -56,13 +59,23 @@ public class CTLicenseDialog extends javax.swing.JDialog {
}
});
}
-
+
+ private void configureHintText() {
+ TextPrompt textPrompt = new TextPrompt(
+ StringUtils.defaultString(this.licenseNumberTextField.getToolTipText()),
+ this.licenseNumberTextField);
+
+ textPrompt.setForeground(Color.LIGHT_GRAY);
+ float alpha = 0.9f; // Mostly opaque
+ textPrompt.changeAlpha(alpha);
+ }
+
String getValue() {
return licenseString;
}
@Messages({
- "CTLicenseDialog_verifyInput_licenseNumberError=Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'"
+ "CTLicenseDialog_verifyInput_licenseNumberError=Please enter a license number"
})
private void verifyInput() {
String licenseInput = StringUtils.defaultString(this.licenseNumberTextField.getText());
@@ -165,6 +178,7 @@ public class CTLicenseDialog extends javax.swing.JDialog {
getContentPane().add(cancelButton, gridBagConstraints);
licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N
+ licenseNumberTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.toolTipText")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
@@ -177,7 +191,8 @@ public class CTLicenseDialog extends javax.swing.JDialog {
}// //GEN-END:initComponents
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
- this.licenseString = this.licenseNumberTextField.getText();
+ String inputText = this.licenseNumberTextField.getText();
+ this.licenseString = inputText == null ? null : inputText.trim();
this.dispose();
}//GEN-LAST:event_okButtonActionPerformed
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java
index 9f7f3b8bf6..43e34a8d2a 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java
@@ -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());
@@ -94,4 +94,8 @@ public class CTLicensePersistence {
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();
+ }
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form
index 77361419b6..dcdfb0a767 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form
@@ -11,103 +11,16 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -121,78 +34,221 @@
-
+
-
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java
index 564eeb7eba..deff21d423 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java
@@ -24,18 +24,22 @@ 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;
+import java.awt.Desktop;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
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;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
@@ -46,6 +50,7 @@ import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.core.UserPreferences;
+import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Options panel to be displayed in the CTOptionsPanel for settings regarding
@@ -71,6 +76,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
private volatile String licenseInfoMessage = null;
private volatile LicenseFetcher licenseFetcher = null;
+ private static final String PURCHASE_URL = "https://cybertriage.com/autopsy-malware-module";
+
private volatile AuthTokenResponse authTokenResponse = null;
private volatile String authTokenMessage = null;
private volatile AuthTokenFetcher authTokenFetcher = null;
@@ -127,12 +134,33 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
setMalwareScansDisplay(null, null);
if (licenseInfo != null) {
loadMalwareScansInfo(licenseInfo);
+ this.purchaseFromLabel.setVisible(false);
+ this.purchaseLink.setVisible(false);
+ } else {
+ this.purchaseFromLabel.setVisible(true);
+ this.purchaseLink.setVisible(true);
+ }
+ }
+
+ private static String getHtmlLink(String url) {
+ return "" + url + "";
+ }
+
+ private void gotoLink(String url) {
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().browse(new URI(url));
+ } catch (IOException | URISyntaxException 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.");
}
}
private synchronized LicenseResponse getLicenseInfo() {
return this.licenseInfo == null ? null : this.licenseInfo.getLicenseResponse();
- }
+ }
private synchronized void setLicenseDisplay(LicenseInfo licenseInfo, String licenseMessage) {
this.licenseInfo = licenseInfo;
@@ -202,48 +230,55 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
+ malwareScansPanel = new javax.swing.JPanel();
+ javax.swing.JLabel disclaimer = new javax.swing.JLabel();
javax.swing.JPanel licenseInfoPanel = new javax.swing.JPanel();
licenseInfoMessageLabel = new javax.swing.JLabel();
- licenseInfoUserLabel = new javax.swing.JLabel();
licenseInfoExpiresLabel = new javax.swing.JLabel();
licenseInfoIdLabel = new javax.swing.JLabel();
+ licenseInfoUserLabel = new javax.swing.JLabel();
licenseInfoAddButton = new javax.swing.JButton();
- malwareScansPanel = new javax.swing.JPanel();
- malwareScansMessageLabel = new javax.swing.JLabel();
- maxHashLookupsLabel = new javax.swing.JLabel();
maxFileUploadsLabel = new javax.swing.JLabel();
- countersResetLabel = new javax.swing.JLabel();
+ maxHashLookupsLabel = new javax.swing.JLabel();
hashLookupsRemainingLabel = new javax.swing.JLabel();
+ malwareScansMessageLabel = new javax.swing.JLabel();
+ countersResetLabel = new javax.swing.JLabel();
fileUploadsRemainingLabel = new javax.swing.JLabel();
+ javax.swing.JPanel purchasePanel = new javax.swing.JPanel();
+ purchaseFromLabel = new javax.swing.JLabel();
+ purchaseLink = new javax.swing.JLabel();
setLayout(new java.awt.GridBagLayout());
- licenseInfoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title"))); // NOI18N
+ malwareScansPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.malwareScansPanel.border.title"))); // NOI18N
+ malwareScansPanel.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(disclaimer, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.disclaimer.text")); // NOI18N
+ disclaimer.setVerticalAlignment(javax.swing.SwingConstants.TOP);
+ 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.insets = new java.awt.Insets(5, 5, 5, 5);
+ malwareScansPanel.add(disclaimer, gridBagConstraints);
+
licenseInfoPanel.setLayout(new java.awt.GridBagLayout());
org.openide.awt.Mnemonics.setLocalizedText(licenseInfoMessageLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text")); // NOI18N
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);
licenseInfoPanel.add(licenseInfoMessageLabel, gridBagConstraints);
- org.openide.awt.Mnemonics.setLocalizedText(licenseInfoUserLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text")); // NOI18N
- gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 1;
- gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
- gridBagConstraints.weightx = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
- licenseInfoPanel.add(licenseInfoUserLabel, gridBagConstraints);
-
org.openide.awt.Mnemonics.setLocalizedText(licenseInfoExpiresLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
@@ -252,12 +287,21 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
org.openide.awt.Mnemonics.setLocalizedText(licenseInfoIdLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ licenseInfoPanel.add(licenseInfoIdLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(licenseInfoUserLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
- licenseInfoPanel.add(licenseInfoIdLabel, gridBagConstraints);
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ licenseInfoPanel.add(licenseInfoUserLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(licenseInfoAddButton, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoAddButton.text")); // NOI18N
licenseInfoAddButton.addActionListener(new java.awt.event.ActionListener() {
@@ -266,79 +310,108 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
licenseInfoPanel.add(licenseInfoAddButton, gridBagConstraints);
- gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 1;
- gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
- gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
- gridBagConstraints.weightx = 1.0;
- add(licenseInfoPanel, gridBagConstraints);
-
- malwareScansPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.malwareScansPanel.border.title"))); // NOI18N
- malwareScansPanel.setLayout(new java.awt.GridBagLayout());
-
- org.openide.awt.Mnemonics.setLocalizedText(malwareScansMessageLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.malwareScansMessageLabel.text")); // NOI18N
- 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);
- malwareScansPanel.add(malwareScansMessageLabel, gridBagConstraints);
-
- org.openide.awt.Mnemonics.setLocalizedText(maxHashLookupsLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.maxHashLookupsLabel.text")); // NOI18N
- gridBagConstraints = new java.awt.GridBagConstraints();
- gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 1;
- gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
- gridBagConstraints.weightx = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
- malwareScansPanel.add(maxHashLookupsLabel, gridBagConstraints);
-
org.openide.awt.Mnemonics.setLocalizedText(maxFileUploadsLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
- gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridy = 4;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
- malwareScansPanel.add(maxFileUploadsLabel, gridBagConstraints);
+ licenseInfoPanel.add(maxFileUploadsLabel, gridBagConstraints);
- org.openide.awt.Mnemonics.setLocalizedText(countersResetLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.countersResetLabel.text")); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(maxHashLookupsLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.maxHashLookupsLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
- gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
- malwareScansPanel.add(countersResetLabel, gridBagConstraints);
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ licenseInfoPanel.add(maxHashLookupsLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(hashLookupsRemainingLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
- gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridy = 3;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
- malwareScansPanel.add(hashLookupsRemainingLabel, gridBagConstraints);
+ licenseInfoPanel.add(hashLookupsRemainingLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(malwareScansMessageLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.malwareScansMessageLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 3;
+ 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);
+ licenseInfoPanel.add(malwareScansMessageLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(countersResetLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.countersResetLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 5;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ licenseInfoPanel.add(countersResetLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(fileUploadsRemainingLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
- gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridy = 4;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
- malwareScansPanel.add(fileUploadsRemainingLabel, gridBagConstraints);
+ licenseInfoPanel.add(fileUploadsRemainingLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ malwareScansPanel.add(licenseInfoPanel, gridBagConstraints);
+
+ purchasePanel.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(purchaseFromLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.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 = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ malwareScansPanel.add(purchasePanel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
@@ -378,6 +451,10 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
}
}//GEN-LAST:event_licenseInfoAddButtonActionPerformed
+ private void purchaseLinkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_purchaseLinkMouseClicked
+ gotoLink(PURCHASE_URL);
+ }//GEN-LAST:event_purchaseLinkMouseClicked
+
@NbBundle.Messages({
"# {0} - userName",
"# {1} - email",
@@ -387,9 +464,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",
@@ -406,7 +485,12 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
this.licenseInfoExpiresLabel.setVisible(false);
this.licenseInfoIdLabel.setVisible(false);
this.licenseInfoUserLabel.setVisible(false);
+ this.purchaseFromLabel.setVisible(true);
+ this.purchaseLink.setVisible(true);
} else {
+ this.purchaseFromLabel.setVisible(false);
+ this.purchaseLink.setVisible(false);
+
this.licenseInfoExpiresLabel.setVisible(true);
this.licenseInfoExpiresLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_expires(
this.licenseInfo.getDecryptedLicense().getExpirationDate() == null
@@ -420,12 +504,10 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
StringUtils.defaultString(this.licenseInfo.getDecryptedLicense().getCustomerEmail())));
}
- this.malwareScansPanel.setVisible(StringUtils.isNotBlank(this.authTokenMessage) || authTokenResponse != null);
-
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 +515,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 "";
}
}
@@ -479,6 +608,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
@NbBundle.Messages({
"CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error",
"CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error",
+ "# {0} - licenseCode",
+ "CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0}",
"CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.",})
private class LicenseFetcher extends SwingWorker {
@@ -500,8 +631,41 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
protected void done() {
try {
LicenseResponse licenseResponse = get();
+ // if no result, show unauthorized
+ if (licenseResponse == null) {
+ logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch returned no result.");
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ CTCloudException.ErrorCode.UN_AUTHORIZED.getDescription(),
+ Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ setLicenseDisplay(licenseInfo, null);
+ loadMalwareScansInfo(licenseInfo);
+ return;
+ }
+
+ // if not successful response
+ if (!Boolean.TRUE.equals(licenseResponse.isSuccess())) {
+ logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch was not successful");
+ // use default message unless error message specified
+ String message = Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc(licenseText);
+ if (!StringUtils.isBlank(licenseResponse.getErrorMsg())) {
+ message = licenseResponse.getErrorMsg();
+ }
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ message,
+ Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ setLicenseDisplay(licenseInfo, null);
+ loadMalwareScansInfo(licenseInfo);
+ return;
+ }
+
+ // otherwise, load
SwingUtilities.invokeLater(() -> acceptEula(licenseResponse));
- } catch (InterruptedException ex) {
+
+ } catch (InterruptedException | CancellationException ex) {
// ignore cancellation; just load current license
setLicenseDisplay(licenseInfo, null);
loadMalwareScansInfo(licenseInfo);
@@ -548,8 +712,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
if (this.isCancelled()) {
return null;
}
-
- return ctApiDAO.getAuthToken(decryptedLicense);
+
+ return ctApiDAO.getAuthToken(decryptedLicense);
}
@Override
@@ -557,7 +721,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) {
@@ -600,5 +764,7 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
private javax.swing.JPanel malwareScansPanel;
private javax.swing.JLabel maxFileUploadsLabel;
private javax.swing.JLabel maxHashLookupsLabel;
+ private javax.swing.JLabel purchaseFromLabel;
+ private javax.swing.JLabel purchaseLink;
// End of variables declaration//GEN-END:variables
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java
index 31056e1f2a..0279cafb9b 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java
@@ -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";
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties
new file mode 100644
index 0000000000..48a2628742
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties
@@ -0,0 +1,16 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+CTIncidentImportOptionsPanel.border.title=Local Settings
+CTIncidentImportOptionsPanel.fileRepoPathLabel.text=Update the Cyber Triage Data Folder if you are not using the default location:
+CTIncidentImportOptionsPanel.fileRepoPathField.text=
+CTIncidentImportOptionsPanel.fileRepoBrowseButton.text=Browse
+CTIncidentImportOptionsPanel.caseOpenWarningLabel.text=Some settings cannot be modified while a case is open.
+CTIncidentImportOptionsPanel.fileRepoFileChooser.title=Cyber Triage Data Folder
+CTIncidentImportOptionsPanel.border.title_1=Incident Importer
+CTIncidentImportOptionsPanel.incidentTextLabel.text=The Cyber Triage Incident Import module allows you to open data collected by Cyber Triage in Autopsy. To use this feature you must install the Cyber Triage Import Module.
+CTincidentImportOptionsPanel.instructionsTextLabel.text=
+CTIncidentImportOptionsPanel.instructionsTextLabel.text=For instructions on obtaining the module refer to:
+CTIncidentImportOptionsPanel.importModule.text=Cyber Triage Import Module:
+CTIncidentImportOptionsPanel.importModuleDetected.text=
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties-MERGED
new file mode 100644
index 0000000000..dd406e6725
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties-MERGED
@@ -0,0 +1,18 @@
+
+# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template
+
+CTIncidentImportOptionsPanel.border.title=Local Settings
+CTIncidentImportOptionsPanel.fileRepoPathLabel.text=Update the Cyber Triage Data Folder if you are not using the default location:
+CTIncidentImportOptionsPanel.fileRepoPathField.text=
+CTIncidentImportOptionsPanel.fileRepoBrowseButton.text=Browse
+CTIncidentImportOptionsPanel.caseOpenWarningLabel.text=Some settings cannot be modified while a case is open.
+CTIncidentImportOptionsPanel.fileRepoFileChooser.title=Cyber Triage Data Folder
+CTIncidentImportOptionsPanel.border.title_1=Incident Importer
+CTIncidentImportOptionsPanel.incidentTextLabel.text=The Cyber Triage Incident Import module allows you to open data collected by Cyber Triage in Autopsy. To use this feature you must install the Cyber Triage Import Module.
+CTincidentImportOptionsPanel.instructionsTextLabel.text=
+CTIncidentImportOptionsPanel.instructionsTextLabel.text=For instructions on obtaining the module refer to:
+CTIncidentImportOptionsPanel.importModule.text=Cyber Triage Import Module:
+CTIncidentImportOptionsPanel.importModuleDetected.text=
+CTIncidentImportOptionsPanel_setModuleDetected_detected=Detected
+CTIncidentImportOptionsPanel_setModuleDetected_notDetected=Not Detected
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.form
new file mode 100644
index 0000000000..fd11f53ee8
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.form
@@ -0,0 +1,199 @@
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.java
new file mode 100644
index 0000000000..40a2c0fe3f
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTIncidentImportOptionsPanel.java
@@ -0,0 +1,336 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.incidentoptions;
+
+import com.basistech.df.cybertriage.autopsy.ctoptions.subpanel.CTOptionsSubPanel;
+import java.awt.Desktop;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Level;
+import java.util.stream.Stream;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import javax.swing.JFileChooser;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.apache.commons.lang3.StringUtils;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.casemodule.AutopsyContentProvider;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
+
+/**
+ * Options panel for CyberTriage options for importing a CyberTriage incident
+ */
+@ServiceProvider(service = CTOptionsSubPanel.class)
+public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
+
+ private static final Logger logger = Logger.getLogger(CTIncidentImportOptionsPanel.class.getName());
+
+ private static final String CT_IMPORTER_DOC_LINK = "https://docs.cybertriage.com/en/latest/chapters/integrations/autopsy.html";
+
+ private static final String CT_STANDARD_CONTENT_PROVIDER_NAME = "CTStandardContentProvider";
+
+ private final JFileChooserFactory fileRepoChooserFactory = new JFileChooserFactory();
+ private final CTSettingsPersistence ctPersistence = CTSettingsPersistence.getInstance();
+
+ private static String getHtmlLink(String url) {
+ return "" + url + "";
+ }
+
+ /**
+ * Creates new form CTIncidentImportOptionsPanel
+ */
+ public CTIncidentImportOptionsPanel() {
+ initComponents();
+ this.fileRepoPathField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ fireSettingsChanged();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ fireSettingsChanged();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ fireSettingsChanged();
+ }
+ });
+
+ Case.addEventTypeSubscriber(Collections.singleton(Case.Events.CURRENT_CASE), (evt) -> {
+ CTIncidentImportOptionsPanel.this.setEnabledItems(evt.getNewValue() != null);
+ });
+ }
+
+ private void setCTSettingsDisplay(CTSettings ctSettings) {
+ this.fileRepoPathField.setText(ctSettings.getFileRepoPath());
+ }
+
+ @Override
+ public synchronized void saveSettings() {
+ ctPersistence.saveCTSettings(getSettings());
+ }
+
+ @Override
+ public synchronized void loadSettings() {
+ CTSettings ctSettings = ctPersistence.loadCTSettings();
+ setCTSettingsDisplay(ctSettings);
+ setModuleDetected();
+ setEnabledItems(Case.isCaseOpen());
+ }
+
+ @Messages({
+ "CTIncidentImportOptionsPanel_setModuleDetected_detected=Detected",
+ "CTIncidentImportOptionsPanel_setModuleDetected_notDetected=Not Detected"
+ })
+ private void setModuleDetected() {
+ Collection extends AutopsyContentProvider> contentProviders = Lookup.getDefault().lookupAll(AutopsyContentProvider.class);
+ boolean detected = ((Collection extends AutopsyContentProvider>) (contentProviders != null ? contentProviders : Collections.emptyList())).stream()
+ .anyMatch(p -> p != null && StringUtils.defaultString(p.getName()).toUpperCase().startsWith(CT_STANDARD_CONTENT_PROVIDER_NAME.toUpperCase()));
+
+ this.importModuleDetected.setText(detected
+ ? Bundle.CTIncidentImportOptionsPanel_setModuleDetected_detected()
+ : Bundle.CTIncidentImportOptionsPanel_setModuleDetected_notDetected());
+ }
+
+ private void setEnabledItems(boolean caseOpen) {
+ this.caseOpenWarningLabel.setVisible(caseOpen);
+ this.fileRepoBrowseButton.setEnabled(!caseOpen);
+ this.fileRepoPathField.setEnabled(!caseOpen);
+ }
+
+ private void fireSettingsChanged() {
+ this.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }
+
+ private CTSettings getSettings() {
+ return new CTSettings().setFileRepoPath(this.fileRepoPathField.getText());
+ }
+
+ @Override
+ public boolean valid() {
+ return new File(this.fileRepoPathField.getText()).isDirectory();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ java.awt.GridBagConstraints gridBagConstraints;
+
+ incidentTextPanel = new javax.swing.JPanel();
+ incidentTextLabel = new javax.swing.JLabel();
+ javax.swing.JLabel importModule = new javax.swing.JLabel();
+ importModuleDetected = new javax.swing.JLabel();
+ instructionsPanel = new javax.swing.JPanel();
+ instructionsTextLabel = new javax.swing.JLabel();
+ instructionsLinkLabel = new javax.swing.JLabel();
+ repoPanel = new javax.swing.JPanel();
+ javax.swing.JLabel fileRepoPathLabel = new javax.swing.JLabel();
+ fileRepoPathField = new javax.swing.JTextField();
+ fileRepoBrowseButton = new javax.swing.JButton();
+ caseOpenWarningLabel = new javax.swing.JLabel();
+
+ setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.border.title_1"))); // NOI18N
+ setLayout(new java.awt.GridBagLayout());
+
+ incidentTextPanel.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(incidentTextLabel, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.incidentTextLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ incidentTextPanel.add(incidentTextLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(importModule, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.importModule.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 3);
+ incidentTextPanel.add(importModule, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(importModuleDetected, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.importModuleDetected.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 0, 5, 5);
+ incidentTextPanel.add(importModuleDetected, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ add(incidentTextPanel, gridBagConstraints);
+
+ instructionsPanel.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(instructionsTextLabel, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.instructionsTextLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 0);
+ instructionsPanel.add(instructionsTextLabel, gridBagConstraints);
+ instructionsTextLabel.getAccessibleContext().setAccessibleName("For instructions on obtaining the module refer to:");
+
+ org.openide.awt.Mnemonics.setLocalizedText(instructionsLinkLabel, getHtmlLink(CT_IMPORTER_DOC_LINK));
+ instructionsLinkLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
+ instructionsLinkLabel.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ instructionsLinkLabelMouseClicked(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ instructionsPanel.add(instructionsLinkLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ add(instructionsPanel, gridBagConstraints);
+
+ repoPanel.setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(fileRepoPathLabel, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.fileRepoPathLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ repoPanel.add(fileRepoPathLabel, gridBagConstraints);
+
+ fileRepoPathField.setText(org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.fileRepoPathField.text")); // NOI18N
+ fileRepoPathField.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ fileRepoPathFieldActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ repoPanel.add(fileRepoPathField, gridBagConstraints);
+ fileRepoPathField.getAccessibleContext().setAccessibleName("");
+
+ org.openide.awt.Mnemonics.setLocalizedText(fileRepoBrowseButton, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.fileRepoBrowseButton.text")); // NOI18N
+ fileRepoBrowseButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ fileRepoBrowseButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ repoPanel.add(fileRepoBrowseButton, gridBagConstraints);
+
+ caseOpenWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/hashdatabase/warning16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(caseOpenWarningLabel, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.caseOpenWarningLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ repoPanel.add(caseOpenWarningLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ add(repoPanel, gridBagConstraints);
+ }// //GEN-END:initComponents
+ private void fileRepoBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileRepoBrowseButtonActionPerformed
+ JFileChooser fileChooser = fileRepoChooserFactory.getChooser();
+ fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fileChooser.setMultiSelectionEnabled(false);
+
+ File curSelectedDir = StringUtils.isBlank(this.fileRepoPathField.getText()) ? null : new File(this.fileRepoPathField.getText());
+ if (curSelectedDir == null || !curSelectedDir.isDirectory()) {
+ curSelectedDir = new File(CTSettings.getDefaultFileRepoPath());
+ }
+
+ fileChooser.setCurrentDirectory(curSelectedDir);
+ fileChooser.setDialogTitle(org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.fileRepoFileChooser.title"));
+ int retVal = fileChooser.showOpenDialog(this);
+ if (retVal == JFileChooser.APPROVE_OPTION) {
+ this.fileRepoPathField.setText(fileChooser.getSelectedFile().getAbsolutePath());
+ }
+ }//GEN-LAST:event_fileRepoBrowseButtonActionPerformed
+
+ private void fileRepoPathFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileRepoPathFieldActionPerformed
+ // TODO add your handling code here:
+ }//GEN-LAST:event_fileRepoPathFieldActionPerformed
+
+ private void instructionsLinkLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_instructionsLinkLabelMouseClicked
+ gotoLink(CT_IMPORTER_DOC_LINK);
+ }//GEN-LAST:event_instructionsLinkLabelMouseClicked
+
+ private void gotoLink(String url) {
+ if (Desktop.isDesktopSupported()) {
+ try {
+ Desktop.getDesktop().browse(new URI(url));
+ } catch (IOException | URISyntaxException 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.");
+ }
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel caseOpenWarningLabel;
+ private javax.swing.JButton fileRepoBrowseButton;
+ private javax.swing.JTextField fileRepoPathField;
+ private javax.swing.JLabel importModuleDetected;
+ private javax.swing.JLabel incidentTextLabel;
+ private javax.swing.JPanel incidentTextPanel;
+ private javax.swing.JLabel instructionsLinkLabel;
+ private javax.swing.JPanel instructionsPanel;
+ private javax.swing.JLabel instructionsTextLabel;
+ private javax.swing.JPanel repoPanel;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettings.java b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettings.java
new file mode 100644
index 0000000000..d6427a641c
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettings.java
@@ -0,0 +1,102 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.incidentoptions;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.modules.Places;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+
+/**
+ * CT settings that don't include license information. This code must be kept
+ * in-sync with code in CT Autopsy Importer NBM.
+ */
+public class CTSettings {
+
+ private static final Logger LOGGER = Logger.getLogger(CTSettings.class.getCanonicalName());
+ private static final String DEFAULT_FILE_REPO_PATH = getAppDataLocalDirectory();
+
+ private static final String CYBERTRIAGE_FOLDER = "cybertriage";
+ private static final String CYBERTRIAGE_DOT_FOLDER = "." + CYBERTRIAGE_FOLDER;
+
+ // based on com.basistech.df.cybertriage.utils.SystemProperties
+ private static String getAppDataLocalDirectory() {
+ if (Objects.nonNull(Places.getUserDirectory()) && Places.getUserDirectory().getAbsolutePath().endsWith("testuserdir")) { // APP is in testing .. this should return the test path
+ LOGGER.log(Level.INFO, "Application Data (test mode) Path: " + Places.getUserDirectory().getAbsolutePath());
+ return Places.getUserDirectory().getAbsolutePath();
+ }
+
+ // try to use LOCALAPPDATA on windows
+ String localDataStr = System.getenv("LOCALAPPDATA");
+ if (StringUtils.isNotBlank(localDataStr)) {
+ Path localAppPath = Paths.get(localDataStr, CYBERTRIAGE_FOLDER);
+ try {
+ Files.createDirectories(localAppPath);
+ LOGGER.log(Level.INFO, "Application Data Path: " + localAppPath.toString());
+ return localAppPath.toString();
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE, "IO Error using " + localAppPath.toString(), ex);
+ }
+ }
+
+ // try to use ~/.cybertriage anywhere else
+ if (!PlatformUtil.isWindowsOS()) {
+ String homePathStr = System.getenv("HOME");
+ if (StringUtils.isNotBlank(homePathStr)) {
+ Path localAppPath = Paths.get(homePathStr, CYBERTRIAGE_DOT_FOLDER);
+ try {
+ Files.createDirectories(localAppPath);
+ LOGGER.log(Level.INFO, "Non-windows Application Data Path: " + localAppPath.toString());
+ return localAppPath.toString();
+ } catch (IOException ex) {
+ LOGGER.log(Level.SEVERE, "IO Error using " + localAppPath.toString(), ex);
+ }
+ }
+ }
+
+ // defer to user directory otherwise
+ return Places.getUserDirectory().getAbsolutePath(); // In case of an IO Error
+ }
+
+ public static String getDefaultFileRepoPath() {
+ return DEFAULT_FILE_REPO_PATH;
+ }
+
+ static CTSettings getDefaultSettings() {
+ return new CTSettings()
+ .setFileRepoPath(DEFAULT_FILE_REPO_PATH);
+ }
+
+ private String fileRepoPath = DEFAULT_FILE_REPO_PATH;
+
+ public String getFileRepoPath() {
+ return fileRepoPath;
+ }
+
+ public CTSettings setFileRepoPath(String fileRepoPath) {
+ this.fileRepoPath = fileRepoPath;
+ return this;
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettingsPersistence.java b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettingsPersistence.java
new file mode 100644
index 0000000000..e5e5b6838f
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/incidentoptions/CTSettingsPersistence.java
@@ -0,0 +1,85 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.incidentoptions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+
+/**
+ * Handles persisting CT Settings. This code must be kept in-sync with code in
+ * CT Autopsy Importer NBM.
+ */
+public class CTSettingsPersistence {
+
+ private static final String CT_SETTINGS_DIR = "CyberTriage";
+ private static final String CT_SETTINGS_FILENAME = "CyberTriageSettings.json";
+
+ private static final Logger logger = Logger.getLogger(CTSettingsPersistence.class.getName());
+
+ private static final CTSettingsPersistence instance = new CTSettingsPersistence();
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public static CTSettingsPersistence getInstance() {
+ return instance;
+ }
+
+ public synchronized boolean saveCTSettings(CTSettings ctSettings) {
+ if (ctSettings != null) {
+
+ File settingsFile = getCTSettingsFile();
+ settingsFile.getParentFile().mkdirs();
+ try {
+ objectMapper.writeValue(settingsFile, ctSettings);
+ return true;
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "There was an error writing CyberTriage settings to file: " + settingsFile.getAbsolutePath(), ex);
+ }
+ }
+
+ return false;
+ }
+
+ public synchronized CTSettings loadCTSettings() {
+
+ CTSettings settings = null;
+ File settingsFile = getCTSettingsFile();
+ if (settingsFile.isFile()) {
+ try {
+ settings = objectMapper.readValue(settingsFile, CTSettings.class);
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "There was an error reading CyberTriage settings to file: " + settingsFile.getAbsolutePath(), ex);
+ }
+ }
+
+ return settings == null
+ ? CTSettings.getDefaultSettings()
+ : settings;
+
+ }
+
+ private File getCTSettingsFile() {
+ return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, CT_SETTINGS_FILENAME).toFile();
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java
index eab025a641..a65d319dc2 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java
@@ -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 {
+class BatchProcessor {
private ExecutorService processingExecutorService = Executors.newSingleThreadExecutor();
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties
new file mode 100644
index 0000000000..fbf6db930c
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties
@@ -0,0 +1,7 @@
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.AccessibleContext.accessibleName=Upload file content for files that do not have results
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.label=Upload file content for files that do not have results
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.text=Upload file content for files that do not have results
+DataSourceIntegrityIngestSettingsPanel.queryFilesTextArea.text=Query for existing results using file\u2019s hash
+MalwareScanIngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings
+MalwareScanIngestSettingsPanel.paidLicenseTextArea.text=This module requires a paid license. \nSee the Global Options panel for details
+MalwareScanIngestSettingsPanel.queryFilesCheckbox.text=Query for existing results using file\u2019s hash
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED
index 0c97a98c2e..decbae37cf 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED
@@ -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,29 @@ 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
-MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables.
+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=Identifies executable files with malware.
MalwareScanIngestModuleFactory_displayName=Cyber Triage Malware Scanner
MalwareScanIngestModuleFactory_version=1.0.0
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.AccessibleContext.accessibleName=Upload file content for files that do not have results
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.label=Upload file content for files that do not have results
+MalwareScanIngestSettingsPanel.uploadFilesCheckbox.text=Upload file content for files that do not have results
+DataSourceIntegrityIngestSettingsPanel.queryFilesTextArea.text=Query for existing results using file\u2019s hash
+MalwareScanIngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings
+MalwareScanIngestSettingsPanel.paidLicenseTextArea.text=This module requires a paid license. \nSee the Global Options panel for details
+MalwareScanIngestSettingsPanel.queryFilesCheckbox.text=Query for existing results using file\u2019s hash
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
index 6d3f7d4766..fee67fdff2 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
@@ -19,15 +19,22 @@
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.FileUploadRequest;
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 +43,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 +58,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,13 +70,20 @@ 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();
-
+ private boolean uploadFiles;
+ private boolean queryFiles;
+
+ MalwareScanIngestModule(MalwareScanIngestSettings settings) {
+ uploadFiles = settings.shouldUploadFiles();
+ queryFiles = settings.shouldQueryFiles();
+ }
+
@Override
public void startUp(IngestJobContext context) throws IngestModuleException {
- sharedProcessing.startUp(context);
+ sharedProcessing.startUp(context, uploadFiles);
}
@Override
@@ -93,6 +110,16 @@ 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;
+
+ // min and max upload size in bytes
+ private static final long MIN_UPLOAD_SIZE = 1;
+ private static final long MAX_UPLOAD_SIZE = 100_000_000; // 100MB
+
+ private static final int NUM_FILE_UPLOAD_RETRIES = 7;
+ private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000;
+
private static final Set EXECUTABLE_MIME_TYPES = Stream.of(
"application/x-bat",//NON-NLS
"application/x-dosexec",//NON-NLS
@@ -106,134 +133,241 @@ public class MalwareScanIngestModule implements FileIngestModule {
"application/msdos-windows",//NON-NLS
"application/x-msdos-program"//NON-NLS
).collect(Collectors.toSet());
-
- private static final String MALWARE_TYPE_NAME = "TSK_MALWARE";
- private static final String MALWARE_CONFIG = "Cyber Triage Cloud";
+
+ private static final String MALWARE_CONFIG = ""; // NOTE: Adding a configuration complicates NTL branch UI
private static final Logger logger = Logger.getLogger(MalwareScanIngestModule.class.getName());
- private final BatchProcessor batchProcessor = new BatchProcessor(BATCH_SIZE, FLUSH_SECS_TIMEOUT, this::handleBatch);
+
+ private final BatchProcessor batchProcessor = new BatchProcessor(
+ 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."
- })
- synchronized void startUp(IngestJobContext context) throws IngestModuleException {
+ "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, boolean uploadFiles) throws IngestModuleException {
// only run this code once per startup
- if (runState == RunState.STARTED_UP || runState == RunState.DISABLED) {
+ if (ingestJobState != null) {
return;
}
try {
- // get saved license
- Optional licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo();
- if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) {
- notifyWarning(
- Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_title(),
- Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_desc(),
- null);
- runState = RunState.DISABLED;
- return;
- }
-
- AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense());
- // syncronously fetch malware scans info
-
- // determine lookups remaining
- long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
- if (lookupsRemaining <= 0) {
- notifyWarning(
- Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_title(),
- Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_desc(),
- null);
- runState = RunState.DISABLED;
- return;
- } else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) {
- notifyWarning(
- Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title(),
- Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc(lookupsRemaining),
- null);
- }
-
- // setup necessary variables for processing
- tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
- 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;
+ ingestJobState = getNewJobState(context, uploadFiles);
} catch (Exception ex) {
- runState = RunState.DISABLED;
+ 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, boolean uploadFiles) throws Exception {
+ // get saved license
+ Optional licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo();
+ if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_title(),
+ Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_desc(),
+ null);
+
+ return IngestJobState.DISABLED;
+ }
+
+ AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense());
+ // syncronously fetch malware scans info
+
+ // determine lookups remaining
+ long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
+ if (lookupsRemaining <= 0) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_title(),
+ Bundle.MalwareScanIngestModule_ShareProcessing_noLookupsRemaining_desc(),
+ null);
+
+ return IngestJobState.DISABLED;
+ } else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_title(),
+ Bundle.MalwareScanIngestModule_ShareProcessing_lowLookupsLimitWarning_desc(lookupsRemaining),
+ null);
+ }
+
+ // determine lookups remaining
+ 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
+ SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
+ return new IngestJobState(
+ context,
+ tskCase,
+ new PathNormalizer(tskCase),
+ new FileTypeDetector(),
+ licenseInfoOpt.get(),
+ BlackboardArtifact.Type.TSK_MALWARE,
+ 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) {
- if (StringUtils.isNotBlank(af.getMd5Hash())) {
- return af.getMd5Hash();
+ /**
+ * 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 hashResults = HashUtility.calculateHashes(af, Collections.singletonList(HashType.MD5));
+ List 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 +388,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 +406,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 fileRecords) {
- if (runState != RunState.STARTED_UP || fileRecords == null || fileRecords.isEmpty()) {
+ private void handleBatch(IngestJobState ingestJobState, List fileRecords) {
+ if (ingestJobState == null
+ || !ingestJobState.isDoFileLookups()
+ || ingestJobState.getIngestJobContext().fileIngestIsCancelled()
+ || fileRecords == null
+ || fileRecords.isEmpty()) {
return;
}
@@ -278,11 +423,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 md5Hashes = new ArrayList<>(md5ToObjId.keySet());
@@ -292,60 +436,8 @@ public class MalwareScanIngestModule implements FileIngestModule {
}
try {
- // get an auth token with the license
- AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense());
-
- // make sure we are in bounds for the remaining scans
- long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
- if (remainingScans <= 0) {
- runState = RunState.DISABLED;
- notifyWarning(
- Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
- Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
- null);
- return;
- }
-
- // using auth token, get results
- List repResult = ctApiDAO.getReputationResults(
- new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse),
- md5Hashes
- );
-
- List createdArtifacts = new ArrayList<>();
- if (!CollectionUtils.isEmpty(repResult)) {
- SleuthkitCase.CaseDbTransaction trans = null;
- try {
- trans = tskCase.beginTransaction();
- for (CTCloudBean result : repResult) {
- String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue());
- List objIds = md5ToObjId.remove(sanitizedMd5);
- if (objIds == null || objIds.isEmpty()) {
- continue;
- }
-
- for (Long objId : objIds) {
- AnalysisResult res = createAnalysisResult(objId, result, trans);
- if (res != null) {
- createdArtifacts.add(res);
- }
- }
- }
-
- trans.commit();
- trans = null;
- } finally {
- if (trans != null) {
- trans.rollback();
- createdArtifacts.clear();
- trans = null;
- }
- }
-
- if (!CollectionUtils.isEmpty(createdArtifacts)) {
- tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId);
- }
- }
+ List repResult = getHashLookupResults(ingestJobState, md5Hashes);
+ handleLookupResults(ingestJobState, md5ToObjId, repResult);
} catch (Exception ex) {
notifyWarning(
Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
@@ -354,16 +446,402 @@ public class MalwareScanIngestModule implements FileIngestModule {
}
}
- private String sanitizedMd5(String orig) {
+ /**
+ * 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> md5ToObjId, List repResult) throws Blackboard.BlackboardException, TskCoreException, TskCoreException, CTCloudException, NoSuchAlgorithmException, ReadContentInputStream.ReadContentInputStreamException {
+ if (CollectionUtils.isEmpty(repResult)) {
+ return;
+ }
+
+ Map> statusGroupings = repResult.stream()
+ .filter(bean -> bean.getMalwareResult() != null)
+ .collect(Collectors.groupingBy(bean -> bean.getMalwareResult().getStatus()));
+
+ // for all found items, create analysis results
+ List 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> md5ToObjId, List 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 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 getHashLookupResults(IngestJobState ingestJobState, List md5Hashes) throws CTCloudException {
+ if (ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
+ return Collections.emptyList();
+ }
+
+ // get an auth token with the license
+ 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) {
+ ingestJobState.disableDoFileLookups();
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
+ null);
+ 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
+ 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(), af.getSize());
+ 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(new FileUploadRequest()
+ .setContentLength(af.getSize())
+ .setFileInputStream(fileInputStream)
+ .setFileName(af.getName())
+ .setFullUrlPath(authTokenResponse.getFileUploadUrl())
+ );
+
+ // 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()
+ );
+ logger.log(Level.INFO, "Begin polling for malware status of file uploads.");
+
+ Map> remaining = new HashMap<>(ingestJobState.getUnidentifiedHashes());
+
+ for (int retry = 0; retry < NUM_FILE_UPLOAD_RETRIES; retry++) {
+ List> md5Batches = Lists.partition(new ArrayList<>(remaining.keySet()), BATCH_SIZE);
+ for (List batch : md5Batches) {
+ // if we have exceeded limits or cancelled, then we're done.
+ if (!ingestJobState.isDoFileLookups() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
+ return;
+ }
+
+ List repResult = getHashLookupResults(ingestJobState, batch);
+
+ Map> statusGroupings = repResult.stream()
+ .filter(bean -> bean.getMalwareResult() != null)
+ .collect(Collectors.groupingBy(bean -> bean.getMalwareResult().getStatus()));
+
+ // for all found items, create analysis results
+ List 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));
+
+ logger.log(Level.INFO, MessageFormat.format("Waiting {0} milliseconds before polling again for malware status of file uploads.", (waitMultiplier * FILE_UPLOAD_RETRY_SLEEP_MILLIS)));
+
+ 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 repResult, Map> md5ToObjId) throws Blackboard.BlackboardException, TskCoreException {
+ if (CollectionUtils.isEmpty(repResult)) {
+ return;
+ }
+
+ List createdArtifacts = new ArrayList<>();
+ SleuthkitCase.CaseDbTransaction trans = null;
+ try {
+ trans = ingestJobState.getTskCase().beginTransaction();
+ for (CTCloudBean result : repResult) {
+ String sanitizedMd5 = normalizedMd5(result.getMd5HashValue());
+ List objIds = md5ToObjId.remove(sanitizedMd5);
+ if (CollectionUtils.isEmpty(objIds)) {
+ continue;
+ }
+
+ for (Long objId : objIds) {
+ AnalysisResult res = createAnalysisResult(ingestJobState, trans, result, objId);
+ if (res != null) {
+ // only post results that have score NOTABLE or LIKELY_NOTABLE
+ Score score = res.getScore();
+ if (score.getSignificance() == Score.Significance.NOTABLE || score.getSignificance() == Score.Significance.LIKELY_NOTABLE) {
+ createdArtifacts.add(res);
+ }
+ }
+ }
+ }
+
+ trans.commit();
+ trans = null;
+ } finally {
+ if (trans != null) {
+ trans.rollback();
+ createdArtifacts.clear();
+ trans = null;
+ }
+ }
+
+ if (!CollectionUtils.isEmpty(createdArtifacts)) {
+ ingestJobState.getTskCase().getBlackboard().postArtifacts(
+ createdArtifacts,
+ Bundle.MalwareScanIngestModuleFactory_displayName(),
+ ingestJobState.getIngestJobId()
+ );
+ }
+
+ }
+
+ /**
+ * 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
+ ? ""
+ : objId,
+ (cloudBean == null || cloudBean.getMalwareResult() == null || cloudBean.getMalwareResult().getStatus() == null)
+ ? ""
+ : cloudBean.getMalwareResult().getStatus().name()
+ ));
return null;
}
@@ -377,10 +855,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 +867,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 +932,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> 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> 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;
+ }
+
+ }
}
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java
index e138116036..83cb93f689 100644
--- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java
@@ -25,6 +25,8 @@ import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
+import org.sleuthkit.autopsy.ingest.NoIngestModuleIngestJobSettings;
/**
* Factory for malware scan ingest modules.
@@ -32,7 +34,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
@ServiceProvider(service = org.sleuthkit.autopsy.ingest.IngestModuleFactory.class)
@Messages({
"MalwareScanIngestModuleFactory_displayName=Cyber Triage Malware Scanner",
- "MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables.",
+ "MalwareScanIngestModuleFactory_description=Identifies executable files with malware.",
"MalwareScanIngestModuleFactory_version=1.0.0"
})
public class MalwareScanIngestModuleFactory extends IngestModuleFactoryAdapter {
@@ -66,7 +68,7 @@ public class MalwareScanIngestModuleFactory extends IngestModuleFactoryAdapter {
@Override
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) {
- return new MalwareScanIngestModule();
+ return new MalwareScanIngestModule((MalwareScanIngestSettings) ingestOptions);
}
@Override
@@ -81,4 +83,23 @@ public class MalwareScanIngestModuleFactory extends IngestModuleFactoryAdapter {
return optionsPanel;
}
+ @Override
+ public boolean hasIngestJobSettingsPanel() {
+ return true;
+ }
+
+ @Override
+ public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) {
+ if (settings instanceof MalwareScanIngestSettings) {
+ return new MalwareScanIngestSettingsPanel((MalwareScanIngestSettings) settings);
+ }
+ /*
+ * Compatibility check for older versions.
+ */
+ if (settings instanceof NoIngestModuleIngestJobSettings) {
+ return new MalwareScanIngestSettingsPanel(new MalwareScanIngestSettings());
+ }
+
+ throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings");
+ }
}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettings.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettings.java
new file mode 100644
index 0000000000..158e967745
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettings.java
@@ -0,0 +1,96 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+
+/**
+ * Ingest job settings for the Malware scanner module.
+ */
+final class MalwareScanIngestSettings implements IngestModuleIngestJobSettings {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final boolean DEFAULT_QUERY_FILES = true;
+ private static final boolean DEFAULT_UPLOAD_FILES = true;
+
+ public boolean queryFiles;
+ public boolean uploadFiles;
+
+ /**
+ * Instantiate the ingest job settings with default values.
+ */
+ MalwareScanIngestSettings() {
+ this.queryFiles = DEFAULT_QUERY_FILES;
+ this.uploadFiles = DEFAULT_UPLOAD_FILES;
+ }
+
+ /**
+ * Instantiate the ingest job settings.
+ *
+ * @param computeHashes Compute hashes if none are present
+ * @param verifyHashes Verify hashes if any are present
+ */
+ MalwareScanIngestSettings(boolean queryFiles, boolean uploadFiles) {
+ this.queryFiles = queryFiles;
+ this.uploadFiles = uploadFiles;
+ }
+
+ @Override
+ public long getVersionNumber() {
+ return serialVersionUID;
+ }
+
+ /**
+ * Should files be queried?
+ *
+ * @return true if files should be queried, false otherwise
+ */
+ public boolean shouldQueryFiles() {
+ return queryFiles;
+ }
+
+ /**
+ * Set whether files should be queried.
+ *
+ * @param queryFiles true if files should be queried
+ */
+ void setqueryFiles(boolean queryFiles) {
+ this.queryFiles = queryFiles;
+ }
+
+
+ /**
+ * Should files be uploaded?
+ *
+ * @return true if files should be uploaded, false otherwise
+ */
+ public boolean shouldUploadFiles() {
+ return uploadFiles;
+ }
+
+ /**
+ * Set whether files should be uploaded.
+ *
+ * @param uploadFiles true if files should be uploaded
+ */
+ void setUploadFiles(boolean uploadFiles) {
+ this.uploadFiles = uploadFiles;
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.form
new file mode 100644
index 0000000000..46a928c98e
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.form
@@ -0,0 +1,123 @@
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.java
new file mode 100644
index 0000000000..a418de0f83
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestSettingsPanel.java
@@ -0,0 +1,149 @@
+/*
+ * Autopsy Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
+import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
+import java.util.Optional;
+import org.sleuthkit.autopsy.modules.dataSourceIntegrity.*;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
+
+/**
+ * Ingest job settings panel for the Malware scanner ingest.
+ */
+@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
+final class MalwareScanIngestSettingsPanel extends IngestModuleIngestJobSettingsPanel {
+
+ private final CTLicensePersistence ctPersistence = CTLicensePersistence.getInstance();
+
+ MalwareScanIngestSettingsPanel(MalwareScanIngestSettings settings) {
+ initComponents();
+ customizeComponents(settings);
+ }
+
+ /**
+ * Update components with values from the ingest job settings.
+ *
+ * @param settings The ingest job settings.
+ */
+ private void customizeComponents(MalwareScanIngestSettings settings) {
+ Optional licenseInfoOpt = ctPersistence.loadLicenseInfo();
+ LicenseInfo licenseInfo = licenseInfoOpt.orElse(null);
+ if (licenseInfo != null) {
+ this.paidLicenseTextArea.setVisible(false);
+ this.queryFilesCheckbox.setVisible(true);
+ this.queryFilesCheckbox.setEnabled(false);
+ this.uploadFilesCheckbox.setVisible(true);
+ this.queryFilesCheckbox.setSelected(true);
+ this.uploadFilesCheckbox.setSelected(settings.shouldUploadFiles());
+ } else {
+ this.paidLicenseTextArea.setVisible(true);
+ this.queryFilesCheckbox.setVisible(false);
+ this.uploadFilesCheckbox.setVisible(false);
+ }
+ }
+
+ @Override
+ public IngestModuleIngestJobSettings getSettings() {
+ return new MalwareScanIngestSettings(queryFilesCheckbox.isSelected(), uploadFilesCheckbox.isSelected());
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ queryFilesCheckbox = new javax.swing.JCheckBox();
+ uploadFilesCheckbox = new javax.swing.JCheckBox();
+ ingestSettingsLabel = new javax.swing.JLabel();
+ paidLicenseTextArea = new javax.swing.JTextArea();
+
+ setPreferredSize(new java.awt.Dimension(300, 155));
+
+ queryFilesCheckbox.setSelected(true);
+ org.openide.awt.Mnemonics.setLocalizedText(queryFilesCheckbox, org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.queryFilesCheckbox.text")); // NOI18N
+ queryFilesCheckbox.setEnabled(false);
+
+ org.openide.awt.Mnemonics.setLocalizedText(uploadFilesCheckbox, org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.uploadFilesCheckbox.text")); // NOI18N
+ uploadFilesCheckbox.setFocusable(false);
+ uploadFilesCheckbox.setLabel(org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.uploadFilesCheckbox.label")); // NOI18N
+
+ ingestSettingsLabel.setFont(ingestSettingsLabel.getFont().deriveFont(ingestSettingsLabel.getFont().getStyle() | java.awt.Font.BOLD));
+ org.openide.awt.Mnemonics.setLocalizedText(ingestSettingsLabel, org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.ingestSettingsLabel.text")); // NOI18N
+
+ paidLicenseTextArea.setColumns(20);
+ paidLicenseTextArea.setFont(new java.awt.Font("Segoe UI", 1, 12)); // NOI18N
+ paidLicenseTextArea.setRows(5);
+ paidLicenseTextArea.setText(org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.paidLicenseTextArea.text")); // NOI18N
+ paidLicenseTextArea.setWrapStyleWord(true);
+ paidLicenseTextArea.setBorder(null);
+ paidLicenseTextArea.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
+ paidLicenseTextArea.setFocusable(false);
+ paidLicenseTextArea.setOpaque(false);
+ paidLicenseTextArea.setRequestFocusEnabled(false);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGap(6, 6, 6)
+ .addComponent(ingestSettingsLabel))
+ .addGroup(layout.createSequentialGroup()
+ .addGap(6, 6, 6)
+ .addComponent(uploadFilesCheckbox))
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(queryFilesCheckbox))
+ .addGroup(layout.createSequentialGroup()
+ .addGap(5, 5, 5)
+ .addComponent(paidLicenseTextArea, javax.swing.GroupLayout.PREFERRED_SIZE, 243, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGap(185, 185, 185))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(ingestSettingsLabel)
+ .addGap(18, 18, 18)
+ .addComponent(queryFilesCheckbox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(uploadFilesCheckbox)
+ .addGap(18, 18, 18)
+ .addComponent(paidLicenseTextArea, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE))
+ );
+
+ uploadFilesCheckbox.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MalwareScanIngestSettingsPanel.class, "MalwareScanIngestSettingsPanel.uploadFilesCheckbox.AccessibleContext.accessibleName")); // NOI18N
+ }// //GEN-END:initComponents
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel ingestSettingsLabel;
+ private javax.swing.JTextArea paidLicenseTextArea;
+ private javax.swing.JCheckBox queryFilesCheckbox;
+ private javax.swing.JCheckBox uploadFilesCheckbox;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/PathNormalizer.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/PathNormalizer.java
new file mode 100644
index 0000000000..f8a0a299a5
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/PathNormalizer.java
@@ -0,0 +1,201 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 = "";
+ private static final String ANONYMIZED_IP = "";
+ private static final String ANONYMIZED_HOSTNAME = "";
+ private static final String FORWARD_SLASH = "/";
+ private static final String BACK_SLASH = "\\";
+
+ private static final Pattern USER_PATH_FORWARD_SLASH_REGEX = Pattern.compile("(? 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 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 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 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;
+ }
+ }
+
+}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
index cae89e41ce..92b64b840b 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties
@@ -66,8 +66,8 @@ Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the
Case.open.msgDlg.updated.title=Case Database Schema Update
Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
-Previously, the image was located at:\n\
-{0}\n\
+Previously, the image with host, {0}, was located at:\n\
+{1}\n\
Please note that you will still be able to browse directories and generate reports\n\
if you choose No, but you will not be able to view file content or run the ingest process.
Case.checkImgExist.confDlg.doesntExist.title=Missing Image
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
index b671940118..ce1fb9aa70 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED
@@ -16,6 +16,7 @@ Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it mus
Case.exceptionMessage.cannotGetLockToDeleteCase=Cannot delete case because it is open for another user or host.
Case.exceptionMessage.cannotLocateMainWindow=Cannot locate main application window
Case.exceptionMessage.cannotOpenMultiUserCaseNoSettings=Multi-user settings are missing (see Tools, Options, Multi-user tab), cannot open a multi-user case.
+Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.
# {0} - exception message
Case.exceptionMessage.couldNotCreatCollaborationMonitor=Failed to create collaboration monitor:\n{0}.
# {0} - exception message
@@ -252,8 +253,8 @@ Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the
Case.open.msgDlg.updated.title=Case Database Schema Update
Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
-Previously, the image was located at:\n\
-{0}\n\
+Previously, the image with host, {0}, was located at:\n\
+{1}\n\
Please note that you will still be able to browse directories and generate reports\n\
if you choose No, but you will not be able to view file content or run the ingest process.
Case.checkImgExist.confDlg.doesntExist.title=Missing Image
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 6ece795e35..eb279febe7 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.casemodule;
+import com.basistech.df.cybertriage.autopsy.CTIntegrationMissingDialog;
import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils;
import com.google.common.annotations.Beta;
import com.google.common.eventbus.Subscribe;
@@ -177,6 +178,7 @@ public class Case {
private static final String CASE_ACTION_THREAD_NAME = "%s-case-action";
private static final String CASE_RESOURCES_THREAD_NAME = "%s-manage-case-resources";
private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode";
+ private static final String CT_PROVIDER_PREFIX = "CTStandardContentProvider_";
private static final Logger logger = Logger.getLogger(Case.class.getName());
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
private static final Object caseActionSerializationLock = new Object();
@@ -192,6 +194,8 @@ public class Case {
private final SleuthkitEventListener sleuthkitEventListener;
private CollaborationMonitor collaborationMonitor;
private Services caseServices;
+ // matches something like '\\.\PHYSICALDRIVE0'
+ private static final String PLACEHOLDER_DS_PATH_REGEX = "^\\s*\\\\\\\\\\.\\\\PHYSICALDRIVE\\d*\\s*$";
private volatile boolean hasDataSource = false;
private volatile boolean hasData = false;
@@ -1301,9 +1305,18 @@ public class Case {
for (Map.Entry entry : imgPaths.entrySet()) {
long obj_id = entry.getKey();
String path = entry.getValue();
- boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
+ boolean fileExists = (new File(path).exists()|| DriveUtils.driveExists(path));
if (!fileExists) {
+ // CT-7336: ignore relocating datasources if file provider is present and placeholder path is used.
+ if (newCurrentCase.getMetadata() != null
+ && !StringUtils.isBlank(newCurrentCase.getMetadata().getContentProviderName())
+ && (path == null || path.matches(PLACEHOLDER_DS_PATH_REGEX))) {
+ continue;
+ }
+
try {
+ DataSource ds = newCurrentCase.getSleuthkitCase().getDataSource(obj_id);
+ String hostName = StringUtils.defaultString(ds.getHost() == null ? "" : ds.getHost().getName());
// Using invokeAndWait means that the dialog will
// open on the EDT but this thread will wait for an
// answer. Using invokeLater would cause this loop to
@@ -1313,7 +1326,7 @@ public class Case {
public void run() {
int response = JOptionPane.showConfirmDialog(
mainFrame,
- NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
+ NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", hostName, path),
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
@@ -1325,7 +1338,7 @@ public class Case {
}
});
- } catch (InterruptedException | InvocationTargetException ex) {
+ } catch (InterruptedException | InvocationTargetException | TskCoreException | TskDataException ex) {
logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
}
}
@@ -2729,6 +2742,7 @@ public class Case {
"Case.progressMessage.openingCaseDatabase=Opening case database...",
"# {0} - exception message", "Case.exceptionMessage.couldNotOpenCaseDatabase=Failed to open case database:\n{0}.",
"# {0} - exception message", "Case.exceptionMessage.unsupportedSchemaVersionMessage=Unsupported case database schema version:\n{0}.",
+ "Case.exceptionMessage.contentProviderCouldNotBeFound=Content provider was specified for the case but could not be loaded.",
"Case.open.exception.multiUserCaseNotEnabled=Cannot open a multi-user case if multi-user cases are not enabled. See Tools, Options, Multi-User."
})
private void openCaseDataBase(ProgressIndicator progressIndicator) throws CaseActionException {
@@ -2737,14 +2751,15 @@ public class Case {
String databaseName = metadata.getCaseDatabaseName();
ContentStreamProvider contentProvider = loadContentProvider(metadata.getContentProviderName());
+ if (StringUtils.isNotBlank(metadata.getContentProviderName()) && contentProvider == null) {
+ if (metadata.getContentProviderName().trim().toUpperCase().startsWith(CT_PROVIDER_PREFIX.toUpperCase())) {
+ new CTIntegrationMissingDialog(WindowManager.getDefault().getMainWindow(), true).showDialog(null);
+ }
+ throw new CaseActionException(Bundle.Case_exceptionMessage_contentProviderCouldNotBeFound());
+ }
if (CaseType.SINGLE_USER_CASE == metadata.getCaseType()) {
- // only prefix with metadata directory if databaseName is a relative path
- String fullDatabasePath = (new File(databaseName).isAbsolute())
- ? databaseName
- : Paths.get(metadata.getCaseDirectory(), databaseName).toString();
-
- caseDb = SleuthkitCase.openCase(fullDatabasePath, contentProvider);
+ caseDb = SleuthkitCase.openCase(metadata.getCaseDatabasePath(), contentProvider);
} else if (UserPreferences.getIsMultiUserModeEnabled()) {
caseDb = SleuthkitCase.openCase(databaseName, UserPreferences.getDatabaseConnectionInfo(), metadata.getCaseDirectory(), contentProvider);
} else {
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
index c9170b1e68..91566a01bb 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java
@@ -29,16 +29,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -51,13 +43,10 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
-import org.openide.util.Lookup;
import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
@@ -121,7 +110,6 @@ public final class CaseMetadata {
private static final String SCHEMA_VERSION_SIX = "6.0";
private final static String CONTENT_PROVIDER_ELEMENT_NAME = "ContentProvider";
private final static String CONTENT_PROVIDER_NAME_ELEMENT_NAME = "Name";
- private final static String CONTENT_PROVIDER_ARG_DEFAULT_KEY = "DEFAULT";
/*
* Unread fields, regenerated on save.
@@ -136,7 +124,7 @@ public final class CaseMetadata {
private String caseName;
private CaseDetails caseDetails;
private String caseDatabaseName;
- private String caseDatabasePath; // Legacy
+ private String caseDatabasePath;
private String textIndexName; // Legacy
private String createdDate;
private String createdByVersion;
@@ -258,7 +246,9 @@ public final class CaseMetadata {
* @return The case directory.
*/
public String getCaseDirectory() {
- return metadataFilePath.getParent().toString();
+ return StringUtils.isBlank(this.caseDatabasePath)
+ ? metadataFilePath.getParent().toString()
+ : Paths.get(this.caseDatabasePath).getParent().toString();
}
/**
@@ -637,6 +627,7 @@ public final class CaseMetadata {
this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
break;
default:
+ this.caseDatabasePath = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, false);
this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
break;
@@ -650,11 +641,9 @@ public final class CaseMetadata {
*/
Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
Path caseDirectoryPath = Paths.get(getCaseDirectory());
- if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
+ if (possibleAbsoluteCaseDbPath.toFile().isAbsolute()) {
this.caseDatabasePath = this.caseDatabaseName;
this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
- } else {
- this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
}
} catch (ParserConfigurationException | SAXException | IOException ex) {
@@ -719,12 +708,12 @@ public final class CaseMetadata {
* @return The full path to the case database file for a single-user case.
*
* @throws UnsupportedOperationException If called for a multi-user case.
- * @deprecated Do not use.
*/
- @Deprecated
public String getCaseDatabasePath() throws UnsupportedOperationException {
if (Case.CaseType.SINGLE_USER_CASE == caseType) {
- return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
+ return StringUtils.isBlank(this.caseDatabasePath)
+ ? this.metadataFilePath.getParent().resolve(this.caseDatabaseName).toString()
+ : this.caseDatabasePath;
} else {
throw new UnsupportedOperationException();
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java
index 3dee283999..4475b682c5 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/Artifacts.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2021 Basis Technology Corp.
+ * Copyright 2011-2023 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -64,6 +64,7 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_IT
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_MALWARE;
/**
* Classes for creating nodes for BlackboardArtifacts.
@@ -72,7 +73,7 @@ public class Artifacts {
private static final Set INGEST_JOB_EVENTS_OF_INTEREST
= EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
-
+
/**
* Base class for a parent node of artifacts.
*/
@@ -242,6 +243,7 @@ public class Artifacts {
*/
@SuppressWarnings("deprecation")
private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) {
+
int typeId = type.getTypeID();
if (TSK_EMAIL_MSG.getTypeID() == typeId) {
EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode();
@@ -267,7 +269,9 @@ public class Artifacts {
} else if (TSK_HASHSET_HIT.getTypeID() == typeId) {
HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode();
return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT);
-
+ } else if (TSK_MALWARE.getTypeID() == typeId) {
+ MalwareHits.RootNode malwareHits = new MalwareHits(skCase, dsObjId).new RootNode();
+ return new TypeNodeKey(malwareHits, TSK_MALWARE);
} else {
return new TypeNodeKey(type, dsObjId);
}
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
index d433d0d6df..5ebd7c4216 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java
@@ -60,6 +60,8 @@ public interface AutopsyItemVisitor {
T visit(KeywordHits kh);
T visit(HashsetHits hh);
+
+ T visit(MalwareHits mh);
T visit(EmailExtracted ee);
@@ -169,6 +171,11 @@ public interface AutopsyItemVisitor {
return defaultVisit(hh);
}
+ @Override
+ public T visit(MalwareHits mh) {
+ return defaultVisit(mh);
+ }
+
@Override
public T visit(InterestingHits ih) {
return defaultVisit(ih);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
index 85e715382e..b80fa67d94 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java
@@ -111,6 +111,8 @@ public interface DisplayableItemNodeVisitor {
T visit(HashsetHits.RootNode hhrn);
T visit(HashsetHits.HashsetNameNode hhsn);
+
+ T visit(MalwareHits.RootNode mhrn);
T visit(EmailExtracted.RootNode eern);
@@ -431,6 +433,11 @@ public interface DisplayableItemNodeVisitor {
return defaultVisit(hhsn);
}
+ @Override
+ public T visit(MalwareHits.RootNode mhrn) {
+ return defaultVisit(mhrn);
+ }
+
@Override
public T visit(InterestingHits.RootNode ihrn) {
return defaultVisit(ihrn);
diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java
new file mode 100755
index 0000000000..2524650178
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/MalwareHits.java
@@ -0,0 +1,343 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2023 Basis Technology Corp.
+ * Contact: carrier sleuthkit 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 org.sleuthkit.autopsy.datamodel;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Set;
+import java.util.logging.Level;
+import org.openide.nodes.Children;
+import org.openide.nodes.Node;
+import org.openide.nodes.Sheet;
+import org.openide.util.NbBundle;
+import org.openide.util.WeakListeners;
+import org.openide.util.lookup.Lookups;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
+import org.sleuthkit.datamodel.AnalysisResult;
+import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_MALWARE;
+import org.sleuthkit.datamodel.Score;
+
+/**
+ * Malware hits node support. Inner classes have all of the nodes in the tree.
+ */
+public class MalwareHits implements AutopsyVisitableItem {
+
+ private static final Logger logger = Logger.getLogger(MalwareHits.class.getName());
+ private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
+ private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
+ private SleuthkitCase skCase;
+ private final MalwareResults malwareResults;
+ private final long filteringDSObjId; // 0 if not filtering/grouping by data source
+
+ /**
+ * Constructor
+ *
+ * @param skCase Case DB
+ *
+ */
+ public MalwareHits(SleuthkitCase skCase) {
+ this(skCase, 0);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param skCase Case DB
+ * @param objId Object id of the data source
+ *
+ */
+ public MalwareHits(SleuthkitCase skCase, long objId) {
+ this.skCase = skCase;
+ this.filteringDSObjId = objId;
+ malwareResults = new MalwareResults();
+ }
+
+ @Override
+ public T accept(AutopsyItemVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ /**
+ * Stores all of the malware results in a single class that is observable
+ * for the child nodes
+ */
+ private class MalwareResults extends Observable implements Observer {
+
+ // list of artifacts
+ // NOTE: the list can be accessed by multiple worker threads and needs to be synchronized
+ private final Set malwareHits = new HashSet<>();
+
+ MalwareResults() {
+ addNotify();
+ update();
+ }
+
+ Set getArtifactIds() {
+ synchronized (malwareHits) {
+ return Collections.unmodifiableSet(malwareHits);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ final void update() {
+ synchronized (malwareHits) {
+ malwareHits.clear();
+ }
+
+ if (skCase == null) {
+ return;
+ }
+
+ String query = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
+ + "FROM blackboard_artifacts,tsk_analysis_results WHERE " //NON-NLS
+ + "blackboard_artifacts.artifact_type_id=" + TSK_MALWARE.getTypeID() //NON-NLS
+ + " AND tsk_analysis_results.artifact_obj_id=blackboard_artifacts.artifact_obj_id" //NON-NLS
+ + " AND (tsk_analysis_results.significance=" + Score.Significance.NOTABLE.getId() //NON-NLS
+ + " OR tsk_analysis_results.significance=" + Score.Significance.LIKELY_NOTABLE.getId() + " )"; //NON-NLS
+ if (filteringDSObjId > 0) {
+ query += " AND blackboard_artifacts.data_source_obj_id = " + filteringDSObjId; //NON-NLS
+ }
+
+ try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
+ ResultSet resultSet = dbQuery.getResultSet();
+ synchronized (malwareHits) {
+ while (resultSet.next()) {
+ long artifactObjId = resultSet.getLong("artifact_obj_id"); //NON-NLS
+ malwareHits.add(artifactObjId);
+ }
+ }
+ } catch (TskCoreException | SQLException ex) {
+ logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS
+ }
+
+ setChanged();
+ notifyObservers();
+ }
+
+ private final PropertyChangeListener pcl = new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String eventType = evt.getPropertyName();
+ if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
+ /**
+ * Checking for a current case is a stop gap measure until a
+ * different way of handling the closing of cases is worked
+ * out. Currently, remote events may be received for a case
+ * that is already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ /**
+ * Due to some unresolved issues with how cases are
+ * closed, it is possible for the event to have a null
+ * oldValue if the event is a remote event.
+ */
+ ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
+ if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_MALWARE.getTypeID()) {
+ malwareResults.update();
+ }
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
+ || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
+ /**
+ * Checking for a current case is a stop gap measure until a
+ * different way of handling the closing of cases is worked
+ * out. Currently, remote events may be received for a case
+ * that is already closed.
+ */
+ try {
+ Case.getCurrentCaseThrows();
+ malwareResults.update();
+ } catch (NoCurrentCaseException notUsed) {
+ /**
+ * Case is closed, do nothing.
+ */
+ }
+ } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
+ // case was closed. Remove listeners so that we don't get called with a stale case handle
+ if (evt.getNewValue() == null) {
+ removeNotify();
+ skCase = null;
+ }
+ }
+ }
+ };
+
+ private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
+
+ protected void addNotify() {
+ IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, weakPcl);
+ IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl);
+ Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
+ }
+
+ protected void removeNotify() {
+ IngestManager.getInstance().removeIngestJobEventListener(weakPcl);
+ IngestManager.getInstance().removeIngestModuleEventListener(weakPcl);
+ Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), weakPcl);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ removeNotify();
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ update();
+ }
+ }
+
+ /**
+ * Top-level node for all malware hits
+ */
+ public class RootNode extends UpdatableCountTypeNode {
+
+ public RootNode() {
+ super(Children.create(new HitFactory(TSK_MALWARE.getDisplayName()), true),
+ Lookups.singleton(TSK_MALWARE.getDisplayName()),
+ TSK_MALWARE.getDisplayName(),
+ filteringDSObjId,
+ TSK_MALWARE);
+
+ super.setName(TSK_MALWARE.getTypeName());
+ // TODO make an icon
+ this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/artifact-icon.png");
+ }
+
+ @Override
+ public boolean isLeafTypeNode() {
+ return true;
+ }
+
+ @Override
+ public T accept(DisplayableItemNodeVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ @Override
+ protected Sheet createSheet() {
+ Sheet sheet = super.createSheet();
+ Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
+ if (sheetSet == null) {
+ sheetSet = Sheet.createPropertiesSet();
+ sheet.put(sheetSet);
+ }
+
+ sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.name"),
+ NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.displayName"),
+ NbBundle.getMessage(this.getClass(), "MalwareHits.createSheet.name.desc"),
+ getName()));
+
+ return sheet;
+ }
+
+ @Override
+ public String getItemType() {
+ return getClass().getName();
+ }
+
+ /**
+ * When this method is called, the count to be displayed will be
+ * updated.
+ */
+ @Override
+ void updateDisplayName() {
+ super.setDisplayName(TSK_MALWARE.getDisplayName() + " (" + malwareResults.getArtifactIds().size() + ")");
+ }
+ }
+
+ /**
+ * Creates the nodes for the malware hits.
+ */
+ private class HitFactory extends BaseChildFactory implements Observer {
+
+ private final Map artifactHits = new HashMap<>();
+
+ private HitFactory(String nodeName) {
+ super(nodeName);
+ }
+
+ @Override
+ protected void onAdd() {
+ malwareResults.addObserver(this);
+ }
+
+ @Override
+ protected void onRemove() {
+ malwareResults.deleteObserver(this);
+ }
+
+ @Override
+ protected Node createNodeForKey(AnalysisResult key) {
+ return new BlackboardArtifactNode(key);
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ refresh(true);
+ }
+
+ @Override
+ protected List makeKeys() {
+ if (skCase != null) {
+
+ malwareResults.getArtifactIds().forEach((id) -> {
+ try {
+ if (!artifactHits.containsKey(id)) {
+ AnalysisResult art = skCase.getBlackboard().getAnalysisResultById(id);
+ //Cache attributes while we are off the EDT.
+ //See JIRA-5969
+ art.getAttributes();
+ artifactHits.put(id, art);
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
+ }
+ });
+ return new ArrayList<>(artifactHits.values());
+ }
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/CoreLibs/manifest.mf b/CoreLibs/manifest.mf
index 1d3168bf2c..9f684569fe 100644
--- a/CoreLibs/manifest.mf
+++ b/CoreLibs/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.corelibs/3
-OpenIDE-Module-Implementation-Version: 7
+OpenIDE-Module-Implementation-Version: 8
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/corelibs/Bundle.properties
OpenIDE-Module-Specification-Version: 1.4
AutoUpdate-Show-In-Client: true
diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml
index 7760187c5e..79af40a112 100644
--- a/Experimental/nbproject/project.xml
+++ b/Experimental/nbproject/project.xml
@@ -144,7 +144,7 @@
10
- 10.24
+ 10.25
@@ -162,7 +162,7 @@
6
- 6.6
+ 6.7
diff --git a/ImageGallery/nbproject/project.properties b/ImageGallery/nbproject/project.properties
index d9bd5f605c..d69f7d273a 100644
--- a/ImageGallery/nbproject/project.properties
+++ b/ImageGallery/nbproject/project.properties
@@ -1,4 +1,4 @@
-file.reference.sqlite-jdbc-3.42.0.0.jar=release/modules/ext/sqlite-jdbc-3.42.0.0.jar
+file.reference.sqlite-jdbc-3.42.0.1.jar=release/modules/ext/sqlite-jdbc-3.42.0.1.jar
javac.source=17
javac.compilerargs=-Xlint -Xlint:-serial
license.file=LICENSE-2.0.txt
diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml
index 7a505d2ea6..49221290cd 100644
--- a/ImageGallery/nbproject/project.xml
+++ b/ImageGallery/nbproject/project.xml
@@ -127,7 +127,7 @@
10
- 10.24
+ 10.25
@@ -142,8 +142,8 @@
- ext/sqlite-jdbc-3.42.0.0.jar
- release/modules/ext/sqlite-jdbc-3.42.0.0.jar
+ ext/sqlite-jdbc-3.42.0.1.jar
+ release/modules/ext/sqlite-jdbc-3.42.0.1.jar
diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf
index 0e947e073b..f4abb2b2b9 100644
--- a/KeywordSearch/manifest.mf
+++ b/KeywordSearch/manifest.mf
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
AutoUpdate-Show-In-Client: true
OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/6
-OpenIDE-Module-Implementation-Version: 23
+OpenIDE-Module-Implementation-Version: 24
OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class
OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties
diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties
index baa7a10c98..16289e9e73 100644
--- a/KeywordSearch/nbproject/project.properties
+++ b/KeywordSearch/nbproject/project.properties
@@ -48,4 +48,4 @@ javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/autopsy/
nbm.needs.restart=true
-spec.version.base=6.6
+spec.version.base=6.7
diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml
index c1048f48ee..98a84faeb5 100644
--- a/KeywordSearch/nbproject/project.xml
+++ b/KeywordSearch/nbproject/project.xml
@@ -128,7 +128,7 @@
10
- 10.24
+ 10.25
diff --git a/NEWS.txt b/NEWS.txt
index 11c56f01c8..f6658a92ed 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,32 @@
+---------------- VERSION 4.21.0 ---------------
+Library Updates
+- Update Java to version 17
+- Update aLeapp/iLeapp executables.
+- Update JNA Version
+- Update SQLite library version
+- Updated 3rd party libraries that have known CVE's
+
+Ingest Module Updates:
+- Recent Activity checks for malicious Chrome extensions from list provided by https://github.com/randomaccess3/detections
+- Keyword Search module now can search without needing to index text into Solr.
+- New Cyber Triage Malware Scanner module that uses Reversing Labs (requires license). https://www.cybertriage.com/autopsy-malware-module/
+
+Add Data Source Updates:
+- Timestamps for logical files can be added. Issue https://github.com/sleuthkit/autopsy/issues/5852, https://github.com/sleuthkit/autopsy/issues/1788
+- List of logical files/folders can be edited before they are added. Issue https://github.com/sleuthkit/autopsy/issues/7347
+
+GUI Updates:
+- Add "has attachments" flag for emails. Issue https://github.com/sleuthkit/autopsy/issues/7358
+- Add Score to tree view
+
+Bugs:
+- Fix path for lnk files
+- Fix exporting of CSV files. Issue https://github.com/sleuthkit/autopsy/issues/6717
+
+Misc:
+- Added File Repository concept for data source files that are in a central location. Required for Cyber Triage import feature.
+- Added Spanish language support, contributor https://github.com/AburtoArielPM
+
---------------- VERSION 4.20.0 --------------
Recent Activity Updates:
- Added Favicons, Profiles and Extensions to Chromium Browsers
diff --git a/RecentActivity/manifest.mf b/RecentActivity/manifest.mf
index 03c6ce986b..6ab9cc4ccc 100644
--- a/RecentActivity/manifest.mf
+++ b/RecentActivity/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/6
-OpenIDE-Module-Implementation-Version: 19
+OpenIDE-Module-Implementation-Version: 20
OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties
OpenIDE-Module-Requires:
diff --git a/RecentActivity/nbproject/project.properties b/RecentActivity/nbproject/project.properties
index 1727284eb3..430436bea9 100644
--- a/RecentActivity/nbproject/project.properties
+++ b/RecentActivity/nbproject/project.properties
@@ -1,6 +1,6 @@
javac.source=17
file.reference.Rejistry-1.1-SNAPSHOT.jar=release/modules/ext/Rejistry-1.1-SNAPSHOT.jar
-file.reference.sqlite-jdbc-3.42.0.0.jar=release/modules/ext/sqlite-jdbc-3.42.0.0.jar
+file.reference.sqlite-jdbc-3.42.0.1.jar=release/modules/ext/sqlite-jdbc-3.42.0.1.jar
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/autopsy/
diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml
index bf1d1df30b..05745bb79a 100644
--- a/RecentActivity/nbproject/project.xml
+++ b/RecentActivity/nbproject/project.xml
@@ -88,8 +88,8 @@
release/modules/ext/Rejistry-1.1-SNAPSHOT.jar
- ext/sqlite-jdbc-3.42.0.0.jar
- release/modules/ext/sqlite-jdbc-3.42.0.0.jar
+ ext/sqlite-jdbc-3.42.0.1.jar
+ release/modules/ext/sqlite-jdbc-3.42.0.1.jar
diff --git a/TSKVersion.xml b/TSKVersion.xml
index 087dac0912..7f6a1a8848 100644
--- a/TSKVersion.xml
+++ b/TSKVersion.xml
@@ -1,3 +1,3 @@
-
+
diff --git a/Testing/nbproject/project.xml b/Testing/nbproject/project.xml
index c27ee220ec..cee5e3ed5b 100644
--- a/Testing/nbproject/project.xml
+++ b/Testing/nbproject/project.xml
@@ -47,7 +47,7 @@
10
- 10.24
+ 10.25
@@ -73,7 +73,7 @@
6
- 6.6
+ 6.7
diff --git a/docs/doxygen-user/ct_malware_scanner.dox b/docs/doxygen-user/ct_malware_scanner.dox
new file mode 100644
index 0000000000..ad875a270d
--- /dev/null
+++ b/docs/doxygen-user/ct_malware_scanner.dox
@@ -0,0 +1,63 @@
+/*! \page ct_malware_scanner_page Cyber Triage Malware Scanner Module
+
+[TOC]
+
+What Does It Do
+========
+
+The Cyber Triage Malware Scanner module will use the malware scanning infrastructure from Cyber Triage to identify if any Windows executables are malware. It will query an online service using the file's hash value to see if the file was already analyzed and allows you to upload files for analysis if they are new.
+
+This module requires a commercial license from Cyber Triage.
+
+For more information on what the module does or obtaining a license, refer to [CyberTriage.com](https://cybertriage.com/autopsy-malware-module). The remainder of this page is about the use of the module once it is licensed.
+
+
+Configuration
+==============
+
+You will need to first get a paid or eval license from the above URL. The code will come in via email. Example license formats include:
+
+- AUT-8ed86eb5-17fc-4b3a-9b75-ce638c11b070
+- b826a555-951f-42ca-86ce-439a81106688
+
+Once you have a license, you must add it on the Autopsy Options panel.
+Choose the 'Cyber Triage' tab and choose 'Add License'.
+
+\image html malware-scanner-global-options-panel-no-license.png
+
+After you enter the license number from your email, you will then need to review and agree to the license terms.
+
+
+The options panel should now display information about the lookup limits. You can always refer back to here about what your limits are and when they reset.
+
+\image html malware-scanner-global-options-panel.png
+
+
+
+Using the Module
+======
+
+Ingest Settings
+------
+
+For each data source, you select if you want files to be uploaded if they have not already been analyzed. By default, they are uploaded. You can choose to not upload them though. Refer to the main [website](https://cybertriage.com/autopsy-malware-module) for details on what happens when files are uploaded.
+
+\image html malware-scanner-ingest-panel.png
+
+
+
+Out of Scans
+-------
+
+If you go beyond your limits, you will get a dialog that not all files were analyzed. You can wait until your limits reset and then start ingest again with only the malware scanning module enabled. It will ignore the files that are already analyzed.
+
+
+Seeing Results
+------
+
+Once ingest has completed, the files with malware will be listed in the Malware node in the tree.
+
+
+\image html malware-artifact-tree.png
+
+*/
diff --git a/docs/doxygen-user/footer.html b/docs/doxygen-user/footer.html
index d648961fdc..cfedffe82c 100644
--- a/docs/doxygen-user/footer.html
+++ b/docs/doxygen-user/footer.html
@@ -1,5 +1,5 @@
-Copyright © 2012-2022 Basis Technology. Generated on $date
+
Copyright © 2012-2023 BasisTech. Generated on $date
This work is licensed under a
Creative Commons Attribution-Share Alike 3.0 United States License.
diff --git a/docs/doxygen-user/images/malware-artifact-tree.png b/docs/doxygen-user/images/malware-artifact-tree.png
new file mode 100644
index 0000000000..b47afb86c1
Binary files /dev/null and b/docs/doxygen-user/images/malware-artifact-tree.png differ
diff --git a/docs/doxygen-user/images/malware-scanner-global-options-panel-no-license.png b/docs/doxygen-user/images/malware-scanner-global-options-panel-no-license.png
new file mode 100644
index 0000000000..de64843872
Binary files /dev/null and b/docs/doxygen-user/images/malware-scanner-global-options-panel-no-license.png differ
diff --git a/docs/doxygen-user/images/malware-scanner-global-options-panel.png b/docs/doxygen-user/images/malware-scanner-global-options-panel.png
new file mode 100644
index 0000000000..b5a0ad4f3d
Binary files /dev/null and b/docs/doxygen-user/images/malware-scanner-global-options-panel.png differ
diff --git a/docs/doxygen-user/images/malware-scanner-ingest-panel.png b/docs/doxygen-user/images/malware-scanner-ingest-panel.png
new file mode 100644
index 0000000000..719768baaf
Binary files /dev/null and b/docs/doxygen-user/images/malware-scanner-ingest-panel.png differ
diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox
index a31367ff6c..193a5abcba 100644
--- a/docs/doxygen-user/main.dox
+++ b/docs/doxygen-user/main.dox
@@ -58,6 +58,7 @@ The following topics are available here:
- \subpage ileapp_page
- \subpage aleapp_page
- \subpage yara_page
+ - \subpage ct_malware_scanner_page
- Reviewing the Results
- \subpage uilayout_page
diff --git a/nbproject/project.properties b/nbproject/project.properties
index e8d17eb3c8..d613ea09d4 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -4,7 +4,7 @@ app.title=Autopsy
### lowercase version of above
app.name=${branding.token}
### if left unset, version will default to today's date
-app.version=4.20.0
+app.version=4.21.0
### build.type must be one of: DEVELOPMENT, RELEASE
#build.type=RELEASE
build.type=DEVELOPMENT
diff --git a/release_scripts/update_sleuthkit_version.pl b/release_scripts/update_sleuthkit_version.pl
index 1d6bdc7e72..f197477c82 100755
--- a/release_scripts/update_sleuthkit_version.pl
+++ b/release_scripts/update_sleuthkit_version.pl
@@ -33,7 +33,7 @@ sub main {
update_core_project_xml();
update_unix_setup();
- print "Files updated. You need to commit and push them\n";
+ print "Files updated and added to git. You need to commit (no -a) and push.\n";
}
diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml
index 5c4fa0e042..abcccdc19c 100644
--- a/thunderbirdparser/nbproject/project.xml
+++ b/thunderbirdparser/nbproject/project.xml
@@ -54,7 +54,7 @@
10
- 10.24
+ 10.25
diff --git a/unix_setup.sh b/unix_setup.sh
index d2572c9de2..ab2d1094f6 100644
--- a/unix_setup.sh
+++ b/unix_setup.sh
@@ -5,7 +5,7 @@
# NOTE: update_sleuthkit_version.pl updates this value and relies
# on it keeping the same name and whitespace. Don't change it.
-TSK_VERSION=4.12.0
+TSK_VERSION=4.12.1
usage() {