diff --git a/Core/ivy.xml b/Core/ivy.xml index fba2d99acd3235b14e78018dd9e42d7ea5d8928a..3d2352648d2edd7084c3d1f0fce5fc9b66155148 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -1,3 +1,6 @@ +<!DOCTYPE ivy-module [ + <!ENTITY httpcomponents.version "4.5.14"> +]> <ivy-module version="2.0"> <info organisation="org.sleuthkit.autopsy" module="core"/> <configurations > @@ -72,6 +75,15 @@ <!-- annotations like guarded by --> <dependency conf="core->default" org="com.github.spotbugs" name="spotbugs-annotations" rev="4.6.0"/> + <dependency conf="core->default" org="com.license4j" name="license4j-runtime-library" rev="4.7.1"/> + + <dependency conf="core->default" org="org.apache.httpcomponents" name="httpclient" rev="&httpcomponents.version;"/> + <dependency conf="core->default" org="org.apache.httpcomponents" name="httpmime" rev="&httpcomponents.version;"/> + <dependency conf="core->default" org="org.apache.httpcomponents" name="httpclient-win" rev="&httpcomponents.version;"> + <exclude name="jna" /> + <exclude name="jna-platform" /> + </dependency> + <override org="org.apache.zookeeper" module="zookeeper" rev="3.8.0"/> <override org="org.apache.zookeeper" module="zookeeper-jute" rev="3.8.0"/> @@ -84,5 +96,6 @@ <override org="org.bouncycastle" module="bcprov-ext-jdk15on" rev="1.70"/> <override org="org.bouncycastle" module="bcprov-jdk15on" rev="1.70"/> <override org="org.bouncycastle" module="bcpkix-jdk15on" rev="1.70"/> + <override org="junit" module="junit" rev="4.13.2"/> </dependencies> </ivy-module> diff --git a/Core/ivysettings.xml b/Core/ivysettings.xml index fd792f6844f0c78fa2357fa110b701e129530917..06c2d9308f9e3cee938c0fb910351f006e24aa43 100644 --- a/Core/ivysettings.xml +++ b/Core/ivysettings.xml @@ -4,6 +4,7 @@ <chain name="main"> <ibiblio name="central" root="https://repo1.maven.org/maven2" m2compatible="true"/> <ibiblio name="maven.restlet.org" root="http://maven.restlet.com" m2compatible="true" /> + <ibiblio name="license4j.com" root="http://www.license4j.com/maven/" m2compatible="true" /> </chain> </resolvers> <property name="packaging.type" value="jar" /> diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index a24bcb423ed0d769482e9c0bda655ee94ad8745d..9adb1c764991c683b75032a735352afc3e842def 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 0553a915ca279a4833b7f03ebad6378ceb400f11..9b960955aa222a2f48a1dc3b87c8e1faec7e18b6 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -66,6 +66,14 @@ <implementation-version/> </run-dependency> </dependency> + <dependency> + <code-name-base>org.netbeans.modules.keyring</code-name-base> + <build-prerequisite/> + <compile-dependency/> + <run-dependency> + <specification-version>1.41</specification-version> + </run-dependency> + </dependency> <dependency> <code-name-base>org.netbeans.modules.options.api</code-name-base> <build-prerequisite/> @@ -165,14 +173,6 @@ <specification-version>9.29</specification-version> </run-dependency> </dependency> - <!-- <dependency> - <code-name-base>org.openide.filesystems.compat8</code-name-base> - <build-prerequisite/> - <compile-dependency/> - <run-dependency> - <specification-version>9.26</specification-version> - </run-dependency> - </dependency> --> <dependency> <code-name-base>org.openide.filesystems.nb</code-name-base> <build-prerequisite/> @@ -323,6 +323,7 @@ </test-type> </test-dependencies> <public-packages> + <package>com.basistech.df.cybertriage.autopsy.ctoptions.subpanel</package> <package>net.sf.sevenzipjbinding</package> <package>net.sf.sevenzipjbinding.impl</package> <package>net.sf.sevenzipjbinding.simple</package> @@ -448,6 +449,10 @@ <runtime-relative-path>ext/checker-qual-3.33.0.jar</runtime-relative-path> <binary-origin>release/modules/ext/checker-qual-3.33.0.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/commons-codec-1.11.jar</runtime-relative-path> + <binary-origin>release/modules/ext/commons-codec-1.11.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/commons-dbcp2-2.9.0.jar</runtime-relative-path> <binary-origin>release/modules/ext/commons-dbcp2-2.9.0.jar</binary-origin> @@ -500,6 +505,22 @@ <runtime-relative-path>ext/guava-32.0.1-jre.jar</runtime-relative-path> <binary-origin>release/modules/ext/guava-32.0.1-jre.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/httpclient-4.5.14.jar</runtime-relative-path> + <binary-origin>release/modules/ext/httpclient-4.5.14.jar</binary-origin> + </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/httpclient-win-4.5.14.jar</runtime-relative-path> + <binary-origin>release/modules/ext/httpclient-win-4.5.14.jar</binary-origin> + </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/httpcore-4.4.16.jar</runtime-relative-path> + <binary-origin>release/modules/ext/httpcore-4.4.16.jar</binary-origin> + </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/httpmime-4.5.14.jar</runtime-relative-path> + <binary-origin>release/modules/ext/httpmime-4.5.14.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/icepdf-core-6.2.2.jar</runtime-relative-path> <binary-origin>release/modules/ext/icepdf-core-6.2.2.jar</binary-origin> @@ -560,6 +581,10 @@ <runtime-relative-path>ext/jaxb-runtime-2.3.3.jar</runtime-relative-path> <binary-origin>release/modules/ext/jaxb-runtime-2.3.3.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path> + <binary-origin>release/modules/ext/jdom-2.0.5-contrib.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/jdom-2.0.5.jar</runtime-relative-path> <binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin> @@ -596,6 +621,10 @@ <runtime-relative-path>ext/libphonenumber-8.12.45.jar</runtime-relative-path> <binary-origin>release/modules/ext/libphonenumber-8.12.45.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/license4j-runtime-library-4.7.1.jar</runtime-relative-path> + <binary-origin>release/modules/ext/license4j-runtime-library-4.7.1.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/listenablefuture-1.0.jar</runtime-relative-path> <binary-origin>release/modules/ext/listenablefuture-1.0.jar</binary-origin> 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 0000000000000000000000000000000000000000..25e264e49a0e0d025753afee41ef672919b3bfdb --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTApiDAO.java @@ -0,0 +1,115 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi; + +import 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.AuthenticatedRequestData; +import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean; +import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBeanResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest; +import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest; +import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.collections.CollectionUtils; +import org.sleuthkit.autopsy.core.UserPreferences; +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 String CTCLOUD_SERVER_HASH_PATH = "/_ah/api/reputation/v1/query/file/hash/md5?query_types=CORRELATION,MALWARE"; + private static final String AUTOPSY_PRODUCT = "AUTOPSY"; + + private static final CTApiDAO instance = new CTApiDAO(); + + private CTApiDAO() { + } + + public static CTApiDAO getInstance() { + return instance; + } + + private static String getAppVersion() { + return Version.getVersion(); + } + + private final CTCloudHttpClient httpClient = CTCloudHttpClient.getInstance(); + + public LicenseResponse getLicenseInfo(String licenseString) throws CTCloudException { + LicenseRequest licenseRequest = new LicenseRequest() + .setBoostLicenseCode(licenseString) + .setHostId(CTHostIDGenerationUtil.generateLicenseHostID()) + .setProduct(AUTOPSY_PRODUCT) + .setTimeZoneId(UserPreferences.getInferredUserTimeZone()); + + return httpClient.doPost(LICENSE_REQUEST_PATH, licenseRequest, LicenseResponse.class); + + } + + public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException { + AuthTokenRequest authTokenRequest = new AuthTokenRequest() + .setAutopsyVersion(getAppVersion()) + .setRequestFileUpload(false) + .setBoostLicenseId(decrypted.getBoostLicenseId()) + .setHostId(decrypted.getLicenseHostId()); + + return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class); + } + + private static Map<String, String> getAuthParams(AuthenticatedRequestData authenticatedRequestData) { + return new HashMap<String, String>() { + { + put("api_key", authenticatedRequestData.getApiKey()); + put("token", authenticatedRequestData.getToken()); + put("host_id", authenticatedRequestData.getHostId()); + } + }; + } + + public List<CTCloudBean> getReputationResults(AuthenticatedRequestData authenticatedRequestData, List<String> md5Hashes) throws CTCloudException { + if (CollectionUtils.isEmpty(md5Hashes)) { + return Collections.emptyList(); + } + + FileReputationRequest fileRepReq = new FileReputationRequest() + .setHashes(md5Hashes); + + CTCloudBeanResponse resp = httpClient.doPost( + CTCLOUD_SERVER_HASH_PATH, + getAuthParams(authenticatedRequestData), + fileRepReq, + CTCloudBeanResponse.class + ); + + return resp == null || resp.getItems() == null + ? Collections.emptyList() + : resp.getItems(); + } +} 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 0000000000000000000000000000000000000000..52d586ee5366add0266082c26129c9ee208fce46 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudException.java @@ -0,0 +1,100 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi; + + +import java.util.Objects; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * An exception thrown due to an error that occurs while making a CT Cloud REST + * API request. + */ +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/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java new file mode 100644 index 0000000000000000000000000000000000000000..9d4b189ee9720faa9d0f4e9a8ebbca5127111dbc --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java @@ -0,0 +1,409 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi; + +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.Authenticator; +import java.net.InetAddress; +import java.net.PasswordAuthentication; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.logging.Level; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.NTCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.SystemDefaultCredentialsProvider; +import org.apache.http.impl.client.WinHttpClients; +import org.sleuthkit.autopsy.coreutils.Version; + +/** + * Makes the http requests to CT cloud. + */ +public class CTCloudHttpClient { + + private static final CTCloudHttpClient instance = new CTCloudHttpClient(); + private static final Logger LOGGER = Logger.getLogger(CTCloudHttpClient.class.getName()); + private static final String HOST_URL = Version.getBuildType() == Version.Type.RELEASE ? Constants.CT_CLOUD_SERVER : Constants.CT_CLOUD_DEV_SERVER; + + private static final List<String> DEFAULT_SCHEME_PRIORITY + = new ArrayList<>(Arrays.asList( + AuthSchemes.SPNEGO, + AuthSchemes.KERBEROS, + AuthSchemes.NTLM, + AuthSchemes.CREDSSP, + AuthSchemes.DIGEST, + AuthSchemes.BASIC)); + + private static final int CONNECTION_TIMEOUT_MS = 58 * 1000; // milli sec + + public static CTCloudHttpClient getInstance() { + return instance; + } + + private final ObjectMapper mapper = ObjectMapperUtil.getInstance().getDefaultObjectMapper(); + private final SSLContext sslContext; + private String hostName = null; + + private CTCloudHttpClient() { + // leave as null for now unless we want to customize this at a later date + this.sslContext = null; + } + + private ProxySettingArgs getProxySettings() { + if (StringUtils.isBlank(hostName)) { + try { + hostName = InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException ex) { + LOGGER.log(Level.WARNING, "An error occurred while fetching the hostname", ex); + } + } + + int proxyPort = 0; + if (StringUtils.isNotBlank(ProxySettings.getHttpPort())) { + try { + proxyPort = Integer.parseInt(ProxySettings.getHttpsPort()); + } catch (NumberFormatException ex) { + LOGGER.log(Level.WARNING, "Unable to convert port to integer"); + } + } + + return new ProxySettingArgs( + ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION, + hostName, + ProxySettings.getHttpsHost(), + proxyPort, + ProxySettings.getAuthenticationUsername(), + ProxySettings.getAuthenticationPassword(), + null + ); + } + + public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) throws CTCloudException { + return doPost(urlPath, Collections.emptyMap(), jsonBody, classType); + } + + public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) throws CTCloudException { + String url = HOST_URL + urlPath; + try { + + LOGGER.log(Level.INFO, "initiating http connection to ctcloud server"); + try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) { + URIBuilder builder = new URIBuilder(url); + + if (!MapUtils.isEmpty(urlReqParams)) { + for (Entry<String, String> e : urlReqParams.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) { + builder.addParameter(key, value); + } + } + } + + URI postURI = builder.build(); + HttpPost postRequest = new HttpPost(postURI); + + + configureRequestTimeout(postRequest); + postRequest.setHeader("Content-type", "application/json"); + + if (jsonBody != null) { + String requestBody = mapper.writeValueAsString(jsonBody); + if (StringUtils.isNotBlank(requestBody)) { + HttpEntity entity = new StringEntity(requestBody, "UTF-8"); + postRequest.setEntity(entity); + } + } + + LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + postRequest.getURI()); + try (CloseableHttpResponse response = httpclient.execute(postRequest)) { + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + LOGGER.log(Level.INFO, "Response Received. - Status OK"); + // Parse Response + HttpEntity entity = response.getEntity(); + String entityStr = EntityUtils.toString(entity); + O respObj = mapper.readValue(entityStr, classType); + return respObj; + } else { + LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine()); + handleNonOKResponse(response, ""); + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex); + throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex); + } + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "IO Exception raised when connecting to CT Cloud using " + url, ex); + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex); + } catch (URISyntaxException ex) { + LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + url, ex); + throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex); + } + + return null; + } + + /** + * A generic way to handle the HTTP response - when the response code is NOT + * 200 OK. + * + * @param response + * @param fileName - used only for logging. + * @throws MalwareScannerException + * @throws IOException + */ + private void handleNonOKResponse(CloseableHttpResponse response, String fileName) throws CTCloudException, IOException { + LOGGER.log(Level.WARNING, MessageFormat.format( + "Response code {0}. Message Body {1}", + response.getStatusLine().getStatusCode(), + EntityUtils.toString(response.getEntity()))); + + switch (response.getStatusLine().getStatusCode()) { + + case HttpStatus.SC_BAD_REQUEST: + //400: Bad request => Unsupported HTTP method or invalid http request (e.g., empty body). + throw new CTCloudException(CTCloudException.ErrorCode.BAD_REQUEST); + case HttpStatus.SC_UNAUTHORIZED: + //401 Invalid API key => An invalid API key, or no API key, has been provided + throw new CTCloudException(CTCloudException.ErrorCode.INVALID_KEY); + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + // 407 Proxy server authentication required. + throw new CTCloudException(CTCloudException.ErrorCode.PROXY_UNAUTHORIZED); + case HttpStatus.SC_FORBIDDEN: + throw new CTCloudException(CTCloudException.ErrorCode.UN_AUTHORIZED); + case HttpStatus.SC_INTERNAL_SERVER_ERROR: + //500 Internal error Server temporarily unavailable; please try again later. If the issue persists, please contact RL. + throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE); + case HttpStatus.SC_SERVICE_UNAVAILABLE: + //503 Server is too busy. Try again later. + //503 Failed to request scan. Try again later. The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. If the issue persists, please contact RL. + throw new CTCloudException(CTCloudException.ErrorCode.TEMP_UNAVAILABLE); + case HttpStatus.SC_GATEWAY_TIMEOUT: + throw new CTCloudException(CTCloudException.ErrorCode.GATEWAY_TIMEOUT); + default: + String returnData = EntityUtils.toString(response.getEntity()); + LOGGER.log(Level.WARNING, MessageFormat.format("upload response content for {0}:\n {1}", fileName, returnData)); + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR); + } + } + + /** + * NOTE That this is not a perfect solution as timeouts set this way does + * not terminate a connection forcefully after a specified interval. so if + * there is data streaming in from the server at a small speed the + * connection will be kept open. + * + * @param request + */ + private void configureRequestTimeout(HttpRequestBase request) { + RequestConfig config = RequestConfig.custom() + .setConnectionRequestTimeout(CONNECTION_TIMEOUT_MS) + .setConnectTimeout(CONNECTION_TIMEOUT_MS) + .setSocketTimeout(CONNECTION_TIMEOUT_MS) + .build(); + request.setConfig(config); + } + + /** + * Creates a connection to CT Cloud with the given arguments. + * @param proxySettings The network proxy settings. + * @param sslContext The ssl context or null. + * @return The connection to CT Cloud. + */ + private static CloseableHttpClient createConnection(ProxySettingArgs proxySettings, SSLContext sslContext) { + HttpClientBuilder builder = getHttpClientBuilder(proxySettings); + + if (sslContext != null) { + builder.setSSLContext(sslContext); + } + return builder.build(); + } + + private static HttpClientBuilder getHttpClientBuilder(ProxySettingArgs proxySettings) { + + if (proxySettings.isSystemOrManualProxy()) { + + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + LOGGER.info("Requesting Password Authentication..."); + return super.getPasswordAuthentication(); + } + }); + + HttpClientBuilder builder = null; + HttpHost proxyHost = null; + CredentialsProvider proxyCredsProvider = null; + RequestConfig config = null; + + if (Objects.nonNull(proxySettings.getProxyHostname()) && proxySettings.getProxyPort() > 0) { + proxyHost = new HttpHost(proxySettings.getProxyHostname(), proxySettings.getProxyPort()); + + proxyCredsProvider = getProxyCredentialsProvider(proxySettings); + if (StringUtils.isNotBlank(proxySettings.getAuthScheme())) { + if (!DEFAULT_SCHEME_PRIORITY.get(0).equalsIgnoreCase(proxySettings.getAuthScheme())) { + DEFAULT_SCHEME_PRIORITY.removeIf(s -> s.equalsIgnoreCase(proxySettings.getAuthScheme())); + DEFAULT_SCHEME_PRIORITY.add(0, proxySettings.getAuthScheme()); + } + } + config = RequestConfig.custom().setProxyPreferredAuthSchemes(DEFAULT_SCHEME_PRIORITY).build(); + } + + if (Objects.isNull(proxyCredsProvider) && WinHttpClients.isWinAuthAvailable()) { + builder = WinHttpClients.custom(); + builder.useSystemProperties(); + LOGGER.log(Level.WARNING, "Using Win HTTP Client"); + } else { + builder = HttpClients.custom(); + builder.setDefaultRequestConfig(config); + if (Objects.nonNull(proxyCredsProvider)) { // make sure non null proxycreds before setting it + builder.setDefaultCredentialsProvider(proxyCredsProvider); + } + LOGGER.log(Level.WARNING, "Using default http client"); + } + if (Objects.nonNull(proxyHost)) { + builder.setProxy(proxyHost); + LOGGER.log(Level.WARNING, MessageFormat.format("Using proxy {0}", proxyHost)); + } + + return builder; + } else { + return HttpClients.custom(); + } + } + + /** + * Returns a CredentialsProvider for proxy, if one is configured. + * + * @return CredentialsProvider, if a proxy is configured with credentials, + * null otherwise + */ + private static CredentialsProvider getProxyCredentialsProvider(ProxySettingArgs proxySettings) { + CredentialsProvider proxyCredsProvider = null; + if (proxySettings.isSystemOrManualProxy()) { + if (StringUtils.isNotBlank(proxySettings.getProxyUserId())) { + if (null != proxySettings.getProxyPassword() && proxySettings.getProxyPassword().length > 0) { // Password will be blank for KERBEROS / NEGOTIATE schemes. + proxyCredsProvider = new SystemDefaultCredentialsProvider(); + String userId = proxySettings.getProxyUserId(); + String domain = null; + if (userId.contains("\\")) { + domain = userId.split("\\\\")[0]; + userId = userId.split("\\\\")[1]; + } + String workStation = proxySettings.getHostName(); + proxyCredsProvider.setCredentials(new AuthScope(proxySettings.getProxyHostname(), proxySettings.getProxyPort()), + new NTCredentials(userId, new String(proxySettings.getProxyPassword()), workStation, domain)); + } + } + } + + return proxyCredsProvider; + } + + private static class ProxySettingArgs { + + private final boolean systemOrManualProxy; + private final String hostName; + private final String proxyHostname; + private final int proxyPort; + private final String proxyUserId; + private final char[] proxyPassword; + private final String authScheme; + + ProxySettingArgs(boolean systemOrManualProxy, String hostName, String proxyHostname, int proxyPort, String proxyUserId, char[] proxyPassword, String authScheme) { + this.systemOrManualProxy = systemOrManualProxy; + this.hostName = hostName; + this.proxyHostname = proxyHostname; + this.proxyPort = proxyPort; + this.proxyUserId = proxyUserId; + this.proxyPassword = proxyPassword; + this.authScheme = authScheme; + } + + boolean isSystemOrManualProxy() { + return systemOrManualProxy; + } + + String getHostName() { + return hostName; + } + + String getProxyHostname() { + return proxyHostname; + } + + int getProxyPort() { + return proxyPort; + } + + String getProxyUserId() { + return proxyUserId; + } + + char[] getProxyPassword() { + return proxyPassword; + } + + public String getAuthScheme() { + return authScheme; + } + } +} 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 0000000000000000000000000000000000000000..9587b3cd44dbf4984aac10a9b5639661e5f4a883 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/Constants.java @@ -0,0 +1,87 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi; + +import java.net.URI; + +/** + * Constants regarding connections to cyber triage cloud. + */ +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_DEV_SERVER = "https://cyber-triage-dev.appspot.com"; + + public static final String CT_CLOUD_SERVER = "https://rep1.cybertriage.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/ProxySettings.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java new file mode 100644 index 0000000000000000000000000000000000000000..f710a6ab1bb1641d4f890a70de926e93647bc021 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/ProxySettings.java @@ -0,0 +1,446 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi; + +import java.net.*; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.PreferenceChangeListener; +import java.util.prefs.Preferences; +import org.netbeans.api.keyring.Keyring; +import org.openide.util.*; +import org.openide.util.lookup.ServiceProvider; + +/** + * Taken from https://raw.githubusercontent.com/apache/netbeans/master/platform/o.n.core/src/org/netbeans/core/ProxySettings.java + * @author Jiri Rechtacek + */ +public class ProxySettings { + + public static final String PROXY_HTTP_HOST = "proxyHttpHost"; // NOI18N + public static final String PROXY_HTTP_PORT = "proxyHttpPort"; // NOI18N + public static final String PROXY_HTTPS_HOST = "proxyHttpsHost"; // NOI18N + public static final String PROXY_HTTPS_PORT = "proxyHttpsPort"; // NOI18N + public static final String PROXY_SOCKS_HOST = "proxySocksHost"; // NOI18N + public static final String PROXY_SOCKS_PORT = "proxySocksPort"; // NOI18N + public static final String NOT_PROXY_HOSTS = "proxyNonProxyHosts"; // NOI18N + public static final String PROXY_TYPE = "proxyType"; // NOI18N + public static final String USE_PROXY_AUTHENTICATION = "useProxyAuthentication"; // NOI18N + public static final String PROXY_AUTHENTICATION_USERNAME = "proxyAuthenticationUsername"; // NOI18N + public static final String PROXY_AUTHENTICATION_PASSWORD = "proxyAuthenticationPassword"; // NOI18N + public static final String USE_PROXY_ALL_PROTOCOLS = "useProxyAllProtocols"; // NOI18N + public static final String DIRECT = "DIRECT"; // NOI18N + public static final String PAC = "PAC"; // NOI18N + + public static final String SYSTEM_PROXY_HTTP_HOST = "systemProxyHttpHost"; // NOI18N + public static final String SYSTEM_PROXY_HTTP_PORT = "systemProxyHttpPort"; // NOI18N + public static final String SYSTEM_PROXY_HTTPS_HOST = "systemProxyHttpsHost"; // NOI18N + public static final String SYSTEM_PROXY_HTTPS_PORT = "systemProxyHttpsPort"; // NOI18N + public static final String SYSTEM_PROXY_SOCKS_HOST = "systemProxySocksHost"; // NOI18N + public static final String SYSTEM_PROXY_SOCKS_PORT = "systemProxySocksPort"; // NOI18N + public static final String SYSTEM_NON_PROXY_HOSTS = "systemProxyNonProxyHosts"; // NOI18N + public static final String SYSTEM_PAC = "systemPAC"; // NOI18N + + // Only for testing purpose (Test connection in General options panel) + public static final String TEST_SYSTEM_PROXY_HTTP_HOST = "testSystemProxyHttpHost"; // NOI18N + public static final String TEST_SYSTEM_PROXY_HTTP_PORT = "testSystemProxyHttpPort"; // NOI18N + public static final String HTTP_CONNECTION_TEST_URL = "https://netbeans.apache.org";// NOI18N + + private static String presetNonProxyHosts; + + /** No proxy is used to connect. */ + public static final int DIRECT_CONNECTION = 0; + + /** Proxy setting is automatically detect in OS. */ + public static final int AUTO_DETECT_PROXY = 1; // as default + + /** Manually set proxy host and port. */ + public static final int MANUAL_SET_PROXY = 2; + + /** Proxy PAC file automatically detect in OS. */ + public static final int AUTO_DETECT_PAC = 3; + + /** Proxy PAC file manually set. */ + public static final int MANUAL_SET_PAC = 4; + + private static final Logger LOGGER = Logger.getLogger(ProxySettings.class.getName()); + + private static Preferences getPreferences() { + return NbPreferences.forModule (ProxySettings.class); + } + + + public static String getHttpHost () { + return normalizeProxyHost (getPreferences ().get (PROXY_HTTP_HOST, "")); + } + + public static String getHttpPort () { + return getPreferences ().get (PROXY_HTTP_PORT, ""); + } + + public static String getHttpsHost () { + if (useProxyAllProtocols ()) { + return getHttpHost (); + } else { + return getPreferences ().get (PROXY_HTTPS_HOST, ""); + } + } + + public static String getHttpsPort () { + if (useProxyAllProtocols ()) { + return getHttpPort (); + } else { + return getPreferences ().get (PROXY_HTTPS_PORT, ""); + } + } + + public static String getSocksHost () { + if (useProxyAllProtocols ()) { + return getHttpHost (); + } else { + return getPreferences ().get (PROXY_SOCKS_HOST, ""); + } + } + + public static String getSocksPort () { + if (useProxyAllProtocols ()) { + return getHttpPort (); + } else { + return getPreferences ().get (PROXY_SOCKS_PORT, ""); + } + } + + public static String getNonProxyHosts () { + String hosts = getPreferences ().get (NOT_PROXY_HOSTS, getDefaultUserNonProxyHosts ()); + return compactNonProxyHosts(hosts); + } + + public static int getProxyType () { + int type = getPreferences ().getInt (PROXY_TYPE, AUTO_DETECT_PROXY); + if (AUTO_DETECT_PROXY == type) { + type = ProxySettings.getSystemPac() != null ? AUTO_DETECT_PAC : AUTO_DETECT_PROXY; + } + return type; + } + + + public static String getSystemHttpHost() { + return getPreferences().get(SYSTEM_PROXY_HTTP_HOST, ""); + } + + public static String getSystemHttpPort() { + return getPreferences().get(SYSTEM_PROXY_HTTP_PORT, ""); + } + + public static String getSystemHttpsHost() { + return getPreferences().get(SYSTEM_PROXY_HTTPS_HOST, ""); + } + + public static String getSystemHttpsPort() { + return getPreferences().get(SYSTEM_PROXY_HTTPS_PORT, ""); + } + + public static String getSystemSocksHost() { + return getPreferences().get(SYSTEM_PROXY_SOCKS_HOST, ""); + } + + public static String getSystemSocksPort() { + return getPreferences().get(SYSTEM_PROXY_SOCKS_PORT, ""); + } + + public static String getSystemNonProxyHosts() { + return getPreferences().get(SYSTEM_NON_PROXY_HOSTS, getModifiedNonProxyHosts("")); + } + + public static String getSystemPac() { + return getPreferences().get(SYSTEM_PAC, null); + } + + + public static String getTestSystemHttpHost() { + return getPreferences().get(TEST_SYSTEM_PROXY_HTTP_HOST, ""); + } + + public static String getTestSystemHttpPort() { + return getPreferences().get(TEST_SYSTEM_PROXY_HTTP_PORT, ""); + } + + + public static boolean useAuthentication () { + return getPreferences ().getBoolean (USE_PROXY_AUTHENTICATION, false); + } + + public static boolean useProxyAllProtocols () { + return getPreferences ().getBoolean (USE_PROXY_ALL_PROTOCOLS, false); + } + + public static String getAuthenticationUsername () { + return getPreferences ().get (PROXY_AUTHENTICATION_USERNAME, ""); + } + + public static char[] getAuthenticationPassword () { + String old = getPreferences().get(PROXY_AUTHENTICATION_PASSWORD, null); + if (old != null) { + getPreferences().remove(PROXY_AUTHENTICATION_PASSWORD); + setAuthenticationPassword(old.toCharArray()); + } + char[] pwd = Keyring.read(PROXY_AUTHENTICATION_PASSWORD); + return pwd != null ? pwd : new char[0]; + } + + public static void setAuthenticationPassword(char[] password) { + Keyring.save(ProxySettings.PROXY_AUTHENTICATION_PASSWORD, password, + // XXX consider including getHttpHost and/or getHttpsHost + NbBundle.getMessage(ProxySettings.class, "ProxySettings.password.description")); // NOI18N + } + + public static void addPreferenceChangeListener (PreferenceChangeListener l) { + getPreferences ().addPreferenceChangeListener (l); + } + + public static void removePreferenceChangeListener (PreferenceChangeListener l) { + getPreferences ().removePreferenceChangeListener (l); + } + + private static String getPresetNonProxyHosts () { + if (presetNonProxyHosts == null) { + presetNonProxyHosts = System.getProperty ("http.nonProxyHosts", ""); // NOI18N + } + return presetNonProxyHosts; + } + + private static String getDefaultUserNonProxyHosts () { + return getModifiedNonProxyHosts (getSystemNonProxyHosts ()); + } + + + private static String concatProxies(String... proxies) { + StringBuilder sb = new StringBuilder(); + for (String n : proxies) { + if (n == null) { + continue; + } + n = n.trim(); + if (n.isEmpty()) { + continue; + } + if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '|') { // NOI18N + if (!n.startsWith("|")) { // NOI18N + sb.append('|'); // NOI18N + } + } + sb.append(n); + } + return sb.toString(); + } + + private static String getModifiedNonProxyHosts (String systemPreset) { + String fromSystem = systemPreset.replace (";", "|").replace (",", "|"); //NOI18N + String fromUser = getPresetNonProxyHosts () == null ? "" : getPresetNonProxyHosts ().replace (";", "|").replace (",", "|"); //NOI18N + if (Utilities.isWindows ()) { + fromSystem = addReguralToNonProxyHosts (fromSystem); + } + final String staticNonProxyHosts = NbBundle.getMessage(ProxySettings.class, "StaticNonProxyHosts"); // NOI18N + String nonProxy = concatProxies(fromUser, fromSystem, staticNonProxyHosts); // NOI18N + String localhost; + try { + localhost = InetAddress.getLocalHost().getHostName(); + if (!"localhost".equals(localhost)) { // NOI18N + nonProxy = nonProxy + "|" + localhost; // NOI18N + } else { + // Avoid this error when hostname == localhost: + // Error in http.nonProxyHosts system property: sun.misc.REException: localhost is a duplicate + } + } + catch (UnknownHostException e) { + // OK. Sometimes a hostname is assigned by DNS, but a computer + // is later pulled off the network. It may then produce a bogus + // name for itself which can't actually be resolved. Normally + // "localhost" is aliased to 127.0.0.1 anyway. + } + /* per Milan's agreement it's removed. See issue #89868 + try { + String localhost2 = InetAddress.getLocalHost().getCanonicalHostName(); + if (!"localhost".equals(localhost2) && !localhost2.equals(localhost)) { // NOI18N + nonProxy = nonProxy + "|" + localhost2; // NOI18N + } else { + // Avoid this error when hostname == localhost: + // Error in http.nonProxyHosts system property: sun.misc.REException: localhost is a duplicate + } + } + catch (UnknownHostException e) { + // OK. Sometimes a hostname is assigned by DNS, but a computer + // is later pulled off the network. It may then produce a bogus + // name for itself which can't actually be resolved. Normally + // "localhost" is aliased to 127.0.0.1 anyway. + } + */ + return compactNonProxyHosts (nonProxy); + } + + + // avoid duplicate hosts + private static String compactNonProxyHosts (String hosts) { + StringTokenizer st = new StringTokenizer(hosts, ","); //NOI18N + StringBuilder nonProxyHosts = new StringBuilder(); + while (st.hasMoreTokens()) { + String h = st.nextToken().trim(); + if (h.length() == 0) { + continue; + } + if (nonProxyHosts.length() > 0) { + nonProxyHosts.append("|"); // NOI18N + } + nonProxyHosts.append(h); + } + st = new StringTokenizer (nonProxyHosts.toString(), "|"); //NOI18N + Set<String> set = new HashSet<String> (); + StringBuilder compactedProxyHosts = new StringBuilder(); + while (st.hasMoreTokens ()) { + String t = st.nextToken (); + if (set.add (t.toLowerCase (Locale.US))) { + if (compactedProxyHosts.length() > 0) { + compactedProxyHosts.append('|'); // NOI18N + } + compactedProxyHosts.append(t); + } + } + return compactedProxyHosts.toString(); + } + + private static String addReguralToNonProxyHosts (String nonProxyHost) { + StringTokenizer st = new StringTokenizer (nonProxyHost, "|"); // NOI18N + StringBuilder reguralProxyHosts = new StringBuilder(); + while (st.hasMoreTokens ()) { + String t = st.nextToken (); + if (t.indexOf ('*') == -1) { //NOI18N + t = t + '*'; //NOI18N + } + if (reguralProxyHosts.length() > 0) + reguralProxyHosts.append('|'); // NOI18N + reguralProxyHosts.append(t); + } + + return reguralProxyHosts.toString(); + } + + public static String normalizeProxyHost (String proxyHost) { + if (proxyHost.toLowerCase (Locale.US).startsWith ("http://")) { // NOI18N + return proxyHost.substring (7, proxyHost.length ()); + } else { + return proxyHost; + } + } + + private static InetSocketAddress analyzeProxy(URI uri) { + Parameters.notNull("uri", uri); // NOI18N + List<Proxy> proxies = ProxySelector.getDefault().select(uri); + assert proxies != null : "ProxySelector cannot return null for " + uri; // NOI18N + assert !proxies.isEmpty() : "ProxySelector cannot return empty list for " + uri; // NOI18N + String protocol = uri.getScheme(); + Proxy p = proxies.get(0); + if (Proxy.Type.DIRECT == p.type()) { + // return null for DIRECT proxy + return null; + } + if (protocol == null + || ((protocol.startsWith("http") || protocol.equals("ftp")) && Proxy.Type.HTTP == p.type()) // NOI18N + || !(protocol.startsWith("http") || protocol.equals("ftp"))) { // NOI18N + if (p.address() instanceof InetSocketAddress) { + // check is + //assert ! ((InetSocketAddress) p.address()).isUnresolved() : p.address() + " must be resolved address."; + return (InetSocketAddress) p.address(); + } else { + LOGGER.log(Level.INFO, p.address() + " is not instanceof InetSocketAddress but " + p.address().getClass()); // NOI18N + return null; + } + } else { + return null; + } + } + + public static void reload() { + Reloader reloader = Lookup.getDefault().lookup(Reloader.class); + reloader.reload(); + } + + @ServiceProvider(service = NetworkSettings.ProxyCredentialsProvider.class, position = 1000) + public static class NbProxyCredentialsProvider extends NetworkSettings.ProxyCredentialsProvider { + + @Override + public String getProxyHost(URI u) { + if (getPreferences() == null) { + return null; + } + InetSocketAddress sa = analyzeProxy(u); + return sa == null ? null : sa.getHostName(); + } + + @Override + public String getProxyPort(URI u) { + if (getPreferences() == null) { + return null; + } + InetSocketAddress sa = analyzeProxy(u); + return sa == null ? null : Integer.toString(sa.getPort()); + } + + @Override + protected String getProxyUserName(URI u) { + if (getPreferences() == null) { + return null; + } + return ProxySettings.getAuthenticationUsername(); + } + + @Override + protected char[] getProxyPassword(URI u) { + if (getPreferences() == null) { + return null; + } + return ProxySettings.getAuthenticationPassword(); + } + + @Override + protected boolean isProxyAuthentication(URI u) { + if (getPreferences() == null) { + return false; + } + return getPreferences().getBoolean(USE_PROXY_AUTHENTICATION, false); + } + + } + + /** A bridge between <code>o.n.core</code> and <code>core.network</code>. + * An implementation of this class brings a facility to reload Network Proxy Settings + * from underlying OS. + * The module <code>core.network</code> provides a implementation which may be accessible + * via <code>Lookup.getDefault()</code>. It's not guaranteed any implementation is found on all distribution. + * + * @since 3.40 + */ + public abstract static class Reloader { + + /** Reloads Network Proxy Settings from underlying system. + * + */ + public abstract void reload(); + } +} 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 0000000000000000000000000000000000000000..6a093fee8ededfa7cb01720fd6687e6677897c74 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenRequest.java @@ -0,0 +1,76 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 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; + + @JsonProperty("host_id") + private String hostId; + + 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; + } + + public String getHostId() { + return hostId; + } + + public AuthTokenRequest setHostId(String hostId) { + this.hostId = hostId; + 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 0000000000000000000000000000000000000000..d4ac70e16cba44561b1dc5ec37dbab61c921121c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthTokenResponse.java @@ -0,0 +1,105 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil.InstantEpochMillisDeserializer; +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil.InstantEpochSecsDeserializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.Instant; + +/** + * POJO for an auth token response. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthTokenResponse { + + private final Long hashLookupCount; + private final Long hashLookupLimit; + private final Long fileUploadLimit; + private final Long fileUploadCount; + private final String fileUploadUrl; + private final Instant expiration; + private final String token; + private final String apiKey; + private final Instant resetDate; + + @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, + @JsonDeserialize(using = InstantEpochSecsDeserializer.class) + @JsonProperty("expiration") Instant expiration, + @JsonDeserialize(using = InstantEpochMillisDeserializer.class) + @JsonProperty("resetDate") Instant resetDate + ) { + this.token = token; + this.apiKey = apiKey; + this.hashLookupCount = hashLookupCount; + this.hashLookupLimit = hashLookupLimit; + this.fileUploadLimit = fileUploadLimit; + this.fileUploadCount = fileUploadCount; + this.fileUploadUrl = fileUploadUrl; + this.expiration = expiration; + this.resetDate = resetDate; + } + + 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 Instant getExpiration() { + return expiration; + } + + public String getToken() { + return token; + } + + public String getApiKey() { + return apiKey; + } + + public Instant getResetDate() { + return resetDate; + } +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java new file mode 100644 index 0000000000000000000000000000000000000000..ebb716871a2e28630c67a8b5153653ea76eef224 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/AuthenticatedRequestData.java @@ -0,0 +1,52 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +/** + * Data required for an authenticated request. + */ +public class AuthenticatedRequestData { + + private final String token; + private final String apiKey; + private final String hostId; + + public AuthenticatedRequestData(DecryptedLicenseResponse decrypted, AuthTokenResponse authResp) { + this(authResp.getToken(), authResp.getApiKey(), decrypted.getLicenseHostId()); + } + + public AuthenticatedRequestData(String token, String apiKey, String hostId) { + this.token = token; + this.apiKey = apiKey; + this.hostId = hostId; + } + + public String getToken() { + return token; + } + + public String getApiKey() { + return apiKey; + } + + public String getHostId() { + return hostId; + } + +} 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 0000000000000000000000000000000000000000..83970a70748929e5e013246c129d353b0f447cfb --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/BoostLicenseResponse.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * POJO for a boost license response object that is a part of the license + * response. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +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/CTCloudBean.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudBean.java new file mode 100644 index 0000000000000000000000000000000000000000..6df2dcbdb84908c93da7686edf6b718007c0761b --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudBean.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * + * @author rishwanth + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CTCloudBean { + + public static enum Status { + FOUND, + NOT_FOUND, + ERROR, + LIMITS_EXCEEDED, + BEING_SCANNED; + } + + @Nonnull + @JsonProperty("malware") + private MalwareResultBean malwareResult; + + @JsonProperty("correlation") + private CorrelationResultBean correlationResult; + + @Nonnull + @JsonProperty("md5_hash") + private String md5HashValue; + + @Nullable + @JsonProperty("sha1_hash") + private String sha1HashValue; + + public String getMd5HashValue() { + return md5HashValue; + } + + public String getSha1HashValue() { + return sha1HashValue; + } + + public void setMd5HashValue(String md5HashValue) { + this.md5HashValue = md5HashValue; + } + + public void setSha1HashValue(String sha1HashValue) { + this.sha1HashValue = sha1HashValue; + } + + public MalwareResultBean getMalwareResult() { + return malwareResult; + } + + public void setMalwareResult(MalwareResultBean malwareResult) { + this.malwareResult = malwareResult; + } + + public CorrelationResultBean getCorrelationResult() { + return correlationResult; + } + + public void setCorrelationResult(CorrelationResultBean correlationResult) { + this.correlationResult = correlationResult; + } + + @Override + public String toString() { + return "CTCloudBean{" + + "status=" + malwareResult.getStatus() + + ", malwareDescription=" + malwareResult.getMalwareDescription() + + ", score=" + malwareResult.getCTScore() + + ", md5HashValue=" + md5HashValue + + ", sha1HashValue=" + sha1HashValue + + ", firstSeen=" + malwareResult.getFirstAnalyzedDate() + + ", lastSeen=" + malwareResult.getLastAnalyzedDate() + + ", statusDescription=" + malwareResult.getStatusDescription() + + ", metadata=" + malwareResult.getMetadata() + + '}'; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudBeanResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudBeanResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..704ee1d19ebebe19cbac027e8fbe7ffc19c4b739 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudBeanResponse.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Container for file reputation result list response. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CTCloudBeanResponse { + + private final List<CTCloudBean> items; + + @JsonCreator + public CTCloudBeanResponse( + @JsonProperty("items") List<CTCloudBean> items + ) { + this.items = items; + } + + public List<CTCloudBean> getItems() { + return items; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCorrelationResultBean.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCorrelationResultBean.java new file mode 100644 index 0000000000000000000000000000000000000000..31de5d0057c2222dd454c3f77c0f66631a89719d --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCorrelationResultBean.java @@ -0,0 +1,58 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.Nonnull; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CTCloudCorrelationResultBean { + + @JsonProperty("correlation") + private CorrelationResultBean correlationResult; + + @Nonnull + @JsonProperty("signature") + private String signature; + + public CorrelationResultBean getCorrelationResult() { + return correlationResult; + } + + public void setCorrelationResult(CorrelationResultBean correlationResult) { + this.correlationResult = correlationResult; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + @Override + public String toString() { + return "CTCloudCorrelationResultBean{" + + "correlationResult=" + correlationResult + + ", signature=" + signature + + '}'; + } +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCostBean.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCostBean.java new file mode 100644 index 0000000000000000000000000000000000000000..9c3272219bee6d83dd99caae234613a1882b5098 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTCloudCostBean.java @@ -0,0 +1,46 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + * @author rishwanth + */ +class CTCloudCostBean { + + private final String provider; + + private final Integer units; + + public CTCloudCostBean(@JsonProperty("provider") String provider, @JsonProperty("units") Integer units) { + this.provider = provider; + this.units = units; + } + + public String getProvider() { + return provider; + } + + public Integer getUnits() { + return units; + } + +} 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 0000000000000000000000000000000000000000..4a9b91054881e8b7c31d1d21cc4ed78f2d34690c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CTScore.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.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/CorrelationFrequency.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CorrelationFrequency.java new file mode 100644 index 0000000000000000000000000000000000000000..4fdf1bcdd8bd033626edca248297e61bc3ad1c0c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CorrelationFrequency.java @@ -0,0 +1,29 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +/** + * + * @author rishwanth + */ +public enum CorrelationFrequency { + UNIQUE, + RARE, + COMMON; +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CorrelationResultBean.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CorrelationResultBean.java new file mode 100644 index 0000000000000000000000000000000000000000..6715acc20b5ec87c39fdfbe5256f264fa3f67e93 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/CorrelationResultBean.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + * @author rishwanth + */ +public class CorrelationResultBean { + + @JsonProperty("frequency") + private CorrelationFrequency frequency; + + @JsonProperty("frequency_description") + private String frequencyDescription; + + public CorrelationFrequency getFrequency() { + return frequency; + } + + public String getFrequencyDescription() { + return frequencyDescription; + } + + public void setFrequency(CorrelationFrequency frequency) { + this.frequency = frequency; + } + + public void setFrequencyDescription(String frequencyDescription) { + this.frequencyDescription = frequencyDescription; + } + +} 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 0000000000000000000000000000000000000000..c6f91721ef7d05f679963727e0b787c11b2ea04a --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/DecryptedLicenseResponse.java @@ -0,0 +1,118 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil.InstantEpochMillisDeserializer; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.Instant; + +/** + * POJO for after encrypted boost license has been decrypted. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class DecryptedLicenseResponse { + + private final String boostLicenseId; + private final String licenseHostId; + private final Instant expirationDate; + private final Long hashLookups; + private final Long fileUploads; + private final Instant activationTime; + private final String product; + private final String limitType; + private final String timezone; + private final String customerEmail; + private final String customerName; + + @JsonCreator + public DecryptedLicenseResponse( + @JsonProperty("boostLicenseId") String boostLicenseId, + @JsonProperty("licenseHostId") String licenseHostId, + @JsonDeserialize(using = InstantEpochMillisDeserializer.class) + @JsonProperty("expirationDate") Instant expirationDate, + @JsonProperty("hashLookups") Long hashLookups, + @JsonProperty("fileUploads") Long fileUploads, + @JsonDeserialize(using = InstantEpochMillisDeserializer.class) + @JsonProperty("activationTime") Instant activationTime, + @JsonProperty("product") String product, + @JsonProperty("limitType") String limitType, + @JsonProperty("timezone") String timezone, + @JsonProperty("customerEmail") String customerEmail, + @JsonProperty("customerName") String customerName + ) { + this.boostLicenseId = boostLicenseId; + this.licenseHostId = licenseHostId; + this.expirationDate = expirationDate; + this.hashLookups = hashLookups; + this.fileUploads = fileUploads; + this.activationTime = activationTime; + this.product = product; + this.limitType = limitType; + this.timezone = timezone; + this.customerEmail = customerEmail; + this.customerName = customerName; + } + + public String getBoostLicenseId() { + return boostLicenseId; + } + + public String getLicenseHostId() { + return licenseHostId; + } + + public Long getHashLookups() { + return hashLookups; + } + + public Long getFileUploads() { + return fileUploads; + } + + public Instant getActivationTime() { + return activationTime; + } + + public String getProduct() { + return product; + } + + public String getLimitType() { + return limitType; + } + + public Instant getExpirationDate() { + return expirationDate; + } + + public String getTimezone() { + return timezone; + } + + public String getCustomerEmail() { + return customerEmail; + } + + public String getCustomerName() { + return customerName; + } +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..46dfcb7bd445079b7e5c551baf0bf3f8839e5c0c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/FileReputationRequest.java @@ -0,0 +1,40 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Request for file reputation results. + */ +public class FileReputationRequest { + + @JsonProperty("hashes") + private List<String> hashes; + + public List<String> getHashes() { + return hashes; + } + + public FileReputationRequest setHashes(List<String> hashes) { + this.hashes = hashes; + return this; + } +} 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 0000000000000000000000000000000000000000..127df9987d0b40b6c25b9b3b8b8484f74c14f63f --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseInfo.java @@ -0,0 +1,40 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +/** + * 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; + } +} 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 0000000000000000000000000000000000000000..18a1b1c4bffbd7435bf9d33f30dfeb9f96187391 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseRequest.java @@ -0,0 +1,77 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * 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; + + @JsonProperty("time_zone_id") + private String timeZoneId; + + 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; + } + + public String getTimeZoneId() { + return timeZoneId; + } + + public LicenseRequest setTimeZoneId(String timeZoneId) { + this.timeZoneId = timeZoneId; + 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 0000000000000000000000000000000000000000..a3a824788400e033cdf0776fbe6fcda2616bad0c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java @@ -0,0 +1,64 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response POJO for request for license. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +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/MalwareResultBean.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MalwareResultBean.java new file mode 100644 index 0000000000000000000000000000000000000000..a77ec55ac069e1fbef17f77bf4abc14cceea17aa --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MalwareResultBean.java @@ -0,0 +1,170 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil.ZonedDateTimeDeserializer; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.ZonedDateTime; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * + * @author rishwanth + */ +public class MalwareResultBean { + + public static enum Status { + FOUND, + NOT_FOUND, + ERROR, + LIMITS_EXCEEDED, + BEING_SCANNED; + } + + @Nullable + @JsonProperty("malware_description") + private String malwareDescription; + + @Nonnull + @JsonProperty("status") + private Status status; + + @Nonnull + @JsonProperty("score") + private String score; + + private CTScore ctScore; + + @Nullable + @JsonProperty("first_scan_date") + @JsonDeserialize(using = ZonedDateTimeDeserializer.class) + private ZonedDateTime firstAnalyzedDate; + + @Nullable + @JsonProperty("last_scan_date") + @JsonDeserialize(using = ZonedDateTimeDeserializer.class) + private ZonedDateTime lastAnalyzedDate; + + @JsonProperty("metadata") + private List<MetadataLabel> metadata; + + @Nullable + @JsonProperty("status_description") + private String statusDescription; + + + @Nullable + @JsonProperty + private List<CTCloudCostBean> cost; + + @JsonIgnore + public CTScore getCTScore() { + return ctScore; + } + + public String getScore() { + return score; + } + + public ZonedDateTime getFirstAnalyzedDate() { + return firstAnalyzedDate; + } + + public ZonedDateTime getLastAnalyzedDate() { + return lastAnalyzedDate; + } + + public List<MetadataLabel> getMetadata() { + if (metadata != null) { + return List.copyOf(metadata); + } else { + return List.of(); // empty list + } + } + + public Status getStatus() { + return status; + } + + public String getStatusDescription() { + return statusDescription; + } + + + public String getMalwareDescription() { + return malwareDescription; + } + + public void setMalwareDescription(String malwareDescription) { + this.malwareDescription = malwareDescription; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setScore(String score) { + this.score = score; + switch(score) { + case "GOOD_HIGH": + this.ctScore = CTScore.NONE; + break; + case "GOOD_MEDIUM": + this.ctScore = CTScore.LIKELY_NONE; + break; + case "BAD_HIGH": + this.ctScore = CTScore.NOTABLE; + break; + case "BAD_MEDIUM": + this.ctScore = CTScore.LIKELY_NOTABLE; + break; + default: + this.ctScore = CTScore.UNKNOWN; + break; + } + } + + public void setFirstAnalyzedDate(ZonedDateTime firstAnalyzedDate) { + this.firstAnalyzedDate = firstAnalyzedDate; + } + + public void setLastAnalyzedDate(ZonedDateTime lastAnalyzedDate) { + this.lastAnalyzedDate = lastAnalyzedDate; + } + + public void setMetadata(List<MetadataLabel> metadata) { + this.metadata = List.copyOf(metadata); + } + + public void setStatusDescription(String statusDescription) { + this.statusDescription = statusDescription; + } + + public List<CTCloudCostBean> getCost() { + return List.copyOf(cost); + } + + public void setCost(List<CTCloudCostBean> cost) { + this.cost = List.copyOf(cost); + } +} 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 0000000000000000000000000000000000000000..60b6ce34ad010822c262fc5980a3059996b09816 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataLabel.java @@ -0,0 +1,58 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Metadata entry. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +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 0000000000000000000000000000000000000000..415a773f59ba12402d6cdc5079eeb00678cf4419 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/CTHostIDGenerationUtil.java @@ -0,0 +1,69 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.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 cachedId = ""; + + /** + * 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(cachedId)) { + try { + String hostName = StringUtils.defaultString(InetAddress.getLocalHost().getCanonicalHostName()); + String macAddressMd5 = StringUtils.isNotBlank(HardwareID.getHardwareIDFromEthernetAddress()) + ? Md5HashUtil.getMD5MessageDigest(HardwareID.getHardwareIDFromEthernetAddress()).substring(0, 16) + : Md5HashUtil.getMD5MessageDigest(hostName).substring(0, 16); + + String usernameMd5 = StringUtils.isNotBlank(USER_NAME) + ? Md5HashUtil.getMD5MessageDigest(USER_NAME).substring(0, 16) + : Md5HashUtil.getMD5MessageDigest(hostName).substring(0, 16); + + cachedId = macAddressMd5 + "_" + usernameMd5; + + } catch (UnknownHostException ex) { + LOGGER.log(Level.WARNING, "Unable to determine host name.", ex); + } + } + + return cachedId; + } +} 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 0000000000000000000000000000000000000000..f62b57d795559275f0329182904dc867fc9c1b83 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java @@ -0,0 +1,177 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.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 (!"AUTOPSY".equalsIgnoreCase(decryptedLicense.getProduct())) { + // license file is expected to contain product of "CYBERTRIAGE" + throw new InvalidLicenseException("Not a valid Autopsy 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 0000000000000000000000000000000000000000..49878f3e712d2a814d5b393fbd2a47e0f0c5da6b --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/Md5HashUtil.java @@ -0,0 +1,45 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.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 0000000000000000000000000000000000000000..5562141255a2ee83b71bd9590be2feee59e08a29 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/ObjectMapperUtil.java @@ -0,0 +1,190 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctapi.util; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.function.Function; + +/** + * 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.registerModule(new JavaTimeModule()); + return defaultMapper; + } + + public static class UTCBaseZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> { + + private final DateTimeFormatter formatter; + + public UTCBaseZonedDateTimeDeserializer(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + @Override + public ZonedDateTime deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JacksonException { + String date = jp.getText(); + if (date == null) { + return null; + } + + try { + LocalDateTime ldt = LocalDateTime.parse(date, formatter); + return ZonedDateTime.of(ldt, ZoneOffset.UTC); + } catch (DateTimeParseException ex) { + return null; + } + } + } + + public static class ZonedDateTimeDeserializer extends UTCBaseZonedDateTimeDeserializer { + + public ZonedDateTimeDeserializer() { + super(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + } + + public static class MDYDateDeserializer extends JsonDeserializer<ZonedDateTime> { + + private final DateTimeFormatter formatter; + + public MDYDateDeserializer() { + this.formatter = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("MMM d, [uuuu][uu]") + .toFormatter(Locale.ENGLISH); + } + + @Override + public ZonedDateTime deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JacksonException { + String date = jp.getText(); + if (date == null) { + return null; + } + + try { + LocalDate ld = LocalDate.parse(date, formatter); + LocalDateTime ldt = ld.atStartOfDay(); + return ZonedDateTime.of(ldt, ZoneOffset.UTC); + } catch (DateTimeParseException ex) { + return null; + } + } + } + + public static class EpochTimeDeserializer<T> extends JsonDeserializer<T> { + + private final Function<Long, T> timeDeserializer; + + public EpochTimeDeserializer(Function<Long, T> timeDeserializer) { + this.timeDeserializer = timeDeserializer; + } + + @Override + public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JacksonException { + JsonNode node = jp.getCodec().readTree(jp); + + Long timeVal = null; + if (node.isNumber()) { + timeVal = node.asLong(); + } else { + String nodeText = node.asText(); + try { + timeVal = Long.parseLong(nodeText); + } catch (NumberFormatException ex) { + // do nothing if can't parse as number + } + } + + if (timeVal != null) { + try { + return timeDeserializer.apply(timeVal); + } catch (DateTimeException ex) { + // do nothing if can't parse to epoch + } + } + + return null; + } + } + + public static class InstantEpochMillisDeserializer extends EpochTimeDeserializer<Instant> { + + public InstantEpochMillisDeserializer() { + super(InstantEpochMillisDeserializer::convert); + } + + private static Instant convert(Long longVal) { + try { + return Instant.ofEpochMilli(longVal); + } catch (DateTimeException ex) { + // do nothing if can't parse to epoch + + return null; + } + } + } + + public static class InstantEpochSecsDeserializer extends EpochTimeDeserializer<Instant> { + + public InstantEpochSecsDeserializer() { + super(InstantEpochSecsDeserializer::convert); + } + + private static Instant convert(Long longVal) { + try { + return Instant.ofEpochSecond(longVal); + } catch (DateTimeException ex) { + // do nothing if can't parse to epoch + + return null; + } + } + } +} 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 0000000000000000000000000000000000000000..46f62fdf7a5185355907a8f9980343d9478c03de --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties @@ -0,0 +1,9 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template +OptionsCategory_Name_CyberTriage=Cyber Triage +OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage +LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a non-free license to use.</html> +LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from +LicenseDisclaimerPanel.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html> +LicenseDisclaimerPanel.border.title=Disclaimer 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 0000000000000000000000000000000000000000..46f62fdf7a5185355907a8f9980343d9478c03de --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties-MERGED @@ -0,0 +1,9 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template +OptionsCategory_Name_CyberTriage=Cyber Triage +OptionsCategory_Keywords_CyberTriage=Cyber Triage,Cyber,Triage +LicenseDisclaimerPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a non-free license to use.</html> +LicenseDisclaimerPanel.purchaseFromLabel.text=You can purchase a license from +LicenseDisclaimerPanel.link.text=<html><span style="color: blue; text-decoration: underline">https://cybertriage.com/autopsy-checkout</span></html> +LicenseDisclaimerPanel.border.title=Disclaimer 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 0000000000000000000000000000000000000000..1aee51eb33016235d897c1b15090599d286c6200 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.form @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,120,0,0,2,2"/> + </AuxValues> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/> + <SubComponents> + <Container class="javax.swing.JScrollPane" name="scrollPane"> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription"> + <BorderConstraints direction="Center"/> + </Constraint> + </Constraints> + + <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> + <SubComponents> + <Container class="javax.swing.JPanel" name="contentPane"> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + </Container> + </SubComponents> + </Container> + </SubComponents> +</Form> 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 0000000000000000000000000000000000000000..745aa1d03db8ef43b393d2659f8d9aad4c6edb1f --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanel.java @@ -0,0 +1,168 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions; + +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 Cyber Triage. + */ +public class CTOptionsPanel extends IngestModuleGlobalSettingsPanel { + + private static final int MAX_SUBPANEL_WIDTH = 650; + + private static final Logger logger = Logger.getLogger(CTOptionsPanel.class.getName()); + + private final List<CTOptionsSubPanel> subPanels; + + /** + * Creates new form CTOptions loading any CTOptionsSubPanel instances to be + * displayed. + */ + public CTOptionsPanel() { + initComponents(); + Collection<? extends CTOptionsSubPanel> coll = Lookup.getDefault().lookupAll(CTOptionsSubPanel.class); + Stream<? extends CTOptionsSubPanel> panelStream = coll != null ? coll.stream() : Stream.empty(); + this.subPanels = panelStream + .map(panel -> { + try { + // lookup is returning singleton instances which means this panel gets messed up when accessed + // from multiple places because the panel's children are being added to a different CTOptionsPanel + return (CTOptionsSubPanel) panel.getClass().getConstructor().newInstance(); + } catch (Exception ex) { + return null; + } + }) + .filter(item -> item != null) + .sorted(Comparator.comparing(p -> p.getClass().getSimpleName().toUpperCase())) + .collect(Collectors.toList()); + addSubOptionsPanels(new LicenseDisclaimerPanel(), this.subPanels); + } + + private void addSubOptionsPanels(JPanel disclaimerPanel, List<CTOptionsSubPanel> subPanels) { + GridBagConstraints disclaimerConstraints = new GridBagConstraints(); + disclaimerConstraints.gridx = 0; + disclaimerConstraints.gridy = 0; + disclaimerConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + disclaimerConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + disclaimerConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + disclaimerConstraints.weighty = 0; + disclaimerConstraints.weightx = 0; + + contentPane.add(disclaimerPanel, disclaimerConstraints); + + 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 + 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(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() + 1; + 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") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//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); + }// </editor-fold>//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 0000000000000000000000000000000000000000..12993e1c390719f9f5b877489ca3cdce3674e6d3 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/CTOptionsPanelController.java @@ -0,0 +1,133 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions; + +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/LicenseDisclaimerPanel.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.form new file mode 100644 index 0000000000000000000000000000000000000000..83e3c8440aa0228fbbdc4166d41dc85f34df8eb0 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.form @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Disclaimer"> + <ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </TitledBorder> + </Border> + </Property> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[2147483647, 90]"/> + </Property> + <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[562, 90]"/> + </Property> + <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[400, 90]"/> + </Property> + </Properties> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,90,0,0,2,50"/> + </AuxValues> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Component class="javax.swing.JLabel" name="disclaimer"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.disclaimer.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="verticalAlignment" type="int" value="1"/> + </Properties> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="purchaseFromLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.purchaseFromLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="-1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="3" anchor="18" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="link"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="LicenseDisclaimerPanel.link.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor"> + <Color id="Hand Cursor"/> + </Property> + </Properties> + <Events> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="linkMouseClicked"/> + </Events> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="-1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Container class="javax.swing.JPanel" name="spacer"> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="1.0"/> + </Constraint> + </Constraints> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + </Layout> + </Container> + </SubComponents> +</Form> diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..4299f02d620e69d06770ed0e5b98a326c58069fa --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/LicenseDisclaimerPanel.java @@ -0,0 +1,131 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Disclaimer for license and place to purchase CT license. + */ +public class LicenseDisclaimerPanel extends javax.swing.JPanel { + + private static final Logger LOGGER = Logger.getLogger(LicenseDisclaimerPanel.class.getName()); + + private static final String CHECKOUT_PAGE_URL = "https://cybertriage.com/autopsy-checkout"; + + /** + * Creates new form LicenseDisclaimerPanel + */ + public LicenseDisclaimerPanel() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + javax.swing.JLabel disclaimer = new javax.swing.JLabel(); + javax.swing.JLabel purchaseFromLabel = new javax.swing.JLabel(); + javax.swing.JLabel link = new javax.swing.JLabel(); + javax.swing.JPanel spacer = new javax.swing.JPanel(); + + setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.border.title"))); // NOI18N + setMaximumSize(new java.awt.Dimension(2147483647, 90)); + setMinimumSize(new java.awt.Dimension(562, 90)); + setPreferredSize(new java.awt.Dimension(400, 90)); + setLayout(new java.awt.GridBagLayout()); + + org.openide.awt.Mnemonics.setLocalizedText(disclaimer, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.disclaimer.text")); // NOI18N + disclaimer.setVerticalAlignment(javax.swing.SwingConstants.TOP); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + add(disclaimer, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(purchaseFromLabel, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.purchaseFromLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 3); + add(purchaseFromLabel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(link, org.openide.util.NbBundle.getMessage(LicenseDisclaimerPanel.class, "LicenseDisclaimerPanel.link.text")); // NOI18N + link.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + link.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + linkMouseClicked(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(link, gridBagConstraints); + + javax.swing.GroupLayout spacerLayout = new javax.swing.GroupLayout(spacer); + spacer.setLayout(spacerLayout); + spacerLayout.setHorizontalGroup( + spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + spacerLayout.setVerticalGroup( + spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.weighty = 1.0; + add(spacer, gridBagConstraints); + }// </editor-fold>//GEN-END:initComponents + + private void linkMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_linkMouseClicked + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().browse(new URI(CHECKOUT_PAGE_URL)); + } catch (IOException | URISyntaxException e) { + LOGGER.log(Level.SEVERE, "Error opening link to: " + CHECKOUT_PAGE_URL, e); + } + } else { + LOGGER.log(Level.WARNING, "Desktop API is not supported. Link cannot be opened."); + } + }//GEN-LAST:event_linkMouseClicked + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties new file mode 100644 index 0000000000000000000000000000000000000000..bd06716288dd34cc480b2c4ae540220ef0714b54 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties @@ -0,0 +1,26 @@ + +# 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.title=Add a License... +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= +EULADialog.cancelButton.text=Cancel +EULADialog.acceptButton.text=Accept +EULADialog.title=Cyber Triage End User License Agreement 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 0000000000000000000000000000000000000000..58a1befb1fec3d8c24c56663ea033db027b5158c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED @@ -0,0 +1,58 @@ + +# 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.title=Add a License... +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=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html> +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=<html>User: {0}<br/>Email: {1}</html> +# {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... +EULADialog.cancelButton.text=Cancel +EULADialog.acceptButton.text=Accept +EULADialog.title=Cyber Triage End User License Agreement 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 0000000000000000000000000000000000000000..e7cd2743a0764cbcbf22a4cfe04867d489ebe5ae --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo"> + <Properties> + <Property name="defaultCloseOperation" type="int" value="2"/> + <Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/Bundle.properties" key="CTLicenseDialog.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="alwaysOnTop" type="boolean" value="true"/> + <Property name="resizable" type="boolean" value="false"/> + </Properties> + <SyntheticProperties> + <SyntheticProperty name="formSizePolicy" type="int" value="1"/> + <SyntheticProperty name="generateCenter" type="boolean" value="false"/> + </SyntheticProperties> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,122,0,0,1,-19"/> + </AuxValues> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Component class="javax.swing.JLabel" name="licenseNumberLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.licenseNumberLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="0" gridWidth="3" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="warningLabel"> + <Properties> + <Property name="foreground" type="java.awt.Color" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> + <Connection code="java.awt.Color.RED" type="code"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.warningLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[419, 36]"/> + </Property> + <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[419, 36]"/> + </Property> + <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[419, 36]"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="2" gridWidth="3" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Container class="javax.swing.JPanel" name="buttonPadding"> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + </Layout> + </Container> + <Component class="javax.swing.JButton" name="okButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.okButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="okButtonActionPerformed"/> + </Events> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="2" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JButton" name="cancelButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/> + </Events> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JTextField" name="licenseNumberTextField"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.licenseNumberTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="1" gridWidth="3" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + </SubComponents> +</Form> 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 0000000000000000000000000000000000000000..608ea6304006f80b9a14299975a12140e2cf7d6c --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java @@ -0,0 +1,196 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; + +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*[a-zA-Z0-9\\-]+?\\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=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html>" + }) + 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") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//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(); + }// </editor-fold>//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 0000000000000000000000000000000000000000..9f7f3b8bf6c9211f227045fbeb9c94ca09dec7db --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java @@ -0,0 +1,97 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; + +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.Files; +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 { + licenseFile.getParentFile().mkdirs(); + 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<LicenseResponse> loadLicenseResponse() { + Optional<LicenseResponse> toRet = Optional.empty(); + File licenseFile = getCTLicenseFile(); + if (licenseFile.exists() && 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<LicenseInfo> 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 0000000000000000000000000000000000000000..77361419b65f0831a21bdd6019e2c7d17309c199 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.form @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.8" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-109,0,0,1,-29"/> + </AuxValues> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Container class="javax.swing.JPanel" name="licenseInfoPanel"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="License Info"> + <ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </TitledBorder> + </Border> + </Property> + </Properties> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Component class="javax.swing.JLabel" name="licenseInfoMessageLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="licenseInfoUserLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoUserLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="licenseInfoExpiresLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoExpiresLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="licenseInfoIdLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoIdLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JButton" name="licenseInfoAddButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.licenseInfoAddButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="licenseInfoAddButtonActionPerformed"/> + </Events> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="12" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + </SubComponents> + </Container> + <Container class="javax.swing.JPanel" name="malwareScansPanel"> + <Properties> + <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> + <Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo"> + <TitledBorder title="Malware Scans"> + <ResourceString PropertyName="titleX" bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.malwareScansPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </TitledBorder> + </Border> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Component class="javax.swing.JLabel" name="malwareScansMessageLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.malwareScansMessageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="maxHashLookupsLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.maxHashLookupsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="maxFileUploadsLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="countersResetLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.countersResetLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="hashLookupsRemainingLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JLabel" name="fileUploadsRemainingLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTMalwareScannerOptionsPanel.fileUploadsRemainingLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + </SubComponents> + </Container> + </SubComponents> +</Form> 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 0000000000000000000000000000000000000000..564eeb7ebaa4551baa66808bbe0d6b660b2fedd7 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java @@ -0,0 +1,604 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; + +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.DecryptedLicenseResponse; +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.LicenseDecryptorUtil.InvalidLicenseException; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.io.IOException; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +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.SwingUtilities; +import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.core.UserPreferences; + +/** + * Options panel to be displayed in the CTOptionsPanel for settings regarding + * Cyber Triage Malware Scanner settings and license setup. + */ +@ServiceProvider(service = CTOptionsSubPanel.class) +public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel { + + private static final Logger logger = Logger.getLogger(CTMalwareScannerOptionsPanel.class.getName()); + + private static final DateTimeFormatter LICENSE_EXPIRES_FORMAT = DateTimeFormatter + .ofPattern("MMMM d, YYYY") + .withZone(ZoneId.of(UserPreferences.getInferredUserTimeZone())); + + private static final DateTimeFormatter MALWARE_SCANS_RESET_FORMAT = DateTimeFormatter + .ofPattern("MMM d, YYYY' at 'h:mma") + .withZone(ZoneId.of(UserPreferences.getInferredUserTimeZone())); + + 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; + + /** + * Main constructor. + */ + 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<LicenseInfo> 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()); + setMalwareScansDisplay(null, null); + 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); + } + + if (licenseInfo == null || licenseInfo.getDecryptedLicense() == null) { + setMalwareScansDisplay(null, null); + return; + } + + setMalwareScansDisplay(null, Bundle.CTOPtionsPanel_loadMalwareScansInfo_loading()); + + this.authTokenFetcher = new AuthTokenFetcher(licenseInfo.getDecryptedLicense()); + 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") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//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); + }// </editor-fold>//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=<html>User: {0}<br/>Email: {1}</html>", + "# {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( + this.licenseInfo.getDecryptedLicense().getExpirationDate() == null + ? "" + : LICENSE_EXPIRES_FORMAT.format(this.licenseInfo.getDecryptedLicense().getExpirationDate()))); + this.licenseInfoIdLabel.setVisible(true); + this.licenseInfoIdLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_id(StringUtils.defaultString(this.licenseInfo.getDecryptedLicense().getBoostLicenseId()))); + this.licenseInfoUserLabel.setVisible(true); + this.licenseInfoUserLabel.setText(Bundle.CTMalwareScannerOptionsPanel_licenseInfo_userInfo( + StringUtils.defaultString(this.licenseInfo.getDecryptedLicense().getCustomerName()), + StringUtils.defaultString(this.licenseInfo.getDecryptedLicense().getCustomerEmail()))); + } + + this.malwareScansPanel.setVisible(StringUtils.isNotBlank(this.authTokenMessage) || authTokenResponse != null); + + this.malwareScansMessageLabel.setVisible(StringUtils.isNotBlank(this.authTokenMessage)); + this.malwareScansMessageLabel.setText(this.authTokenMessage); + + if (authTokenResponse == null) { + 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(this.authTokenResponse.getResetDate() == null ? "" : MALWARE_SCANS_RESET_FORMAT.format(this.authTokenResponse.getResetDate()))); + 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; + } + + private void acceptEula(LicenseResponse licenseResponse) { + try { + final EULADialog eulaDialog = new EULADialog(WindowManager.getDefault().getMainWindow(), true); + eulaDialog.setLocationRelativeTo(this); + eulaDialog.setSize(eulaDialog.getPreferredSize()); + eulaDialog.setVisible(true); + + if (eulaDialog.isAcceptPressed()) { + // only indicate a change to save if we have accepted EULA for a license + this.licenseInfo = LicenseDecryptorUtil.getInstance().createLicenseInfo(licenseResponse); + this.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + } catch (IOException | InvalidLicenseException ex) { + 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 { + setLicenseDisplay(this.licenseInfo, null); + loadMalwareScansInfo(this.licenseInfo); + } + } + + @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<LicenseResponse, Void> { + + private final String licenseText; + + public LicenseFetcher(String licenseText) { + this.licenseText = licenseText; + } + + @Override + protected LicenseResponse doInBackground() throws Exception { + if (this.isCancelled()) { + return null; + } + return ctApiDAO.getLicenseInfo(licenseText); + } + + @Override + protected void done() { + try { + LicenseResponse licenseResponse = get(); + SwingUtilities.invokeLater(() -> acceptEula(licenseResponse)); + } catch (InterruptedException ex) { + // ignore cancellation; just load current license + setLicenseDisplay(licenseInfo, null); + loadMalwareScansInfo(licenseInfo); + } 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); + } + setLicenseDisplay(licenseInfo, null); + loadMalwareScansInfo(licenseInfo); + } finally { + synchronized (CTMalwareScannerOptionsPanel.this) { + CTMalwareScannerOptionsPanel.this.licenseFetcher = null; + } + } + } + } + + @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<AuthTokenResponse, Void> { + + private final DecryptedLicenseResponse decryptedLicense; + + public AuthTokenFetcher(DecryptedLicenseResponse decryptedLicense) { + this.decryptedLicense = decryptedLicense; + } + + @Override + protected AuthTokenResponse doInBackground() throws Exception { + if (this.isCancelled()) { + return null; + } + + return ctApiDAO.getAuthToken(decryptedLicense); + } + + @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/ctcloud/EULA.htm b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULA.htm new file mode 100644 index 0000000000000000000000000000000000000000..5b4989807b0666c425d2163aa6790e608da0881b --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULA.htm @@ -0,0 +1 @@ +<html><head><meta content="text/html; charset=UTF-8" http-equiv="content-type"><style type="text/css">@import url('https://themes.googleusercontent.com/fonts/css?kit=fpjTOVmNbO4Lz34iLyptLUXza5VhXqVC6o75Eld_V98');ol.lst-kix_list_7-0{list-style-type:none}.lst-kix_list_2-1>li{counter-increment:lst-ctn-kix_list_2-1}ol.lst-kix_list_9-0.start{counter-reset:lst-ctn-kix_list_9-0 0}ol.lst-kix_list_7-4.start{counter-reset:lst-ctn-kix_list_7-4 0}.lst-kix_list_5-0>li{counter-increment:lst-ctn-kix_list_5-0}.lst-kix_list_9-0>li{counter-increment:lst-ctn-kix_list_9-0}ol.lst-kix_list_2-3.start{counter-reset:lst-ctn-kix_list_2-3 0}ol.lst-kix_list_7-5{list-style-type:none}ol.lst-kix_list_7-6{list-style-type:none}ol.lst-kix_list_7-7{list-style-type:none}ol.lst-kix_list_7-8{list-style-type:none}ol.lst-kix_list_7-1{list-style-type:none}ol.lst-kix_list_7-2{list-style-type:none}ol.lst-kix_list_11-8.start{counter-reset:lst-ctn-kix_list_11-8 0}ol.lst-kix_list_7-3{list-style-type:none}ol.lst-kix_list_7-4{list-style-type:none}ol.lst-kix_list_5-3.start{counter-reset:lst-ctn-kix_list_5-3 0}ol.lst-kix_list_12-0.start{counter-reset:lst-ctn-kix_list_12-0 0}.lst-kix_list_4-3>li{counter-increment:lst-ctn-kix_list_4-3}ol.lst-kix_list_8-8.start{counter-reset:lst-ctn-kix_list_8-8 0}ol.lst-kix_list_10-4.start{counter-reset:lst-ctn-kix_list_10-4 0}.lst-kix_list_7-2>li{counter-increment:lst-ctn-kix_list_7-2}.lst-kix_list_5-0>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) ". "}.lst-kix_list_5-4>li{counter-increment:lst-ctn-kix_list_5-4}.lst-kix_list_1-4>li{counter-increment:lst-ctn-kix_list_1-4}ol.lst-kix_list_1-6.start{counter-reset:lst-ctn-kix_list_1-6 0}.lst-kix_list_5-3>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) ". "}ol.lst-kix_list_9-5.start{counter-reset:lst-ctn-kix_list_9-5 0}.lst-kix_list_5-2>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) ". "}.lst-kix_list_8-3>li{counter-increment:lst-ctn-kix_list_8-3}.lst-kix_list_5-1>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) ". "}.lst-kix_list_5-7>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) "." counter(lst-ctn-kix_list_5-4,decimal) "." counter(lst-ctn-kix_list_5-5,decimal) "." counter(lst-ctn-kix_list_5-6,decimal) "." counter(lst-ctn-kix_list_5-7,decimal) ". "}.lst-kix_list_5-6>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) "." counter(lst-ctn-kix_list_5-4,decimal) "." counter(lst-ctn-kix_list_5-5,decimal) "." counter(lst-ctn-kix_list_5-6,decimal) ". "}.lst-kix_list_5-8>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) "." counter(lst-ctn-kix_list_5-4,decimal) "." counter(lst-ctn-kix_list_5-5,decimal) "." counter(lst-ctn-kix_list_5-6,decimal) "." counter(lst-ctn-kix_list_5-7,decimal) "." counter(lst-ctn-kix_list_5-8,decimal) ". "}.lst-kix_list_9-4>li{counter-increment:lst-ctn-kix_list_9-4}.lst-kix_list_5-4>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) "." counter(lst-ctn-kix_list_5-4,decimal) ". "}.lst-kix_list_5-5>li:before{content:"" counter(lst-ctn-kix_list_5-0,decimal) "." counter(lst-ctn-kix_list_5-1,decimal) "." counter(lst-ctn-kix_list_5-2,decimal) "." counter(lst-ctn-kix_list_5-3,decimal) "." counter(lst-ctn-kix_list_5-4,decimal) "." counter(lst-ctn-kix_list_5-5,decimal) ". "}ol.lst-kix_list_12-5.start{counter-reset:lst-ctn-kix_list_12-5 0}.lst-kix_list_6-1>li:before{content:"o "}.lst-kix_list_6-3>li:before{content:"\0025cf "}.lst-kix_list_6-0>li:before{content:"\0025aa "}.lst-kix_list_6-4>li:before{content:"o "}.lst-kix_list_6-2>li:before{content:"\0025aa "}.lst-kix_list_2-5>li{counter-increment:lst-ctn-kix_list_2-5}.lst-kix_list_2-8>li{counter-increment:lst-ctn-kix_list_2-8}.lst-kix_list_6-8>li:before{content:"\0025aa "}.lst-kix_list_6-5>li:before{content:"\0025aa "}.lst-kix_list_6-7>li:before{content:"o "}.lst-kix_list_6-6>li:before{content:"\0025cf "}ol.lst-kix_list_10-6.start{counter-reset:lst-ctn-kix_list_10-6 0}.lst-kix_list_7-4>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) "." counter(lst-ctn-kix_list_7-4,decimal) ". "}.lst-kix_list_7-6>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) "." counter(lst-ctn-kix_list_7-4,decimal) "." counter(lst-ctn-kix_list_7-5,decimal) "." counter(lst-ctn-kix_list_7-6,decimal) ". "}.lst-kix_list_7-2>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) ". "}.lst-kix_list_7-6>li{counter-increment:lst-ctn-kix_list_7-6}.lst-kix_list_8-6>li{counter-increment:lst-ctn-kix_list_8-6}.lst-kix_list_12-6>li{counter-increment:lst-ctn-kix_list_12-6}ol.lst-kix_list_4-6.start{counter-reset:lst-ctn-kix_list_4-6 0}ol.lst-kix_list_9-7{list-style-type:none}ol.lst-kix_list_9-8{list-style-type:none}ol.lst-kix_list_9-3{list-style-type:none}ol.lst-kix_list_9-4{list-style-type:none}.lst-kix_list_5-7>li{counter-increment:lst-ctn-kix_list_5-7}ol.lst-kix_list_9-5{list-style-type:none}.lst-kix_list_7-8>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) "." counter(lst-ctn-kix_list_7-4,decimal) "." counter(lst-ctn-kix_list_7-5,decimal) "." counter(lst-ctn-kix_list_7-6,decimal) "." counter(lst-ctn-kix_list_7-7,decimal) "." counter(lst-ctn-kix_list_7-8,decimal) ". "}ol.lst-kix_list_9-6{list-style-type:none}.lst-kix_list_4-7>li{counter-increment:lst-ctn-kix_list_4-7}ol.lst-kix_list_9-0{list-style-type:none}ol.lst-kix_list_9-1{list-style-type:none}ol.lst-kix_list_9-2{list-style-type:none}ol.lst-kix_list_2-5.start{counter-reset:lst-ctn-kix_list_2-5 0}.lst-kix_list_9-8>li{counter-increment:lst-ctn-kix_list_9-8}.lst-kix_list_4-1>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) ". "}.lst-kix_list_4-3>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) ". "}.lst-kix_list_4-5>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) "." counter(lst-ctn-kix_list_4-4,decimal) "." counter(lst-ctn-kix_list_4-5,decimal) ". "}.lst-kix_list_1-8>li{counter-increment:lst-ctn-kix_list_1-8}.lst-kix_list_10-5>li{counter-increment:lst-ctn-kix_list_10-5}ol.lst-kix_list_1-4.start{counter-reset:lst-ctn-kix_list_1-4 0}ol.lst-kix_list_1-1.start{counter-reset:lst-ctn-kix_list_1-1 0}ol.lst-kix_list_4-4.start{counter-reset:lst-ctn-kix_list_4-4 0}ol.lst-kix_list_9-2.start{counter-reset:lst-ctn-kix_list_9-2 0}.lst-kix_list_9-3>li{counter-increment:lst-ctn-kix_list_9-3}.lst-kix_list_11-2>li{counter-increment:lst-ctn-kix_list_11-2}ol.lst-kix_list_2-8.start{counter-reset:lst-ctn-kix_list_2-8 0}ol.lst-kix_list_8-8{list-style-type:none}.lst-kix_list_12-3>li:before{content:"" counter(lst-ctn-kix_list_12-3,decimal) ". "}ol.lst-kix_list_7-6.start{counter-reset:lst-ctn-kix_list_7-6 0}ol.lst-kix_list_8-4{list-style-type:none}.lst-kix_list_12-1>li:before{content:"" counter(lst-ctn-kix_list_12-1,decimal) ". "}ol.lst-kix_list_8-5{list-style-type:none}ol.lst-kix_list_8-6{list-style-type:none}ol.lst-kix_list_8-7{list-style-type:none}ol.lst-kix_list_8-0{list-style-type:none}ol.lst-kix_list_8-1{list-style-type:none}ol.lst-kix_list_8-2{list-style-type:none}ol.lst-kix_list_8-3{list-style-type:none}.lst-kix_list_10-4>li{counter-increment:lst-ctn-kix_list_10-4}ol.lst-kix_list_5-8.start{counter-reset:lst-ctn-kix_list_5-8 0}.lst-kix_list_1-3>li{counter-increment:lst-ctn-kix_list_1-3}.lst-kix_list_12-5>li:before{content:"" counter(lst-ctn-kix_list_12-5,decimal) ". "}ol.lst-kix_list_12-2.start{counter-reset:lst-ctn-kix_list_12-2 0}.lst-kix_list_12-7>li:before{content:"" counter(lst-ctn-kix_list_12-7,decimal) ". "}.lst-kix_list_4-2>li{counter-increment:lst-ctn-kix_list_4-2}.lst-kix_list_5-1>li{counter-increment:lst-ctn-kix_list_5-1}ol.lst-kix_list_11-6{list-style-type:none}ol.lst-kix_list_11-7{list-style-type:none}ol.lst-kix_list_11-8{list-style-type:none}.lst-kix_list_1-1>li{counter-increment:lst-ctn-kix_list_1-1}.lst-kix_list_7-1>li{counter-increment:lst-ctn-kix_list_7-1}ol.lst-kix_list_11-2{list-style-type:none}ol.lst-kix_list_11-3{list-style-type:none}ol.lst-kix_list_2-6.start{counter-reset:lst-ctn-kix_list_2-6 0}.lst-kix_list_3-0>li:before{content:"\0025cf "}ol.lst-kix_list_11-4{list-style-type:none}ol.lst-kix_list_11-5{list-style-type:none}ol.lst-kix_list_11-0{list-style-type:none}ol.lst-kix_list_11-1{list-style-type:none}.lst-kix_list_4-0>li{counter-increment:lst-ctn-kix_list_4-0}.lst-kix_list_8-0>li{counter-increment:lst-ctn-kix_list_8-0}.lst-kix_list_10-0>li{counter-increment:lst-ctn-kix_list_10-0}.lst-kix_list_3-4>li:before{content:"o "}.lst-kix_list_3-3>li:before{content:"\0025cf "}.lst-kix_list_8-0>li:before{content:"" counter(lst-ctn-kix_list_8-0,upper-latin) ". "}.lst-kix_list_8-7>li:before{content:"" counter(lst-ctn-kix_list_8-7,lower-latin) ". "}.lst-kix_list_3-8>li:before{content:"\0025aa "}ol.lst-kix_list_10-7.start{counter-reset:lst-ctn-kix_list_10-7 0}.lst-kix_list_8-3>li:before{content:"" counter(lst-ctn-kix_list_8-3,decimal) ". "}.lst-kix_list_3-7>li:before{content:"o "}.lst-kix_list_8-4>li:before{content:"" counter(lst-ctn-kix_list_8-4,lower-latin) ". "}.lst-kix_list_10-2>li{counter-increment:lst-ctn-kix_list_10-2}ol.lst-kix_list_8-5.start{counter-reset:lst-ctn-kix_list_8-5 0}.lst-kix_list_11-1>li:before{content:"" counter(lst-ctn-kix_list_11-1,lower-latin) ". "}.lst-kix_list_11-0>li:before{content:"" counter(lst-ctn-kix_list_11-0,upper-latin) ". "}ol.lst-kix_list_9-3.start{counter-reset:lst-ctn-kix_list_9-3 0}.lst-kix_list_8-8>li:before{content:"" counter(lst-ctn-kix_list_8-8,lower-roman) ". "}ol.lst-kix_list_2-2{list-style-type:none}ol.lst-kix_list_2-3{list-style-type:none}ol.lst-kix_list_2-4{list-style-type:none}ol.lst-kix_list_7-2.start{counter-reset:lst-ctn-kix_list_7-2 0}ol.lst-kix_list_2-5{list-style-type:none}ol.lst-kix_list_2-0{list-style-type:none}ol.lst-kix_list_2-1{list-style-type:none}.lst-kix_list_4-8>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) "." counter(lst-ctn-kix_list_4-4,decimal) "." counter(lst-ctn-kix_list_4-5,decimal) "." counter(lst-ctn-kix_list_4-6,decimal) "." counter(lst-ctn-kix_list_4-7,decimal) "." counter(lst-ctn-kix_list_4-8,decimal) ". "}ol.lst-kix_list_12-5{list-style-type:none}ol.lst-kix_list_12-6{list-style-type:none}.lst-kix_list_4-7>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) "." counter(lst-ctn-kix_list_4-4,decimal) "." counter(lst-ctn-kix_list_4-5,decimal) "." counter(lst-ctn-kix_list_4-6,decimal) "." counter(lst-ctn-kix_list_4-7,decimal) ". "}ol.lst-kix_list_12-7{list-style-type:none}ol.lst-kix_list_12-8{list-style-type:none}ol.lst-kix_list_12-1{list-style-type:none}ol.lst-kix_list_12-2{list-style-type:none}ol.lst-kix_list_12-3{list-style-type:none}ol.lst-kix_list_12-4{list-style-type:none}ol.lst-kix_list_12-0{list-style-type:none}ol.lst-kix_list_4-8.start{counter-reset:lst-ctn-kix_list_4-8 0}.lst-kix_list_8-4>li{counter-increment:lst-ctn-kix_list_8-4}ol.lst-kix_list_10-8.start{counter-reset:lst-ctn-kix_list_10-8 0}ol.lst-kix_list_2-6{list-style-type:none}ol.lst-kix_list_2-7{list-style-type:none}ol.lst-kix_list_2-8{list-style-type:none}.lst-kix_list_11-3>li{counter-increment:lst-ctn-kix_list_11-3}ol.lst-kix_list_7-1.start{counter-reset:lst-ctn-kix_list_7-1 0}ol.lst-kix_list_8-6.start{counter-reset:lst-ctn-kix_list_8-6 0}ol.lst-kix_list_8-0.start{counter-reset:lst-ctn-kix_list_8-0 0}.lst-kix_list_7-0>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) ". "}.lst-kix_list_2-2>li{counter-increment:lst-ctn-kix_list_2-2}ol.lst-kix_list_4-7.start{counter-reset:lst-ctn-kix_list_4-7 0}ol.lst-kix_list_5-0{list-style-type:none}ol.lst-kix_list_5-1{list-style-type:none}ol.lst-kix_list_9-7.start{counter-reset:lst-ctn-kix_list_9-7 0}ol.lst-kix_list_5-2{list-style-type:none}.lst-kix_list_2-4>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) "." counter(lst-ctn-kix_list_2-4,decimal) ". "}.lst-kix_list_2-8>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) "." counter(lst-ctn-kix_list_2-4,decimal) "." counter(lst-ctn-kix_list_2-5,decimal) "." counter(lst-ctn-kix_list_2-6,decimal) "." counter(lst-ctn-kix_list_2-7,decimal) "." counter(lst-ctn-kix_list_2-8,decimal) ". "}.lst-kix_list_7-3>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) ". "}.lst-kix_list_10-0>li:before{content:"" counter(lst-ctn-kix_list_10-0,upper-latin) ". "}.lst-kix_list_9-7>li{counter-increment:lst-ctn-kix_list_9-7}ol.lst-kix_list_5-7{list-style-type:none}ol.lst-kix_list_5-8{list-style-type:none}ol.lst-kix_list_5-3{list-style-type:none}.lst-kix_list_8-7>li{counter-increment:lst-ctn-kix_list_8-7}ol.lst-kix_list_5-4{list-style-type:none}ol.lst-kix_list_5-5{list-style-type:none}ol.lst-kix_list_5-6{list-style-type:none}.lst-kix_list_7-7>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) "." counter(lst-ctn-kix_list_7-4,decimal) "." counter(lst-ctn-kix_list_7-5,decimal) "." counter(lst-ctn-kix_list_7-6,decimal) "." counter(lst-ctn-kix_list_7-7,decimal) ". "}ol.lst-kix_list_8-1.start{counter-reset:lst-ctn-kix_list_8-1 0}.lst-kix_list_9-5>li{counter-increment:lst-ctn-kix_list_9-5}.lst-kix_list_5-8>li{counter-increment:lst-ctn-kix_list_5-8}.lst-kix_list_10-4>li:before{content:"" counter(lst-ctn-kix_list_10-4,lower-latin) ". "}.lst-kix_list_10-8>li:before{content:"" counter(lst-ctn-kix_list_10-8,lower-roman) ". "}.lst-kix_list_4-0>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) ". "}.lst-kix_list_4-4>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) "." counter(lst-ctn-kix_list_4-4,decimal) ". "}ol.lst-kix_list_2-2.start{counter-reset:lst-ctn-kix_list_2-2 0}.lst-kix_list_9-3>li:before{content:"" counter(lst-ctn-kix_list_9-3,decimal) ". "}ol.lst-kix_list_7-0.start{counter-reset:lst-ctn-kix_list_7-0 0}.lst-kix_list_12-8>li{counter-increment:lst-ctn-kix_list_12-8}ol.lst-kix_list_4-0{list-style-type:none}ol.lst-kix_list_4-1{list-style-type:none}ol.lst-kix_list_4-2{list-style-type:none}ol.lst-kix_list_4-3{list-style-type:none}.lst-kix_list_9-7>li:before{content:"" counter(lst-ctn-kix_list_9-7,lower-latin) ". "}.lst-kix_list_2-4>li{counter-increment:lst-ctn-kix_list_2-4}.lst-kix_list_11-4>li:before{content:"" counter(lst-ctn-kix_list_11-4,lower-latin) ". "}.lst-kix_list_12-4>li:before{content:"" counter(lst-ctn-kix_list_12-4,decimal) ". "}ul.lst-kix_list_6-6{list-style-type:none}ul.lst-kix_list_6-7{list-style-type:none}.lst-kix_list_5-3>li{counter-increment:lst-ctn-kix_list_5-3}ul.lst-kix_list_6-4{list-style-type:none}ul.lst-kix_list_6-5{list-style-type:none}ul.lst-kix_list_6-8{list-style-type:none}ol.lst-kix_list_4-8{list-style-type:none}.lst-kix_list_7-4>li{counter-increment:lst-ctn-kix_list_7-4}.lst-kix_list_1-0>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) ". "}ol.lst-kix_list_4-4{list-style-type:none}ul.lst-kix_list_6-2{list-style-type:none}.lst-kix_list_11-8>li:before{content:"" counter(lst-ctn-kix_list_11-8,lower-roman) ". "}.lst-kix_list_12-3>li{counter-increment:lst-ctn-kix_list_12-3}ol.lst-kix_list_4-5{list-style-type:none}ul.lst-kix_list_6-3{list-style-type:none}ol.lst-kix_list_2-0.start{counter-reset:lst-ctn-kix_list_2-0 0}ol.lst-kix_list_4-6{list-style-type:none}ul.lst-kix_list_6-0{list-style-type:none}.lst-kix_list_12-0>li:before{content:"" counter(lst-ctn-kix_list_12-0,decimal) ". "}ol.lst-kix_list_4-7{list-style-type:none}ul.lst-kix_list_6-1{list-style-type:none}ol.lst-kix_list_8-4.start{counter-reset:lst-ctn-kix_list_8-4 0}.lst-kix_list_1-4>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) "." counter(lst-ctn-kix_list_1-4,decimal) ". "}.lst-kix_list_1-6>li{counter-increment:lst-ctn-kix_list_1-6}.lst-kix_list_10-7>li{counter-increment:lst-ctn-kix_list_10-7}.lst-kix_list_2-0>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) ". "}ol.lst-kix_list_2-1.start{counter-reset:lst-ctn-kix_list_2-1 0}ol.lst-kix_list_8-3.start{counter-reset:lst-ctn-kix_list_8-3 0}.lst-kix_list_11-5>li{counter-increment:lst-ctn-kix_list_11-5}.lst-kix_list_4-5>li{counter-increment:lst-ctn-kix_list_4-5}ol.lst-kix_list_9-8.start{counter-reset:lst-ctn-kix_list_9-8 0}.lst-kix_list_1-8>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) "." counter(lst-ctn-kix_list_1-4,decimal) "." counter(lst-ctn-kix_list_1-5,decimal) "." counter(lst-ctn-kix_list_1-6,decimal) "." counter(lst-ctn-kix_list_1-7,decimal) "." counter(lst-ctn-kix_list_1-8,decimal) ". "}.lst-kix_list_12-8>li:before{content:"" counter(lst-ctn-kix_list_12-8,decimal) ". "}.lst-kix_list_8-2>li{counter-increment:lst-ctn-kix_list_8-2}.lst-kix_list_4-1>li{counter-increment:lst-ctn-kix_list_4-1}ol.lst-kix_list_12-6.start{counter-reset:lst-ctn-kix_list_12-6 0}.lst-kix_list_8-1>li{counter-increment:lst-ctn-kix_list_8-1}ol.lst-kix_list_8-2.start{counter-reset:lst-ctn-kix_list_8-2 0}.lst-kix_list_7-0>li{counter-increment:lst-ctn-kix_list_7-0}.lst-kix_list_11-0>li{counter-increment:lst-ctn-kix_list_11-0}ol.lst-kix_list_1-5.start{counter-reset:lst-ctn-kix_list_1-5 0}ol.lst-kix_list_9-6.start{counter-reset:lst-ctn-kix_list_9-6 0}.lst-kix_list_2-3>li{counter-increment:lst-ctn-kix_list_2-3}ol.lst-kix_list_4-5.start{counter-reset:lst-ctn-kix_list_4-5 0}.lst-kix_list_1-2>li{counter-increment:lst-ctn-kix_list_1-2}ol.lst-kix_list_11-2.start{counter-reset:lst-ctn-kix_list_11-2 0}.lst-kix_list_5-2>li{counter-increment:lst-ctn-kix_list_5-2}.lst-kix_list_9-2>li{counter-increment:lst-ctn-kix_list_9-2}ol.lst-kix_list_8-7.start{counter-reset:lst-ctn-kix_list_8-7 0}.lst-kix_list_10-3>li{counter-increment:lst-ctn-kix_list_10-3}.lst-kix_list_12-1>li{counter-increment:lst-ctn-kix_list_12-1}ol.lst-kix_list_1-0.start{counter-reset:lst-ctn-kix_list_1-0 0}ol.lst-kix_list_4-0.start{counter-reset:lst-ctn-kix_list_4-0 0}li.li-bullet-2:before{margin-left:-21.6pt;white-space:nowrap;display:inline-block;min-width:21.6pt}ol.lst-kix_list_11-7.start{counter-reset:lst-ctn-kix_list_11-7 0}ol.lst-kix_list_2-4.start{counter-reset:lst-ctn-kix_list_2-4 0}ol.lst-kix_list_1-3{list-style-type:none}ol.lst-kix_list_1-4{list-style-type:none}.lst-kix_list_2-7>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) "." counter(lst-ctn-kix_list_2-4,decimal) "." counter(lst-ctn-kix_list_2-5,decimal) "." counter(lst-ctn-kix_list_2-6,decimal) "." counter(lst-ctn-kix_list_2-7,decimal) ". "}.lst-kix_list_2-7>li{counter-increment:lst-ctn-kix_list_2-7}ol.lst-kix_list_1-5{list-style-type:none}ol.lst-kix_list_1-6{list-style-type:none}ol.lst-kix_list_1-0{list-style-type:none}.lst-kix_list_2-5>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) "." counter(lst-ctn-kix_list_2-4,decimal) "." counter(lst-ctn-kix_list_2-5,decimal) ". "}ol.lst-kix_list_1-1{list-style-type:none}ol.lst-kix_list_1-2{list-style-type:none}ol.lst-kix_list_10-3.start{counter-reset:lst-ctn-kix_list_10-3 0}ol.lst-kix_list_9-4.start{counter-reset:lst-ctn-kix_list_9-4 0}ul.lst-kix_list_3-7{list-style-type:none}ul.lst-kix_list_3-8{list-style-type:none}.lst-kix_list_10-1>li:before{content:"" counter(lst-ctn-kix_list_10-1,lower-latin) ". "}ul.lst-kix_list_3-1{list-style-type:none}ul.lst-kix_list_3-2{list-style-type:none}.lst-kix_list_7-7>li{counter-increment:lst-ctn-kix_list_7-7}ul.lst-kix_list_3-0{list-style-type:none}ol.lst-kix_list_4-3.start{counter-reset:lst-ctn-kix_list_4-3 0}ol.lst-kix_list_1-7{list-style-type:none}ul.lst-kix_list_3-5{list-style-type:none}ol.lst-kix_list_1-8{list-style-type:none}ul.lst-kix_list_3-6{list-style-type:none}ul.lst-kix_list_3-3{list-style-type:none}ul.lst-kix_list_3-4{list-style-type:none}.lst-kix_list_10-7>li:before{content:"" counter(lst-ctn-kix_list_10-7,lower-latin) ". "}.lst-kix_list_7-8>li{counter-increment:lst-ctn-kix_list_7-8}.lst-kix_list_10-5>li:before{content:"" counter(lst-ctn-kix_list_10-5,lower-roman) ". "}li.li-bullet-1:before{margin-left:-21.6pt;white-space:nowrap;display:inline-block;min-width:21.6pt}.lst-kix_list_10-3>li:before{content:"" counter(lst-ctn-kix_list_10-3,decimal) ". "}.lst-kix_list_2-6>li{counter-increment:lst-ctn-kix_list_2-6}ol.lst-kix_list_7-3.start{counter-reset:lst-ctn-kix_list_7-3 0}.lst-kix_list_11-7>li{counter-increment:lst-ctn-kix_list_11-7}.lst-kix_list_9-2>li:before{content:"" counter(lst-ctn-kix_list_9-2,lower-roman) ". "}ol.lst-kix_list_5-7.start{counter-reset:lst-ctn-kix_list_5-7 0}.lst-kix_list_12-5>li{counter-increment:lst-ctn-kix_list_12-5}.lst-kix_list_5-5>li{counter-increment:lst-ctn-kix_list_5-5}.lst-kix_list_9-0>li:before{content:"" counter(lst-ctn-kix_list_9-0,upper-latin) ". "}ol.lst-kix_list_10-7{list-style-type:none}.lst-kix_list_9-6>li:before{content:"" counter(lst-ctn-kix_list_9-6,decimal) ". "}ol.lst-kix_list_10-8{list-style-type:none}ol.lst-kix_list_10-3{list-style-type:none}.lst-kix_list_9-4>li:before{content:"" counter(lst-ctn-kix_list_9-4,lower-latin) ". "}ol.lst-kix_list_10-4{list-style-type:none}ol.lst-kix_list_10-5{list-style-type:none}.lst-kix_list_11-3>li:before{content:"" counter(lst-ctn-kix_list_11-3,decimal) ". "}ol.lst-kix_list_10-6{list-style-type:none}ol.lst-kix_list_10-0{list-style-type:none}ol.lst-kix_list_10-1{list-style-type:none}ol.lst-kix_list_1-3.start{counter-reset:lst-ctn-kix_list_1-3 0}ol.lst-kix_list_10-2{list-style-type:none}ol.lst-kix_list_12-1.start{counter-reset:lst-ctn-kix_list_12-1 0}ol.lst-kix_list_1-2.start{counter-reset:lst-ctn-kix_list_1-2 0}.lst-kix_list_11-5>li:before{content:"" counter(lst-ctn-kix_list_11-5,lower-roman) ". "}.lst-kix_list_9-8>li:before{content:"" counter(lst-ctn-kix_list_9-8,lower-roman) ". "}.lst-kix_list_1-1>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) ". "}.lst-kix_list_11-7>li:before{content:"" counter(lst-ctn-kix_list_11-7,lower-latin) ". "}.lst-kix_list_8-5>li{counter-increment:lst-ctn-kix_list_8-5}.lst-kix_list_1-3>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) ". "}ol.lst-kix_list_10-5.start{counter-reset:lst-ctn-kix_list_10-5 0}.lst-kix_list_4-8>li{counter-increment:lst-ctn-kix_list_4-8}.lst-kix_list_1-7>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) "." counter(lst-ctn-kix_list_1-4,decimal) "." counter(lst-ctn-kix_list_1-5,decimal) "." counter(lst-ctn-kix_list_1-6,decimal) "." counter(lst-ctn-kix_list_1-7,decimal) ". "}ol.lst-kix_list_2-7.start{counter-reset:lst-ctn-kix_list_2-7 0}.lst-kix_list_1-5>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) "." counter(lst-ctn-kix_list_1-4,decimal) "." counter(lst-ctn-kix_list_1-5,decimal) ". "}ol.lst-kix_list_9-1.start{counter-reset:lst-ctn-kix_list_9-1 0}.lst-kix_list_5-6>li{counter-increment:lst-ctn-kix_list_5-6}ol.lst-kix_list_7-5.start{counter-reset:lst-ctn-kix_list_7-5 0}.lst-kix_list_2-1>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) ". "}.lst-kix_list_2-3>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) ". "}.lst-kix_list_11-8>li{counter-increment:lst-ctn-kix_list_11-8}.lst-kix_list_9-1>li{counter-increment:lst-ctn-kix_list_9-1}ol.lst-kix_list_7-7.start{counter-reset:lst-ctn-kix_list_7-7 0}.lst-kix_list_3-1>li:before{content:"o "}.lst-kix_list_3-2>li:before{content:"\0025aa "}.lst-kix_list_8-1>li:before{content:"" counter(lst-ctn-kix_list_8-1,lower-latin) ". "}ol.lst-kix_list_1-8.start{counter-reset:lst-ctn-kix_list_1-8 0}.lst-kix_list_8-2>li:before{content:"" counter(lst-ctn-kix_list_8-2,lower-roman) ". "}.lst-kix_list_3-5>li:before{content:"\0025aa "}.lst-kix_list_12-0>li{counter-increment:lst-ctn-kix_list_12-0}ol.lst-kix_list_12-3.start{counter-reset:lst-ctn-kix_list_12-3 0}ol.lst-kix_list_11-5.start{counter-reset:lst-ctn-kix_list_11-5 0}.lst-kix_list_8-5>li:before{content:"" counter(lst-ctn-kix_list_8-5,lower-roman) ". "}.lst-kix_list_11-1>li{counter-increment:lst-ctn-kix_list_11-1}.lst-kix_list_8-6>li:before{content:"" counter(lst-ctn-kix_list_8-6,decimal) ". "}.lst-kix_list_2-0>li{counter-increment:lst-ctn-kix_list_2-0}.lst-kix_list_3-6>li:before{content:"\0025cf "}ol.lst-kix_list_5-0.start{counter-reset:lst-ctn-kix_list_5-0 0}.lst-kix_list_11-2>li:before{content:"" counter(lst-ctn-kix_list_11-2,lower-roman) ". "}ol.lst-kix_list_4-2.start{counter-reset:lst-ctn-kix_list_4-2 0}ol.lst-kix_list_11-6.start{counter-reset:lst-ctn-kix_list_11-6 0}ol.lst-kix_list_12-4.start{counter-reset:lst-ctn-kix_list_12-4 0}ol.lst-kix_list_10-1.start{counter-reset:lst-ctn-kix_list_10-1 0}.lst-kix_list_4-4>li{counter-increment:lst-ctn-kix_list_4-4}ol.lst-kix_list_5-6.start{counter-reset:lst-ctn-kix_list_5-6 0}ol.lst-kix_list_4-1.start{counter-reset:lst-ctn-kix_list_4-1 0}.lst-kix_list_7-3>li{counter-increment:lst-ctn-kix_list_7-3}ol.lst-kix_list_7-8.start{counter-reset:lst-ctn-kix_list_7-8 0}.lst-kix_list_12-4>li{counter-increment:lst-ctn-kix_list_12-4}ol.lst-kix_list_11-0.start{counter-reset:lst-ctn-kix_list_11-0 0}ol.lst-kix_list_10-2.start{counter-reset:lst-ctn-kix_list_10-2 0}.lst-kix_list_12-7>li{counter-increment:lst-ctn-kix_list_12-7}ol.lst-kix_list_5-5.start{counter-reset:lst-ctn-kix_list_5-5 0}.lst-kix_list_2-6>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) "." counter(lst-ctn-kix_list_2-3,decimal) "." counter(lst-ctn-kix_list_2-4,decimal) "." counter(lst-ctn-kix_list_2-5,decimal) "." counter(lst-ctn-kix_list_2-6,decimal) ". "}.lst-kix_list_7-1>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) ". "}.lst-kix_list_7-5>li:before{content:"" counter(lst-ctn-kix_list_7-0,decimal) "." counter(lst-ctn-kix_list_7-1,decimal) "." counter(lst-ctn-kix_list_7-2,decimal) "." counter(lst-ctn-kix_list_7-3,decimal) "." counter(lst-ctn-kix_list_7-4,decimal) "." counter(lst-ctn-kix_list_7-5,decimal) ". "}.lst-kix_list_9-6>li{counter-increment:lst-ctn-kix_list_9-6}ol.lst-kix_list_5-4.start{counter-reset:lst-ctn-kix_list_5-4 0}ol.lst-kix_list_11-1.start{counter-reset:lst-ctn-kix_list_11-1 0}ol.lst-kix_list_5-1.start{counter-reset:lst-ctn-kix_list_5-1 0}.lst-kix_list_10-6>li{counter-increment:lst-ctn-kix_list_10-6}.lst-kix_list_11-6>li{counter-increment:lst-ctn-kix_list_11-6}.lst-kix_list_1-7>li{counter-increment:lst-ctn-kix_list_1-7}ol.lst-kix_list_10-0.start{counter-reset:lst-ctn-kix_list_10-0 0}.lst-kix_list_7-5>li{counter-increment:lst-ctn-kix_list_7-5}.lst-kix_list_11-4>li{counter-increment:lst-ctn-kix_list_11-4}.lst-kix_list_10-2>li:before{content:"" counter(lst-ctn-kix_list_10-2,lower-roman) ". "}.lst-kix_list_4-6>li{counter-increment:lst-ctn-kix_list_4-6}ol.lst-kix_list_1-7.start{counter-reset:lst-ctn-kix_list_1-7 0}.lst-kix_list_1-5>li{counter-increment:lst-ctn-kix_list_1-5}.lst-kix_list_4-2>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) ". "}.lst-kix_list_4-6>li:before{content:"" counter(lst-ctn-kix_list_4-0,decimal) "." counter(lst-ctn-kix_list_4-1,decimal) "." counter(lst-ctn-kix_list_4-2,decimal) "." counter(lst-ctn-kix_list_4-3,decimal) "." counter(lst-ctn-kix_list_4-4,decimal) "." counter(lst-ctn-kix_list_4-5,decimal) "." counter(lst-ctn-kix_list_4-6,decimal) ". "}.lst-kix_list_10-8>li{counter-increment:lst-ctn-kix_list_10-8}.lst-kix_list_10-6>li:before{content:"" counter(lst-ctn-kix_list_10-6,decimal) ". "}.lst-kix_list_9-1>li:before{content:"" counter(lst-ctn-kix_list_9-1,lower-latin) ". "}ol.lst-kix_list_12-7.start{counter-reset:lst-ctn-kix_list_12-7 0}.lst-kix_list_12-2>li{counter-increment:lst-ctn-kix_list_12-2}.lst-kix_list_9-5>li:before{content:"" counter(lst-ctn-kix_list_9-5,lower-roman) ". "}.lst-kix_list_12-2>li:before{content:"" counter(lst-ctn-kix_list_12-2,decimal) ". "}ol.lst-kix_list_12-8.start{counter-reset:lst-ctn-kix_list_12-8 0}.lst-kix_list_11-6>li:before{content:"" counter(lst-ctn-kix_list_11-6,decimal) ". "}ol.lst-kix_list_11-3.start{counter-reset:lst-ctn-kix_list_11-3 0}.lst-kix_list_1-2>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) ". "}.lst-kix_list_10-1>li{counter-increment:lst-ctn-kix_list_10-1}.lst-kix_list_1-0>li{counter-increment:lst-ctn-kix_list_1-0}.lst-kix_list_8-8>li{counter-increment:lst-ctn-kix_list_8-8}.lst-kix_list_1-6>li:before{content:"" counter(lst-ctn-kix_list_1-0,decimal) "." counter(lst-ctn-kix_list_1-1,decimal) "." counter(lst-ctn-kix_list_1-2,decimal) "." counter(lst-ctn-kix_list_1-3,decimal) "." counter(lst-ctn-kix_list_1-4,decimal) "." counter(lst-ctn-kix_list_1-5,decimal) "." counter(lst-ctn-kix_list_1-6,decimal) ". "}li.li-bullet-0:before{margin-left:-18pt;white-space:nowrap;display:inline-block;min-width:18pt}.lst-kix_list_12-6>li:before{content:"" counter(lst-ctn-kix_list_12-6,decimal) ". "}ol.lst-kix_list_11-4.start{counter-reset:lst-ctn-kix_list_11-4 0}.lst-kix_list_2-2>li:before{content:"" counter(lst-ctn-kix_list_2-0,decimal) "." counter(lst-ctn-kix_list_2-1,decimal) "." counter(lst-ctn-kix_list_2-2,decimal) ". "}ol.lst-kix_list_5-2.start{counter-reset:lst-ctn-kix_list_5-2 0}ol{margin:0;padding:0}table td,table th{padding:0}.c13{-webkit-text-decoration-skip:none;color:#000000;font-weight:700;text-decoration:underline;vertical-align:baseline;text-decoration-skip-ink:none;font-size:10pt;font-family:"Times New Roman";font-style:normal}.c9{-webkit-text-decoration-skip:none;color:#000000;font-weight:400;text-decoration:underline;vertical-align:baseline;text-decoration-skip-ink:none;font-size:10pt;font-family:"Times New Roman";font-style:normal}.c12{margin-left:36pt;padding-top:6pt;padding-left:3.6pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:justify}.c1{color:#000000;font-weight:700;text-decoration:none;vertical-align:baseline;font-size:10pt;font-family:"Times New Roman";font-style:normal}.c8{margin-left:22.5pt;padding-top:6pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:justify}.c3{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:10pt;font-family:"Times New Roman";font-style:normal}.c2{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:12pt;font-family:"Calibri";font-style:normal}.c11{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:11pt;font-family:"Calibri";font-style:normal}.c10{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:12pt;font-family:"Times New Roman";font-style:normal}.c15{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:10pt;font-family:"Times New Roman";font-style:italic}.c23{color:#00a6dd;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:12pt;font-family:"Calibri";font-style:normal}.c28{color:#00a6dd;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:14pt;font-family:"Calibri";font-style:normal}.c27{margin-left:36pt;padding-top:6pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:left}.c31{color:#000000;font-weight:400;text-decoration:none;vertical-align:baseline;font-size:8pt;font-family:"Calibri";font-style:normal}.c29{padding-top:0pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:center}.c25{padding-top:0pt;padding-bottom:10pt;line-height:1.1500000000000001;orphans:2;widows:2;text-align:center}.c30{padding-top:6pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:left}.c0{padding-top:0pt;padding-bottom:10pt;line-height:1.1500000000000001;orphans:2;widows:2;text-align:left}.c16{padding-top:0pt;padding-bottom:0pt;line-height:1.1500000000000001;orphans:2;widows:2;text-align:left}.c6{padding-top:0pt;padding-bottom:0pt;line-height:1.1500000000000001;orphans:2;widows:2;text-align:center}.c32{padding-top:6pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:justify}.c18{padding-top:0pt;padding-bottom:0pt;line-height:1.0;orphans:2;widows:2;text-align:left}.c21{-webkit-text-decoration-skip:none;color:#0000ff;text-decoration:underline;vertical-align:baseline;text-decoration-skip-ink:none;font-style:normal}.c26{font-size:10pt;font-family:"Times New Roman";font-weight:700}.c17{font-size:10pt;font-family:"Times New Roman";font-weight:400}.c5{background-color:#ffffff;max-width:468pt;padding:72pt 72pt 72pt 72pt}.c19{margin-left:22.5pt;padding-left:0pt}.c7{padding:0;margin:0}.c24{color:inherit;text-decoration:inherit}.c4{height:11pt}.c22{margin-left:18pt}.c20{padding-left:0pt}.c14{background-color:#ffff00}.title{padding-top:24pt;color:#000000;font-weight:700;font-size:36pt;padding-bottom:6pt;font-family:"Calibri";line-height:1.1500000000000001;page-break-after:avoid;orphans:2;widows:2;text-align:left}.subtitle{padding-top:18pt;color:#666666;font-size:24pt;padding-bottom:4pt;font-family:"Georgia";line-height:1.1500000000000001;page-break-after:avoid;font-style:italic;orphans:2;widows:2;text-align:left}li{color:#000000;font-size:11pt;font-family:"Calibri"}p{margin:0;color:#000000;font-size:11pt;font-family:"Calibri"}h1{padding-top:0pt;color:#000000;font-weight:700;font-size:24pt;padding-bottom:0pt;font-family:"Times New Roman";line-height:1.0;orphans:2;widows:2;text-align:left}h2{padding-top:0pt;color:#000000;font-weight:700;font-size:18pt;padding-bottom:0pt;font-family:"Times New Roman";line-height:1.0;orphans:2;widows:2;text-align:left}h3{padding-top:14pt;color:#000000;font-weight:700;font-size:14pt;padding-bottom:4pt;font-family:"Calibri";line-height:1.1500000000000001;page-break-after:avoid;orphans:2;widows:2;text-align:left}h4{padding-top:12pt;color:#000000;font-weight:700;font-size:12pt;padding-bottom:2pt;font-family:"Calibri";line-height:1.1500000000000001;page-break-after:avoid;orphans:2;widows:2;text-align:left}h5{padding-top:11pt;color:#000000;font-weight:700;font-size:11pt;padding-bottom:2pt;font-family:"Calibri";line-height:1.1500000000000001;page-break-after:avoid;orphans:2;widows:2;text-align:left}h6{padding-top:10pt;color:#000000;font-weight:700;font-size:10pt;padding-bottom:2pt;font-family:"Calibri";line-height:1.1500000000000001;page-break-after:avoid;orphans:2;widows:2;text-align:left}</style></head><body class="c5 doc-content" style="max-width: unset;padding: 0pt 25pt 0pt 0pt;"><div><p class="c18 c4"><span class="c11"></span></p></div><p class="c6"><span class="c1">CYBER TRIAGE</span></p><p class="c6"><span class="c1">END USER LICENSE AGREEMENT</span></p><p class="c29"><span class="c11">v. 1.1</span><span>7</span><span class="c11">, 1/202</span><span>3</span></p><p class="c4 c6"><span class="c1"></span></p><p class="c16 c4"><span class="c3"></span></p><p class="c16 c22"><span class="c17">BASISTECH LLC, A DELAWARE LIMITED LIABILITY COMPANY HAVING ITS MAIN OFFICE AT 1070 BROADWAY, SOMERVILLE,, MASSACHUSETTS 02144 (“</span><span class="c26">BASIS</span><span class="c17">”) IS WILLING TO LICENSE THE SOFTWARE (DEFINED BELOW) TO YOU, AS CUSTOMER (“</span><span class="c26">YOU</span><span class="c17">”) ONLY IF YOU ACCEPT ALL TERMS AND CONDITIONS CONTAINED IN THIS END USER LICENSE AGREEMENT (“</span><span class="c26">AGREEMENT</span><span class="c3">”). PLEASE READ THE TERMS AND CONDITIONS CAREFULLY. BY CLICKING OR CHECKING ANY “I ACCEPT,” “I AGREE” OR OTHER SIMILAR BUTTON/CHECK­BOX, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS STATED IN THIS AGREEMENT. IF YOU ARE ENTERING INTO THIS AGREEMENT ON BEHALF OF A COMPANY OR OTHER LEGAL ENTITY, YOU REPRESENT THAT YOU HAVE THE AUTHORITY TO BIND SUCH ENTITY TO THESE TERMS AND CONDITIONS, IN WHICH CASE THE TERMS “YOU” OR “YOUR” SHALL REFER TO SUCH ENTITY. IF YOU DO NOT AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT, DO NOT CLICK OR CHECK ANY “I ACCEPT,” “I AGREE” OR OTHER SIMILAR BUTTON/CHECK­BOX.</span></p><ol class="c7 lst-kix_list_4-0 start" start="1"><li class="c8 c20 li-bullet-0"><span class="c13">SOFTWARE LICENSE</span><span class="c3">. </span></li></ol><ol class="c7 lst-kix_list_4-1 start" start="1"><li class="c12 li-bullet-1"><span class="c15">Software</span><span class="c3">. This Agreement applies to the Basis Cyber Triage software (“</span><span class="c1">Software</span><span class="c3">”) provided to You by Basis with this Agreement, and related user/operating documentation (“</span><span class="c1">Documentation</span><span class="c3">”). The Software includes free features (“</span><span class="c1">Free Features</span><span class="c3">”), as well as features that require payment of license fees (“</span><span class="c1">Paid Features</span><span class="c3">”). Each copy of the Software shall have a version number and a release date (the “</span><span class="c1">Release Date</span><span class="c3">”). </span></li><li class="c12 li-bullet-2"><span class="c15">Trial License. </span><span class="c3">If You have downloaded a copy of the Software for the first time, and You have not purchased a paid license for Software (directly or via a reseller),, Basis grants to You a non-exclusive trial license to use the Software and Documentation for a trial period of seven (7) days (the “</span><span class="c1">Trial Period</span><span class="c3">”), commencing on Your download of the Software (the “</span><span class="c1">Start Date</span><span class="c3">”). During the Trial Period, You will be able and permitted to use all Free Features and Paid Features of the Software, subject to compliance with the terms of this Agreement, provided that the Trial License granted to You will be for use of object code only. Upon submission of Your written request, Basis may (but shall have no obligation to) provide You with a special license key to extend the Trial Period for a mutually agreed period. At the end of the Trial Period (including any agreed extension thereof), Your access to and right to use the Paid Features will automatically terminate, unless You have purchased a license for the Paid Features from Basis.</span></li><li class="c12 li-bullet-2"><span class="c15">Free Features License. </span><span class="c3">Following the Trial Period, or if You download a copy of the Software not for the first time and have not purchased a paid license for Software, Basis grants to You a non-exclusive license to use the Free Features of the Software only (“</span><span class="c1">Free License</span><span class="c3">”), and the Documentation, subject to the terms and conditions of this Agreement, commencing upon termination of the Trial Period and/or Your free download of the Software (the “</span><span class="c1">Free License Start Date</span><span class="c3">”) and terminating on the first anniversary of the applicable Release Date (unless earlier terminated pursuant to the terms of this Agreement), provided that the license granted to You will be for use of object code only. Upon termination of Your Free License, You shall have no right to use the Software unless You have purchased a paid license for the Software, or You have received a new Free License from Basis. </span></li><li class="c12 li-bullet-1"><span class="c15">Paid License Grant</span><span class="c3">. If You have purchased a paid license from Basis (directly or via a reseller), Basis grants to You a non-exclusive license to use the Software and Documentation, including the Free Features and the Paid Features (“</span><span class="c1">Paid License</span><span class="c3">”), commencing on delivery of a production license key for the Paid License (“</span><span class="c1">Paid License Start Date</span><span class="c3">”), subject to the terms and conditions of this Agreement.</span><span class="c31">.</span><span class="c3"> </span></li><li class="c12 li-bullet-1"><span class="c15">Paid License Term</span><span class="c3">: The license term for a Paid License, which may be annual or perpetual, shall be determined by the specific Software product that You have purchased, as set forth in an applicable quote and as described on the following webpage: </span><span class="c17 c21"><a class="c24" href="https://www.google.com/url?q=https://www.cybertriage.com/product-sku/&sa=D&source=editors&ust=1673620076055313&usg=AOvVaw2q7fo0WG-l5_e9O--CwY9o"><br>https://www.cybertriage.com/product-sku/</a></span><span class="c3"> (each of the products and services identified by a separate SKU are referred to herein as “Products”). </span></li><li class="c12 li-bullet-2"><span class="c15">Delivery/Acceptance</span><span class="c3">. All Software provided under this Agreement shall be deemed to have been accepted by You upon Your download of the Software or delivery of the applicable license key, whichever occurs first. A production license key for a Paid License shall be delivered/offered for download promptly </span><span class="c9">after</span><span class="c3"> Basis receives payment of the applicable license fees. </span></li><li class="c12 li-bullet-2"><span class="c15">Copies. </span><span class="c3">All copies of the Software are subject to the provisions of this Agreement, and all titles trademarks, and copyright and restricted rights notices shall be reproduced in any copies. Copying of the Documentation is permitted for internal use only. </span></li><li class="c12 li-bullet-2"><span class="c15">Ownership. </span><span class="c3">You acquire only the right to use the Software; You do not acquire any rights of ownership. All rights, title, and interest in the Software and feedback from You regarding the Software shall at all times remain the property of Basis (or, as applicable, Basis's third party providers). Basis reserves all rights not expressly granted herein.</span></li><li class="c12 li-bullet-2"><span class="c15">Optional License to Hosted Scanning Service.</span><span class="c3"> The Paid Features include an opt-in module for a hosted scanning service (“</span><span class="c1">Scanning Service</span><span class="c3">”) provided by Reversing Labs International, GMBH (“ReversingLabs”). The Scanning Service allows You to upload files and hashes and have them scanned for malware, with the results being publically available. You must have a valid Trial License or Paid License to access and use the Scanning Service. </span><span class="c3">YOUR USE OF THE SCANNING SERVICE IS OPTIONAL AND UNLESS OTHERWISE AGREED TO IN WRITING BY BASIS, YOU WILL HAVE AN HOURLY OR DAILY LIMIT OF FILE SCANS AND HASH SCANS AS DESCRIBED BY THE APPLICABLE PRODUCT. TRIAL LICENSES ARE SUBJECT TO DAILY OR HOURLY LIMITS APPLIED AT BASIS’S DISCRETION. EXCEPT IN THE CASE OF A TRIAL LICENSE, YOUR SUBSCRIPTION PERIOD FOR THE SCANNING SERVICE IS AS DESCRIBED BY THE APPLICABLE PRODUCT. IF YOU USE THE SCANNING SERVICE, YOU WILL BE DEEMED AN END-USER OF THE SCANNING SERVICE WHO’S PERMISSION TO USE THE SCANNING SERVICE IS SPONSORED BY BASIS, THROUGH BASIS’S REVERSINGLABS ACCOUNT, AND YOU AGREE TO COMPLY WITH THE TERMS OF THE REVERSINGLABS END-USER SOFTWARE LICENSE AGREEMENT, A COPY OF WHICH IS ATTACHED AS EXHIBIT A TO THIS AGREEMENT (THE “SCANNING SERVICE EULA”), AS SUCH TERMS APPLY TO END-USERS, IF YOU DO NOT WANT TO UPLOAD YOUR FILES OR HASHES, THEN DO NOT USE THE </span><span class="c3">SCANNING SERVICE.</span></li><li class="c12 li-bullet-2"><span class="c15">Information Stored by Basis.</span><span class="c3"> Basis may retain and store an anonymized list of the hash values uploaded by each End-User, for purposes of improving the analytics available to users of the Software (e.g., to provide statistics on the frequency with which a particular hash value is uploaded). Such information may be stored on a third-party hosted cloud server, controlled by Basis. The foregoing server may also store the following information: (a) License ID numbers (without associating such numbers with named End-Users), for authentication purposes; and (b) the daily number of hash lookups performed by each License ID, for purposes of enforcing license limits. Basis may also retain and store anonymized analytical results. Such results will include the count and type of items scored as “Suspicious” and “Bad”, for purposes of identifying errors in the Software. By using the Software, You agree to the storage of such information by Basis.</span></li></ol><ol class="c7 lst-kix_list_4-0" start="2"><li class="c8 c20 li-bullet-0"><span class="c13">OBLIGATIONS</span><span class="c3">.</span></li></ol><ol class="c7 lst-kix_list_4-1 start" start="1"><li class="c12 li-bullet-2"><span class="c15">User Limit. </span><span class="c3">Each Paid License is limited to use by a single individual. Any use of the Software provided under this Agreement by more than one user is a material breach of this Agreement. If you have a Trial License or a Free License, your license is limited to use by the individual who downloaded the Software. </span><span class="c3 c14"> </span><span class="c3"> </span></li><li class="c12 li-bullet-2"><span class="c15">Usage Restrictions</span><span class="c3">. You shall not: (A) sublicense or redistribute the Software; (B) use the Software in a manner that is contrary to applicable law or in violation of any third party rights of privacy or intellectual property rights; or (C) translate, create a derivative work of, reverse engineer, reverse assemble, dissemble, or decompile the Software or any part thereof or otherwise attempt to discover any source code or modify or adapt the Software in any manner or form unless expressly allowed in the Documentation or under applicable law (solely for the purpose of achieving interoperability). You are responsible for all use of the Software and You shall cause Your users to be in compliance with this Agreement. </span></li></ol><ol class="c7 lst-kix_list_4-0" start="3"><li class="c8 c20 li-bullet-0"><span class="c13">FEES AND PAYMENT</span><span class="c3">. If You purchased a Paid License, You shall pay the license fees for the applicable Product as specified by Basis. All fees are non-refundable. </span></li><li class="c8 c20 li-bullet-0"><span class="c13">TERM AND TERMINATION</span><span class="c3">. </span></li></ol><ol class="c7 lst-kix_list_4-1 start" start="1"><li class="c12 li-bullet-1"><span class="c15">Agreement Term</span><span class="c3">. This Agreement shall remain in effect for as long as you have either a Free License or a Paid License from Basis (“</span><span class="c1">Agreement Term</span><span class="c3">”), unless earlier terminated in accordance with this Section. </span></li><li class="c12 li-bullet-2"><span class="c15">Termination for Breach</span><span class="c3">. Basis may terminate this Agreement effective immediately upon written notice to You if You breach any provision of this Agreement or of the Scanning Service EULA, and if such breach is curable, fail to cure within thirty (30) days after receipt of Basis’s written notice thereof. </span></li><li class="c12 li-bullet-2"><span class="c15">Termination of Scanning Service</span><span class="c3">. If as a result of a change in the relationship between Basis and the vendor of the Scanning Service, Basis terminates, replaces or changes vendors, Basis will use commercially reasonable efforts to (A) allow You to continue using the service until the end of Your paid subscription period (if any) and (B) communicate details and terms regarding the new service, if any, to You at or before your next annual subscription renewal. </span></li><li class="c12 li-bullet-2"><span class="c15">Effect of Termination</span><span class="c3">. Upon termination of any license granted hereunder, You shall immediately cease all use of the applicable Software and delete all copies and license keys in Your possession or under Your control. Termination or expiration of this Agreement or any license shall not limit either party from pursuing any other remedies available to it, including injunctive relief, nor shall such termination or expiration relieve Your obligation to pay all fees that accrued prior to such termination or expiration. The following Sections shall survive any termination or expiration: 1.8, 4.4, 8-9 and 10, 12, 13 and 14; and to assist in interpreting such surviving provisions, the definitions of defined terms used in those provisions shall also survive.</span></li></ol><ol class="c7 lst-kix_list_4-0" start="5"><li class="c8 c20 li-bullet-0"><span class="c13">TAXES</span><span class="c3">. Basis fees do not include any local, state, federal or foreign taxes, levies or duties of any nature, including value-added, sale, use or withholding taxes (“</span><span class="c1">Taxes</span><span class="c3">”). You are responsible for paying all Taxes, excluding only taxes based on Basis's net income. If Basis has the legal obligation to pay or collect Taxes for which You are obligated to pay pursuant to the terms of this Section, the appropriate amount shall be invoiced to and paid by You unless You provide Basis with a valid tax exemption certificate authorized by the appropriate taxing authority. </span></li></ol><p class="c8 c4"><span class="c3"></span></p><ol class="c7 lst-kix_list_4-0" start="6"><li class="c16 c19 li-bullet-0"><span class="c13">MAINTENANCE AND SUPPORT SERVICES</span><span class="c3">. If the Product you have purchased includes Support Services, as defined below, You shall be entitled to receive the Support Services for the period indicated in the applicable Product description, subject to full payment of the applicable fees. “</span><span class="c1">Support Services</span><span class="c3">” shall mean e-mail support during regular business hours, Boston, MA, time, as well as updates, bug-fixes and minor version releases, each as defined by Basis and on a “when-and-if-available” basis (“Updates”). Updates shall be subject to this Agreement, as it may be amended and provided to You at the time of the applicable Update. </span></li><li class="c8 c20 li-bullet-0"><span class="c13">AUDIT</span><span class="c3">. Basis may audit Your use of the Software on thirty (30) days’ advanced written notice. You will cooperate with the audit, including by providing access to any books, computers, records, or other information that relate or may relate to use of the Software. Such audit will not unreasonably interfere with Your business activities. In the event that an audit reveals unauthorized use of the Software, You will reimburse Basis for the reasonable cost of the audit, in addition to such other rights and remedies as Basis may have. Basis will not conduct an audit more than once per calendar year.</span></li><li class="c8 c20 li-bullet-0"><span class="c13">WARRANTIES</span><span class="c3">. </span></li></ol><ol class="c7 lst-kix_list_4-1 start" start="1"><li class="c12 li-bullet-1"><span class="c15">Software Warranty, Duration and Remedy</span><span class="c3">. Basis warrants that it has title to the Software and/or the authority to grant licenses to use the Software. During a period of thirty (30) days starting from the Paid License Start Date, Basis warrants that the Software will substantially perform as described in the Documentation, provided that the Software: (A) has been properly installed and used at all times in accordance with the Documentation, and (B) has not been modified or added to by persons other than Basis. In the event of breach of the warranty in this Subsection 8.1, You shall promptly give Basis written notice and upon receipt, Basis will promptly repair the Software or replace it with software of substantially similar functionality, or if the error is irreparable in Basis’s reasonable determination, then Basis shall provide You with a refund of Your paid fees. </span><span class="c3">THE REMEDIES SET FORTH IN THE PREVIOUS SENTENCE ARE YOUR SOLE AND EXCLUSIVE REMEDIES FOR A BREACH OF WARRANTY</span><span class="c3">. </span></li><li class="c12 li-bullet-2"><span class="c15">Disclaimer. </span><span class="c3">EXCEPT FOR THE WARRANTIES DESCRIBED IN SECTION 8.1 IN CONNECTION WITH A PAID LICENSE, BASIS MAKES NO REPRESENTATIONS OR WARRANTIES REGARDING THE CONTENT, EFFECTIVENESS, USEFULNESS, RELIABILITY, AVAILABILITY, TIMELINESS, QUALITY, NON-INFRINGEMENT, SUITABILITY, ACCURACY OR COMPLETENESS OF THE SOFTWARE OR THE SCANNING SERVICE OR THEIR OPERATION OR THAT THE SOFTWARE OR SCANNING SERVICE WILL OPERATE IN CONJUNCTION WITH OTHER SYSTEM SOFTWARE WHICH YOU MAY SELECT OR THE RESULTS YOU MAY OBTAIN BY USING THE SOFTWARE OR THE SCANNING SERVICE OR THAT THE SOFTWARE OR SCANNING SERVICE WILL BE UNINTERRUPTED OR ERROR-FREE OR THAT THE SOFTWARE OR SCANNING SERVICE WILL MEET YOUR REQUIREMENTS OR THAT ALL SOFTWARE OR SCANNING SERVICE ERRORS WILL BE CORRECTED OR THAT IT IS COMPLETELY SECURE. THERE ARE NO OTHER WARRANTIES OR REPRESENTATIONS, GUARANTEES OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, THOSE OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT OF THIRD PARTY RIGHTS. YOU ASSUME ALL RESPONSIBILITY FOR DETERMINING WHETHER THE SOFTWARE AND THE SCANNING SERIVCE OR THE INFORMATION GENERATED BY THEM IS ACCURATE OR SUFFICIENT FOR YOUR PURPOSES.</span></li></ol><ol class="c7 lst-kix_list_4-0" start="9"><li class="c8 c20 li-bullet-0"><span class="c13">LIMITATION OF LIABILITY.</span></li></ol><ol class="c7 lst-kix_list_4-1 start" start="1"><li class="c12 li-bullet-2"><span class="c15">Exclusion of Consequential Damages</span><span class="c3">. YOU AGREE THAT THE CONSIDERATION WHICH BASIS IS CHARGING HEREUNDER DOES NOT INCLUDE CONSIDERATION FOR ASSUMPTION BY BASIS OF THE RISK OF YOUR INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES. TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL BASIS AND ITS LICENSORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, EXEMPLARY, PUNITIVE, SPECIAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF PROFITS, BUSINESS OPPORTUNITIES, REVENUE, DATA, OR USE, INCURRED BY YOU OR ANY THIRD PARTY, WHETHER IN AN ACTION IN CONTRACT OR TORT OR OTHERWISE, ARISING FROM OR RELATED TO THE USE OF THE SOFTWARE OR THE SCANNING SERVICE OR ANY DATA DERIVED THEREFROM, EVEN IF BASIS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</span></li><li class="c12 li-bullet-1"><span class="c15">Limitation on Direct Damages</span><span class="c3">. TO THE MAXIMUM EXTENT PERMITTED BY LAW, LIABILITY OF BASIS AND ITS LICENSORS UNDER THIS AGREEMENT FOR DIRECT DAMAGES TO YOU SHALL IN NO EVENT EXCEED THE AMOUNT OF FEES PAID BY YOU UNDER THIS AGREEMENT.</span></li><li class="c12 li-bullet-2"><span class="c3">THE FOREGOING LIMITATIONS SHALL APPLY REGARDLESS OF WHETHER BASIS OR ITS LICENSORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF WHETHER ANY REMEDY FAILS OF ITS ESSENTIAL PURPOSE. </span></li></ol><ol class="c7 lst-kix_list_4-0" start="10"><li class="c8 c20 li-bullet-0" id="h.gjdgxs"><span class="c13">COMPLIANCE WITH LAW</span><span class="c3">.</span><span class="c10"> </span><span class="c3">You shall comply with all laws applicable to the actions contemplated by this Agreement. You acknowledge that the Software is of United States origin, is provided subject to the U.S. Export Administration Regulations, may be subject to the export control laws of the applicable territory, and that diversion contrary to applicable export control laws is prohibited. You represent that (A) You are not, and are not acting on behalf of, (1) any person who is a citizen, national, or resident of, or who is controlled by the government of any country to which the United States has prohibited export transactions; or (2) any person or entity listed on the U.S. Treasury Department list of Specially Designated Nationals and Blocked Persons, or the U.S. Commerce Department Denied Persons List or Entity List; and (B) you will not permit the Software to be used for, any purposes prohibited by law, including, any prohibited development, design, manufacture or production of missiles or nuclear, chemical or biological weapons. The Software and accompanying documentation are deemed to be “commercial computer software” and “commercial computer software documentation”, respectively, pursuant to DFARS Section 227.7202 and FAR Section 12.212(b), as applicable. Any use, modification, reproduction, release, performing, displaying or disclosing of the Software and documentation by or for the U.S. Government shall be governed solely by the terms and conditions of this Agreement. You assume sole responsibility for any required export approval and/or licenses and all related costs and for the violation of any United States export law or regulation or any other law or regulation of an applicable jurisdiction. </span></li><li class="c8 c20 li-bullet-0"><span class="c13">THIRD PARTY COMPONENTS</span><span class="c3">. The Software may contain third party owned components, some of which are subject to open source licenses. Except as permitted by any applicable open source licenses, You shall not use, or permit others to use, such third party owned components apart from the Software. Your license rights with respect to components subject to open source licenses are defined by the terms of such licenses; nothing in this Agreement is intended to alter, enlarge, or restrict Your rights or obligations under the applicable open source licenses with respect to such open source code.</span></li><li class="c8 c20 li-bullet-0"><span class="c13">ASSIGNMENT</span><span class="c3">. You may assign or transfer all of your rights and interests in this Agreement to another party provided that (A) You give prompt prior written notice to Basis; (B) the proposed transferee is not a direct competitor of Basis (as reasonably determined by Basis); (C) You retain no copies after assignment or transfer; and (D) the transferee agrees to all of the terms of this Agreement. </span></li><li class="c8 c20 li-bullet-0"><span class="c1">GDPR ADDENDUM</span><span class="c3">. If You reside in the European Union, or, if You upload to the Scanning Service files or data samples that contain data relating to real persons that reside in the European Union, then this EULA is subject to the GDPR Addendum that is either attached to this EULA or provided to you separately. In those instances where the GDPR Addendum applies and when there is a conflict between this EULA and the GDPR Addendum, the GDPR Addendum controls.</span></li><li class="c8 c20 li-bullet-0"><span class="c13">GENERAL TERMS</span><span class="c3">. For the avoidance of doubt, the Product descriptions are incorporated into this Agreement by this reference. This Agreement constitutes the complete agreement between the parties and supersedes all previous agreements or representations, written or oral (including any evaluation agreement), with respect to the Software and services specified herein. ANY CUSTOMER TERMS AND CONDITIONS ATTACHED TO OR REFERENCED IN A CUSTOMER PURCHASE ORDER SHALL BE OF NO FORCE AND EFFECT. </span></li></ol><p class="c8"><span class="c3">This Agreement may not be modified or amended except in a writing signed by a duly authorized representative of each party. Wherever possible, each provision of this Agreement shall be interpreted in such manner as to be effective and valid under applicable law, but if any provision of this Agreement shall be prohibited by or invalid under applicable law, such provision shall be ineffective only to the extent of such prohibition or invalidity without invalidating the remainder of such provision or the remaining provisions of this Agreement. The waiver by either party of any default or breach of this Agreement shall not constitute a waiver of any other or subsequent default or breach. This Agreement is entered into solely for the benefit of Basis and You, and no third party shall be deemed a beneficiary of this Agreement and no third party shall have the right to make any claim or assert any right under it. This Agreement and all matters relating to or arising out of this Agreement shall be governed and interpreted by and construed under the laws of the Commonwealth of Massachusetts, without reference to its choice of law provisions and without reference to the United Nations Convention on Contracts for the International Sale of Goods. The parties agree to submit to the exclusive jurisdiction and venue of state or federal courts located in Boston, Massachusetts. All notices, including notices of address change, required to be sent hereunder shall be in writing and shall be deemed to have been given when sent by registered mail, overnight mail, or a nationally recognized courier to the addresses listed in the Order Form, or by email to the addresses listed in the Order Form, with receipt </span><span class="c17">of acknowledgement (not automatically generated)</span><span class="c3">. Basis and You agree that any principle of construction or rule of law that provides that agreement be construed against the drafter of the agreement in the event of any inconsistency or ambiguity shall not apply to this Agreement. Except for any obligation by You to make a payment required under this Agreement, neither party will be liable for any failure or delay in performance due, in whole or in part, to causes beyond its reasonable control, such as natural disasters, wars, strikes and other upheavals.</span></p><p class="c4 c32"><span class="c3"></span></p><hr style="page-break-before:always;display:none;"><p class="c4 c16"><span class="c10"></span></p><p class="c27 c4"><span class="c10"></span></p><p class="c4 c27"><span class="c10"></span></p><p class="c25"><span class="c28">EXHIBIT 1</span></p><p class="c25"><span class="c28">REVERSING LABS INTERNATIONAL, GMBH</span></p><p class="c25"><span class="c28">END-USER SOFTWARE LICENSE AGREEMENT</span></p><p class="c25"><span class="c23">[Agreement follows on the next page]</span></p><hr style="page-break-before:always;display:none;"><p class="c16 c4"><span class="c23"></span></p><p class="c0"><span class="c2">END-USER SOFTWARE LICENSE AGREEMENT </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">PLEASE READ THIS END USER LICENSE AGREEMENT (“EULA”) CAREFULLY. THIS EULA IS A LEGAL AGREEMENT BETWEEN YOU (EITHER AN INDIVIDUAL OR A SINGLE ENTITY) (“LICENSEE” OR “YOU”) AND REVERSING LABS INTERNATIONAL, GMBH, A SWISS LIMITED LIABILITY COMPANY (“REVERSINGLABS”). YOU MUST REVIEW AND EITHER ACCEPT OR REJECT THE TERMS OF THIS EULA BEFORE INSTALLING OR USING THE SOFTWARE. BY CLICKING THE “I ACCEPT” BUTTON, INSTALLING OR OTHERWISE USING THE SOFTWARE, YOU ACKNOWLEDGE THAT YOU HAVE READ ALL OF THE TERMS AND CONDITIONS OF THIS EULA, UNDERSTAND THEM, AND AGREE TO BE LEGALLY BOUND BY THEM. THIS AGREEMENT IS ENFORCEABLE AGAINST YOU AND ANY LEGAL ENTITY THAT OBTAINED THE SOFTWARE AND ON WHOSE BEHALF IT IS USED: FOR EXAMPLE, IF APPLICABLE, YOUR EMPLOYER. IF YOU DO NOT AGREE TO THE TERMS OF THIS AGREEMENT, DO NOT USE THE SOFTWARE. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">If you have entered into a separate agreement with ReversingLabs permitting you to use the Software, that agreement, rather than this EULA, will govern your use of the Software. If all or some portion of the Software has been licensed by another party for your use (e.g., your employer or an individual or company with which you conduct business), your right to use that Software or Data Service and to obtain any related Services is subject to the terms and conditions of the agreement(s) between ReversingLabs and the other party, whether the other party has agreed to the terms of this EULA or to the terms of a separate, written agreement. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">If you or your users reside in the European Union, or, if you provide us files or data samples that contain data relating to real persons that reside in the European Union, then this EULA is subject to the GDPR Addendum that is either attached to this EULA or provided to you separately. In those instances where the GDPR Addendum applies and when there is a conflict between this EULA and the GDPR Addendum, the GDPR Addendum controls. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">1. DEFINITIONS. The following capitalized terms used in this EULA have the meanings indicated: </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">1.1 “Account” means an instance of the Software accessible by a single login to the Software. </span></p><p class="c0"><span class="c2">1.2 “API” means application programming interfaces that ReversingLabs may make available with the Software or as a separate option from time to time. These interfaces allow your programmers to build integrations with the Software. </span></p><p class="c0"><span class="c2">1.3 “Computer” means an end user personal computer or personal computing device containing one or more CPUs that is not utilized as a Server. </span></p><p class="c0"><span class="c2">1.4 “Data” means the detailed information related to and samples of known good files, known bad files and Internet locations stored, processed and otherwise maintained in the ReversingLabs’ threat intelligence database, which information is sometimes licensed provided for use with Software and sometimes provided separately, either on a stand-alone basis or as part of a Data Service. </span></p><p class="c0"><span class="c2">1.5 “Data Service” means any Internet-based service by which ReversingLabs provides Data to you. </span></p><p class="c0"><span class="c2">1.6 “Delivery Date” means (i) for cloud-based Software, the date on which ReversingLabs sends or otherwise makes available to you the access code(s) and other instructions, if applicable, for utilizing the Software; and (ii) for installed Software, the date on which ReversingLabs sends or makes available to you a digital file containing the Software and any instructions for installation. </span></p><p class="c0"><span class="c2">1.7 “Documentation” means any online help text and/or manuals, readme files, build notes, functional specifications, instructions, and any related materials provided with the Software. The term "Documentation" includes any updates or modifications ReversingLabs may choose to make available with respect to the Documentation. </span></p><p class="c0"><span class="c2">1.8 “End User” means you and any human being whose permitted use of any Software or Data Service you sponsor. </span></p><p class="c0"><span class="c2">1.9 “Intellectual Property Rights" means any rights existing under patent law, copyright law, data and database protection law, trade secret law, and any and all similar proprietary rights of ReversingLabs embodied in the Software. </span></p><p class="c0"><span class="c2">1.10 “Order Form(s)” means the proposal form provided by ReversingLabs and accepted by you, evidencing your initial purchase of a license and/or subscription to the Software, Data Service and/or Data, plus any subsequent Order Forms submitted online or in written form, in each case specifying, among other things, the specific ReversingLabs Products and Services to be provided to you, the applicable fees, and the term of the agreement. Each Order Form is incorporated into, and therefore is a part of, this Agreement. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">1.11 “ReversingLabs Products and Services” means the Software, Data Service, and/or Data licensed to you, as provided in the Order Form associated with this Agreement. </span></p><p class="c0"><span class="c2">1.12 “Server” means a computer server owned, leased or otherwise controlled by you or a third party on which a licensed copy of a ReversingLabs’ server-based Software product is installed. </span></p><p class="c0"><span class="c2">1.13 “Services” means hosting services, software maintenance services, support services (including deployment support services), and any other services ReversingLabs may provide you in connection with your use of the Software. </span></p><p class="c0"><span class="c2">1.14 “Software” means the ReversingLabs software accompanying this EULA, in object code form, together with any of the following that may form a part of it or subsequently be provided by ReversingLabs for use with it: (i) the Data Service; (ii) APIs, tools, toolsets, and other software applications or components; (iii) artwork, photographs, and video or audio content; (iv) Documentation; and (v) any Updates to or Upgrades of any of the foregoing that may be covered by this EULA.</span></p><p class="c0"><span class="c2">1.15 "Source Code" shall mean computer programs, instructions and related material for implementing proprietary algorithms (and other trade secret or otherwise proprietary methods and processes) written in a human-readable source language in a form capable of serving as the input to a compiler or assembler program, and in form capable of being modified, supported and enhanced by programmers reasonably familiar with the source language. By way of clarification, and not limitation, the term "Source Code" means the preferred form of the code for making modifications to it, including all modules it contains, plus any associated interface definition files, and scripts used to control compilation and installation of an executable or otherwise, create, manage, administer, or operate the hosting environment where the software service is deployed. </span></p><p class="c0"><span class="c2">1.16 “Updates” means bug fixes, patches, or other additions, revisions to or modifications of Software and/or Data that ReversingLabs provides to you or any End User, including those it makes generally available to customers that subscribe to its maintenance services. An Update to Software typically is identified by a change in a number and/or letter to the right of the first decimal point in a product’s version number. Updates do not include Upgrades. </span></p><p class="c0"><span class="c2">1.17 “Upgrade” means a major release of Software and/or Data, as determined by ReversingLabs in its sole discretion. An Upgrade to Software and/or Data typically is identified by a new product name or a new number to the left of the first decimal point in the version number of an existing product name. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">1.18 “Web Site” means ReversingLabs’ web site located at http://www.reversinglabs.com. </span></p><p class="c0"><span class="c2">2. OWNERSHIP. The ReversingLabs Products and Services are licensed, not sold. You acknowledge that the ReversingLabs Products and Services (including any changes you may request or suggest) are the property of ReversingLabs and/or its licensors. </span></p><p class="c0"><span class="c2">Title to the ReversingLabs Products and Services (including each copy of any of them) and all related intellectual property rights embodied in or represented by the ReversingLabs Products and Services will remain with ReversingLabs and/or its licensors at all times. All rights and licenses not expressly granted are reserved to ReversingLabs, and there shall be no licenses or rights implied under this EULA, based on any course of conduct, or otherwise. </span></p><p class="c0"><span class="c2">3. LICENSE GRANT. Subject to the terms and conditions of this EULA, ReversingLabs, under its Intellectual Property Rights, hereby grants to Licensee: </span></p><p class="c0"><span class="c2">3.1 For Software and Data: a limited, non-exclusive, non-transferable, worldwide license to access, execute, display, perform, and otherwise use the Software and/or Data, in machine-readable object code, solely for the number of authorized users set forth on your Order Form (if applicable). Licensee shall be entitled to make one (1) copy of any client-side components of the Software (if applicable), solely for archival or backup purposes. To the extent the Order Form includes rights to a Reversing Labs software development kit ("SDK"), ReversingLabs hereby grants you a non-exclusive and non-transferable (except as provided in Section 12.9 (Software, Data and EULA Transfer)) right and license under its Intellectual Property Rights to use and reproduce the SDK on any tangible media in binary code for the sole purpose of integrating the Software with other software and systems via the Software's APIs solely for your internal business use. If the Software you are installing is evaluation or trial use Software, your rights are limited as described below in Section 6. </span></p><p class="c0"><span class="c2">3.2 License Term. The license granted in Section 3.1 shall be either (i) perpetual or (ii) subscription based for a one year term commencing upon the date the ReversingLabs Products and Services are first made available to you, as set forth in the Order Form. </span></p><p class="c0"><span class="c2">4. Restrictions. During the term of your license and/or subscription, you agree to comply with the following restrictions and limitations, and you agree not to permit others (including any End User whose use of the ReversingLabs Products and Services you sponsor) to violate them: </span></p><p class="c0"><span class="c2">4.1 You shall: (i) adopt and enforce such internal policies, procedures and monitoring mechanisms as are reasonably necessary to ensure that the Software is used only in accordance with the terms of this EULA and (ii) take all steps necessary to ensure that no </span></p><p class="c0"><span class="c2">person or entity will have unauthorized access to the ReversingLabs Products and Services. </span></p><p class="c0"><span class="c2">4.2 You shall not: (i) copy (except as provided above), sell, rent, assign, sublicense, lease, encumber or otherwise transfer or attempt to transfer the Software, Data, or any portion thereof; (ii) permit any third party to use or have access to the ReversingLabs Products and Services, whether by timesharing, networking (except as expressly permitted hereunder) or any other means; (iii) reverse engineer, decompile, disassemble, or otherwise seek to discover the source code of the ReversingLabs Products and Services; (iv) possess or use the Software or any portion thereof, other than in machine readable object code; (v) make any copies of the Software or Data, other than as permitted by Section 3 hereof; (vi) remove any copyright, trademark, patent or other proprietary notices from the ReversingLabs Products and Services or any portion thereof; (vii) modify, translate, or create any derivative work thereof, but your computer code written to current APIs for the Software that are published by ReversingLabs or otherwise disclosed by ReversingLabs to you will not be considered modifications or derivative works for purposes of this restriction; (viii) modify, disable, circumvent, avoid, bypass, remove, deactivate, impair or otherwise interfere with features of the ReversingLabs Products and Services that enforce license restrictions or limits or report technical or statistical information regarding the ReversingLabs Products and Services or its use to ReversingLabs; or (ix) continue to use prior versions of any ReversingLabs Products and Services after installing an Upgrade of the ReversingLabs Products and Services or any Update or Upgrade that wholly replaces the ReversingLabs Products and Services. To the extent that the right to decompile, disassemble, or reverse engineer the Software is permitted by applicable law, you agree not to do so if ReversingLabs makes available to you a separate software module that allows you to achieve interoperability of an independently created computer program for use with the Software. You agree that, prior to attempting to achieve such interoperability, you will obtain written notification from ReversingLabs that it is unwilling to make such a software module available within a reasonable period of time. </span></p><p class="c0"><span class="c2">4.3 The ReversingLabs Products and Services activation key(s) and/or user account(s) (if any) is intended solely for your use. You are solely responsible for maintaining the confidentiality and security of your activation key(s) and/or user account(s) (if any). You are solely responsible and liable for any and all use of your activation key(s) and/or user account(s) (if any) and for activities that occur on or through your activation key(s) and/or user account(s) (if any). You agree to notify ReversingLabs immediately about any unauthorized access to, or use of, any of your activation key(s) and/or user account(s) (if any). </span></p><p class="c0"><span class="c2">4.4 Open Source Material; License Restrictions. </span></p><p class="c0"><span class="c2">4.4.1 Relevant Definitions. The term "Open Source Software License" means a license under which software is publicly distributed </span></p><p class="c0"><span class="c2">(or otherwise made publicly available) in source code format, including licenses such as the GNU General Public License (GPL), GNU Lesser General Public License (LGPL), Affero General Public License, Mozilla Public License (MPL), BSD licenses, Artistic License, Apache License and other, similar open source software licenses. The term "Open Source Material" means software that is subject to Open Source Software Licenses. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">4.4.2 Use of Open Source Material; Restrictions. ReversingLabs may provide Open Source Material for use in connection with certain Software. You acknowledge that (i) your use of any such Open Source Material is subject to the associated Open Source Software Licenses, and (ii) you shall comply with all such Open Source Software Licenses. A list of applicable Open Source Material and the associated Open Source Software Licenses are available upon request. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">5. U.S. Government End Users. The ReversingLabs Products and Services are “commercial item(s)” as defined at 48 C.F.R. 2.101, consisting of "commercial computer software," “computer database,” and "computer software documentation." Notwithstanding anything to the contrary in this EULA, the U.S. Government sometimes makes certain minimum rights of use, reproduction, and disclosure a condition of its purchase or acquisition of commercial software. Accordingly: </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">5.1 GSA Multiple Award and Federal Supply Schedule Acquisitions. For government purchases or acquisitions through a GSA Multiple Award or Federal Supply Schedule contract, use, reproduction, and disclosure of the Software and Data are subject only to the rights of use, reproduction, and disclosure as stated in Sections 3 and 4 of this EULA. Provided, however, that in the event of a conflict between any provision in Sections 3 and 4 of this EULA and the restrictions set forth in ¶¶ 6 and 9 of GSA’s “Terms and Conditions </span></p><p class="c0"><span class="c2">Applicable to . . . [SINs] 132-32 . . ., 132-33 . . . 132-34 . . . “ and GSA’s “Terms and Conditions Applicable to . . . [SINs] 132-51 . . . and </span></p><p class="c0"><span class="c2">132-52,” The foregoing GSA Terms and Conditions shall take precedence. Note, however, that any modification or combination of the ReversingLabs Products and Services under those rights will entirely void the warranty per Section 9.1 of this EULA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">5.2 FAR Acquisitions. For government agency purchases or acquisitions, other than DOD acquisitions subject to 5.3, under the authority of Federal Acquisition Regulation (“FAR”) Part 12, the rights of use, reproduction, and disclosure are only as stated in Sections 3 and 4 of this EULA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">5.3 DOD Acquisitions. For government purchases or acquisitions by the Department of Defense, a Military Department or Defense Agency, the rights of use, reproduction, and disclosure are only as stated in Sections 3 and 4 of this EULA, per DFARS 227.7202-3(a). </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">6. EVALUATION AND TRIAL USE. If ReversingLabs provides ReversingLabs Products and Services to you for evaluation or trial use (“Evaluation/Trial Use Products and Services”), then your rights are limited as described in this section. You may use the ReversingLabs Evaluation/Trial Use Products and Services in a manner consistent with the terms of this EULA solely for evaluation/trial purposes for a period ranging from two (2) weeks up to sixty (60) days from the Delivery Date, as specified in your Order Form, or for such other period as may be indicated in writing by ReversingLabs at or after the time of delivery. In light of the fact that Evaluation/Trial Use Products and Services are provided to you free of charge, ReversingLabs disclaims the limited warranty set forth below in Section 9.1, and neither ReversingLabs nor any Released Party will be liable for direct damages related to Evaluation/Trial Use Products and Services, as explained more fully in Section 10.2. Access to Evaluation/Trial Use Products and Services may include a "time-out" mechanism that will automatically downgrade or disable the Evaluation/Trial Use Products and Services or otherwise prevent access to the Evaluation/Trial Use Products and Services at the end of the evaluation/trial period. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">7. MAINTENANCE AND SUPPORT. Technical support for the ReversingLabs Products and Services may be accessed via email or other contact information provided on the ReversingLabs website (if applicable). Unless you subscribe to an enhanced maintenance and/or support offering, you are not entitled to receive additional maintenance or support for the ReversingLabs Products and Services (though any Updates or Upgrades ReversingLabs may provide you will be covered by this EULA, unless ReversingLabs requires you to accept a new agreement at the time they are provided). If you subscribe to a ReversingLabs maintenance and/or support offering, ReversingLabs will provide you with maintenance and/or support services corresponding to the service level(s) to which you have subscribed, as set forth in a separate agreement you may enter into with ReversingLabs related to such services. Whether or not you subscribe to a maintenance and/or support offering, ReversingLabs reserves the right to provide you with Updates or supplements to the Software when we consider it necessary to do so to ensure that the Software functions properly. Any technical information you provide ReversingLabs in connection with support services it provides you may be used by ReversingLabs for its business purposes, including product and service development. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">8. SAMPLE FILE ANALYSIS. You may submit sample files to ReversingLabs for analysis and inclusion in our threat intelligence database ("Sample Files"). We may, in our sole discretion, unpack and analyze Sample Files to expose internal information, including but not limited to IP addresses, names, custom application information, images, compression, encryption, and executable files (the "Analyzed Information"). We may, in our sole discretion, include the Analyzed Information in our database, which is accessible to our other customers. By submitting Sample Files to us, you grant to ReversingLabs under your intellectual property rights a perpetual, irrevocable, worldwide, transferrable, sub-licensable, royalty-free right to use, copy, modify, display, perform, distribute and otherwise exploit the Sample Files. It is your responsibility to use discretion in submitting files to ReversingLabs for analysis. If you believe a file may contain proprietary or other sensitive information that you do not want shared with our other customers, DO NOT SUBMIT SUCH FILE TO REVERSINGLABS. REVERSINGLABS FULLY DISCLAIMS ANY RESPONSIBILITY FOR THE CONTENT OF SAMPLE FILES UPLOADED TO OUR DATABASE. YOU EXPRESSLY AGREE THAT YOUR SUBMISSION OF SAMPLE FILES FOR ANALYSIS AND INCLUSION IN THE REVERSINGLABS DATABASE IS AT YOUR SOLE RISK. If you believe a Sample File has been submitted to us in error (a "Submission Error"), you may contact us and request that the particular file be removed from our database. </span></p><p class="c0"><span class="c2">REMOVAL OF THE FILE IN QUESTION SHALL BE THE SOLE REMEDY FOR SUBMISSION ERRORS. </span></p><p class="c0"><span class="c2">9. LIMITED WARRANTIES AND WARRANTY DISCLAIMER. </span></p><p class="c0"><span class="c2">9.1 ReversingLabs warrants that, for a period of 90 days after the Delivery Date, the Software and/or Data (including any Upgrades for which ReversingLabs does not require you to accept the terms of a replacement agreement, but excluding Updates) will function substantially in accordance with relevant, published specifications (or substantially error free where there are no published specifications). As your exclusive remedy for breach of this warranty, ReversingLabs will, at its option, either replace or repair the defective Software and/or Data or refund all fees paid for it, as well as any fees paid for maintenance, support and Hosted Services associated with the defective Software and/or Data that were and/or are to be provided after the Delivery Date of the defective Software and/or Data. Notwithstanding the foregoing, ReversingLabs will not be responsible for (i) any breach of warranty not reported during </span></p><p class="c0"><span class="c2">the warranty period; (ii) any malfunctioning of Software and/or Data that you, an End User, or a third party has modified, misused, or damaged; (iii) any malfunctioning of Software and/or Data caused by hardware or network configuration, (iv) any malfunctioning of Software and/or Data caused by third party software or services, or (v) any malfunctioning of Software and/or Data caused by your failure to incorporate all Updates and/or Upgrades provided to you by ReversingLabs. THIS WARRANTY DOES NOT APPLY TO REVERSINGLABS PRODUCTS AND SERVICES COVERED BY SECTION 6 OF THIS EULA. </span></p><p class="c0"><span class="c2">This warranty gives you specific legal rights. You may also have other legal rights that vary from state to state and country to country. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">9.2 LICENSEE SPECIFICALLY ACKNOWLEDGES THAT FILES CONTAINED IN THE REVERSINGLABS DATA SERVICE INCLUDES MALWARE (HARMFUL VIRUSES, TIME BOMBS AND OTHER DISRUPTIVE OR MALICIOUS CODE OR MECHANISMS). LICENSEE EXPRESSLY ASSUMES ALL RISK AND RESPONSIBILITY ASSOCIATED WITH THE POSSESSION, HANDLING AND USE OF MALWARE INCLUDED WITHIN THE DATA SERVICE. </span></p><p class="c0"><span class="c2">9.3 EXCEPT FOR THE LIMITED WARRANTY SET FORTH IN SECTION 9.1, REVERSINGLABS, ITS LICENSORS AND LICENSORS’ DISTRIBUTORS DISCLAIM ALL WARRANTIES WITH RESPECT TO ALL REVERSINGLABS PRODUCTS AND SERVICES AND ALL THIRD PARTY PRODUCTS OR SERVICES YOU OR END USERS MAY UTILIZE IN CONNECTION WITH REVERSINGLABS PRODUCTS AND SERVICES, WHETHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NONINFRINGEMENT. IN PARTICULAR, REVERSINGLABS DOES NOT REPRESENT THAT THE REVERSINGLABS PRODUCTS AND SERVICES ARE ERROR FREE, WILL OPERATE IN AN UNINTERRUPTED MANNER, ARE COMPLETELY SECURE, OR WILL INTEROPERATE WITH THIRD PARTY SOFTWARE OR SERVICES. UNLESS YOU HAVE SUBSCRIBED TO A SERVICES OFFERING THAT GUARANTEES A PARTICULAR LEVEL OF SERVICE AND/OR A FIXED TERM OF SERVICE, ALL SERVICES ARE PROVIDED ON AN “AS IS” AND “AS AVAILABLE” BASIS AND ARE SUBJECT TO CHANGE OR TERMINATION AT ANY TIME AND FOR ANY REASON WITHOUT NOTICE. THE REVERSINGLABS PRODUCTS AND SERVICES ARE NOT DESIGNED OR MANUFACTURED FOR USE IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE SUPPORT SYSTEMS, OR WEAPON OR COMBAT SYSTEMS, IN WHICH THEIR FAILURE COULD LEAD DIRECTLY TO PERSONAL INJURY, DEATH, OR PROPERTY OR ENVIRONMENTAL DAMAGE. REVERSINGLABS DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR SUCH USES. REVERSINGLABS EXPRESSLY DISCLAIMS ALL EXPRESS AND IMPLIED WARRANTIES WITH RESPECT TO ANY AND ALL MALWARE INCLUDED WITHIN THE DATA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">9.4 U.S. Government Customers and End Users. The ReversingLabs Products and Services are “commercial items” as defined at 48 C.F.R. 2.101, consisting of "commercial computer software," “computer database,” and " computer software documentation." For government purchases or acquisitions through a GSA Supply Schedule contract, the government customer and end user accept the standard, commercial ReversingLabs warranty terms per ¶ 3.a of GSA’s “Terms and Conditions Applicable to . . . [SINs] 132-32 . . ., 132-33 . . . and 132-34 . . .” For government purchases or acquisitions under the authority of Federal Acquisition Regulation (“FAR”) Part 12, the government customer and end user accept the standard, commercial ReversingLabs warranty terms and FAR 52.212-4(p). For all government purchases or acquisitions that are not through a GSA Multiple Award or Federal Supply Schedule contract, the government customer and end user accept the standard, commercial ReversingLabs warranty per FAR 46.709. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">10. EXCLUSION OF DAMAGES AND LIMITATION OF LIABILITY. </span></p><p class="c0"><span class="c2">10.1 TO THE MAXIMUM EXTENT PERMITTED BY LAW (INCLUDING ANY APPLICABLE CONSUMER PROTECTION LAW OF A FOREIGN JURISDICTION), NEITHER REVERSINGLABS NOR ANY OF ITS DIRECTORS, OFFICERS, EMPLOYEES, CONTROLLED OR CONTROLLING ENTITIES, LICENSORS OR LICENSORS’ DISTRIBUTORS (EACH, A “RELEASED PARTY”), WILL HAVE ANY LIABILITY TO YOU OR ANY END USERS FOR INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES (INCLUDING, WITHOUT LIMITATION, ANY LOSS OF USE, LOST PROFITS, BUSINESS OR REVENUE, LOSS OF GOODWILL OR OTHER ECONOMIC ADVANTAGE, OR LOSS OF PRIVACY) ARISING OUT OF OR RELATED TO THIS EULA, OR THE REVERSINGLABS PRODUCTS AND SERVICES, EVEN IF REVERSINGLABS OR A RELEASED PARTY HAS BEEN ADVISED OF, OR KNEW OR SHOULD HAVE KNOWN OF, THE POSSIBILITY OF SUCH DAMAGES. FOR SAKE OF CLARITY, REVERSINGLABS EXPRESSLY DISCLAIMS ALL LIABILITY WITH RESPECT TO ANY AND ALL MALWARE INCLUDED WITHIN THE DATA. TO THE EXTENT THIS EXCLUSION OF LIABILITY IS UNENFORCEABLE, DESPITE THE PARTIES' EXPRESS AGREEMENT TO IT AS AN ESSENTIAL ELEMENT OF THIS AGREEMENT, REVERSINGLABS' LIABILITY WITH RESPECT TO SUCH MALWARE WILL BE LIMITED AS PROVIDED IN SECTION 10.3. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">10.2 NOTWITHSTANDING PARAGRAPH 10.1 ABOVE OR ANYTHING ELSE TO THE CONTRARY SET FORTH IN THIS EULA, IF </span></p><p class="c0"><span class="c2">YOUR CLAIMED DAMAGES ARISE FROM OR RELATE TO REVERSINGLABS PRODUCTS AND SERVICES COVERED BY SECTION 6 OF THIS EULA, THEN, TO THE MAXIMUM EXTENT PERMITTED BY LAW (INCLUDING ANY APPLICABLE CONSUMER PROTECTION LAW OF A FOREIGN JURISDICTION), NEITHER REVERSINGLABS NOR ANY RELEASED PARTY WILL HAVE ANY LIABILITY TO YOU OR ANY END USERS FOR DAMAGES OF ANY KIND ARISING OUT OF OR RELATED TO THIS EULA, THE REVERSINGLABS PRODUCTS AND SERVICES, INCLUDING BUT NOT LIMITED TO DIRECT DAMAGES, EVEN IF REVERSINGLABS OR A RELEASED PARTY HAS BEEN ADVISED OF, OR KNEW OR SHOULD HAVE KNOWN OF, THE POSSIBILITY OF SUCH DAMAGES. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">10.3 WITHOUT LIMITING THE SCOPE OR EFFECT OF SECTIONS 10.1 OR 10.2 ABOVE, IN NO EVENT WILL REVERSINGLABS’ AND THE RELEASED PARTIES’ TOTAL LIABILITY WITH RESPECT TO ALL CLAIMS ARISING OUT OF OR RELATED TO THIS EULA, THE REVERSINGLABS PRODUCTS AND SERVICES (INCLUDING CLAIMS OF NEGLIGENCE AND STRICT LIABILITY) EXCEED THE LOWER OF (i) THE AGGREGATE DIRECT DAMAGES ACTUALLY INCURRED BY YOU AND YOUR END USERS, OR (ii) US$5OO. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">10.4 SOME JURISDICTIONS LIMIT THE EXCLUSION OF DAMAGES OR LIMITATION OF LIABILITY, SO THE ABOVE EXCLUSIONS AND LIMITATIONS MAY NOT APPLY TO YOU. IF ANY PART OF THE EXCLUSIONS OF DAMAGES OR LIMITATIONS OF LIABILITY SET FORTH IN THIS EULA IS UNENFORCEABLE UNDER APPLICABLE LAW, REVERSINGLABS’ AND THE RELEASED PARTIES’ AGGREGATE LIABILITY WILL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW, EVEN IF ANY REMEDY FAILS ITS ESSENTIAL PURPOSE. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">11. TERM AND TERMINATION. The term of this EULA shall be as set forth in Section 3.2 unless you and ReversingLabs enter into a new agreement that entirely replaces this EULA or unless ReversingLabs terminates this EULA as provided herein. Without prejudice to any other rights, ReversingLabs may terminate this EULA if you fail to comply with its terms and conditions. If ReversingLabs terminates this EULA, (i) you must immediately stop using the ReversingLabs Products and Services and destroy all copies of the Software, Data and all of its component parts (if applicable), and (ii) ReversingLabs will have no further obligation to provide any Services being provided to you or any End Users as of the termination date. Termination of this Agreement shall not affect rights of your external End Users receiving any Software or Data integrated in or otherwise combined with Licensee’s own products/services </span></p><p class="c0"><span class="c2">prior to the date of termination, provided, however, that ReversingLabs shall have received payment of any fees owing from Licensee therefor. The parties’ respective rights and obligations under Sections 2 (Ownership), 4 (Restrictions), 8 (Limited Warranty and Warranty Disclaimer), 10 (Exclusion of Damages and Limitation of Liability), and Section 12 (General Provisions) will survive the termination of this EULA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12. GENERAL PROVISIONS. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.1 Export Restrictions. You agree to comply with all applicable laws and regulations of governmental bodies and agencies related to use of the ReversingLabs Products and Services and your performance under this EULA. In particular, you acknowledge that the Software and Data is of United States origin, is subject to United States export laws and regulations, and may not be exported or re-exported to certain countries or to persons or entities prohibited from receiving U.S. exports (including Denied Parties, Specially Designated Nationals, and entities on the Bureau of Export Administration Entity List or involved with missile technology or nuclear, chemical or biological weapons). The Software and Data also may be subject to the export, import or other laws of other countries. </span></p><p class="c0"><span class="c2">You represent that you are eligible to receive favorable treatment under current United States export control laws and regulations, and that you will not use or transfer the Software and Data in violation of any U.S. or foreign laws or regulations, or permit others to do so. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.2 Data Protection. Each party undertakes to comply with its obligations under the relevant EU data protection and privacy legislation including (where applicable) the EU Data Protection Directive (95/46) and equivalent national legislation. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.3 Waiver. No delay or omission by either party to exercise any right or power arising upon the other party’s nonperformance or breach will impair that right or power or be construed as a waiver of it. Any waiver must be in writing and signed by the waiving party. A waiver on one occasion will not be construed as a waiver of any subsequent event of nonperformance or breach. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.4 Severability. If any provision of this EULA is declared to be unenforceable for any reason, the remainder of this EULA will continue in full force and effect, and the unenforceable provision will be deemed modified to the extent necessary to comply with the applicable requirements of law, while retaining to the maximum extent permitted by law its intended effect, scope and economic effect. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.5 Governing Law. The interpretation and performance of this EULA will be governed by the laws of the Commonwealth of Massachusetts, USA, applicable to contracts executed in and performed entirely within Massachusetts, but excluding any choice of law principles that would result in the application of the laws of another jurisdiction. The parties expressly agree that the United Nations Convention on Contracts for the International Sale of Goods will not apply to this EULA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.6 Dispute Resolution. Any litigation arising under or related to this EULA will be brought only in the United States District Court for the District of Massachusetts, or, if federal subject matter jurisdiction is lacking, then in the Massachusetts state court for the division and county in which ReversingLabs’ or its successor’s or assign’s principal office in Massachusetts is then located. You hereby submit to the personal jurisdiction of these courts and waive all objections to placing venue exclusively before them. The prevailing party in any litigation arising under or related to this EULA, in addition to any other relief granted to it, will be entitled to recover from the losing party its reasonable attorneys’ fees and costs incurred in connection with the litigation. Notwithstanding the foregoing, ReversingLabs acknowledges that the Contract Disputes Act, its implementing regulations, and its judicial interpretations may take precedence when the U.S. Government is the party accepting this EULA, if required by law; whenever commercial item protections or other exceptions permit the commercially offered disputes resolution clause to apply, however, it applies in full force. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.7 Returns. If you are a consumer in a European Union member state, you have the rights conferred by this section. In the European Union, you are entitled to cancel your order for the Software and any associated Services within 14 working days from the date on which you downloaded the Software. You are not entitled to cancel your order for Software or Services if you accept this EULA and install the Software. To cancel your order please discontinue the installation process and notify us of your decision in writing or by email within 14 working days of download at the postal or e-mail address indicated below. ReversingLabs will refund the amount you paid for the cancelled Software and Services within 30 days. If you request a refund, you will not be entitled to use the Software or obtain Services unless you place a new order and pay all charges that then apply. </span></p><p class="c0"><span class="c2">Reversing Labs International, GmbH <br>C/O Herbert Trachsler <br>Seefeldstrasse 283 <br>8008 Zurich <br>Switzerland </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.8 Payment and Taxes. You agree to pay all applicable fees and other charges for Software and Services you acquire. Unless prepaid, all fees and charges are payable in U.S. dollars and are due net thirty (30) days from the date of invoice. ReversingLabs may charge a late fee of 1.5% per month or the maximum rate allowable by law, whichever is greater, on any balance remaining unpaid for more than thirty (30) days, except that interest on payments by U.S. government customers will be calculated according to the Prompt Payment Act and its implementing regulations. Prices are exclusive of all applicable taxes. You agree to pay all taxes (including but not limited to sales, use, excise, and value-added taxes), tariffs, duties, customs fees or similar charges imposed or levied on all Software and Services you acquire, with the exception of taxes on ReversingLabs' net income. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.9 Software, Data and EULA Transfer. Except with respect to the ReversingLabs Products and Services covered by Section 6, the initial licensee of any ReversingLabs Products and Services utilized by you on your own Computer or Server may make a one-time, permanent transfer of this EULA and the ReversingLabs Products and Services directly to an individual or a single entity. The transfer must include all of the Software (including all component parts and Documentation) and this EULA, and it may not occur by way of consignment or any other indirect transfer. The transferee of the one-time transfer must agree to comply with the terms of this EULA, including the obligation not to further transfer this Software. You may not otherwise transfer the Software or assign any of your rights or obligations under this EULA. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">12.10 Entire Agreement. This EULA and the Order Form(s) incorporated herein set forth the entire agreement between you and ReversingLabs with respect to the ReversingLabs Products and Services, and supersedes all prior communications, understandings and agreements, as well as the terms and conditions set forth in or on any purchase order, acknowledgement form, check, or any other document or instrument you may issue to ReversingLabs or transmit in connection with any payment for the ReversingLabs Products and Services. </span></p><p class="c0 c4"><span class="c2"></span></p><p class="c0"><span class="c2">Copyright ReversingLabs, Inc. 2012. All Rights Reserved. ReversingLabs, TitaniumCore, TiCore, TitaniumCloud, and TiCloud are trademarks of ReversingLabs Corp. </span></p><p class="c4 c30"><span class="c3"></span></p><div><p class="c18"><span class="c11">v. 1.1</span><span>7</span><span class="c11">, 1/202</span><span>3</span></p><p class="c18 c4"><span class="c11"></span></p></div></body></html> \ No newline at end of file diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.form new file mode 100644 index 0000000000000000000000000000000000000000..ea67d1f7d76757fd7209188b02934fc9589682ba --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.form @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo"> + <Properties> + <Property name="defaultCloseOperation" type="int" value="2"/> + <Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="EULADialog.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[32767, 32767]"/> + </Property> + <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[550, 550]"/> + </Property> + <Property name="size" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[550, 550]"/> + </Property> + </Properties> + <SyntheticProperties> + <SyntheticProperty name="formSizePolicy" type="int" value="1"/> + <SyntheticProperty name="generateCenter" type="boolean" value="false"/> + </SyntheticProperties> + <AuxValues> + <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/> + <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/> + <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/> + <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/> + <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/> + <AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,2,40,0,0,2,41"/> + </AuxValues> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> + <SubComponents> + <Container class="javax.swing.JPanel" name="viewablePanel"> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="0" gridWidth="3" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="10" weightX="1.0" weightY="1.0"/> + </Constraint> + </Constraints> + + <Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/> + </Container> + <Container class="javax.swing.JPanel" name="paddingPanel"> + <Properties> + <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> + <Dimension value="[32767, 0]"/> + </Property> + </Properties> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="0.0"/> + </Constraint> + </Constraints> + + <Layout> + <DimensionLayout dim="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + <DimensionLayout dim="1"> + <Group type="103" groupAlignment="0" attributes="0"> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> + </DimensionLayout> + </Layout> + </Container> + <Component class="javax.swing.JButton" name="acceptButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="EULADialog.acceptButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + <Property name="enabled" type="boolean" value="false"/> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="acceptButtonActionPerformed"/> + </Events> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="2" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="5" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + <Component class="javax.swing.JButton" name="cancelButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="EULADialog.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/> + </Events> + <AuxValues> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> + <Constraints> + <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> + <GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/> + </Constraint> + </Constraints> + </Component> + </SubComponents> +</Form> diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java new file mode 100644 index 0000000000000000000000000000000000000000..31056e1f2a982412340a782755ffe77e98c5fdce --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/EULADialog.java @@ -0,0 +1,175 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; + +import java.awt.BorderLayout; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import javafx.application.Platform; +import javafx.concurrent.Worker.State; +import javafx.embed.swing.JFXPanel; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.web.WebView; +import javax.swing.SwingUtilities; +import org.apache.commons.io.IOUtils; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Dialog for displaying the Cyber Triage EULA before the license is saved. + */ +public class EULADialog extends javax.swing.JDialog { + + private static final Logger LOGGER = Logger.getLogger(EULADialog.class.getName()); + private static final String EULA_RESOURCE = "EULA.htm"; + + private boolean acceptPressed = false; + + /** + * Creates new form EULADialog + */ + public EULADialog(java.awt.Frame parent, boolean modal) throws IOException { + super(parent, modal); + initComponents(); + loadEULA(); + } + + boolean isAcceptPressed() { + return acceptPressed; + } + + private void loadEULA() throws IOException { + InputStream eulaInputStream = EULADialog.class.getResourceAsStream(EULA_RESOURCE); + final String htmlString = IOUtils.toString(eulaInputStream, StandardCharsets.UTF_8); + final JFXPanel fxPanel = new JFXPanel(); + this.viewablePanel.add(fxPanel, BorderLayout.CENTER); + Platform.runLater(() -> { + WebView webView = new WebView(); + webView.setMaxSize(Short.MAX_VALUE, Short.MAX_VALUE); + webView.setPrefSize(Short.MAX_VALUE, Short.MAX_VALUE); + webView.setMinSize(100, 100); + webView.getEngine().getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> { + if (newState == State.SUCCEEDED) { + SwingUtilities.invokeLater(() -> EULADialog.this.acceptButton.setEnabled(true)); + } + }); + webView.getEngine().loadContent(htmlString, "text/html"); + VBox root = new VBox(webView); + Scene scene = new Scene(root, Color.RED); + fxPanel.setScene(scene); + }); + } + + /** + * 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") + // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + viewablePanel = new javax.swing.JPanel(); + javax.swing.JPanel paddingPanel = new javax.swing.JPanel(); + acceptButton = new javax.swing.JButton(); + javax.swing.JButton cancelButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle(org.openide.util.NbBundle.getMessage(EULADialog.class, "EULADialog.title")); // NOI18N + setMaximumSize(new java.awt.Dimension(32767, 32767)); + setPreferredSize(new java.awt.Dimension(550, 550)); + setSize(new java.awt.Dimension(550, 550)); + getContentPane().setLayout(new java.awt.GridBagLayout()); + + viewablePanel.setLayout(new java.awt.BorderLayout()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(viewablePanel, gridBagConstraints); + + paddingPanel.setMaximumSize(new java.awt.Dimension(32767, 0)); + + javax.swing.GroupLayout paddingPanelLayout = new javax.swing.GroupLayout(paddingPanel); + paddingPanel.setLayout(paddingPanelLayout); + paddingPanelLayout.setHorizontalGroup( + paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + paddingPanelLayout.setVerticalGroup( + paddingPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + getContentPane().add(paddingPanel, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(acceptButton, org.openide.util.NbBundle.getMessage(EULADialog.class, "EULADialog.acceptButton.text")); // NOI18N + acceptButton.setEnabled(false); + acceptButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + acceptButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + getContentPane().add(acceptButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(EULADialog.class, "EULADialog.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 = 1; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + getContentPane().add(cancelButton, gridBagConstraints); + + pack(); + }// </editor-fold>//GEN-END:initComponents + + private void acceptButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_acceptButtonActionPerformed + acceptPressed = true; + dispose(); + }//GEN-LAST:event_acceptButtonActionPerformed + + private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed + dispose(); + }//GEN-LAST:event_cancelButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton acceptButton; + private javax.swing.JPanel viewablePanel; + // 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 0000000000000000000000000000000000000000..73f1fa20c30042904966e8acac543477d56ac5f5 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/subpanel/CTOptionsSubPanel.java @@ -0,0 +1,31 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.ctoptions.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 Binary files /dev/null and b/Core/src/com/basistech/df/cybertriage/autopsy/images/logo.png differ diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..eab025a6416569645480ca93437cc353a22ed467 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/BatchProcessor.java @@ -0,0 +1,88 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.malwarescan; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; + +/** + * Processes a batch when number of items reaches batchSize or flush. Processing + * blocks (and subsequently add and flush operations) until previous batch + * finishes. + */ +public class BatchProcessor<T> { + + private ExecutorService processingExecutorService = Executors.newSingleThreadExecutor(); + + private final BlockingQueue<T> batchingQueue; + private final int batchSize; + private final Consumer<List<T>> itemsConsumer; + private final long secondsTimeout; + + public BatchProcessor(int batchSize, long secondsTimeout, Consumer<List<T>> itemsConsumer) { + this.batchingQueue = new LinkedBlockingQueue<>(batchSize); + this.batchSize = batchSize; + this.itemsConsumer = itemsConsumer; + this.secondsTimeout = secondsTimeout; + } + + public synchronized void add(T item) throws InterruptedException { + batchingQueue.add(item); + if (batchingQueue.size() >= batchSize) { + asyncProcessBatch(); + } + } + + public synchronized void flushAndReset() throws InterruptedException { + // get any remaining + asyncProcessBatch(); + + // don't accept any new additions + processingExecutorService.shutdown(); + + // await termination + processingExecutorService.awaitTermination(secondsTimeout, TimeUnit.SECONDS); + + // get new (not shut down executor) + processingExecutorService = Executors.newSingleThreadExecutor(); + } + + private synchronized void asyncProcessBatch() throws InterruptedException { + if (!batchingQueue.isEmpty()) { + final List<T> processingList = new ArrayList<>(); + + // transfer batching queue to processing queue + batchingQueue.drainTo(processingList); + + // submit to be processed + processingExecutorService.submit(() -> itemsConsumer.accept(processingList)); + } + } + +} 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 0000000000000000000000000000000000000000..0c97a98c2eeb0a162daa3d5feca53a591f77df7d --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/Bundle.properties-MERGED @@ -0,0 +1,27 @@ +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 +MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled. +MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License +MalwareScanIngestModule_ShareProcessing_noRemaining_desc=There are no more remaining hash lookups for this license at this time. Cyber Triage processing will be disabled. +MalwareScanIngestModule_ShareProcessing_noRemaining_title=No remaining lookups +MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables. +MalwareScanIngestModuleFactory_displayName=Cyber Triage Malware Scanner +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 0000000000000000000000000000000000000000..6d3f7d4766d9a53a02b5b8e5589639ee43ed66c3 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java @@ -0,0 +1,445 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.malwarescan; + +import com.basistech.df.cybertriage.autopsy.ctapi.CTApiDAO; +import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData; +import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean; +import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo; +import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence; +import java.text.MessageFormat; +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.collections4.CollectionUtils; +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.AnalysisResult; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.HashUtility; +import org.sleuthkit.datamodel.HashUtility.HashResult; +import org.sleuthkit.datamodel.HashUtility.HashType; +import org.sleuthkit.datamodel.Score; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +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; + // 1 day timeout for all API requests + private static final long FLUSH_SECS_TIMEOUT = 24 * 60 * 60; + + //minimum lookups left before issuing warning + private static final long LOW_LOOKUPS_REMAINING = 250; + + private static final Set<String> 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<FileRecord> batchProcessor = new BatchProcessor<FileRecord>(BATCH_SIZE, FLUSH_SECS_TIMEOUT, this::handleBatch); + + private final CTLicensePersistence ctSettingsPersistence = CTLicensePersistence.getInstance(); + private final CTApiDAO ctApiDAO = CTApiDAO.getInstance(); + + private RunState runState = null; + + private SleuthkitCase tskCase = null; + private FileTypeDetector fileTypeDetector = null; + private LicenseInfo licenseInfo = null; + private BlackboardArtifact.Type malwareType = null; + private long dsId = 0; + private long ingestJobId = 0; + + @Messages({ + "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low", + "# {0} - remainingLookups", + "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc=This license only has {0} lookups remaining", + "MalwareScanIngestModule_malwareTypeDisplayName=Malware", + "MalwareScanIngestModule_ShareProcessing_noLicense_title=No Cyber Triage License", + "MalwareScanIngestModule_ShareProcessing_noLicense_desc=No Cyber Triage license could be loaded. Cyber Triage processing will be disabled.", + "MalwareScanIngestModule_ShareProcessing_noRemaining_title=No remaining lookups", + "MalwareScanIngestModule_ShareProcessing_noRemaining_desc=There are no more remaining hash lookups for this license at this time. Cyber Triage processing will be disabled." + }) + synchronized void startUp(IngestJobContext context) throws IngestModuleException { + // only run this code once per startup + if (runState == RunState.STARTED_UP || runState == RunState.DISABLED) { + return; + } + + try { + // get saved license + Optional<LicenseInfo> licenseInfoOpt = ctSettingsPersistence.loadLicenseInfo(); + if (licenseInfoOpt.isEmpty() || licenseInfoOpt.get().getDecryptedLicense() == null) { + notifyWarning( + Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_title(), + Bundle.MalwareScanIngestModule_ShareProcessing_noLicense_desc(), + null); + runState = RunState.DISABLED; + return; + } + + AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfoOpt.get().getDecryptedLicense()); + // syncronously fetch malware scans info + + // determine lookups remaining + long lookupsRemaining = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount()); + if (lookupsRemaining <= 0) { + notifyWarning( + Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_title(), + Bundle.MalwareScanIngestModule_ShareProcessing_noRemaining_desc(), + null); + runState = RunState.DISABLED; + return; + } else if (lookupsRemaining < LOW_LOOKUPS_REMAINING) { + notifyWarning( + Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title(), + Bundle.MalwareScanIngestModule_ShareProcessing_lowLimitWarning_desc(lookupsRemaining), + null); + } + + // setup necessary variables for processing + tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + malwareType = tskCase.getBlackboard().getOrAddArtifactType( + MALWARE_TYPE_NAME, + Bundle.MalwareScanIngestModule_malwareTypeDisplayName(), + BlackboardArtifact.Category.ANALYSIS_RESULT); + fileTypeDetector = new FileTypeDetector(); + dsId = context.getDataSource().getId(); + ingestJobId = context.getJobId(); + licenseInfo = licenseInfoOpt.get(); + + // set run state to initialized + runState = RunState.STARTED_UP; + } catch (Exception ex) { + runState = RunState.DISABLED; + throw new IngestModuleException("An exception occurred on MalwareScanIngestModule startup", ex); + } + } + + private static long remaining(Long limit, Long used) { + limit = limit == null ? 0 : limit; + used = used == null ? 0 : used; + return limit - used; + } + + private String getOrCalcHash(AbstractFile af) { + if (StringUtils.isNotBlank(af.getMd5Hash())) { + return af.getMd5Hash(); + } + + try { + List<HashResult> hashResults = HashUtility.calculateHashes(af, Collections.singletonList(HashType.MD5)); + if (CollectionUtils.isNotEmpty(hashResults)) { + for (HashResult hashResult : hashResults) { + if (hashResult.getType() == HashType.MD5) { + return hashResult.getValue(); + } + } + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, + MessageFormat.format("An error occurred while processing file name: {0} and obj id: {1}.", + af.getName(), + af.getId()), + ex); + } + + return null; + } + + @Messages({ + "MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout", + "MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out" + }) + IngestModule.ProcessResult process(AbstractFile af) { + try { + if (runState == RunState.STARTED_UP + && af.getKnown() != TskData.FileKnown.KNOWN + && EXECUTABLE_MIME_TYPES.contains(StringUtils.defaultString(fileTypeDetector.getMIMEType(af)).trim().toLowerCase()) + && CollectionUtils.isEmpty(af.getAnalysisResults(malwareType))) { + + String md5 = getOrCalcHash(af); + if (StringUtils.isNotBlank(md5)) { + batchProcessor.add(new FileRecord(af.getId(), md5)); + } + } + return ProcessResult.OK; + } catch (TskCoreException ex) { + notifyWarning( + Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_title(), + Bundle.MalwareScanIngestModule_SharedProcessing_generalProcessingError_desc(), + ex); + return IngestModule.ProcessResult.ERROR; + } 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<FileRecord> fileRecords) { + if (runState != RunState.STARTED_UP || fileRecords == null || fileRecords.isEmpty()) { + return; + } + + // create mapping of md5 to corresponding object ids as well as just the list of md5's + Map<String, List<Long>> md5ToObjId = new HashMap<>(); + + 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()); + + } + + List<String> md5Hashes = new ArrayList<>(md5ToObjId.keySet()); + + if (md5Hashes.isEmpty()) { + return; + } + + try { + // get an auth token with the license + AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(licenseInfo.getDecryptedLicense()); + + // make sure we are in bounds for the remaining scans + long remainingScans = remaining(authTokenResponse.getHashLookupLimit(), authTokenResponse.getHashLookupCount()); + if (remainingScans <= 0) { + runState = RunState.DISABLED; + notifyWarning( + Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_title(), + Bundle.MalwareScanIngestModule_SharedProcessing_exhaustedHashLookups_desc(), + null); + return; + } + + // using auth token, get results + List<CTCloudBean> repResult = ctApiDAO.getReputationResults( + new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse), + md5Hashes + ); + + List<BlackboardArtifact> createdArtifacts = new ArrayList<>(); + if (!CollectionUtils.isEmpty(repResult)) { + SleuthkitCase.CaseDbTransaction trans = null; + try { + trans = tskCase.beginTransaction(); + for (CTCloudBean result : repResult) { + String sanitizedMd5 = sanitizedMd5(result.getMd5HashValue()); + List<Long> objIds = md5ToObjId.remove(sanitizedMd5); + if (objIds == null || objIds.isEmpty()) { + continue; + } + + for (Long objId : objIds) { + AnalysisResult res = createAnalysisResult(objId, result, trans); + if (res != null) { + createdArtifacts.add(res); + } + } + } + + trans.commit(); + trans = null; + } finally { + if (trans != null) { + trans.rollback(); + createdArtifacts.clear(); + trans = null; + } + } + + if (!CollectionUtils.isEmpty(createdArtifacts)) { + tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId); + } + } + } 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 AnalysisResult createAnalysisResult(Long objId, CTCloudBean cloudBean, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException { + if (objId == null || cloudBean == null || cloudBean.getMalwareResult() == null) { + return null; + } + + Score score = cloudBean.getMalwareResult().getCTScore() == null + ? Score.SCORE_UNKNOWN + : cloudBean.getMalwareResult().getCTScore().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 = cloudBean.getMalwareResult().getStatusDescription(); + + return tskCase.getBlackboard().newAnalysisResult( + malwareType, + objId, + dsId, + score, + conclusion, + MALWARE_CONFIG, + justification, + Collections.emptyList(), + trans).getAnalysisResult(); + } + + @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.flushAndReset(); + } 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 + runState = RunState.SHUT_DOWN; + } + } + + 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, DISABLED, 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 0000000000000000000000000000000000000000..e1381160363d093f7daff876c69d2b7febca2d9b --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModuleFactory.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.malwarescan; + +import com.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=Cyber Triage Malware Scanner", + "MalwareScanIngestModuleFactory_description=The malware scan ingest module queries the Cyber Triage cloud API for any possible malicious executables.", + "MalwareScanIngestModuleFactory_version=1.0.0" +}) +public class MalwareScanIngestModuleFactory extends IngestModuleFactoryAdapter { + + /** + * @return The display name for the factory (static method). + */ + public static String getDisplayName() { + return Bundle.MalwareScanIngestModuleFactory_displayName(); + } + + @Override + public String getModuleDisplayName() { + return MalwareScanIngestModuleFactory.getDisplayName(); + } + + @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/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index cee09547a30468b837882105371c7376d35dfe6c..da9e2629f1dfb0dba3f1b0c34f6ab4d64db9c56b 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -22,6 +22,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Paths; +import java.time.ZoneId; import java.util.Arrays; import java.util.List; import java.util.Properties; @@ -385,6 +386,30 @@ public static void setDisplayTimesInLocalTime(boolean value) { public static String getTimeZoneForDisplays() { return viewPreferences.get(TIME_ZONE_FOR_DISPLAYS, TimeZone.GMT_ZONE.getID()); } + + /** + * Returns the inferred preferred time zone deriving in order: + * 1) Starts with user preference if set + * 2) Gets system time zone if can be determined + * 3) Otherwise uses GMT + * + * @return Returns preferred time zone id. + */ + public static String getInferredUserTimeZone() { + String timeZonePref = viewPreferences.get(TIME_ZONE_FOR_DISPLAYS, null); + if (StringUtils.isBlank(timeZonePref)) { + ZoneId systemZoneId = ZoneId.systemDefault(); + if (systemZoneId != null) { + timeZonePref = systemZoneId.getId(); + } + } + + if (StringUtils.isBlank(timeZonePref)) { + timeZonePref = TimeZone.GMT_ZONE.getID(); + } + + return timeZonePref; + } public static void setTimeZoneForDisplays(String timeZone) { viewPreferences.put(TIME_ZONE_FOR_DISPLAYS, timeZone); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index f66305432537244fae632041179afbaa241d1c90..75b612a0b4843c5ba9670879bc4cf0fa51b0a90d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -101,7 +101,7 @@ ImageNode.createSheet.name.displayName=Name ImageNode.createSheet.name.desc=no description Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null\! Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""\! -Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n\nDetails: {0} +Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n Is Autopsy or Cyber Triage already running?\n\nDetails: {0} Installer.tskLibErr.err=Fatal Error\! InterestingHits.interestingItems.text=INTERESTING ITEMS InterestingHits.displayName.text=Interesting Items diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 5dbdff8b898b6320bd3fdd0442342033a79894e1..4d958cf39bc82ae530a5dcadf7ea0df4f2336720 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -315,7 +315,7 @@ ImageNode.createSheet.name.displayName=Name ImageNode.createSheet.name.desc=no description Installer.exception.tskVerStringNull.msg=Sleuth Kit JNI test call returned without error, but version string was null\! Installer.exception.taskVerStringBang.msg=Sleuth Kit JNI test call returned without error, but version string was ""\! -Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n\nDetails: {0} +Installer.tskLibErr.msg=Problem with Sleuth Kit JNI. Test call failed\!\n Is Autopsy or Cyber Triage already running?\n\nDetails: {0} Installer.tskLibErr.err=Fatal Error\! InterestingHits.interestingItems.text=INTERESTING ITEMS InterestingHits.displayName.text=Interesting Items @@ -418,6 +418,7 @@ ScoreContent_createSheet_filterType_displayName=Type ScoreContent_createSheet_name_desc=no description ScoreContent_createSheet_name_displayName=Name ScoreContent_ScoreContentNode_name=Score +ScoreContent_ScoreFileNode_type=File ScoreContent_susFilter_text=Suspicious Items SlackFileNode.getActions.viewInNewWin.text=View in New Window SlackFileNode.getActions.viewFileInDir.text=View File in Directory diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Installer.java b/Core/src/org/sleuthkit/autopsy/datamodel/Installer.java index 81b19800d9f9f3f8eb72a1e374e7ce3222d90e31..323424efdf375574bf673164a0d83cf42344a2d8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Installer.java @@ -70,8 +70,9 @@ public void validate() throws IllegalStateException { logger.log(Level.CONFIG, "Sleuth Kit Version: {0}", skVersion); //NON-NLS } - } catch (Exception e) { + } catch (Exception | UnsatisfiedLinkError e) { logger.log(Level.SEVERE, "Error calling Sleuth Kit library (test call failed)", e); //NON-NLS + logger.log(Level.SEVERE, "Is Autopsy or Cyber Triage already running?)", e); //NON-NLS // Normal error box log handler won't be loaded yet, so show error here. final Component parentComponent = null; // Use default window frame. diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index a89d0ec6808e79dc8fa329d4bb664114401adbb4..9f2214c520b61777a8dd411cc95b8bf4765f2be7 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.ingest; +import com.basistech.df.cybertriage.autopsy.malwarescan.MalwareScanIngestModuleFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -33,7 +34,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.openide.util.NbBundle; import org.openide.util.io.NbObjectInputStream; import org.openide.util.io.NbObjectOutputStream; @@ -54,6 +58,11 @@ public final class IngestJobSettings { private static final String LAST_FILE_INGEST_FILTER_PROPERTY = "Last_File_Ingest_Filter"; //NON-NLS private static final String MODULE_SETTINGS_FOLDER_NAME = "IngestSettings"; //NON-NLS + private static final Set<String> DEFAULT_DISABLED_MODULES = Stream.of( + "Plaso", + MalwareScanIngestModuleFactory.getDisplayName() + ).collect(Collectors.toSet()); + private static final String MODULE_SETTINGS_FOLDER = Paths.get( Paths.get(PlatformUtil.getUserConfigDirectory()).relativize(Paths.get(PlatformUtil.getModuleConfigDirectory())).toString(), MODULE_SETTINGS_FOLDER_NAME @@ -361,37 +370,36 @@ private void load() { loadedModuleNames.add(moduleFactory.getModuleDisplayName()); } - /** - * Hard coding Plaso to be disabled by default. loadedModuleNames is - * passed below as the default list of enabled modules so briefly remove - * Plaso from loaded modules to get the list of enabled and disabled - * modules names. Then put Plaso back into loadedModulesNames to let the - * rest of the code continue as before. - */ - final String plasoModuleName = "Plaso"; - boolean plasoLoaded = loadedModuleNames.contains(plasoModuleName); - if (plasoLoaded) { - loadedModuleNames.remove(plasoModuleName); + + Set<String> defaultEnabledAndLoaded = new HashSet<>(); + Set<String> defaultDisabledAndLoaded = new HashSet<>(); + for (String loadedModule: loadedModuleNames) { + if (DEFAULT_DISABLED_MODULES.contains(loadedModule)) { + defaultDisabledAndLoaded.add(loadedModule); + } else { + defaultEnabledAndLoaded.add(loadedModule); + } } /** * Get the enabled/disabled ingest modules settings for this context. By * default, all loaded modules except Plaso are enabled. */ - HashSet<String> enabledModuleNames = getModulesNames(this.executionContext, IngestJobSettings.ENABLED_MODULES_PROPERTY, makeCsvList(loadedModuleNames)); - HashSet<String> disabledModuleNames = getModulesNames(this.executionContext, IngestJobSettings.DISABLED_MODULES_PROPERTY, plasoModuleName); //NON-NLS - - // If plaso was loaded, but appears in neither the enabled nor the - // disabled list, add it to the disabled list. - if (!enabledModuleNames.contains(plasoModuleName) && !disabledModuleNames.contains(plasoModuleName)) { - disabledModuleNames.add(plasoModuleName); - } - - //Put plaso back into loadedModuleNames - if (plasoLoaded) { - loadedModuleNames.add(plasoModuleName); + HashSet<String> enabledModuleNames = getModulesNames(this.executionContext, IngestJobSettings.ENABLED_MODULES_PROPERTY, makeCsvList(defaultEnabledAndLoaded)); + HashSet<String> disabledModuleNames = getModulesNames(this.executionContext, IngestJobSettings.DISABLED_MODULES_PROPERTY, makeCsvList(defaultDisabledAndLoaded)); //NON-NLS + + // double check to ensure all loaded modules are present in one of the lists in case more modules (in case settings didn't have a module) + for (String loadedModule : loadedModuleNames) { + // if neither enabled modules or disabled modules contains the loaded module, add it to the default location + if (!enabledModuleNames.contains(loadedModule) && !disabledModuleNames.contains(loadedModule)) { + if (DEFAULT_DISABLED_MODULES.contains(loadedModule)) { + disabledModuleNames.add(loadedModule); + } else { + enabledModuleNames.add(loadedModule); + } + } } - + /** * Check for missing modules and create warnings if any are found. */ diff --git a/Core/src/org/sleuthkit/autopsy/integrationtesting/ConfigurationModuleManager.java b/Core/src/org/sleuthkit/autopsy/integrationtesting/ConfigurationModuleManager.java index 9aabc396b1eb7e7703d2c5e3a056309c05e313df..dabb034d5f1d51737603925a1af2c213313f4513 100644 --- a/Core/src/org/sleuthkit/autopsy/integrationtesting/ConfigurationModuleManager.java +++ b/Core/src/org/sleuthkit/autopsy/integrationtesting/ConfigurationModuleManager.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.integrationtesting; +import com.basistech.df.cybertriage.autopsy.malwarescan.MalwareScanIngestModuleFactory; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -46,7 +47,10 @@ public class ConfigurationModuleManager { private static final Logger logger = Logger.getLogger(ConfigurationModuleManager.class.getName()); private static final IngestJobSettings.IngestType DEFAULT_INGEST_FILTER_TYPE = IngestJobSettings.IngestType.ALL_MODULES; - private static final Set<String> DEFAULT_EXCLUDED_MODULES = Stream.of("Plaso").collect(Collectors.toSet()); + private static final Set<String> DEFAULT_EXCLUDED_MODULES = Stream.of( + "Plaso", + MalwareScanIngestModuleFactory.getDisplayName() + ).collect(Collectors.toSet()); private static final ConfigDeserializer configDeserializer = new ConfigDeserializer(); /** diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index 51bbeb822023e2b238f62270509f1ac26f372639..d60ce176007d96a202ba8b4358a6b05b90bbdb04 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -1,5 +1,6 @@ <!DOCTYPE ivy-module [ <!ENTITY javafx.version "17.0.7"> + <!ENTITY jackson.version "2.15.2"> ]> <ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra"> <info organisation="org.sleuthkit.autopsy" module="corelibs"/> @@ -87,7 +88,8 @@ <dependency conf="autopsy_core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.4"/> - <dependency conf="autopsy_core->default" org="com.fasterxml.jackson.dataformat" name="jackson-dataformat-csv" rev="2.15.2"/> + <dependency conf="autopsy_core->default" org="com.fasterxml.jackson.dataformat" name="jackson-dataformat-csv" rev="&jackson.version;"/> + <dependency conf="autopsy_core->default" org="com.fasterxml.jackson.datatype" name="jackson-datatype-jsr310" rev="&jackson.version;"/> <!-- better image resizing --> <dependency conf="autopsy_core->default" org="org.imgscalr" name="imgscalr-lib" rev="4.2" /> @@ -142,8 +144,8 @@ <override org="com.google.code.gson" module="gson" rev="2.9.0"/> <override org="com.google.guava" module="guava" rev="32.0.1-jre"/> - <override org="com.fasterxml.jackson.core" module="jackson-databind" rev="2.15.2"/> - <override org="com.fasterxml.jackson.core" module="jackson-core" rev="2.15.2"/> + <override org="com.fasterxml.jackson.core" module="jackson-databind" rev="&jackson.version;"/> + <override org="com.fasterxml.jackson.core" module="jackson-core" rev="&jackson.version;"/> <!-- changes to bouncy castle version may also be reflected in thirdparty/IcePDF 6.2.2 --> <override org="org.bouncycastle" module="bcprov-jdk15on" rev="1.70"/> diff --git a/CoreLibs/manifest.mf b/CoreLibs/manifest.mf index b8faef25275f4f1b59019dc32a9a55fd5b1bf3db..1d3168bf2ceb9a52c57a78eb05e4a5f4972e016a 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 fe5e78acffee0357254968f4833ac23fcb76ee49..320a1e36e5d59395a9908ed38b9d06dc36bb9d35 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 f7b139f50237a2d456d2e0d6c74258f23638b9ee..ff5e70f4e8246e58775254abd51531b49e792eff 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -63,6 +63,12 @@ <package>com.fasterxml.jackson.databind.type</package> <package>com.fasterxml.jackson.databind.util</package> <package>com.fasterxml.jackson.dataformat.csv</package> + <package>com.fasterxml.jackson.datatype.jsr310</package> + <package>com.fasterxml.jackson.datatype.jsr310.deser</package> + <package>com.fasterxml.jackson.datatype.jsr310.deser.key</package> + <package>com.fasterxml.jackson.datatype.jsr310.ser</package> + <package>com.fasterxml.jackson.datatype.jsr310.ser.key</package> + <package>com.fasterxml.jackson.datatype.jsr310.util</package> <package>com.github.lgooddatepicker.components</package> <package>com.github.lgooddatepicker.optionalusertools</package> <package>com.github.lgooddatepicker.zinternaltools</package> @@ -197,8 +203,6 @@ <package>com.google.rpc.context</package> <package>com.google.thirdparty.publicsuffix</package> <package>com.google.type</package> - <package>com.microsoft.schemas.vml</package> - <package>com.microsoft.schemas.vml.impl</package> <package>com.sun.javafx</package> <package>com.sun.javafx.animation</package> <package>com.sun.javafx.application</package> @@ -321,9 +325,6 @@ <package>com.twelvemonkeys.util.regex</package> <package>com.twelvemonkeys.util.service</package> <package>com.twelvemonkeys.xml</package> - <package>javax.annotation</package> - <package>javax.annotation.concurrent</package> - <package>javax.annotation.meta</package> <package>javafx.animation</package> <package>javafx.application</package> <package>javafx.beans</package> @@ -340,7 +341,6 @@ <package>javafx.event</package> <package>javafx.fxml</package> <package>javafx.geometry</package> - <package>javafx.graphics</package> <package>javafx.print</package> <package>javafx.scene</package> <package>javafx.scene.canvas</package> @@ -362,19 +362,9 @@ <package>javafx.stage</package> <package>javafx.util</package> <package>javafx.util.converter</package> - <package>javax.jms</package> - <package>javax.mail</package> - <package>javax.mail.event</package> - <package>javax.mail.internet</package> - <package>javax.mail.search</package> - <package>javax.mail.util</package> - <package>javax.servlet</package> - <package>javax.servlet.http</package> - <package>javax.xml.parsers</package> - <package>javax.xml.transform</package> - <package>javax.xml.transform.dom</package> - <package>javax.xml.transform.sax</package> - <package>javax.xml.transform.stream</package> + <package>javax.annotation</package> + <package>javax.annotation.concurrent</package> + <package>javax.annotation.meta</package> <package>jfxtras.animation</package> <package>jfxtras.css</package> <package>jfxtras.css.converters</package> @@ -442,16 +432,6 @@ <package>org.apache.commons.lang3.tuple</package> <package>org.apache.commons.logging</package> <package>org.apache.commons.logging.impl</package> - <package>org.apache.log</package> - <package>org.apache.log.filter</package> - <package>org.apache.log.format</package> - <package>org.apache.log.output</package> - <package>org.apache.log.output.db</package> - <package>org.apache.log.output.io</package> - <package>org.apache.log.output.io.rotate</package> - <package>org.apache.log.output.jms</package> - <package>org.apache.log.output.net</package> - <package>org.apache.log.util</package> <package>org.apache.commons.text</package> <package>org.apache.commons.validator.routines</package> <package>org.apache.commons.validator.routines.checkdigit</package> @@ -460,14 +440,7 @@ <package>org.apache.log4j.config</package> <package>org.apache.log4j.helpers</package> <package>org.apache.log4j.jdbc</package> - <package>org.apache.log4j.jmx</package> - <package>org.apache.log4j.lf5</package> - <package>org.apache.log4j.lf5.util</package> - <package>org.apache.log4j.lf5.viewer</package> - <package>org.apache.log4j.lf5.viewer.categoryexplorer</package> - <package>org.apache.log4j.lf5.viewer.configure</package> <package>org.apache.log4j.net</package> - <package>org.apache.log4j.nt</package> <package>org.apache.log4j.or</package> <package>org.apache.log4j.or.jms</package> <package>org.apache.log4j.or.sax</package> @@ -931,6 +904,10 @@ <runtime-relative-path>ext/jackson-dataformat-csv-2.15.2.jar</runtime-relative-path> <binary-origin>release/modules/ext/jackson-dataformat-csv-2.15.2.jar</binary-origin> </class-path-extension> + <class-path-extension> + <runtime-relative-path>ext/jackson-datatype-jsr310-2.15.2.jar</runtime-relative-path> + <binary-origin>release/modules/ext/jackson-datatype-jsr310-2.15.2.jar</binary-origin> + </class-path-extension> <class-path-extension> <runtime-relative-path>ext/javafx-base-17.0.7-linux.jar</runtime-relative-path> <binary-origin>release/modules/ext/javafx-base-17.0.7-linux.jar</binary-origin> 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 6d7443a4a6fb434154bef3db464823bda5cd8e91..4973262834b51d2e88ce9cfcfd90c52a93371adc 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 19:55:04 -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 f28a6b96b3a4c077acea5ede72be916cea81c779..a6687c35a064d842e78448ca24f0ee62b2677882 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 19:55:04 -0400 +CTL_MainWindow_Title=Autopsy 4.20.0 +CTL_MainWindow_Title_No_Project=Autopsy 4.20.0