From 0c3394e88f6909ccb1f8176485798cf739a06cf6 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 20 Jul 2023 14:07:52 -0400 Subject: [PATCH] initial commit --- Core/ivy.xml | 13 + Core/nbproject/project.properties | 7 + Core/nbproject/project.xml | 44 +- .../autopsy/ctapi/CTCloudException.java | 97 +++ .../cybertriage/autopsy/ctapi/Constants.java | 83 +++ .../cybertriage/autopsy/ctapi/CtApiDAO.java | 83 +++ .../autopsy/ctapi/ProxySettings.java | 443 ++++++++++++++ .../autopsy/ctapi/json/AuthTokenRequest.java | 59 ++ .../autopsy/ctapi/json/AuthTokenResponse.java | 85 +++ .../ctapi/json/BoostLicenseResponse.java | 59 ++ .../autopsy/ctapi/json/CTScore.java | 77 +++ .../ctapi/json/DecryptedLicenseResponse.java | 87 +++ .../ctapi/json/FileReputationResult.java | 123 ++++ .../autopsy/ctapi/json/LicenseInfo.java | 45 ++ .../autopsy/ctapi/json/LicenseRequest.java | 59 ++ .../autopsy/ctapi/json/LicenseResponse.java | 59 ++ .../autopsy/ctapi/json/MetadataLabel.java | 43 ++ .../ctapi/util/CTHostIDGenerationUtil.java | 57 ++ .../ctapi/util/LicenseDecryptorUtil.java | 172 ++++++ .../autopsy/ctapi/util/Md5HashUtil.java | 40 ++ .../autopsy/ctapi/util/ObjectMapperUtil.java | 41 ++ .../autopsy/ctoptions/Bundle.properties | 5 + .../ctoptions/Bundle.properties-MERGED | 5 + .../autopsy/ctoptions/CTOptionsPanel.form | 39 ++ .../autopsy/ctoptions/CTOptionsPanel.java | 141 +++++ .../ctoptions/CTOptionsPanelController.java | 128 ++++ .../ctoptions/ctcloud/Bundle.properties | 23 + .../ctcloud/Bundle.properties-MERGED | 55 ++ .../ctoptions/ctcloud/CTLicenseDialog.form | 138 +++++ .../ctoptions/ctcloud/CTLicenseDialog.java | 192 ++++++ .../ctcloud/CTLicensePersistence.java | 90 +++ .../ctcloud/CTMalwareScannerOptionsPanel.form | 199 +++++++ .../ctcloud/CTMalwareScannerOptionsPanel.java | 551 ++++++++++++++++++ .../ctoptions/subpanel/CTOptionsSubPanel.java | 26 + .../df/cybertriage/autopsy/images/logo.png | Bin 0 -> 10482 bytes .../autopsy/malwarescan/BatchProcessor.java | 88 +++ .../malwarescan/Bundle.properties-MERGED | 23 + .../malwarescan/MalwareScanIngestModule.java | 395 +++++++++++++ .../MalwareScanIngestModuleFactory.java | 72 +++ CoreLibs/ivy.xml | 8 +- CoreLibs/manifest.mf | 2 +- CoreLibs/nbproject/project.properties | 1 + CoreLibs/nbproject/project.xml | 45 +- .../netbeans/core/startup/Bundle.properties | 4 +- .../core/windows/view/ui/Bundle.properties | 6 +- 45 files changed, 3959 insertions(+), 53 deletions(-) create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/BoostLicenseResponse.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTScore.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationResult.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseInfo.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseRequest.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataLabel.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/Md5HashUtil.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/ObjectMapperUtil.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.form create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanelController.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/subpanel/CTOptionsSubPanel.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/images/logo.png create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java create mode 100644 Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java 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 coll = Lookup.getDefault().lookupAll(CTOptionsSubPanel.class); + Stream 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 0000000000000000000000000000000000000000..7f5ab5ba4c2cd031b422d0ed333d665261a39af8 GIT binary patch literal 10482 zcmeHtcU03$w|3}Nnu3ZT0i+2bA+!LYNDsaD7Lov=1w*Jxk)kvyQlu$LN9hPkQBXlZ z0hKBp>Ai@Eh{6r%Imh$e_j~X6uJzsj4r`^%%(I`p=b61{@|#7pfu6<*T6S6h0B{1K zscJ<01RlMpDT(jifq|a^0Hzy(CKd!Eq#w`=k8{GhV}OJJFANalk97h7{71?%-0(9n zmWacdQ&-5RPjFDZje7e0d`L67kfpDmzRqeqCYD9*nv$uR6Px>B$idU@=C3pgd?y`6 zO9}~FCtZunw{mTE_2u4vpDR-L8az zbA8_6>femB52pV-HtePzD4%m}Sv=8im+H&ed*g2!j6i$dG(qF-dsc}0!??T3dkSmz z6VAEPyt`XHAl#3(n(g(wp+h;+&$f8BOA|6G85ex&-ztVPCr-94eM?W>%Shc7-oLS} zcx$>ZXu@my3Z!rU>*%)B0ql_8MK4NmeYJhRXR;^Z@>x zu1(pFp_Aj@U2u!mm6(Y0U*BZIw<ik+b=wnBY z>J`yAjt#4t00lB8o^{TQxE=JA)}Th#YU-UggDmaJ!R%zL3*jsIEw2{&YY5NxRq-)> zPxtid7OYN>2eJ3?FuxbjdYE`my_~d_G8ixDP^`wK5+~Nip@ zhgqEiJ8RfgoTn@OBSOF_Ga${<+1i(+fYrp@n#=J(-p(H*~GpT~ZKrS*N^ z-pp(`cR#D1*DE9-gPm_y%ztNp5u=xjSAS(wiG~Z40Y-$J&8xU!dHuxC$oZ>;F6c#> zzKbX{L}X>UZeuJ6vu-`1Lw#sIF(&M-Ua~I$bxvteM6VAu{or>qb`=-J>0LLuPP;rO zq$uiIz8TB^W9bQgxy!%t!yx;+4JmB=u>S@7hO(-+nd1-Y-x^xEi8suDbCvpbH_yI` zxkbKWf`*T)mI1M5GA+?m@}ANk>__c2a)vsp;&x6q%k&_1GiRGH9J4t7blKAX2=nq*B&sNHg!os+D;BO0ci z-M)-@q;N(Bo})4Oo_pB*A!_^8$(M$DcHhQ=X#zawn`S!1N0-z;gu0~CE8lnF3eaa^tGO<^W2@s@iw5@)lwFbeyPkpQ~s^e(`|ea3`%RWkL8^KsQH4L&tFGU9^oeKt;Yr|nNr@jqw;4_jV z;2PFsnJ3`pZyxyCP!%+!M5#XSXZNGdw)!x`9!@BhP*|%I18a@z7TvFJSM#MVULcf; zr_@tM^D^A?axz;7#zD3rSP}dQZ}MA@?qc#yCZ^))(yZcYY%VO)rLirNt1yyvlk;ed ztHUC`10>^WOB5S})~fl+jPJ`TnNMY7x)t`usZOm_>oEM-yl}a~gQq0!VsucqS(*jq zRq-p@$S+Rj*BL~1l*)#vg8E-*fsATXQd!f}B*#|6=AZN}S3wHg(yF-Ux+`{0N=)*Z zA*0<|TL<`-mu2oLm9JapyKJ3Cj0(XuHkQ}-fPvOp1h=(_@Gy3eKI2;I zYaww;bDr}ih~8F>_P8?HH7y8AYnJg9iYB(8mm=;XMMxN{DU_BqmScR?awGa8nWJ59PL~Xi`AuH76T7caPC*T@Rnn|Kqe6GjL&QtlDI?2?Z2R0* z+lGfB>oE(^B6k-{{K`d^#xS5xq~9h(V@gV(QAF&!@X#tfEi$dJew=8Vmb7!A=BCzq zerPCAvzPNm&%Lqu+;p8z@62hJEPoF=4xS3`IqIpcI$eTgQD}msuC}oP$2w z@Vz^t#fsgG5dU6ONl-{nB#oo2hSmI7%?^wZ|INeGTM1E$0sE@GNcy{ zt|qy5v>EoB}6)nA9>GTEQSR$XI1F4q(clKu0vuz{(9pUlGOreU@DAIR3GCZdOx^FPdz-Y_k z8d%imm;f>Cu(=a@^Co^veyIshtD*Ik))vgG4#A6u(U-6`yZjD%t$ab^_)}1`{5lha zEQzVD=qd7t@AB~UxZJ3`mM2R=&X$0-5_gx}bHxtHi-Zq=Xn~TH9a)NtauHEjkt?^o zNZLrQzAQG88c^wEahz>ejg`43Spu65BWsx^Tss-k)JKO|qk3)A%oI7tWYY}syHgPT za9`K;)?1P0TXN0)Z)`W>bZC5JyqYvEA~WyzSAXR?pBnC-ZfMHBsjG5f?M-a5Q0gSx z$hOre>@D*`hiqIE?3)soUUfb1bfwo6>_kLleXdAdXG>`t*Em2k4>YXN9S`noe{1+i zRb^LzErs>u2Uxb3bK#5cCr&!>kp?wpu_hM9SPZIw=X}NAC}qxk0l%|z;{PGL>NllZ zy|pE{+`1wfnGwk9U4Grlk^WSk05f&$^ZF28BpPao9sJixyOBK#iGQ~!-6WbvK$ zqovfkL6lOgi1o(^0Z_xh4~csO`qw_5DzC2AJ?*$LwX&Y8#pI<+S7uq3NrClqg>*a* z=Zs)(i_K^5Ed}@>4}_AEDhKKKmOpD-8E=~_7|4m#cEze0JZi39!|lh9)XZ8WiPoy? zTx21QTVc`|5W1kt)T5MdRodAdc%EH|PCQ*i%ls(xL5kn>f_UUf)0q!Z#VH z7uBR%t0~-F-Di47pOGoA0{91!wme#$O*Zok5(f!%=j|h%gH5K|nX7dx0r>zkZsP{6 zv&@1l3UUYbI~pkv>T<_~`{bk0#Y<wOQCU!M$Xt|WMJim|5p}^1ql{b zUc|Xwi5RV{;@3Sc0P_$*#AkXkcfZHBdq!M7dFrir@$|snsIv?Ei_UHReGYcKXJ4=S z&$zw>*G8_W)`o&iS`7zw()rLdKU(i|HL8X$(GP3)s#n;NxW$=PcR03BzIW*DcHDc; zNRaya^<<+LpV3pjuL2`EyOqm1Tx-`af$6`qVYxPzaEIb=U-k(Cf<&OeDJdBB&R`|2 zc1@HDtLS%nQmXUqW-{!eF9KX48GM_Ox<<`POVK+C`Y(h$0*YLK7fCY)PJKOhGF_Cm z5d^((Qi~>;p^&~J`)dPtt+`4r0k^1O{an2ktOVZ^FnRfO7uI9gNP*KZYQK6#XSc(4 zJDBfeYl+T{`vK~=&)H5z9rW?%&*h*xa_^ z{={FU*i)gh7w%4vXyYsf-^Xp>A-a#s?(k(|=W{ShHBqc1?IKdE9g!T}DD9*(owzh! z=IEp|55`$69{X@nAK1AD?o|r85wiWw8a*@}p1LprF>s0@`rG&TS_$adJAwynd4^{` zJ>at?@YAuv(9MMg(Z=rcvy{0gh24X9@%swBk#&BjW7Z>X&jffdfHEhw6z$$$Y2e+| zP-Z)k#JBqKtJTQoOwYslTEpZC5vXv)lcnvqQStMXiHWI%9xNv@MOab@_PSDouNIbY-< z_b#QG(hCuZ$#HO=ZtMuqG}pKqcodd-8qogo>1$`lQ_(V!(4nE!1C4c-1V`N z=%I;doi@67Upvba4h@aHa|NX|3mUQeuKA~<3%Rcc)xR5Syg85)gsR`0JL?S7@*?6&R-)sFme8h7D#a`riA@m_6;IwZ zwzubOV~4IY^$i6x_JA0w`=yw3PVVVtFa%8wHZoZ4A}6=2N9bQfB}@8od?zoB@ogN& zx`|tM6%Df)rPRN5FKx8x-~xRPWxV(F5|@fdr2Fc4%t(>yE$;zeo&25lp_LH2*UxW< zU$abN2#MV+5^7sz=MY>~0zo?Oio+k2k>Bh<7|ggOHxpgM%>3tt*t|Ljmw4d2|Bp5z(U?q=YR=|Mm8k8u>eDper+ z$@UJ@hH+-I+5A~21D>4j&cnfom_8L~ceR1wU2>3Dpgu_?Y+kk7sjrZyO!;wgfYFz@ zn6XttdTV!l{`CDW?WnNB9eeT$U1-D!s2a!Ud(m$KA>RZn`!zzcl4_ONTI@8;2dPd^ zeX?0|y_t|un&)C|EoVS@HFC0R3q>_a?m@S@-0ZuzV*KQeum6DBzU@Qb3$su&%hO4D zWc=SRbgR_UZ71~J?WQ!}@;cK6S5Mk19m^o)j+GiK>P#CtZ#sC$y{M;5x6guAPZW@n z8VI1)7w=%b+=WnX7WKoWUNK22e! z$wI%coa0+UO|$o|jC|~Q4#L^!Z#dO-gN8v}H7f=-dED)=6>id804Nbv z6ms~k!lXDVo@=N)1i<~0deQ&-Qu>$9xbo;%8!pUM_?M|>K)PUF1v{&?rU6f@wCx}c zMw|ZT7Pn`4XI~WTK@TZJUz#zlI{I+|03?xE6%_-7ipn2X62yy$?4Trh&6i3XZT7|m zMi*mznG8D&?ubOiz_eIn9qvD3weo?~e^znnP0W8n{24?+rO5V>LA0kq_6cp+6jjv2 z*yP;Ul&{VC?|5E?&jdF$?moFfMZE(x?vB5#G$6vunh-ra7IVg54RGo6{nIa5#CKE! zxyjyc`7mi!X+?CfMAz z4W6+fSWIOgd6P4F8BWHwNu`aZr_!hT$G~!n+2`miZ?pDTXAj_0B{w?l&%AbfgDvml zQ09o$1kx}=AAC?c%a;4dH9uYReQ?)H#l^45v$r~HNz~&5OztV;zfXm1Hd@y}WIpmo zIw~#1dW6IQuG!u*tXbPYje2GS4xFd7-1dZh9OjchTWQB~kjRg=wE1W!?}onNbeKZi zrteJpE)l6r;;)#8drk7=v69!oT3LNU9~Dr=7Bmi^y3yj2R6PWr7$2AzZFbK5@dIF4 zg}+0wcZV7PV9dr6?}#jPb!1UE4^bo<=ZF#Y_wXX#AprnzMSm|O$_+yRI%1r$p7J~! zjm!pHm!D=NeN697K!Xv)h6HgwL;x5HfxtizCEn$#>N<|w9qj}r26gQDcN-0auEC!+M>0BNJ-m)Bj?lj&(WpOh zUOssDV+X zgr_$F>50M|Q4z^Su|ys*aj3W?3-i7i6o{Q)&uE`0eg8mAM+k9j4W|FL}`&n)j?!F?kCPh zRt1ki5^#7E9L`;y=O|j>5#@1F0pY*OMOFugIyO88W6(zx_p9cVkHelnc_+8AIG}e-+yw z{n&qz$WjmpMjC~Z06{U1G9U?Y;+rE9;t0YZWnhwMF$qU$r=J<|C%ZS!iQtFCV=g%p zbAY(c#Ns+$XQ1G({1f_nG=46aqvV1>#X%5hkQl^7OhQ%+CJT|_`MKIaIQVFX{bRQ9 zqdiDhSN3;3;75CuEaE5;jeWel+_4z^-*x&UZ~ia1-|TKH?JM5>m3eGElSj8>` zeLv5C>;7K=e=_J`Q5a8e+`kL`cgRm!emjVWG5_o%9&p6-68z_Z`729~3h96G?^pKy zFM1$S|KsF8()S;^{*miHQs6%V|C3$+$n_s7@E?Ky$*%uza?$?v$c^zN{_gW5K2Xb6 zh8Gea#wi`OHBTf&SuGqNy>CK#G*aebX5)sVzt<=IYtyDKDE*WT%-Z~v2 zM6r@D&ZkL3E~59G?H!O}wm@9%fer|KO_|={5-BH@vKl2OrZ?te8AoC(xw2^e*uLlC z6FJIh3Q?V$L*Lziy>*4gx7)Ut;-Cdu@_kdh@@(UG&7O^>2tP!@ogExn#(81E*a2VVWUzg?_WL{<}>$bk)jW;46aDqE;`sq9`4Z66~Vd^@{iU45Q zF80mHd{?6rmZ${lp9x}#sHovQF_GV-5a|n`FiVHlJ2~TJ_MM zGNIGvc}}wYJ+8tH3Aatb*@`?3SRFdEU@LONI;b;?s$$h__-1e+8QQ`!hqe7`R{qZX zOr~*}z~Mv08w;gY8XHSWI`I<|l*QXg(j`Xnr&&V>t+KJGRC`pl_OmMzsnrSFn7vAL zaJmyF&+;BXuiihzsrJBKE0rDJIMwNNpdgX4uPB<>d@iFiE1JIt02?d);IEa{Om**` zk-j_a<|(Cy=V6cUrh)*%8gClNBJm@<2zrFrwti?KKv7|7w!@osxk7yRL7WSKUfkL& zA?Y?i>xQPj{W)(yW;lK6w*=8=6fM>49*wg|%QJh?_VZDUva;W&iavd)gKwb^{rLsb zpPV9pzv$e%CuP?>U+=9|7kAsVgzW(dhuuWBaarIPU~`GpcA37{JhZ1$%#Dy1^{wa1 zaKynPUwwc(wn_VP`INSKly%kZa3z1Q-z zjcQD*IZBoq;S`4k(O^&GIk~q>C#qJf6P^>-p`d1m%g6b??er(NE@#`)M7y@_kAAKX zJTTZE+x?t9x)G07#6FsZwVNWHilmNnM{+$FIfze_K? z!JvofoOVXLEer@ag&(@tBL-KMQ zD_)(Sm};XBX9<|-9@N~F5N6fCu-!62TE93ywarlctYF_%s}zt;g8$B5ql@6^{g*69rNG(f4Tb>GX+shpD+>`U+;R{L^a{aN>qtczH$ z6}f7`hZhpDmA?KL$u0`K_UYqdAqka4< zbH#PM^b2L>7){HEXDO?~iG0H9oo<{%@lu=DsqJ3{({!b-w7(u92)h9?zv~>fc}hMJ z-V>e3qkLAW+a0`L*{~Jp1Q)f=ThbdXz_b6*Iq`Vkbj3bp;ewJcwE3|%`*(I@k8=+n z4T>cVG;z8PFAVK{YPLHtn48L?kuynpd~%ennQg(Ql->m`jj+{w%Y;k$r23OUtjzv}^J6pao;T^}k)1U(LXl(*h*+fXdjVh}n(Spg1|C1^>H zxOC1qqN+2%B%)U=%Nn$(-O zW0f|vSmF$KH;{cKmerc!X|PVydsv;2lchxDU%XNOsYcIotAn<7Cnlj7R4-f#8?nla z)TN4RLwSN??yfanT+cgaIN+<#RpE3RjyM6R8`!Z zK1Fw>=A{3DLlQ@kPm}>Ehd0xZ@=$ zURtTI;SLV8+n#>^W%u3PDuJx^zTh-*fLzR;4a p8`e-4X+W?oKh{q)ty(-_yc}Lsu608?h { + + 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