Merge pull request #7824 from gdicristofaro/CT-7160-ctCloudIngest

Ct-7160 ct malware ingest
This commit is contained in:
Mark McKinnon 2023-07-26 16:28:32 -04:00 committed by GitHub
commit 5a31898151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 5949 additions and 77 deletions

View File

@ -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>

View File

@ -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" />

View File

@ -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

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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 `~!@#$&^*(){}[]\\\\|;'\",<>/?";
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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()
+ '}';
}
}

View File

@ -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;
}
}

View File

@ -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
+ '}';
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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 "";
}
}

View File

@ -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;
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -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
}

View File

@ -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();
}
}

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -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
}

File diff suppressed because one or more lines are too long

View File

@ -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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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, &quot;{key}&quot;)"/>
</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>

View File

@ -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
}

View File

@ -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();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -22,6 +22,7 @@ import java.io.File;
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 final class UserPreferences {
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);

View File

@ -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.HashSet;
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 @@ public final class IngestJobSettings {
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
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
// 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);
// 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);
}
}
}
//Put plaso back into loadedModuleNames
if (plasoLoaded) {
loadedModuleNames.add(plasoModuleName);
}
/**
* Check for missing modules and create warnings if any are found.
*/

View File

@ -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();
/**

View File

@ -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"/>

View File

@ -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
Implementation-Vendor: CoreLibs ImageIO Fields

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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