diff --git a/Core/ivy.xml b/Core/ivy.xml
index fba2d99acd..3d2352648d 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -1,3 +1,6 @@
+
+]>
@@ -72,6 +75,15 @@
+
+
+
+
+
+
+
+
+
@@ -84,5 +96,6 @@
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index a24bcb423e..9adb1c7649 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -18,6 +18,7 @@ file.reference.bcprov-jdk15on-1.70.jar=release/modules/ext/bcprov-jdk15on-1.70.j
file.reference.bcutil-jdk15on-1.70.jar=release/modules/ext/bcutil-jdk15on-1.70.jar
file.reference.c3p0-0.9.5.5.jar=release/modules/ext/c3p0-0.9.5.5.jar
file.reference.checker-qual-3.33.0.jar=release/modules/ext/checker-qual-3.33.0.jar
+file.reference.commons-codec-1.11.jar=release/modules/ext/commons-codec-1.11.jar
file.reference.commons-dbcp2-2.9.0.jar=release/modules/ext/commons-dbcp2-2.9.0.jar
file.reference.commons-io-2.11.0.jar=release/modules/ext/commons-io-2.11.0.jar
file.reference.commons-lang3-3.10.jar=release/modules/ext/commons-lang3-3.10.jar
@@ -31,6 +32,10 @@ file.reference.decodetect-core-0.3.jar=release/modules/ext/decodetect-core-0.3.j
file.reference.error_prone_annotations-2.18.0.jar=release/modules/ext/error_prone_annotations-2.18.0.jar
file.reference.failureaccess-1.0.1.jar=release/modules/ext/failureaccess-1.0.1.jar
file.reference.guava-32.0.1-jre.jar=release/modules/ext/guava-32.0.1-jre.jar
+file.reference.httpclient-4.5.14.jar=release/modules/ext/httpclient-4.5.14.jar
+file.reference.httpclient-win-4.5.14.jar=release/modules/ext/httpclient-win-4.5.14.jar
+file.reference.httpcore-4.4.16.jar=release/modules/ext/httpcore-4.4.16.jar
+file.reference.httpmime-4.5.14.jar=release/modules/ext/httpmime-4.5.14.jar
file.reference.icepdf-core-6.2.2.jar=release/modules/ext/icepdf-core-6.2.2.jar
file.reference.icepdf-viewer-6.2.2.jar=release/modules/ext/icepdf-viewer-6.2.2.jar
file.reference.istack-commons-runtime-3.0.11.jar=release/modules/ext/istack-commons-runtime-3.0.11.jar
@@ -46,6 +51,7 @@ file.reference.javax.activation-api-1.2.0.jar=release/modules/ext/javax.activati
file.reference.javax.ws.rs-api-2.1.1.jar=release/modules/ext/javax.ws.rs-api-2.1.1.jar
file.reference.jaxb-api-2.3.1.jar=release/modules/ext/jaxb-api-2.3.1.jar
file.reference.jaxb-runtime-2.3.3.jar=release/modules/ext/jaxb-runtime-2.3.3.jar
+file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
file.reference.jfreechart-1.5.3.jar=release/modules/ext/jfreechart-1.5.3.jar
file.reference.jgraphx-4.2.2.jar=release/modules/ext/jgraphx-4.2.2.jar
@@ -55,6 +61,7 @@ file.reference.jutf7-1.0.0.jar=release/modules/ext/jutf7-1.0.0.jar
file.reference.jxmapviewer2-2.6.jar=release/modules/ext/jxmapviewer2-2.6.jar
file.reference.jython-standalone-2.7.2.jar=release/modules/ext/jython-standalone-2.7.2.jar
file.reference.libphonenumber-8.12.45.jar=release/modules/ext/libphonenumber-8.12.45.jar
+file.reference.license4j-runtime-library-4.7.1.jar=release/modules/ext/license4j-runtime-library-4.7.1.jar
file.reference.listenablefuture-1.0.jar=release/modules/ext/listenablefuture-1.0.jar
file.reference.logback-classic-1.2.10.jar=release/modules/ext/logback-classic-1.2.10.jar
file.reference.logback-core-1.2.10.jar=release/modules/ext/logback-core-1.2.10.jar
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 0553a915ca..2cab2a535f 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -66,6 +66,14 @@
+
+ org.netbeans.modules.keyring
+
+
+
+ 1.41
+
+
org.netbeans.modules.options.api
@@ -165,14 +173,6 @@
9.29
-
org.openide.filesystems.nb
@@ -448,6 +448,10 @@
ext/checker-qual-3.33.0.jar
release/modules/ext/checker-qual-3.33.0.jar
+
+ ext/commons-codec-1.11.jar
+ release/modules/ext/commons-codec-1.11.jar
+
ext/commons-dbcp2-2.9.0.jar
release/modules/ext/commons-dbcp2-2.9.0.jar
@@ -500,6 +504,22 @@
ext/guava-32.0.1-jre.jar
release/modules/ext/guava-32.0.1-jre.jar
+
+ ext/httpclient-4.5.14.jar
+ release/modules/ext/httpclient-4.5.14.jar
+
+
+ ext/httpclient-win-4.5.14.jar
+ release/modules/ext/httpclient-win-4.5.14.jar
+
+
+ ext/httpcore-4.4.16.jar
+ release/modules/ext/httpcore-4.4.16.jar
+
+
+ ext/httpmime-4.5.14.jar
+ release/modules/ext/httpmime-4.5.14.jar
+
ext/icepdf-core-6.2.2.jar
release/modules/ext/icepdf-core-6.2.2.jar
@@ -560,6 +580,10 @@
ext/jaxb-runtime-2.3.3.jar
release/modules/ext/jaxb-runtime-2.3.3.jar
+
+ ext/jdom-2.0.5-contrib.jar
+ release/modules/ext/jdom-2.0.5-contrib.jar
+
ext/jdom-2.0.5.jar
release/modules/ext/jdom-2.0.5.jar
@@ -596,6 +620,10 @@
ext/libphonenumber-8.12.45.jar
release/modules/ext/libphonenumber-8.12.45.jar
+
+ ext/license4j-runtime-library-4.7.1.jar
+ release/modules/ext/license4j-runtime-library-4.7.1.jar
+
ext/listenablefuture-1.0.jar
release/modules/ext/listenablefuture-1.0.jar
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java
new file mode 100644
index 0000000000..8b0ff55ee5
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java
@@ -0,0 +1,97 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2020 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi;
+
+
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+/**
+ *
+ * @author rishwanth
+ */
+
+
+public class CTCloudException extends Exception{
+ private final ErrorCode errorCode;
+
+ public enum ErrorCode {
+ BAD_REQUEST("CT-400", "Unknown or Bad request. Please contact Basis support at " + Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM + " for help diagnosing the problem."),
+ INVALID_KEY("CT-401", "An invalid license ID was used to access CyberTriage Cloud Service. Please contact Basis support " + Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM + " for help diagnosing the problem."),
+ GATEWAY_TIMEOUT("CT-504", "Request to CyberTriage Cloud Service timed out. Please retry after some time. If issue persists, please contact Basis support at " + Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM + " for assistance."),
+ UN_AUTHORIZED("CT-403", "An authorization error occurred. Please contact Basis support " + Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM + " for help diagnosing the problem."),
+ PROXY_UNAUTHORIZED("CT-407", "Proxy authentication failed. Please validate the connection settings from the Options panel Proxy Settings."),
+ TEMP_UNAVAILABLE("CT-500", "CyberTriage Cloud Service temporarily unavailable; please try again later. If this problem persists, contact Basis support at " + Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM),
+ UNKNOWN("CT-080", "Unknown error while communicating with CyberTriage Cloud Service. If this problem persists, contact Basis support at "+ Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM +" for assistance."),
+ UNKNOWN_HOST("CT-081", "Unknown host error. If this problem persists, contact Basis support at "+ Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM +" for assistance."),
+ NETWORK_ERROR("CT-015", "Error connecting to CyberTriage Cloud.\n"
+ + "Check your firewall or proxy settings.\n"
+ + "Contact Support (support@cybertriage.com) for further assistance");
+ private final String errorcode;
+ private final String description;
+
+ private ErrorCode(String errorcode, String description) {
+ this.errorcode = errorcode;
+ this.description = description;
+ }
+
+ public String getCode() {
+ return errorcode;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ }
+
+ public CTCloudException(CTCloudException.ErrorCode errorCode) {
+ super(errorCode.name());
+ this.errorCode = errorCode;
+ }
+
+ public CTCloudException(CTCloudException.ErrorCode errorCode, Throwable throwable) {
+ super(errorCode.name(), throwable);
+ this.errorCode = errorCode;
+ }
+
+ public ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ 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.",
+ StringUtils.isNotBlank(getCause().getLocalizedMessage()) ? "("+getCause().getLocalizedMessage()+")": "(Unknown)",
+ Constants.SUPPORT_AT_CYBERTRIAGE_DOT_COM );
+ }else {
+ return getErrorCode().getDescription();
+ }
+ }
+
+ /*
+ * Attempts to find a more specific error code than "Unknown" for the given exception.
+ */
+ public static ErrorCode parseUnknownException(Throwable throwable) {
+
+ String stackTrace = ExceptionUtils.getStackTrace(throwable);
+ if (stackTrace.contains("UnknownHostException")) {
+ return ErrorCode.UNKNOWN_HOST;
+ }
+
+ return ErrorCode.UNKNOWN;
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java
new file mode 100644
index 0000000000..3cc077c06d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java
@@ -0,0 +1,83 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2016 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi;
+
+import com.google.common.collect.ImmutableList;
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// TODO take out anything sensitive or not used
+final public class Constants {
+
+ public static final String CYBER_TRIAGE = "CyberTriage";
+
+ public static final String IS_MEMORY_IMAGE = "IS_MEMORY_IMAGE";
+
+
+ public static final String SSLTEST_URL = "https://www2.cybertriage.com/ssl_test.html";
+
+
+ public static final String CT_CLOUD_SERVER = "https://rep1.cybertriage.com";
+
+ public static final String CT_CLOUD_DEV_SERVER = "https://cyber-triage-dev.appspot.com";
+
+ /**
+ * Link to watch demo video
+ * @since 3.1.0
+ */
+ public static final String DEMO_VIDEO_URL = "https://www.cybertriage.com/video/cyber-triage-demo-video/?utm_source=Cyber+Triage+Tool&utm_campaign=Eval+Demo+Video";
+
+ /**
+ * Link request quote
+ * @since 3.1.0
+ */
+ public static final String REQUEST_QUOTE_URL = "https://www.cybertriage.com/request-quote/?utm_source=Cyber+Triage+Tool&utm_campaign=Eval+Quote";
+
+ /**
+ * Latest help document URL
+ * @since 3.2.0
+ */
+ public static final URI USER_GUIDE_LATEST_URL = URI.create("https://docs.cybertriage.com/en/latest/?utm_source=Cyber+Triage+Tool&utm_campaign=Help+Docs");
+
+ /**
+ * Visit website URL
+ * @since 3.1.0
+ */
+ public static final String VISIT_WEBSITE_URL ="https://www.cybertriage.com/eval_data_202109/?utm_source=Cyber+Triage+Tool&utm_campaign=Eval+Data+Button";
+
+
+ /**
+ * URL for visiting the website after the data is ingested on the dashboard.
+ */
+ public static final String EVAL_WEBSITE_AUTO_URL = "https://www.cybertriage.com/eval_data_202109_auto/?utm_source=Cyber+Triage+Tool&utm_campaign=Eval+Data+Auto/"; //CT-4045
+
+
+ public static final String SUPPORT_AT_CYBERTRIAGE_DOT_COM = "support@cybertriage.com";
+
+ public static final String SALES_AT_CYBERTRIAGE_DOT_COM = "sales@cybertriage.com";
+
+ public final static String AUTODETECT = "Auto Detect";
+
+ public final static int RESTAPI_PORT = 9443;
+
+ public static final String INVALID_HOSTNAME_REQUEST = "Request rejected. Invalid host name. Hostname contains characters that are not allowed. \n"
+ + "Characters that are not allowed include `~!@#$&^*(){}[]\\\\|;'\",<>/? \n"
+ + "You may input the host IP address if the name is not resolving.";
+ public static final String INVALID_HOSTNAME_UI = "Invalid host name. Hostname contains characters that are not allowed. \n"
+ + "Characters that are not allowed include `~!@#$&^*(){}[]\\\\|;'\",<>/?";
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java
new file mode 100644
index 0000000000..66fa03b8f1
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java
@@ -0,0 +1,83 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi;
+
+import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenRequest;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationResult;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
+import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil;
+import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.List;
+import org.sleuthkit.autopsy.coreutils.Version;
+
+/**
+ *
+ * Data access layer for handling the CT api.
+ */
+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 CtApiDAO instance = new CtApiDAO();
+ private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
+
+ private CtApiDAO() {
+ }
+
+ public static CtApiDAO getInstance() {
+ return instance;
+ }
+
+ private static String getAppVersion() {
+ return Version.getName() + " " + Version.getVersion();
+ }
+
+ private T doPost(String urlPath, Object requestBody, Class responseTypeRef) throws CTCloudException {
+ return null;
+ // TODO
+ }
+
+ public LicenseResponse getLicenseInfo(String licenseString) throws CTCloudException {
+ LicenseRequest licenseRequest = new LicenseRequest()
+ .setBoostLicenseCode(licenseString)
+ .setHostId(CTHostIDGenerationUtil.generateLicenseHostID())
+ .setProduct(getAppVersion());
+
+ return doPost(LICENSE_REQUEST_PATH, licenseRequest, LicenseResponse.class);
+
+ }
+
+ public AuthTokenResponse getAuthToken(String boostLicenseId) throws CTCloudException {
+ AuthTokenRequest authTokenRequest = new AuthTokenRequest()
+ .setAutopsyVersion(getAppVersion())
+ .setRequestFileUpload(true)
+ .setBoostLicenseId(boostLicenseId);
+
+ return doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
+ }
+
+ public List getReputationResults(String authToken, List md5Hashes) throws CTCloudException {
+ // TODO
+// return cloudServiceApi.lookupFileResults(md5Hashes, HashTypes.md5);
+ return null;
+ }
+
+ public enum ResultType {
+ OK, SERVER_ERROR, NOT_AUTHORIZED
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java
new file mode 100644
index 0000000000..d341b99e57
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java
@@ -0,0 +1,443 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi;
+
+
+
+import 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
new file mode 100644
index 0000000000..6cda146c84
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java
@@ -0,0 +1,59 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * POJO for an auth token request.
+ */
+public class AuthTokenRequest {
+
+ @JsonProperty("autopsy_version")
+ private String autopsyVersion;
+
+ @JsonProperty("boost_license_id")
+ private String boostLicenseId;
+
+ @JsonProperty("requestFileUpload")
+ private boolean requestFileUpload;
+
+ public String getAutopsyVersion() {
+ return autopsyVersion;
+ }
+
+ public AuthTokenRequest setAutopsyVersion(String autopsyVersion) {
+ this.autopsyVersion = autopsyVersion;
+ return this;
+ }
+
+ public String getBoostLicenseId() {
+ return boostLicenseId;
+ }
+
+ public AuthTokenRequest setBoostLicenseId(String boostLicenseId) {
+ this.boostLicenseId = boostLicenseId;
+ return this;
+ }
+
+ public boolean isRequestFileUpload() {
+ return requestFileUpload;
+ }
+
+ public AuthTokenRequest setRequestFileUpload(boolean requestFileUpload) {
+ this.requestFileUpload = requestFileUpload;
+ return this;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java
new file mode 100644
index 0000000000..a010bbe13c
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java
@@ -0,0 +1,85 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.ZonedDateTime;
+
+/**
+ * POJO for an auth token response.
+ */
+public class AuthTokenResponse {
+ private final String token;
+ private final String apiKey;
+ private final Long hashLookupCount;
+ private final Long hashLookupLimit;
+ private final Long fileUploadLimit;
+ private final Long fileUploadCount;
+ private final String fileUploadUrl;
+ private final ZonedDateTime expiration;
+
+ @JsonCreator
+ public AuthTokenResponse(
+ @JsonProperty("token") String token,
+ @JsonProperty("api_key") String apiKey,
+ @JsonProperty("hashLookupCount") Long hashLookupCount,
+ @JsonProperty("hashLookupLimit") Long hashLookupLimit,
+ @JsonProperty("fileUploadLimit") Long fileUploadLimit,
+ @JsonProperty("fileUploadCount") Long fileUploadCount,
+ @JsonProperty("fileUploadUrl") String fileUploadUrl,
+ @JsonProperty("expiration") ZonedDateTime expiration
+ ) {
+ this.token = token;
+ this.apiKey = apiKey;
+ this.hashLookupCount = hashLookupCount;
+ this.hashLookupLimit = hashLookupLimit;
+ this.fileUploadLimit = fileUploadLimit;
+ this.fileUploadCount = fileUploadCount;
+ this.fileUploadUrl = fileUploadUrl;
+ this.expiration = expiration;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ public Long getHashLookupCount() {
+ return hashLookupCount;
+ }
+
+ public Long getHashLookupLimit() {
+ return hashLookupLimit;
+ }
+
+ public Long getFileUploadLimit() {
+ return fileUploadLimit;
+ }
+
+ public Long getFileUploadCount() {
+ return fileUploadCount;
+ }
+
+ public String getFileUploadUrl() {
+ return fileUploadUrl;
+ }
+
+ public ZonedDateTime getExpiration() {
+ return expiration;
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/BoostLicenseResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/BoostLicenseResponse.java
new file mode 100644
index 0000000000..ce213aeba4
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/BoostLicenseResponse.java
@@ -0,0 +1,59 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * POJO for a boost license response object that is a part of the license
+ * response.
+ */
+public class BoostLicenseResponse {
+
+ private final String version;
+ private final String iv;
+ private final String encryptedKey;
+ private final String encryptedJson;
+
+ @JsonCreator
+ public BoostLicenseResponse(
+ @JsonProperty("version") String version,
+ @JsonProperty("iv") String iv,
+ @JsonProperty("encryptedKey") String encryptedKey,
+ @JsonProperty("encryptedJson") String encryptedJson) {
+
+ this.version = version;
+ this.iv = iv;
+ this.encryptedKey = encryptedKey;
+ this.encryptedJson = encryptedJson;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getIv() {
+ return iv;
+ }
+
+ public String getEncryptedKey() {
+ return encryptedKey;
+ }
+
+ public String getEncryptedJson() {
+ return encryptedJson;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTScore.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTScore.java
new file mode 100644
index 0000000000..fbb5665fba
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTScore.java
@@ -0,0 +1,77 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2014 - 2016 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.google.common.base.MoreObjects;
+import static com.google.common.base.Preconditions.checkArgument;
+import org.sleuthkit.datamodel.Score;
+import org.sleuthkit.datamodel.Score.Priority;
+import org.sleuthkit.datamodel.Score.Significance;
+
+/**
+ *
+ * Score class represents a conclusion and the relative confidence in the conclusion about
+ * a subject. A subject may be an Item, a category/analysis result etc.
+ * @since 1.7.0
+ *
+ */
+public enum CTScore {
+
+ /*
+ Enum names without method defaults to AUTO
+ NOTABLE -> NOTABLE
+ */
+
+ // Unknown None
+ UNKNOWN(new Score(Significance.UNKNOWN, Priority.NORMAL)),
+ // GOOD_MEDIUM
+ LIKELY_NONE(new Score(Significance.LIKELY_NONE, Priority.NORMAL)),
+ // SUSPICIOUS_HIGH / BAD_MEDIUM
+ LIKELY_NOTABLE(new Score(Significance.LIKELY_NOTABLE, Priority.NORMAL)),
+ // GOOD_HIGH
+ NONE(new Score(Significance.NONE, Priority.NORMAL)),
+ // BAD_HIGH
+ NOTABLE(new Score(Significance.NOTABLE, Priority.NORMAL)),
+ // SUSPICIOUS (User flagged)
+ LIKELY_NOTABLE_MANUAL(new Score(Significance.LIKELY_NOTABLE, Priority.OVERRIDE)),
+ // Good (User flagged)
+ NONE_MANUAL(new Score(Significance.NONE, Priority.OVERRIDE)),
+ // Bad (User flagged)
+ NOTABLE_MANUAL(new Score(Significance.NOTABLE, Priority.OVERRIDE));
+
+
+ private final Score tskScore;
+
+ /**
+ * Create a CTScore instance based on score
+ * @param tskScore
+ */
+ private CTScore(Score tskScore) {
+
+ checkArgument(tskScore.getSignificance() == Significance.UNKNOWN ? tskScore.getPriority() == Priority.NORMAL : true, "Unknown Conclusions expects no (NORMAL) priority");
+ this.tskScore = tskScore;
+ }
+
+ public Score getTskCore() {
+ return tskScore;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("Method Category", tskScore.getPriority())
+ .add("Significance", tskScore.getSignificance()).toString();
+ }
+
+}
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
new file mode 100644
index 0000000000..ec9e74a2ed
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java
@@ -0,0 +1,87 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.ZonedDateTime;
+
+/**
+ * POJO for after encrypted boost license has been decrypted.
+ */
+public class DecryptedLicenseResponse {
+
+ private final String boostLicenseId;
+ private final String licenseHostId;
+ private final ZonedDateTime expirationDate;
+ private final Long hashLookups;
+ private final Long fileUploads;
+ private final ZonedDateTime activationTime;
+ private final String product;
+ private final String limitType;
+
+ @JsonCreator
+ public DecryptedLicenseResponse(
+ @JsonProperty("boostLicenseId") String boostLicenseId,
+ @JsonProperty("licenseHostId") String licenseHostId,
+ @JsonProperty("expirationDate") ZonedDateTime expirationDate,
+ @JsonProperty("hashLookups") Long hashLookups,
+ @JsonProperty("fileUploads") Long fileUploads,
+ @JsonProperty("activationTime") ZonedDateTime activationTime,
+ @JsonProperty("product") String product,
+ @JsonProperty("limitType") String limitType
+ ) {
+ this.boostLicenseId = boostLicenseId;
+ this.licenseHostId = licenseHostId;
+ this.expirationDate = expirationDate;
+ this.hashLookups = hashLookups;
+ this.fileUploads = fileUploads;
+ this.activationTime = activationTime;
+ this.product = product;
+ this.limitType = limitType;
+ }
+
+ public String getBoostLicenseId() {
+ return boostLicenseId;
+ }
+
+ public String getLicenseHostId() {
+ return licenseHostId;
+ }
+
+ public Long getHashLookups() {
+ return hashLookups;
+ }
+
+ public Long getFileUploads() {
+ return fileUploads;
+ }
+
+ public ZonedDateTime getActivationTime() {
+ return activationTime;
+ }
+
+ public String getProduct() {
+ return product;
+ }
+
+ public String getLimitType() {
+ return limitType;
+ }
+
+ public ZonedDateTime getExpirationDate() {
+ return expirationDate;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResult.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResult.java
new file mode 100644
index 0000000000..53e604e381
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResult.java
@@ -0,0 +1,123 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.time.ZonedDateTime;
+import java.util.List;
+
+/**
+ * A file reputation result regarding malware status.
+ */
+public class FileReputationResult {
+
+ public static enum Status {
+ FOUND,
+ NOT_FOUND,
+ ERROR,
+ LIMITS_EXCEEDED,
+ BEING_SCANNED;
+ }
+
+ public enum CorrelationFrequency {
+ UNIQUE,
+ RARE,
+ COMMON;
+ }
+
+ private final String malwareDescription;
+ private final Status status;
+ private final CTScore score;
+ private final String md5Hash;
+ private final String sha1Hash;
+ private final ZonedDateTime firstScanDate;
+ private final ZonedDateTime lastScanDate;
+ private final List metadata;
+ private final String statusDescription;
+ private final CorrelationFrequency frequency;
+ private final String frequencyDescription;
+
+ @JsonCreator
+ public FileReputationResult(
+ @JsonProperty("malwareDescription") String malwareDescription,
+ @JsonProperty("status") Status status,
+ @JsonProperty("score") CTScore score,
+ @JsonProperty("md5Hash") String md5Hash,
+ @JsonProperty("sha1Hash") String sha1Hash,
+ @JsonProperty("firstScanDate") ZonedDateTime firstScanDate,
+ @JsonProperty("lastScanDate") ZonedDateTime lastScanDate,
+ @JsonProperty("metadata") List metadata,
+ @JsonProperty("statusDescription") String statusDescription,
+ @JsonProperty("frequency") CorrelationFrequency frequency,
+ @JsonProperty("frequencyDescription") String frequencyDescription
+ ) {
+ this.malwareDescription = malwareDescription;
+ this.status = status;
+ this.score = score;
+ this.md5Hash = md5Hash;
+ this.sha1Hash = sha1Hash;
+ this.firstScanDate = firstScanDate;
+ this.lastScanDate = lastScanDate;
+ this.metadata = metadata;
+ this.statusDescription = statusDescription;
+ this.frequency = frequency;
+ this.frequencyDescription = frequencyDescription;
+ }
+
+ public String getMalwareDescription() {
+ return malwareDescription;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public CTScore getScore() {
+ return score;
+ }
+
+ public String getMd5Hash() {
+ return md5Hash;
+ }
+
+ public String getSha1Hash() {
+ return sha1Hash;
+ }
+
+ public ZonedDateTime getFirstScanDate() {
+ return firstScanDate;
+ }
+
+ public ZonedDateTime getLastScanDate() {
+ return lastScanDate;
+ }
+
+ public List getMetadata() {
+ return metadata;
+ }
+
+ public String getStatusDescription() {
+ return statusDescription;
+ }
+
+ public CorrelationFrequency getFrequency() {
+ return frequency;
+ }
+
+ public String getFrequencyDescription() {
+ return frequencyDescription;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseInfo.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseInfo.java
new file mode 100644
index 0000000000..8045686133
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseInfo.java
@@ -0,0 +1,45 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+/**
+ * Contains license info and decrypted boost license.
+ */
+public class LicenseInfo {
+ private final LicenseResponse licenseResponse;
+ private final DecryptedLicenseResponse decryptedLicense;
+
+ public LicenseInfo(LicenseResponse licenseResponse, DecryptedLicenseResponse decryptedLicense) {
+ this.licenseResponse = licenseResponse;
+ this.decryptedLicense = decryptedLicense;
+ }
+
+ public LicenseResponse getLicenseResponse() {
+ return licenseResponse;
+ }
+
+ public DecryptedLicenseResponse getDecryptedLicense() {
+ return decryptedLicense;
+ }
+
+ // TODO
+ public String getUser() {
+ return "TBD";
+ }
+
+ // TODO
+ public String getEmail() {
+ return "TBD";
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseRequest.java
new file mode 100644
index 0000000000..c87878596f
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseRequest.java
@@ -0,0 +1,59 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * POJO for license request information.
+ */
+public class LicenseRequest {
+ @JsonProperty("host_id")
+ private String hostId;
+
+ @JsonProperty("boost_license_code")
+ private String boostLicenseCode;
+
+ @JsonProperty("product")
+ private String product;
+
+ public String getHostId() {
+ return hostId;
+ }
+
+ public LicenseRequest setHostId(String hostId) {
+ this.hostId = hostId;
+ return this;
+ }
+
+ public String getBoostLicenseCode() {
+ return boostLicenseCode;
+ }
+
+ public LicenseRequest setBoostLicenseCode(String boostLicenseCode) {
+ this.boostLicenseCode = boostLicenseCode;
+ return this;
+ }
+
+ public String getProduct() {
+ return product;
+ }
+
+ public LicenseRequest setProduct(String product) {
+ this.product = product;
+ return this;
+ }
+
+
+}
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
new file mode 100644
index 0000000000..39184322e2
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java
@@ -0,0 +1,59 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Response POJO for request for license.
+ */
+public class LicenseResponse {
+ private final Boolean success;
+ private final Boolean hostChanged;
+ private final Long hostChangesRemaining;
+ private final BoostLicenseResponse boostLicense;
+
+ @JsonCreator
+ public LicenseResponse(
+ @JsonProperty("success") Boolean success,
+ @JsonProperty("hostChanged") Boolean hostChanged,
+ @JsonProperty("hostChangesRemaining") Long hostChangesRemaining,
+ @JsonProperty("boostLicense") BoostLicenseResponse boostLicense
+ ) {
+ this.success = success;
+ this.hostChanged = hostChanged;
+ this.hostChangesRemaining = hostChangesRemaining;
+ this.boostLicense = boostLicense;
+ }
+
+ public Boolean isSuccess() {
+ return success;
+ }
+
+ public Boolean isHostChanged() {
+ return hostChanged;
+ }
+
+ public Long getHostChangesRemaining() {
+ return hostChangesRemaining;
+ }
+
+ public BoostLicenseResponse getBoostLicense() {
+ return boostLicense;
+ }
+
+
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataLabel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataLabel.java
new file mode 100644
index 0000000000..8077250c8b
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataLabel.java
@@ -0,0 +1,43 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package com.basistech.df.cybertriage.autopsy.ctapi.json;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ * @author gregd
+ */
+public class MetadataLabel {
+
+ private final String key;
+ private final String value;
+ private final String extendedInfo;
+
+ @JsonCreator
+ public MetadataLabel(
+ @JsonProperty("key") String key,
+ @JsonProperty("value") String value,
+ @JsonProperty("info") String extendedInfo
+ ) {
+ this.key = key;
+ this.value = value;
+ this.extendedInfo = extendedInfo;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getExtendedInfo() {
+ return extendedInfo;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java
new file mode 100644
index 0000000000..f7b68f6bea
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java
@@ -0,0 +1,57 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2021 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.util;
+
+import com.license4j.HardwareID;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.logging.Level;
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.coreutils.Logger;
+
+/**
+ * Utility class to generate license hostID and Target hostID for malware scan
+ *
+ * @author rishwanth
+ */
+public class CTHostIDGenerationUtil {
+
+ private static final Logger LOGGER = Logger.getLogger(CTHostIDGenerationUtil.class.getName());
+ private static final String USER_NAME = System.getProperty("user.name");
+ private static String HOST_NAME = "";
+
+ /**
+ * Host ID Algorithm: Get MAC address from License4J. Get MD5 hash of it and
+ * grab the first 16 characters of the hash. Get user name that Cyber Triage
+ * is running as. MD5 hash of user name. Grab first 16 characters.
+ * Concatenate them and separate with underscore. Example:
+ * c84f70d1baf96420_7d7519bf21602c24
+ *
+ * @return
+ */
+ public static String generateLicenseHostID() {
+ if (StringUtils.isBlank(HOST_NAME)) {
+
+ try {
+ HOST_NAME = StringUtils.defaultString(InetAddress.getLocalHost().getCanonicalHostName());
+ } catch (UnknownHostException ex) {
+ LOGGER.log(Level.WARNING, "UNable to determine host name.", ex);
+ }
+ }
+ String macAddressMd5 = StringUtils.isNotBlank(HardwareID.getHardwareIDFromEthernetAddress()) ? Md5HashUtil.getMD5MessageDigest(HardwareID.getHardwareIDFromEthernetAddress()).substring(0, 16) : Md5HashUtil.getMD5MessageDigest(HOST_NAME).substring(0, 16);
+ String usernameMd5 = StringUtils.isNotBlank(USER_NAME) ? Md5HashUtil.getMD5MessageDigest(USER_NAME).substring(0, 16) : Md5HashUtil.getMD5MessageDigest(HOST_NAME).substring(0, 16);
+ String md5 = macAddressMd5 + "_" + usernameMd5;
+ return md5;
+ }
+}
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
new file mode 100644
index 0000000000..efa91afd8d
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java
@@ -0,0 +1,172 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.util;
+
+import com.basistech.df.cybertriage.autopsy.ctapi.json.BoostLicenseResponse;
+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.LicenseResponse;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Decrypts the payload of boost license.
+ */
+public class LicenseDecryptorUtil {
+
+ private static final LicenseDecryptorUtil instance = new LicenseDecryptorUtil();
+
+ public static LicenseDecryptorUtil getInstance() {
+ return instance;
+ }
+
+ private final ObjectMapper objectMapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
+
+ 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");
+ }
+
+ DecryptedLicenseResponse decrypted = parseLicenseJSON(licenseResponse.getBoostLicense());
+ return new LicenseInfo(licenseResponse, decrypted);
+ }
+
+ /**
+ * Decrypts a boost license response.
+ *
+ * @param licenseResponse The boost license response.
+ * @return The decrypted license response.
+ * @throws JsonProcessingException
+ * @throws
+ * com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException
+ */
+ public DecryptedLicenseResponse parseLicenseJSON(BoostLicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException {
+
+ String decryptedJsonResponse;
+ try {
+ decryptedJsonResponse = decryptLicenseString(
+ licenseResponse.getEncryptedJson(),
+ licenseResponse.getIv(),
+ licenseResponse.getEncryptedKey(),
+ licenseResponse.getVersion()
+ );
+ } catch (IOException | GeneralSecurityException ex) {
+ throw new InvalidLicenseException("An exception occurred while parsing the license string", ex);
+ }
+
+ DecryptedLicenseResponse decryptedLicense = objectMapper.readValue(decryptedJsonResponse, DecryptedLicenseResponse.class);
+ if (!"CYBERTRIAGE".equalsIgnoreCase(decryptedLicense.getProduct())) {
+ // license file is expected to contain product of "CYBERTRIAGE"
+ throw new InvalidLicenseException("Not a valid Cyber Triage license");
+ }
+
+ return decryptedLicense;
+ }
+
+ private String decryptLicenseString(String encryptedJson, String ivBase64, String encryptedKey, String version) throws IOException, GeneralSecurityException, InvalidLicenseException {
+ if (!"1.0".equals(version)) {
+ throw new InvalidLicenseException("Unexpected file version: " + version);
+ }
+
+ byte[] encryptedKeyBytes = Base64.getDecoder().decode(encryptedKey);
+ byte[] keyBytes = decryptKey(encryptedKeyBytes);
+ SecretKey key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
+
+ byte[] ivBytes = Base64.getDecoder().decode(ivBase64);
+ IvParameterSpec iv = new IvParameterSpec(ivBytes);
+
+ byte[] encryptedLicenseJsonBytes = Base64.getDecoder().decode(encryptedJson);
+
+ String algorithm = "AES/CBC/PKCS5Padding";
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, key, iv);
+ byte[] licenseJsonBytes = cipher.doFinal(encryptedLicenseJsonBytes);
+
+ return new String(licenseJsonBytes, StandardCharsets.UTF_8);
+ }
+
+ private PublicKey getPublicKey() throws InvalidKeySpecException, NoSuchAlgorithmException {
+
+ String publicKeyString = """
+ -----BEGIN PUBLIC KEY-----
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwIKulLyaLQ2WeO0gIW2G
+ 3jQqny3Y/7VUevBKulAEywaUbvECvZ4zGsnaMyACjXxMNkA1xU2WeSMP/WqC03wz
+ 4d71liUeAqOYKMdGHXFN2qswWz/ufK6An0pTEqYaoiUfcwSBVo2ZTUcMQexScKaS
+ ghmaWqBHBYx+lBkVMcLG2PtLDRZbqgJvJr2QCzMSVUpEGGQEWs7YolIq46KCgqsq
+ pTdfrdqd59x6oRhTLegswzxwLyouvrKbRqKR2ZRbVvlGtUnnnlLDuhEfd0flMxuv
+ W98Siw6dWe1K3x45nDu5py2G9Q9fZS8/2KHUC6QcLLstLIoPnZjCl9Lcur1U6s9N
+ f5aLI9mwMfmSJsoVOuwx2/MC98uHvPoPbG4ZjiT0aaGg4JccTGD6pssDA35zPhkk
+ 1l6wktEYtyF2A7zjzuFxioQz8fHBzIbHPCxzu4S2gh3qOVFf7c9COmX9MsnB70o2
+ EZ1rxlFIJ7937IGJNwWOQuiMKTpEeT6BwTdQNZQPqCUGvZ5eEjhrm57yCF4zuyrt
+ AR8DG7ahK2YAarADHRyxTuxH1qY7E5/CTQKYk9tIYsV4O05CKj7B8rBMtjVNjb4b
+ d7JwPW43Z3J6jo/gLlVdGSPg8vQDNVLl6sdDM4Pm1eJEzgR2JlqXDCRDUGNNsXH2
+ qt9Ru8ykX7PAfF2Q3/qg1jkCAwEAAQ==
+ -----END PUBLIC KEY-----
+ """;
+
+ publicKeyString = publicKeyString.replaceAll("-----BEGIN PUBLIC KEY-----", "").replaceAll("-----END PUBLIC KEY-----", "").replaceAll("\\s", "");
+ byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
+
+ KeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ PublicKey publicKey = keyFactory.generatePublic(keySpec);
+
+ return publicKey;
+ }
+
+ private byte[] decryptKey(byte[] encryptedKeyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+
+ PublicKey publicKey = getPublicKey();
+
+ Cipher decryptCipher = Cipher.getInstance("RSA");
+ decryptCipher.init(Cipher.DECRYPT_MODE, publicKey);
+
+ byte[] decryptedBytes = decryptCipher.doFinal(encryptedKeyBytes);
+
+ return decryptedBytes;
+ }
+
+ public class InvalidLicenseException extends Exception {
+
+ public InvalidLicenseException(String message) {
+ super(message);
+ }
+
+ public InvalidLicenseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/Md5HashUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/Md5HashUtil.java
new file mode 100644
index 0000000000..4ff7d262b7
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/Md5HashUtil.java
@@ -0,0 +1,40 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2018 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.util;
+import com.google.common.base.Charsets;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import org.apache.commons.lang3.StringUtils;
+/**
+ *
+ * @author jayaram
+ */
+public class Md5HashUtil {
+ /**
+ * Returns MD5 hash value for the lower case value of the string provided.
+ * @param inp
+ * @return
+ */
+ public static String getMD5MessageDigest(String inp) {
+ if (StringUtils.isNotBlank(inp)) {
+ HashFunction hf = Hashing.md5(); // Using despite its deprecation as md5 is good enough for our uses.
+ HashCode hc = hf.newHasher()
+ .putString(inp.toLowerCase(), Charsets.UTF_8)
+ .hash();
+ return hc.toString();
+ }
+ return "";
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/ObjectMapperUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/ObjectMapperUtil.java
new file mode 100644
index 0000000000..79098ce650
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/ObjectMapperUtil.java
@@ -0,0 +1,41 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctapi.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+/**
+ * Creates default ObjectMapper
+ */
+public class ObjectMapperUtil {
+
+ private static final ObjectMapperUtil instance = new ObjectMapperUtil();
+
+ public static ObjectMapperUtil getInstance() {
+ return instance;
+ }
+
+ private ObjectMapperUtil() {
+
+ }
+
+ public ObjectMapper getDefaultObjectMapper() {
+ ObjectMapper defaultMapper = new ObjectMapper();
+ defaultMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ defaultMapper.registerModule(new JavaTimeModule());
+ return defaultMapper;
+ }
+
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties
new file mode 100644
index 0000000000..2d9daa4c7b
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties
@@ -0,0 +1,5 @@
+
+# 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
+OptionsCategory_Name_CyberTriage=CyberTriage
+OptionsCategory_Keywords_CyberTriage=CyberTriage,Cyber,Triage
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
new file mode 100644
index 0000000000..2d9daa4c7b
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED
@@ -0,0 +1,5 @@
+
+# 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
+OptionsCategory_Name_CyberTriage=CyberTriage
+OptionsCategory_Keywords_CyberTriage=CyberTriage,Cyber,Triage
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.form
new file mode 100644
index 0000000000..1aee51eb33
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.form
@@ -0,0 +1,39 @@
+
+
+
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java
new file mode 100644
index 0000000000..fd235b580e
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java
@@ -0,0 +1,141 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions;
+
+import com.basistech.df.cybertriage.autopsy.ctoptions.subpanel.CTOptionsSubPanel;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.beans.PropertyChangeEvent;
+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.ingest.IngestModuleGlobalSettingsPanel;
+
+/**
+ * Options panel for CyberTriage.
+ */
+public class CTOptionsPanel extends IngestModuleGlobalSettingsPanel {
+ private static final int MAX_SUBPANEL_WIDTH = 500;
+
+ private static final Logger logger = Logger.getLogger(CTOptionsPanel.class.getName());
+
+ private final List subPanels;
+
+ /**
+ * Creates new form CTOptions
+ */
+ public CTOptionsPanel() {
+ initComponents();
+ Collection extends CTOptionsSubPanel> coll = Lookup.getDefault().lookupAll(CTOptionsSubPanel.class);
+ Stream extends CTOptionsSubPanel> panelStream = coll != null ? coll.stream() : Stream.empty();
+ this.subPanels = panelStream
+ .sorted(Comparator.comparing(p -> p.getClass().getSimpleName().toUpperCase()))
+ .collect(Collectors.toList());
+ addSubOptionsPanels(this.subPanels);
+ }
+
+ private void addSubOptionsPanels(List subPanels) {
+ for (int i = 0; i < subPanels.size(); i++) {
+ CTOptionsSubPanel subPanel = subPanels.get(i);
+
+ subPanel.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) {
+ CTOptionsPanel.this.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }
+ }
+ });
+
+ GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = i;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(i == 0 ? 5 : 0, 5, 5, 5);
+ gridBagConstraints.weighty = 0;
+ gridBagConstraints.weightx = 0;
+
+ contentPane.add(subPanel, gridBagConstraints);
+ }
+
+ GridBagConstraints verticalConstraints = new GridBagConstraints();
+ verticalConstraints.gridx = 0;
+ verticalConstraints.gridy = subPanels.size();
+ verticalConstraints.weighty = 1;
+ verticalConstraints.weightx = 0;
+
+ JPanel verticalSpacer = new JPanel();
+
+ verticalSpacer.setMinimumSize(new Dimension(MAX_SUBPANEL_WIDTH, 0));
+ verticalSpacer.setPreferredSize(new Dimension(MAX_SUBPANEL_WIDTH, 0));
+ verticalSpacer.setMaximumSize(new Dimension(MAX_SUBPANEL_WIDTH, Short.MAX_VALUE));
+ contentPane.add(verticalSpacer, verticalConstraints);
+
+
+ GridBagConstraints horizontalConstraints = new GridBagConstraints();
+ horizontalConstraints.gridx = 1;
+ horizontalConstraints.gridy = 0;
+ horizontalConstraints.weighty = 0;
+ horizontalConstraints.weightx = 1;
+
+ JPanel horizontalSpacer = new JPanel();
+ contentPane.add(horizontalSpacer, horizontalConstraints);
+ }
+
+ /**
+ * 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() {
+
+ javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
+ contentPane = new javax.swing.JPanel();
+
+ setLayout(new java.awt.BorderLayout());
+
+ contentPane.setLayout(new java.awt.GridBagLayout());
+ scrollPane.setViewportView(contentPane);
+
+ add(scrollPane, java.awt.BorderLayout.CENTER);
+ }// //GEN-END:initComponents
+
+ @Override
+ public void saveSettings() {
+ subPanels.forEach(panel -> panel.saveSettings());
+ }
+
+ public void loadSavedSettings() {
+ subPanels.forEach(panel -> panel.loadSettings());
+ }
+
+ public boolean valid() {
+ return subPanels.stream().allMatch(panel -> panel.valid());
+ }
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel contentPane;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanelController.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanelController.java
new file mode 100644
index 0000000000..93dfe4960c
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanelController.java
@@ -0,0 +1,128 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import javax.swing.JComponent;
+import org.netbeans.spi.options.OptionsPanelController;
+import org.openide.util.HelpCtx;
+import org.openide.util.Lookup;
+
+/**
+ * Options panel controller for CyberTriage.
+ */
+@OptionsPanelController.TopLevelRegistration(categoryName = "#OptionsCategory_Name_CyberTriage",
+ iconBase = "com/basistech/df/cybertriage/autopsy/images/logo.png",
+ position = 999999,
+ keywords = "#OptionsCategory_Keywords_CyberTriage",
+ keywordsCategory = "CyberTriage")
+public final class CTOptionsPanelController extends OptionsPanelController {
+
+ private CTOptionsPanel panel;
+ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+ private boolean changed;
+
+ /**
+ * Component should load its data here.
+ */
+ @Override
+ public void update() {
+ getPanel().loadSavedSettings();
+ changed = false;
+ }
+
+ /**
+ * This method is called when both the Ok and Apply buttons are pressed. It
+ * applies to any of the panels that have been opened in the process of
+ * using the options pane.
+ */
+ @Override
+ public void applyChanges() {
+ if (changed) {
+ getPanel().saveSettings();
+ changed = false;
+ }
+ }
+
+ /**
+ * This method is called when the Cancel button is pressed. It applies to
+ * any of the panels that have been opened in the process of using the
+ * options pane.
+ */
+ @Override
+ public void cancel() {
+ }
+
+ @Override
+ public boolean isValid() {
+ return getPanel().valid();
+ }
+
+ /**
+ * Used to determine whether any changes have been made to this controller's
+ * panel.
+ *
+ * @return Whether or not a change has been made.
+ */
+ @Override
+ public boolean isChanged() {
+ return changed;
+ }
+
+ @Override
+ public HelpCtx getHelpCtx() {
+ return null;
+ }
+
+ @Override
+ public JComponent getComponent(Lookup masterLookup) {
+ return getPanel();
+ }
+
+ @Override
+ public void addPropertyChangeListener(PropertyChangeListener l) {
+ pcs.addPropertyChangeListener(l);
+ }
+
+ @Override
+ public void removePropertyChangeListener(PropertyChangeListener l) {
+ pcs.removePropertyChangeListener(l);
+ }
+
+ private CTOptionsPanel getPanel() {
+ if (panel == null) {
+ panel = new CTOptionsPanel();
+ panel.addPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (evt.getPropertyName().equals(OptionsPanelController.PROP_CHANGED)) {
+ changed();
+ }
+ }
+ });
+ }
+ return panel;
+ }
+
+ void changed() {
+ if (!changed) {
+ changed = true;
+ pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true);
+ }
+ pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null);
+
+ }
+}
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
new file mode 100644
index 0000000000..5df431e10c
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties
@@ -0,0 +1,23 @@
+
+# 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
+
+
+CTLicenseDialog.licenseNumberLabel.text=License Number:
+CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+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.licenseInfoAddButton.text=Add License
+CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text=
+CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text=
+CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text=
+CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
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
new file mode 100644
index 0000000000..6eaa730072
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED
@@ -0,0 +1,55 @@
+
+# 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
+
+
+CTLicenseDialog.licenseNumberLabel.text=License Number:
+CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+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'
+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.licenseInfoAddButton.text=Add License
+CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text=
+CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text=
+CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text=
+CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text=
+CTMalwareScannerOptionsPanel_licenseAddDialog_desc=License Number:
+CTMalwareScannerOptionsPanel_licenseAddDialog_title=Add a License...
+CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc=The license number has already been entered
+CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title=License Number Already Entered
+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
+CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.
+CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error
+# {0} - expiresDate
+CTMalwareScannerOptionsPanel_licenseInfo_expires=Expires: {0}
+# {0} - idNumber
+CTMalwareScannerOptionsPanel_licenseInfo_id=ID: {0}
+# {0} - userName
+# {1} - email
+CTMalwareScannerOptionsPanel_licenseInfo_userInfo=User: {0}
Email: {1}
+# {0} - countersResetDate
+CTMalwareScannerOptionsPanel_malwareScans_countersReset=Counters reset: {0}
+# {0} - fileUploadsRemaining
+CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining=File uploads remaining: {0}
+# {0} - hashLookupsRemaining
+CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining=Hash lookups remaining: {0}
+# {0} - maxDailyFileLookups
+CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day
+# {0} - maxDailyLookups
+CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}/day
+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
+CTOPtionsPanel_loadLicenseInfo_loading=Loading...
+CTOPtionsPanel_loadMalwareScansInfo_loading=Loading...
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
new file mode 100644
index 0000000000..e7cd2743a0
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form
@@ -0,0 +1,138 @@
+
+
+
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
new file mode 100644
index 0000000000..8af5795517
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java
@@ -0,0 +1,192 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
+
+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;
+
+/**
+ * License dialog
+ */
+public class CTLicenseDialog extends javax.swing.JDialog {
+
+ private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[0-9a-zA-Z]{8}\\-[0-9a-zA-Z]{4}\\-[0-9a-zA-Z]{4}\\-[0-9a-zA-Z]{4}\\-[0-9a-zA-Z]{12}\\s*$");
+
+ private String licenseString = null;
+
+ /**
+ * Creates new form CTLicenseDialog
+ */
+ public CTLicenseDialog(java.awt.Frame parent, boolean modal) {
+ super(parent, modal);
+ initComponents();
+ this.licenseNumberTextField.getDocument().putProperty("filterNewlines", Boolean.TRUE);
+ this.licenseNumberTextField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ verifyInput();
+ }
+
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ verifyInput();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ verifyInput();
+ }
+ });
+ }
+
+ String getValue() {
+ return licenseString;
+ }
+
+ @Messages({
+ "CTLicenseDialog_verifyInput_licenseNumberError=Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'"
+ })
+ private void verifyInput() {
+ String licenseInput = StringUtils.defaultString(this.licenseNumberTextField.getText());
+ if (LICENSE_PATTERN.matcher(licenseInput).matches()) {
+ this.warningLabel.setText("");
+ this.okButton.setEnabled(true);
+ } else {
+ this.warningLabel.setText(Bundle.CTLicenseDialog_verifyInput_licenseNumberError());
+ this.okButton.setEnabled(false);
+ }
+ }
+
+ /**
+ * 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 licenseNumberLabel = new javax.swing.JLabel();
+ warningLabel = new javax.swing.JLabel();
+ javax.swing.JPanel buttonPadding = new javax.swing.JPanel();
+ okButton = new javax.swing.JButton();
+ cancelButton = new javax.swing.JButton();
+ licenseNumberTextField = new javax.swing.JTextField();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.title")); // NOI18N
+ setAlwaysOnTop(true);
+ setResizable(false);
+ getContentPane().setLayout(new java.awt.GridBagLayout());
+
+ org.openide.awt.Mnemonics.setLocalizedText(licenseNumberLabel, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberLabel.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 0;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ getContentPane().add(licenseNumberLabel, gridBagConstraints);
+
+ warningLabel.setForeground(java.awt.Color.RED);
+ org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.warningLabel.text")); // NOI18N
+ warningLabel.setMaximumSize(new java.awt.Dimension(419, 36));
+ warningLabel.setMinimumSize(new java.awt.Dimension(419, 36));
+ warningLabel.setPreferredSize(new java.awt.Dimension(419, 36));
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(warningLabel, gridBagConstraints);
+
+ javax.swing.GroupLayout buttonPaddingLayout = new javax.swing.GroupLayout(buttonPadding);
+ buttonPadding.setLayout(buttonPaddingLayout);
+ buttonPaddingLayout.setHorizontalGroup(
+ buttonPaddingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+ buttonPaddingLayout.setVerticalGroup(
+ buttonPaddingLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 0, Short.MAX_VALUE)
+ );
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 3;
+ gridBagConstraints.weightx = 1.0;
+ getContentPane().add(buttonPadding, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.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 = 2;
+ gridBagConstraints.gridy = 3;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(okButton, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.cancelButton.text")); // NOI18N
+ cancelButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ cancelButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ gridBagConstraints.gridy = 3;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(cancelButton, gridBagConstraints);
+
+ licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 1;
+ gridBagConstraints.gridwidth = 3;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ getContentPane().add(licenseNumberTextField, gridBagConstraints);
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ this.licenseString = this.licenseNumberTextField.getText();
+ this.dispose();
+ }//GEN-LAST:event_okButtonActionPerformed
+
+ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
+ this.licenseString = null;
+ this.dispose();
+ }//GEN-LAST:event_cancelButtonActionPerformed
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JButton cancelButton;
+ private javax.swing.JTextField licenseNumberTextField;
+ private javax.swing.JButton okButton;
+ private javax.swing.JLabel warningLabel;
+ // End of variables declaration//GEN-END:variables
+}
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
new file mode 100644
index 0000000000..8f9d61a79e
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java
@@ -0,0 +1,90 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
+
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
+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.ObjectMapperUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.logging.Level;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+
+/**
+ * Handles persisting CT Settings.
+ */
+public class CTLicensePersistence {
+
+ private static final String CT_SETTINGS_DIR = "CyberTriage";
+ private static final String CT_LICENSE_FILENAME = "CyberTriageLicense.json";
+
+ private static final Logger logger = Logger.getLogger(CTLicensePersistence.class.getName());
+
+ private static final CTLicensePersistence instance = new CTLicensePersistence();
+
+ private final ObjectMapper objectMapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper();
+
+ public static CTLicensePersistence getInstance() {
+ return instance;
+ }
+
+ public synchronized boolean saveLicenseResponse(LicenseResponse licenseResponse) {
+ if (licenseResponse != null) {
+ File licenseFile = getCTLicenseFile();
+ try {
+ objectMapper.writeValue(licenseFile, licenseResponse);
+ return true;
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "There was an error writing CyberTriage license to file: " + licenseFile.getAbsolutePath(), ex);
+ }
+ }
+
+ return false;
+ }
+
+ public synchronized Optional loadLicenseResponse() {
+ Optional toRet = Optional.empty();
+ File licenseFile = getCTLicenseFile();
+ if (licenseFile.isFile()) {
+ try {
+ toRet = Optional.ofNullable(objectMapper.readValue(licenseFile, LicenseResponse.class));
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "There was an error reading CyberTriage license to file: " + licenseFile.getAbsolutePath(), ex);
+ }
+ }
+
+ return toRet;
+ }
+
+ public synchronized Optional loadLicenseInfo() {
+ return loadLicenseResponse().flatMap((license) -> {
+ try {
+ return Optional.ofNullable(LicenseDecryptorUtil.getInstance().createLicenseInfo(license));
+ } catch (JsonProcessingException | LicenseDecryptorUtil.InvalidLicenseException ex) {
+ logger.log(Level.WARNING, "There was an error decrypting license data from license file", ex);
+ return Optional.empty();
+ }
+ });
+ }
+
+ private File getCTLicenseFile() {
+ return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, CT_LICENSE_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
new file mode 100644
index 0000000000..77361419b6
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form
@@ -0,0 +1,199 @@
+
+
+
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
new file mode 100644
index 0000000000..069d9fb653
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java
@@ -0,0 +1,551 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
+
+import com.basistech.df.cybertriage.autopsy.ctoptions.subpanel.CTOptionsSubPanel;
+import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException;
+import com.basistech.df.cybertriage.autopsy.ctapi.CtApiDAO;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
+import com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.text.SimpleDateFormat;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.JOptionPane;
+import javax.swing.SwingWorker;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+import org.openide.windows.WindowManager;
+
+/**
+ * Options panel for CyberTriage options for importing a CyberTriage incident
+ */
+@ServiceProvider(service = CTOptionsSubPanel.class)
+public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
+
+ private static final Logger logger = Logger.getLogger(CTMalwareScannerOptionsPanel.class.getName());
+
+ private static final SimpleDateFormat LICENSE_EXPIRES_FORMAT = new SimpleDateFormat("MMMM d, YYYY");
+ private static final SimpleDateFormat MALWARE_SCANS_RESET_FORMAT = new SimpleDateFormat("MMM d, YYYY' at 'h:mma");
+
+ private final CtApiDAO ctApiDAO = CtApiDAO.getInstance();
+ private final CTLicensePersistence ctPersistence = CTLicensePersistence.getInstance();
+
+ private volatile LicenseInfo licenseInfo = null;
+ private volatile String licenseInfoMessage = null;
+ private volatile LicenseFetcher licenseFetcher = null;
+
+ private volatile AuthTokenResponse authTokenResponse = null;
+ private volatile String authTokenMessage = null;
+ private volatile AuthTokenFetcher authTokenFetcher = null;
+
+ /**
+ * Creates new form CTIncidentImportOptionsPanel
+ */
+ public CTMalwareScannerOptionsPanel() {
+ initComponents();
+
+ this.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentHidden(ComponentEvent e) {
+ synchronized (CTMalwareScannerOptionsPanel.this) {
+ if (CTMalwareScannerOptionsPanel.this.isLicenseAddRunning()) {
+ CTMalwareScannerOptionsPanel.this.licenseFetcher.cancel(true);
+ CTMalwareScannerOptionsPanel.this.licenseFetcher = null;
+ }
+
+ if (CTMalwareScannerOptionsPanel.this.isMalwareScansRunning()) {
+ CTMalwareScannerOptionsPanel.this.authTokenFetcher.cancel(true);
+ CTMalwareScannerOptionsPanel.this.authTokenFetcher = null;
+ }
+ }
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e) {
+ synchronized (CTMalwareScannerOptionsPanel.this) {
+ if (CTMalwareScannerOptionsPanel.this.licenseInfo != null) {
+ loadMalwareScansInfo(CTMalwareScannerOptionsPanel.this.licenseInfo);
+ }
+ }
+ }
+ }
+ );
+ }
+
+ @Override
+ public synchronized void saveSettings() {
+ ctPersistence.saveLicenseResponse(getLicenseInfo());
+ }
+
+ @Override
+ public boolean valid() {
+ return true;
+ }
+
+ @Override
+ public synchronized void loadSettings() {
+ Optional licenseInfoOpt = ctPersistence.loadLicenseInfo();
+ LicenseInfo licenseInfo = licenseInfoOpt.orElse(null);
+ setLicenseDisplay(licenseInfo, null);
+ setMalwareScansDisplay(null, null);
+ if (licenseInfo != null) {
+ loadMalwareScansInfo(licenseInfo);
+ }
+ }
+
+ private synchronized LicenseResponse getLicenseInfo() {
+ return this.licenseInfo == null ? null : this.licenseInfo.getLicenseResponse();
+ }
+
+ private synchronized void setLicenseDisplay(LicenseInfo licenseInfo, String licenseMessage) {
+ this.licenseInfo = licenseInfo;
+ this.licenseInfoMessage = licenseMessage;
+ renderLicenseState();
+ }
+
+ private synchronized void setMalwareScansDisplay(AuthTokenResponse authTokenResponse, String authTokenMessage) {
+ this.authTokenResponse = authTokenResponse;
+ this.authTokenMessage = authTokenMessage;
+ renderLicenseState();
+ }
+
+ /**
+ * @return True if there is an operation to fetch the license.
+ */
+ private synchronized boolean isLicenseAddRunning() {
+ return this.licenseFetcher != null && !this.licenseFetcher.isCancelled() && !this.licenseFetcher.isDone();
+ }
+
+ /**
+ * @return True if there is an operation to fetch malware scans information.
+ */
+ private synchronized boolean isMalwareScansRunning() {
+ return this.authTokenFetcher != null && !this.authTokenFetcher.isCancelled() && !this.authTokenFetcher.isDone();
+ }
+
+ @Messages({
+ "CTOPtionsPanel_loadLicenseInfo_loading=Loading..."
+ })
+ private synchronized void loadLicenseInfo(String licenseNumber) {
+ if (isLicenseAddRunning()) {
+ this.licenseFetcher.cancel(true);
+ }
+ setLicenseDisplay(null, Bundle.CTOPtionsPanel_loadLicenseInfo_loading());
+ this.licenseFetcher = new LicenseFetcher(licenseNumber);
+ this.licenseFetcher.execute();
+ }
+
+ @Messages({
+ "CTOPtionsPanel_loadMalwareScansInfo_loading=Loading..."
+ })
+ private synchronized void loadMalwareScansInfo(LicenseInfo licenseInfo) {
+ if (isMalwareScansRunning()) {
+ this.authTokenFetcher.cancel(true);
+ }
+
+ setMalwareScansDisplay(null, Bundle.CTOPtionsPanel_loadMalwareScansInfo_loading());
+
+ if (licenseInfo == null || licenseInfo.getDecryptedLicense() == null) {
+ return;
+ }
+
+ this.authTokenFetcher = new AuthTokenFetcher(licenseInfo.getDecryptedLicense().getBoostLicenseId());
+ this.authTokenFetcher.execute();
+ }
+
+ /**
+ * 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.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();
+ 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();
+ hashLookupsRemainingLabel = new javax.swing.JLabel();
+ fileUploadsRemainingLabel = 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
+ 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.gridy = 1;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ licenseInfoPanel.add(licenseInfoExpiresLabel, gridBagConstraints);
+
+ org.openide.awt.Mnemonics.setLocalizedText(licenseInfoIdLabel, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoIdLabel.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);
+
+ org.openide.awt.Mnemonics.setLocalizedText(licenseInfoAddButton, org.openide.util.NbBundle.getMessage(CTMalwareScannerOptionsPanel.class, "CTMalwareScannerOptionsPanel.licenseInfoAddButton.text")); // NOI18N
+ licenseInfoAddButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ licenseInfoAddButtonActionPerformed(evt);
+ }
+ });
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 1;
+ 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.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ malwareScansPanel.add(maxFileUploadsLabel, 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 = 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);
+
+ 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.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
+ malwareScansPanel.add(hashLookupsRemainingLabel, 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.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
+ malwareScansPanel.add(fileUploadsRemainingLabel, gridBagConstraints);
+
+ gridBagConstraints = new java.awt.GridBagConstraints();
+ gridBagConstraints.gridx = 0;
+ gridBagConstraints.gridy = 2;
+ gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+ gridBagConstraints.weightx = 1.0;
+ add(malwareScansPanel, gridBagConstraints);
+ }// //GEN-END:initComponents
+
+ @Messages({
+ "CTMalwareScannerOptionsPanel_licenseAddDialog_title=Add a License...",
+ "CTMalwareScannerOptionsPanel_licenseAddDialog_desc=License Number:",
+ "CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title=License Number Already Entered",
+ "CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc=The license number has already been entered",
+ "CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_title=Invalid License Number",
+ "CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_desc=Please verify that license number is of format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'"})
+ private void licenseInfoAddButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_licenseInfoAddButtonActionPerformed
+ CTLicenseDialog licenseDialog = new CTLicenseDialog(WindowManager.getDefault().getMainWindow(), true);
+ licenseDialog.setLocationRelativeTo(this);
+ licenseDialog.setVisible(true);
+ String licenseNumber = licenseDialog.getValue();
+ if (licenseNumber != null) {
+ synchronized (this) {
+ if (this.licenseInfo == null || !licenseNumber.trim().equalsIgnoreCase(this.licenseInfo.getDecryptedLicense().getBoostLicenseId())) {
+ loadLicenseInfo(licenseNumber);
+ return;
+ }
+ }
+
+ JOptionPane.showMessageDialog(
+ this,
+ Bundle.CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_desc(),
+ Bundle.CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title(),
+ JOptionPane.INFORMATION_MESSAGE);
+
+ }
+ }//GEN-LAST:event_licenseInfoAddButtonActionPerformed
+
+ @NbBundle.Messages({
+ "# {0} - userName",
+ "# {1} - email",
+ "CTMalwareScannerOptionsPanel_licenseInfo_userInfo=User: {0}
Email: {1}",
+ "# {0} - expiresDate",
+ "CTMalwareScannerOptionsPanel_licenseInfo_expires=Expires: {0}",
+ "# {0} - idNumber",
+ "CTMalwareScannerOptionsPanel_licenseInfo_id=ID: {0}",
+ "# {0} - maxDailyLookups",
+ "CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups=Max Hash lookups: {0}/day",
+ "# {0} - maxDailyFileLookups",
+ "CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups=Max file uploads: {0}/day",
+ "# {0} - countersResetDate",
+ "CTMalwareScannerOptionsPanel_malwareScans_countersReset=Counters reset: {0}",
+ "# {0} - hashLookupsRemaining",
+ "CTMalwareScannerOptionsPanel_malwareScans_hashLookupsRemaining=Hash lookups remaining: {0}",
+ "# {0} - fileUploadsRemaining",
+ "CTMalwareScannerOptionsPanel_malwareScans_fileUploadsRemaining=File uploads remaining: {0}"})
+ private synchronized void renderLicenseState() {
+ this.licenseInfoAddButton.setEnabled(!isLicenseAddRunning());
+
+ this.licenseInfoMessageLabel.setVisible(StringUtils.isNotBlank(this.licenseInfoMessage));
+ this.licenseInfoMessageLabel.setText(this.licenseInfoMessage);
+
+ if (licenseInfo == null) {
+ this.licenseInfoExpiresLabel.setVisible(false);
+ this.licenseInfoIdLabel.setVisible(false);
+ this.licenseInfoUserLabel.setVisible(false);
+ } else {
+ this.licenseInfoExpiresLabel.setVisible(true);
+ this.licenseInfoExpiresLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_expires(LICENSE_EXPIRES_FORMAT.format(this.licenseInfo.getDecryptedLicense().getExpirationDate())));
+ this.licenseInfoIdLabel.setVisible(true);
+ this.licenseInfoIdLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_id(this.licenseInfo.getDecryptedLicense().getBoostLicenseId()));
+ this.licenseInfoUserLabel.setVisible(true);
+ this.licenseInfoUserLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_userInfo(this.licenseInfo.getUser(), this.licenseInfo.getEmail()));
+ }
+
+ this.malwareScansPanel.setVisible(StringUtils.isNotBlank(this.authTokenMessage) || authTokenResponse != null);
+
+ this.malwareScansMessageLabel.setVisible(StringUtils.isNotBlank(this.authTokenMessage));
+ this.malwareScansMessageLabel.setText(this.authTokenMessage);
+
+ if (authTokenResponse == null) {
+ this.maxHashLookupsLabel.setVisible(false);
+ this.maxFileUploadsLabel.setVisible(false);
+ this.countersResetLabel.setVisible(false);
+ this.hashLookupsRemainingLabel.setVisible(false);
+ this.fileUploadsRemainingLabel.setVisible(false);
+ } else {
+ this.maxHashLookupsLabel.setVisible(true);
+ this.maxHashLookupsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyHashLookups(this.authTokenResponse.getHashLookupLimit()));
+ this.maxFileUploadsLabel.setVisible(true);
+ this.maxFileUploadsLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_maxDailyFileLookups(this.authTokenResponse.getFileUploadLimit()));
+ this.countersResetLabel.setVisible(true);
+ this.countersResetLabel.setText(Bundle.CTMalwareScannerOptionsPanel_malwareScans_countersReset(MALWARE_SCANS_RESET_FORMAT.format(this.authTokenResponse.getExpiration())));
+ this.hashLookupsRemainingLabel.setVisible(true);
+ 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())));
+ }
+ }
+
+ private long remaining(Long total, Long used) {
+ total = total == null ? 0 : total;
+ used = used == null ? 0 : used;
+ return total - used;
+ }
+
+ @NbBundle.Messages({
+ "CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error",
+ "CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error",
+ "CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.",})
+ private class LicenseFetcher extends SwingWorker {
+
+ private final String licenseText;
+
+ public LicenseFetcher(String licenseText) {
+ this.licenseText = licenseText;
+ }
+
+ @Override
+ protected LicenseInfo doInBackground() throws Exception {
+ LicenseResponse licenseResponse = ctApiDAO.getLicenseInfo(licenseText);
+ ctPersistence.saveLicenseResponse(licenseResponse);
+ return LicenseDecryptorUtil.getInstance().createLicenseInfo(licenseResponse);
+ }
+
+ @Override
+ protected void done() {
+ LicenseInfo licenseInfo = null;
+ try {
+ licenseInfo = get();
+ } catch (InterruptedException ex) {
+ // ignore cancellation
+ } catch (ExecutionException ex) {
+ if (ex.getCause() != null && ex.getCause() instanceof CTCloudException cloudEx) {
+ logger.log(Level.WARNING, "An API error occurred while fetching license information", cloudEx);
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ cloudEx.getErrorCode().getDescription(),
+ Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ } else {
+ logger.log(Level.WARNING, "An error occurred while fetching data", ex);
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc(),
+ Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ } finally {
+
+ synchronized (CTMalwareScannerOptionsPanel.this) {
+ CTMalwareScannerOptionsPanel.this.licenseFetcher = null;
+ if (!this.isCancelled()) {
+ setLicenseDisplay(licenseInfo, null);
+ loadMalwareScansInfo(licenseInfo);
+ }
+ }
+ }
+ }
+ }
+
+ @NbBundle.Messages({
+ "CTMalwareScannerOptionsPanel_MalwareScansFetcher_apiErr_title=Server Error",
+ "CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_title=General Error",
+ "CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_desc=A general error occurred while fetching malware scans information. Please try again later.",})
+ private class AuthTokenFetcher extends SwingWorker {
+
+ private final String boostLicenseId;
+
+ public AuthTokenFetcher(String boostLicenseId) {
+ this.boostLicenseId = boostLicenseId;
+ }
+
+ @Override
+ protected AuthTokenResponse doInBackground() throws Exception {
+ return ctApiDAO.getAuthToken(boostLicenseId);
+ }
+
+ @Override
+ protected void done() {
+ AuthTokenResponse authTokenResponse = null;
+ try {
+ authTokenResponse = get();
+ } catch (InterruptedException ex) {
+ // ignore cancellation
+ } catch (ExecutionException ex) {
+ if (ex.getCause() != null && ex.getCause() instanceof CTCloudException cloudEx) {
+ logger.log(Level.WARNING, "An API error occurred while fetching malware scans information for license", cloudEx);
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ cloudEx.getErrorDetails(),
+ Bundle.CTMalwareScannerOptionsPanel_MalwareScansFetcher_apiErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ } else {
+ logger.log(Level.WARNING, "An error occurred while fetching data", ex);
+ JOptionPane.showMessageDialog(
+ CTMalwareScannerOptionsPanel.this,
+ Bundle.CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_desc(),
+ Bundle.CTMalwareScannerOptionsPanel_MalwareScansFetcher_localErr_title(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ } finally {
+ synchronized (CTMalwareScannerOptionsPanel.this) {
+ CTMalwareScannerOptionsPanel.this.authTokenFetcher = null;
+ if (!this.isCancelled()) {
+ setMalwareScansDisplay(authTokenResponse, null);
+ }
+ }
+ }
+ }
+ }
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel countersResetLabel;
+ private javax.swing.JLabel fileUploadsRemainingLabel;
+ private javax.swing.JLabel hashLookupsRemainingLabel;
+ private javax.swing.JButton licenseInfoAddButton;
+ private javax.swing.JLabel licenseInfoExpiresLabel;
+ private javax.swing.JLabel licenseInfoIdLabel;
+ private javax.swing.JLabel licenseInfoMessageLabel;
+ private javax.swing.JLabel licenseInfoUserLabel;
+ private javax.swing.JLabel malwareScansMessageLabel;
+ private javax.swing.JPanel malwareScansPanel;
+ private javax.swing.JLabel maxFileUploadsLabel;
+ private javax.swing.JLabel maxHashLookupsLabel;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/subpanel/CTOptionsSubPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/subpanel/CTOptionsSubPanel.java
new file mode 100644
index 0000000000..67f727bb13
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/subpanel/CTOptionsSubPanel.java
@@ -0,0 +1,26 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.ctoptions.subpanel;
+
+import javax.swing.JPanel;
+
+/**
+ * A panel to be put in the CyberTriage options.
+ */
+
+public abstract class CTOptionsSubPanel extends JPanel {
+ public abstract void loadSettings();
+ public abstract void saveSettings();
+ public abstract boolean valid();
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/images/logo.png b/Core/src/com/basistech/df/cybertriage/autopsy/images/logo.png
new file mode 100644
index 0000000000..7f5ab5ba4c
Binary files /dev/null and b/Core/src/com/basistech/df/cybertriage/autopsy/images/logo.png differ
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java
new file mode 100644
index 0000000000..9adb7410ce
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java
@@ -0,0 +1,88 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.malwarescan;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.function.Consumer;
+
+/**
+ * Processes a batch when number of items reaches batchSize or flush. Processing
+ * blocks (and subsequently add and flush operations) until previous batch
+ * finishes.
+ */
+public class BatchProcessor {
+
+ private final ExecutorService processingExecutorService = Executors.newSingleThreadExecutor();
+
+ private final BlockingQueue batchingQueue;
+ private final List processingQueue;
+ private final int batchSize;
+ private final Consumer> itemsConsumer;
+ private final long millisTimeout;
+
+ private Future> lastProcessingFuture = CompletableFuture.runAsync(() -> {
+ });
+
+ public BatchProcessor(int batchSize, long millisTimeout, Consumer> itemsConsumer) {
+ this.batchingQueue = new LinkedBlockingQueue<>(batchSize);
+ this.processingQueue = new ArrayList<>(batchSize);
+ this.batchSize = batchSize;
+ this.itemsConsumer = itemsConsumer;
+ this.millisTimeout = millisTimeout;
+ }
+
+ public synchronized void clearCurrentBatch() {
+ batchingQueue.clear();
+ }
+
+ public synchronized void flush(boolean blockUntilFinished) throws InterruptedException {
+ asyncProcessBatch();
+ if (blockUntilFinished) {
+ lastProcessingFuture.wait(millisTimeout);
+ }
+ }
+
+ public synchronized void add(T item) throws InterruptedException {
+ batchingQueue.add(item);
+ if (batchingQueue.size() >= batchSize) {
+ asyncProcessBatch();
+ }
+ }
+
+ private synchronized void asyncProcessBatch() throws InterruptedException {
+ if (!batchingQueue.isEmpty()) {
+ // wait for previous processing to finish
+ lastProcessingFuture.wait(millisTimeout);
+
+ // if 'andThen' doesn't run, clear the processing queue
+ processingQueue.clear();
+
+ // transfer batching queue to processing queue
+ batchingQueue.drainTo(processingQueue);
+
+ // submit to processor and then clear processing queue
+ lastProcessingFuture = processingExecutorService.submit(
+ () -> itemsConsumer.andThen(processingQueue -> processingQueue.clear()).accept(processingQueue)
+ );
+ }
+ }
+
+}
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
new file mode 100644
index 0000000000..9b715cf1fb
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED
@@ -0,0 +1,23 @@
+MalwareScanIngestModule_malwareTypeDisplayName=Malware
+# {0} - errorResponse
+MalwareScanIngestModule_SharedProcessing_authTokenResponseError_desc=Received error: ''{0}'' when fetching the API authentication token for the license
+MalwareScanIngestModule_SharedProcessing_authTokenResponseError_title=Authentication API error
+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_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
+MalwareScanIngestModule_SharedProcessing_generalProcessingError_title=Hash Lookup Error
+# {0} - errorResponse
+MalwareScanIngestModule_SharedProcessing_repServicenResponseError_desc=Received error: ''{0}'' when fetching hash lookup results
+MalwareScanIngestModule_SharedProcessing_repServicenResponseError_title=Lookup API error
+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
+MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the CyberTriage cloud API for any possible malicious executables.
+MalwareScanIngestModuleFactory_displayName=Malware Scan
+MalwareScanIngestModuleFactory_version=1.0.0
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
new file mode 100644
index 0000000000..2b049124bf
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java
@@ -0,0 +1,395 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.malwarescan;
+
+import com.basistech.df.cybertriage.autopsy.ctapi.CtApiDAO;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationResult;
+import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
+import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.util.NbBundle.Messages;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
+import org.sleuthkit.autopsy.ingest.FileIngestModule;
+import org.sleuthkit.autopsy.ingest.IngestJobContext;
+import org.sleuthkit.autopsy.ingest.IngestModule;
+import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
+import org.sleuthkit.datamodel.AbstractFile;
+import org.sleuthkit.datamodel.Blackboard;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import org.sleuthkit.datamodel.Score;
+import org.sleuthkit.datamodel.SleuthkitCase;
+import org.sleuthkit.datamodel.TskData;
+
+/**
+ * Uses CT cloud API to determine if file is malware
+ */
+public class MalwareScanIngestModule implements FileIngestModule {
+
+ private static final SharedProcessing sharedProcessing = new SharedProcessing();
+
+ @Override
+ public void startUp(IngestJobContext context) throws IngestModuleException {
+ sharedProcessing.startUp(context);
+ }
+
+ @Override
+ public ProcessResult process(AbstractFile af) {
+ return sharedProcessing.process(af);
+ }
+
+ @Override
+ public void shutDown() {
+ sharedProcessing.shutDown();
+ }
+
+ /**
+ * Does the bulk of processing for the ingest module and handles concurrent
+ * ingest modules adding files simultaneously.
+ */
+ private static class SharedProcessing {
+
+ // batch size of 200 files max
+ private static final int BATCH_SIZE = 200;
+ // 3 minute timeout for an API request
+ private static final long BATCH_MILLIS_TIMEOUT = 3 * 60 * 1000;
+
+ //minimum lookups left before issuing warning
+ private static final long LOW_LOOKUPS_REMAINING = 250;
+
+ private static final Set EXECUTABLE_MIME_TYPES = Stream.of(
+ "application/x-bat",//NON-NLS
+ "application/x-dosexec",//NON-NLS
+ "application/vnd.microsoft.portable-executable",//NON-NLS
+ "application/x-msdownload",//NON-NLS
+ "application/exe",//NON-NLS
+ "application/x-exe",//NON-NLS
+ "application/dos-exe",//NON-NLS
+ "vms/exe",//NON-NLS
+ "application/x-winexe",//NON-NLS
+ "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 Logger logger = Logger.getLogger(MalwareScanIngestModule.class.getName());
+ private final BatchProcessor batchProcessor = new BatchProcessor(BATCH_SIZE, BATCH_MILLIS_TIMEOUT, this::handleBatch);
+
+ private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance();
+ private final CtApiDAO ctApiDAO = CtApiDAO.getInstance();
+
+ private FileTypeDetector fileTypeDetector;
+ private RunState runState = null;
+ private SleuthkitCase tskCase = null;
+ private LicenseInfo licenseInfo = null;
+ private BlackboardArtifact.Type malwareType = null;
+ private boolean noMoreHashLookups = false;
+ private IngestModuleException startupException;
+ private long dsId = 0;
+
+ @Messages({
+ "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low",
+ "# {0} - remainingLookups",
+ "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc=This license only has {0} lookups remaining",
+ "MalwareScanIngestModule_malwareTypeDisplayName=Malware"
+ })
+ synchronized void startUp(IngestJobContext context) throws IngestModuleException {
+ // only run this code once per startup
+ if (runState == RunState.STARTED_UP) {
+ if (startupException != null) {
+ throw startupException;
+ } else {
+ return;
+ }
+ }
+
+ try {
+ // get saved license
+ Optional licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo();
+ if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) {
+ throw new IngestModuleException("No saved license was found");
+ }
+
+ String licenseStr = licenseInfoOpt.get().getDecryptedLicense().getBoostLicenseId();
+ AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseStr);
+ // syncronously fetch malware scans info
+
+ // determine lookups remaining
+ long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
+ if (lookupsRemaining <= 0) {
+ throw new IngestModuleException("There are no more file hash lookups for this license");
+ } 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();
+ licenseInfo = licenseInfoOpt.get();
+ startupException = null;
+ noMoreHashLookups = false;
+ } catch (IngestModuleException ex) {
+ startupException = ex;
+ throw startupException;
+ } catch (Exception ex) {
+ startupException = new IngestModuleException("An exception occurred on MalwareScanIngestModule startup", ex);
+ throw startupException;
+ }
+ }
+
+ private static long remaining(Long limit, Long used) {
+ limit = limit == null ? 0 : limit;
+ used = used == null ? 0 : used;
+ return limit - used;
+ }
+
+ @Messages({
+ "MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout",
+ "MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out"
+ })
+ IngestModule.ProcessResult process(AbstractFile af) {
+ try {
+ if (af.getKnown() != TskData.FileKnown.KNOWN
+ && EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(fileTypeDetector.getMIMEType(af)).trim().toLowerCase())) {
+ batchProcessor.add(new FileRecord(af.getId(), af.getMd5Hash()));
+
+ }
+ return ProcessResult.OK;
+ } catch (InterruptedException ex) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_ShareProcessing_batchTimeout_title(),
+ Bundle.MalwareScanIngestModule_ShareProcessing_batchTimeout_desc(),
+ ex);
+ return IngestModule.ProcessResult.ERROR;
+ }
+ }
+
+ @Messages({
+ "MalwareScanIngestModule_SharedProcessing_authTokenResponseError_title=Authentication API error",
+ "# {0} - errorResponse",
+ "MalwareScanIngestModule_SharedProcessing_authTokenResponseError_desc=Received error: ''{0}'' when fetching the API authentication token for the license",
+ "MalwareScanIngestModule_SharedProcessing_repServicenResponseError_title=Lookup API error",
+ "# {0} - errorResponse",
+ "MalwareScanIngestModule_SharedProcessing_repServicenResponseError_desc=Received error: ''{0}'' when fetching hash lookup results",
+ "MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title=Hash Lookups Exhausted",
+ "MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc=The remaining hash lookups for this license have been exhausted",
+ "MalwareScanIngestModule_SharedProcessing_generalProcessingError_title=Hash Lookup Error",
+ "MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc=An error occurred while processing hash lookup results",})
+ private void handleBatch(List fileRecords) {
+ if (fileRecords == null || fileRecords.isEmpty() || noMoreHashLookups) {
+ return;
+ }
+
+ // create mapping of md5 to corresponding object ids as well as just the list of md5's
+ Map> md5ToObjId = new HashMap<>();
+ List md5Hashes = new ArrayList<>();
+ for (FileRecord fr : fileRecords) {
+ if (fr == null || StringUtils.isBlank(fr.getMd5hash()) || fr.getObjId() <= 0) {
+ continue;
+ }
+
+ String sanitizedMd5 = sanitizedMd5(fr.getMd5hash());
+ md5ToObjId
+ .computeIfAbsent(sanitizedMd5, (k) -> new ArrayList<>())
+ .add(fr.getObjId());
+
+ md5Hashes.add(sanitizedMd5);
+ }
+
+ if (md5Hashes.isEmpty()) {
+ return;
+ }
+
+ try {
+ // get an auth token with the license
+ AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense().getBoostLicenseId());
+
+ // make sure we are in bounds for the remaining scans
+ long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount());
+ if (remainingScans <= 0) {
+ noMoreHashLookups = true;
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
+ null);
+ return;
+ }
+
+ // if the size of this batch will exceed limit, shrink list to limit and fail after processing
+ boolean exceededScanLimit = false;
+ if (remainingScans < md5Hashes.size()) {
+ md5Hashes = md5Hashes.subList(0, (int) remainingScans);
+ exceededScanLimit = true;
+ }
+
+ // using auth token, get results
+ List repResult = ctApiDAO.getReputationResults(authTokenResponse.getToken(), md5Hashes);
+
+ if (repResult != null && !repResult.isEmpty()) {
+ SleuthkitCase.CaseDbTransaction trans = null;
+ try {
+ trans = tskCase.beginTransaction();
+ for (FileReputationResult result : repResult) {
+ String sanitizedMd5 = sanitizedMd5(result.getMd5Hash());
+ List objIds = md5ToObjId.remove(sanitizedMd5);
+ if (objIds == null || objIds.isEmpty()) {
+ continue;
+ }
+
+ for (Long objId : objIds) {
+ createAnalysisResult(objId, result, trans);
+ }
+ }
+
+ trans.commit();
+ trans = null;
+ } finally {
+ if (trans != null) {
+ trans.rollback();
+ trans = null;
+ }
+ }
+
+ // if we only processed part of the batch, after processing, notify that we are out of scans.
+ if (exceededScanLimit) {
+ noMoreHashLookups = true;
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(),
+ Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(),
+ null);
+ return;
+ }
+ }
+ } catch (Exception ex) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(),
+ Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(),
+ ex);
+ }
+ }
+
+ private String sanitizedMd5(String orig) {
+ return StringUtils.defaultString(orig).trim().toLowerCase();
+ }
+
+ @Messages({
+ "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES",
+ "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO"
+ })
+ private void createAnalysisResult(Long objId, FileReputationResult fileReputationResult, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException {
+ if (objId == null || fileReputationResult == null) {
+ return;
+ }
+
+ Score score = fileReputationResult.getScore() == null ? Score.SCORE_UNKNOWN : fileReputationResult.getScore().getTskCore();
+
+ String conclusion = score.getSignificance() == Score.Significance.NOTABLE || score.getSignificance() == Score.Significance.LIKELY_NOTABLE
+ ? Bundle.MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes()
+ : Bundle.MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No();
+
+ String justification = fileReputationResult.getStatusDescription();
+
+ tskCase.getBlackboard().newAnalysisResult(
+ malwareType,
+ objId,
+ dsId,
+ score,
+ conclusion,
+ MALWARE_CONFIG,
+ justification,
+ Collections.emptyList(),
+ trans);
+ }
+
+ @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) {
+ return;
+ }
+
+ // flush any remaining items
+ try {
+ batchProcessor.flush(true);
+ } catch (InterruptedException ex) {
+ notifyWarning(
+ Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_title(),
+ Bundle.MalwareScanIngestModule_SharedProcessing_flushTimeout_desc(),
+ ex);
+ } finally {
+ // set state to shut down and clear any remaining
+ malwareType = null;
+ fileTypeDetector = null;
+ noMoreHashLookups = false;
+ runState = RunState.SHUT_DOWN;
+ startupException = null;
+ batchProcessor.clearCurrentBatch();
+ }
+ }
+
+ private void notifyWarning(String title, String message, Exception ex) {
+ MessageNotifyUtil.Notify.warn(title, message);
+ logger.log(Level.WARNING, message, ex);
+ }
+
+ private enum RunState {
+ STARTED_UP, SHUT_DOWN
+ }
+
+ class FileRecord {
+
+ private final long objId;
+ private final String md5hash;
+
+ FileRecord(long objId, String md5hash) {
+ this.objId = objId;
+ this.md5hash = md5hash;
+ }
+
+ long getObjId() {
+ return objId;
+ }
+
+ String getMd5hash() {
+ return md5hash;
+ }
+
+ }
+ }
+}
diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java
new file mode 100644
index 0000000000..0a7fffb416
--- /dev/null
+++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java
@@ -0,0 +1,72 @@
+/** *************************************************************************
+ ** This data and information is proprietary to, and a valuable trade secret
+ ** of, Basis Technology Corp. It is given in confidence by Basis Technology
+ ** and may only be used as permitted under the license agreement under which
+ ** it has been distributed, and in no other way.
+ **
+ ** Copyright (c) 2023 Basis Technology Corp. All rights reserved.
+ **
+ ** The technical data and information provided herein are provided with
+ ** `limited rights', and the computer software provided herein is provided
+ ** with `restricted rights' as those terms are defined in DAR and ASPR
+ ** 7-104.9(a).
+ ************************************************************************** */
+package com.basistech.df.cybertriage.autopsy.malwarescan;
+
+import com.basistech.df.cybertriage.autopsy.ctoptions.CTOptionsPanel;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.ingest.FileIngestModule;
+import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
+import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+
+/**
+ * Factory for malware scan ingest modules.
+ */
+@ServiceProvider(service = org.sleuthkit.autopsy.ingest.IngestModuleFactory.class)
+@Messages({
+ "MalwareScanIngestModuleFactory_displayName=Malware Scan",
+ "MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the CyberTriage cloud API for any possible malicious executables.",
+ "MalwareScanIngestModuleFactory_version=1.0.0"
+})
+public class MalwareScanIngestModuleFactory extends IngestModuleFactoryAdapter {
+
+ @Override
+ public String getModuleDisplayName() {
+ return Bundle.MalwareScanIngestModuleFactory_displayName();
+ }
+
+ @Override
+ public String getModuleDescription() {
+ return Bundle.MalwareScanIngestModuleFactory_description();
+ }
+
+ @Override
+ public String getModuleVersionNumber() {
+ return Bundle.MalwareScanIngestModuleFactory_version();
+ }
+
+ @Override
+ public boolean isFileIngestModuleFactory() {
+ return true;
+ }
+
+ @Override
+ public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) {
+ return new MalwareScanIngestModule();
+ }
+
+ @Override
+ public boolean hasGlobalSettingsPanel() {
+ return true;
+ }
+
+ @Override
+ public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() {
+ CTOptionsPanel optionsPanel = new CTOptionsPanel();
+ optionsPanel.loadSavedSettings();
+ return optionsPanel;
+ }
+
+}
diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml
index 51bbeb8220..d60ce17600 100644
--- a/CoreLibs/ivy.xml
+++ b/CoreLibs/ivy.xml
@@ -1,5 +1,6 @@
+
]>
@@ -87,7 +88,8 @@
-
+
+
@@ -142,8 +144,8 @@
-
-
+
+
diff --git a/CoreLibs/manifest.mf b/CoreLibs/manifest.mf
index b8faef2527..1d3168bf2c 100644
--- a/CoreLibs/manifest.mf
+++ b/CoreLibs/manifest.mf
@@ -11,4 +11,4 @@ Specification-Version: 1.0
Specification-Vendor: CoreLibs ImageIO Fields
Implementation-Title: org.sleuthkit.autopsy.corelibs.ImageIO
Implementation-Version: 1.0
-Implementation-Vendor: CoreLibs ImageIO Fields
\ No newline at end of file
+Implementation-Vendor: CoreLibs ImageIO Fields
diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties
index fe5e78acff..320a1e36e5 100644
--- a/CoreLibs/nbproject/project.properties
+++ b/CoreLibs/nbproject/project.properties
@@ -84,6 +84,7 @@ file.reference.jackson-annotations-2.15.2.jar=release/modules/ext/jackson-annota
file.reference.jackson-core-2.15.2.jar=release/modules/ext/jackson-core-2.15.2.jar
file.reference.jackson-databind-2.15.2.jar=release/modules/ext/jackson-databind-2.15.2.jar
file.reference.jackson-dataformat-csv-2.15.2.jar=release/modules/ext/jackson-dataformat-csv-2.15.2.jar
+file.reference.jackson-datatype-jsr310-2.15.2.jar=release/modules/ext/jackson-datatype-jsr310-2.15.2.jar
file.reference.javafx-base-17.0.7-linux.jar=release/modules/ext/javafx-base-17.0.7-linux.jar
file.reference.javafx-base-17.0.7-mac.jar=release/modules/ext/javafx-base-17.0.7-mac.jar
file.reference.javafx-base-17.0.7-win.jar=release/modules/ext/javafx-base-17.0.7-win.jar
diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml
index f7b139f502..b2f08dc42b 100644
--- a/CoreLibs/nbproject/project.xml
+++ b/CoreLibs/nbproject/project.xml
@@ -63,6 +63,8 @@
com.fasterxml.jackson.databind.type
com.fasterxml.jackson.databind.util
com.fasterxml.jackson.dataformat.csv
+ com.fasterxml.jackson.datatype.jsr310
+ com.fasterxml.jackson.datatype.jsr310.util
com.github.lgooddatepicker.components
com.github.lgooddatepicker.optionalusertools
com.github.lgooddatepicker.zinternaltools
@@ -197,8 +199,6 @@
com.google.rpc.context
com.google.thirdparty.publicsuffix
com.google.type
- com.microsoft.schemas.vml
- com.microsoft.schemas.vml.impl
com.sun.javafx
com.sun.javafx.animation
com.sun.javafx.application
@@ -321,9 +321,6 @@
com.twelvemonkeys.util.regex
com.twelvemonkeys.util.service
com.twelvemonkeys.xml
- javax.annotation
- javax.annotation.concurrent
- javax.annotation.meta
javafx.animation
javafx.application
javafx.beans
@@ -340,7 +337,6 @@
javafx.event
javafx.fxml
javafx.geometry
- javafx.graphics
javafx.print
javafx.scene
javafx.scene.canvas
@@ -362,19 +358,9 @@
javafx.stage
javafx.util
javafx.util.converter
- javax.jms
- javax.mail
- javax.mail.event
- javax.mail.internet
- javax.mail.search
- javax.mail.util
- javax.servlet
- javax.servlet.http
- javax.xml.parsers
- javax.xml.transform
- javax.xml.transform.dom
- javax.xml.transform.sax
- javax.xml.transform.stream
+ javax.annotation
+ javax.annotation.concurrent
+ javax.annotation.meta
jfxtras.animation
jfxtras.css
jfxtras.css.converters
@@ -442,16 +428,6 @@
org.apache.commons.lang3.tuple
org.apache.commons.logging
org.apache.commons.logging.impl
- org.apache.log
- org.apache.log.filter
- org.apache.log.format
- org.apache.log.output
- org.apache.log.output.db
- org.apache.log.output.io
- org.apache.log.output.io.rotate
- org.apache.log.output.jms
- org.apache.log.output.net
- org.apache.log.util
org.apache.commons.text
org.apache.commons.validator.routines
org.apache.commons.validator.routines.checkdigit
@@ -460,14 +436,7 @@
org.apache.log4j.config
org.apache.log4j.helpers
org.apache.log4j.jdbc
- org.apache.log4j.jmx
- org.apache.log4j.lf5
- org.apache.log4j.lf5.util
- org.apache.log4j.lf5.viewer
- org.apache.log4j.lf5.viewer.categoryexplorer
- org.apache.log4j.lf5.viewer.configure
org.apache.log4j.net
- org.apache.log4j.nt
org.apache.log4j.or
org.apache.log4j.or.jms
org.apache.log4j.or.sax
@@ -931,6 +900,10 @@
ext/jackson-dataformat-csv-2.15.2.jar
release/modules/ext/jackson-dataformat-csv-2.15.2.jar
+
+ ext/jackson-datatype-jsr310-2.15.2.jar
+ release/modules/ext/jackson-datatype-jsr310-2.15.2.jar
+
ext/javafx-base-17.0.7-linux.jar
release/modules/ext/javafx-base-17.0.7-linux.jar
diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
index 6d7443a4a6..f8cea13909 100644
--- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
+++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties
@@ -1,5 +1,5 @@
#Updated by build script
-#Wed, 28 Sep 2022 13:57:05 -0400
+#Thu, 20 Jul 2023 14:02:30 -0400
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538
@@ -8,4 +8,4 @@ SplashRunningTextBounds=0,289,538,18
SplashRunningTextColor=0x0
SplashRunningTextFontSize=19
-currentVersion=Autopsy 4.19.3
+currentVersion=Autopsy 4.20.0
diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
index f28a6b96b3..2387b67597 100644
--- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
+++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties
@@ -1,4 +1,4 @@
#Updated by build script
-#Wed, 28 Sep 2022 13:57:05 -0400
-CTL_MainWindow_Title=Autopsy 4.19.3
-CTL_MainWindow_Title_No_Project=Autopsy 4.19.3
+#Thu, 20 Jul 2023 14:02:30 -0400
+CTL_MainWindow_Title=Autopsy 4.20.0
+CTL_MainWindow_Title_No_Project=Autopsy 4.20.0