Merge branch 'sleuthkit:develop' into develop

This commit is contained in:
Mark McKinnon 2023-08-30 10:15:17 -04:00 committed by GitHub
commit 443bedba91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 466 additions and 191 deletions

View File

@ -2,7 +2,7 @@ Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.core/10
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
OpenIDE-Module-Implementation-Version: 37
OpenIDE-Module-Implementation-Version: 38
OpenIDE-Module-Requires: org.openide.windows.WindowManager
AutoUpdate-Show-In-Client: true
AutoUpdate-Essential-Module: true

View File

@ -85,8 +85,8 @@ file.reference.Rejistry-1.1-SNAPSHOT.jar=release/modules/ext/Rejistry-1.1-SNAPSH
file.reference.serializer-2.7.2.jar=release/modules/ext/serializer-2.7.2.jar
file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
file.reference.sleuthkit-4.12.0.jar=release/modules/ext/sleuthkit-4.12.0.jar
file.reference.sleuthkit-caseuco-4.12.0.jar=release/modules/ext/sleuthkit-caseuco-4.12.0.jar
file.reference.sleuthkit-4.12.1.jar=release/modules/ext/sleuthkit-4.12.1.jar
file.reference.sleuthkit-caseuco-4.12.1.jar=release/modules/ext/sleuthkit-caseuco-4.12.1.jar
file.reference.slf4j-api-1.7.36.jar=release/modules/ext/slf4j-api-1.7.36.jar
file.reference.snakeyaml-2.0.jar=release/modules/ext/snakeyaml-2.0.jar
file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar
@ -107,4 +107,4 @@ license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
nbm.needs.restart=true
spec.version.base=10.24
spec.version.base=10.25

View File

@ -718,12 +718,12 @@
<binary-origin>release/modules/ext/sevenzipjbinding.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-4.12.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-4.12.0.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-4.12.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-4.12.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/sleuthkit-caseuco-4.12.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-caseuco-4.12.0.jar</binary-origin>
<runtime-relative-path>ext/sleuthkit-caseuco-4.12.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sleuthkit-caseuco-4.12.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/slf4j-api-1.7.36.jar</runtime-relative-path>

View File

@ -25,6 +25,7 @@ import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBeanResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
@ -78,21 +79,22 @@ public class CTApiDAO {
}
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException {
return getAuthToken(decrypted, false);
return getAuthToken(decrypted, null);
}
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, boolean fileUpload) throws CTCloudException {
public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, Long fileUploadSize) throws CTCloudException {
AuthTokenRequest authTokenRequest = new AuthTokenRequest()
.setAutopsyVersion(getAppVersion())
.setRequestFileUpload(fileUpload)
.setRequestFileUpload(fileUploadSize != null && fileUploadSize > 0)
.setFileUploadSize(fileUploadSize != null && fileUploadSize > 0 ? fileUploadSize : null)
.setBoostLicenseId(decrypted.getBoostLicenseId())
.setHostId(decrypted.getLicenseHostId());
return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
}
public void uploadFile(String url, String fileName, InputStream fileIs) throws CTCloudException {
httpClient.doFileUploadPost(url, fileName, fileIs);
public void uploadFile(FileUploadRequest fileUploadRequest) throws CTCloudException {
httpClient.doFileUploadPut(fileUploadRequest);
}
public void uploadMeta(AuthenticatedRequestData authenticatedRequestData, MetadataUploadRequest metaRequest) throws CTCloudException {

View File

@ -18,6 +18,8 @@
*/
package com.basistech.df.cybertriage.autopsy.ctapi;
import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException.ErrorCode;
import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
@ -55,13 +57,14 @@ import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
@ -155,16 +158,23 @@ class CTCloudHttpClient {
// Parse Response
if (classType != null) {
HttpEntity entity = response.getEntity();
String entityStr = EntityUtils.toString(entity);
O respObj = mapper.readValue(entityStr, classType);
return respObj;
} else {
return null;
if (entity != null) {
String entityStr = EntityUtils.toString(entity);
if (StringUtils.isNotBlank(entityStr)) {
O respObj = mapper.readValue(entityStr, classType);
return respObj;
}
}
}
return null;
} else {
LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine());
handleNonOKResponse(response, "");
}
// transform all non-CTCloudException's into a CTCloudException
} catch (CTCloudException ex) {
throw ex;
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex);
throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex);
@ -184,10 +194,23 @@ class CTCloudHttpClient {
return null;
}
public void doFileUploadPost(String fullUrlPath, String fileName, InputStream fileIs) throws CTCloudException {
URI postUri;
public void doFileUploadPut(FileUploadRequest fileUploadRequest) throws CTCloudException {
if (fileUploadRequest == null) {
throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fileUploadRequest cannot be null"));
}
String fullUrlPath = fileUploadRequest.getFullUrlPath();
String fileName = fileUploadRequest.getFileName();
InputStream fileInputStream = fileUploadRequest.getFileInputStream();
Long contentLength = fileUploadRequest.getContentLength();
if (StringUtils.isBlank(fullUrlPath) || fileInputStream == null || contentLength == null || contentLength <= 0) {
throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fullUrlPath, fileInputStream, contentLength must not be empty, null or less than 0"));
}
URI putUri;
try {
postUri = new URI(fullUrlPath);
putUri = new URI(fullUrlPath);
} catch (URISyntaxException ex) {
LOGGER.log(Level.WARNING, "Wrong URL syntax for CT Cloud " + fullUrlPath, ex);
throw new CTCloudException(CTCloudException.ErrorCode.UNKNOWN, ex);
@ -195,23 +218,13 @@ class CTCloudHttpClient {
try (CloseableHttpClient httpclient = createConnection(proxySelector, sslContext)) {
LOGGER.log(Level.INFO, "initiating http post request to ctcloud server " + fullUrlPath);
HttpPost post = new HttpPost(postUri);
configureRequestTimeout(post);
HttpPut put = new HttpPut(putUri);
configureRequestTimeout(put);
post.addHeader("Connection", "keep-alive");
put.addHeader("Connection", "keep-alive");
put.setEntity(new InputStreamEntity(fileInputStream, contentLength, ContentType.APPLICATION_OCTET_STREAM));
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody(
"file",
fileIs,
ContentType.APPLICATION_OCTET_STREAM,
fileName
);
HttpEntity multipart = builder.build();
post.setEntity(multipart);
try (CloseableHttpResponse response = httpclient.execute(post)) {
try (CloseableHttpResponse response = httpclient.execute(put)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
LOGGER.log(Level.INFO, "Response Received. - Status OK");
@ -381,7 +394,7 @@ class CTCloudHttpClient {
HttpClientBuilder builder;
if (ProxySettings.getProxyType() != ProxySettings.DIRECT_CONNECTION
&& StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
&& StringUtils.isBlank(ProxySettings.getAuthenticationUsername())
&& ArrayUtils.isEmpty(ProxySettings.getAuthenticationPassword())
&& WinHttpClients.isWinAuthAvailable()) {

View File

@ -34,6 +34,9 @@ public class AuthTokenRequest {
@JsonProperty("requestFileUpload")
private boolean requestFileUpload;
@JsonProperty("fileUploadSize")
private Long fileUploadSize;
@JsonProperty("host_id")
private String hostId;
@ -64,6 +67,16 @@ public class AuthTokenRequest {
return this;
}
public Long getFileUploadSize() {
return fileUploadSize;
}
public AuthTokenRequest setFileUploadSize(Long fileUploadSize) {
this.fileUploadSize = fileUploadSize;
return this;
}
public String getHostId() {
return hostId;
}

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.json;
import java.io.InputStream;
/**
* Data for a file upload request.
*/
public class FileUploadRequest {
private String fullUrlPath;
private String fileName;
private InputStream fileInputStream;
private Long contentLength;
public String getFullUrlPath() {
return fullUrlPath;
}
public FileUploadRequest setFullUrlPath(String fullUrlPath) {
this.fullUrlPath = fullUrlPath;
return this;
}
public String getFileName() {
return fileName;
}
public FileUploadRequest setFileName(String fileName) {
this.fileName = fileName;
return this;
}
public InputStream getFileInputStream() {
return fileInputStream;
}
public FileUploadRequest setFileInputStream(InputStream fileInputStream) {
this.fileInputStream = fileInputStream;
return this;
}
public Long getContentLength() {
return contentLength;
}
public FileUploadRequest setContentLength(Long contentLength) {
this.contentLength = contentLength;
return this;
}
}

View File

@ -32,18 +32,21 @@ public class LicenseResponse {
private final Boolean hostChanged;
private final Long hostChangesRemaining;
private final BoostLicenseResponse boostLicense;
private final String errorMsg;
@JsonCreator
public LicenseResponse(
@JsonProperty("success") Boolean success,
@JsonProperty("hostChanged") Boolean hostChanged,
@JsonProperty("hostChangesRemaining") Long hostChangesRemaining,
@JsonProperty("boostLicense") BoostLicenseResponse boostLicense
@JsonProperty("boostLicense") BoostLicenseResponse boostLicense,
@JsonProperty("errorMsg") String errorMsg
) {
this.success = success;
this.hostChanged = hostChanged;
this.hostChangesRemaining = hostChangesRemaining;
this.boostLicense = boostLicense;
this.errorMsg = errorMsg;
}
public Boolean isSuccess() {
@ -61,4 +64,8 @@ public class LicenseResponse {
public BoostLicenseResponse getBoostLicense() {
return boostLicense;
}
public String getErrorMsg() {
return errorMsg;
}
}

View File

@ -34,6 +34,7 @@ import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.MessageFormat;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -42,6 +43,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.ObjectUtils;
/**
* Decrypts the payload of boost license.
@ -58,12 +60,12 @@ public class LicenseDecryptorUtil {
private LicenseDecryptorUtil() {
}
public LicenseInfo createLicenseInfo(LicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException {
if (licenseResponse == null || licenseResponse.getBoostLicense() == null) {
throw new InvalidLicenseException("License or boost license are null");
if (licenseResponse == null) {
throw new InvalidLicenseException("License is null");
}
DecryptedLicenseResponse decrypted = parseLicenseJSON(licenseResponse.getBoostLicense());
return new LicenseInfo(licenseResponse, decrypted);
}
@ -78,6 +80,9 @@ public class LicenseDecryptorUtil {
* com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException
*/
public DecryptedLicenseResponse parseLicenseJSON(BoostLicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException {
if (licenseResponse == null) {
throw new InvalidLicenseException("Boost license is null");
}
String decryptedJsonResponse;
try {
@ -101,6 +106,12 @@ public class LicenseDecryptorUtil {
}
private String decryptLicenseString(String encryptedJson, String ivBase64, String encryptedKey, String version) throws IOException, GeneralSecurityException, InvalidLicenseException {
if (ObjectUtils.anyNull(encryptedJson, ivBase64, encryptedKey, version)) {
throw new InvalidLicenseException(MessageFormat.format(
"encryptedJson: {0}, iv: {1}, encryptedKey: {2}, version: {3} must all be non-null",
encryptedJson, ivBase64, encryptedKey, version));
}
if (!"1.0".equals(version)) {
throw new InvalidLicenseException("Unexpected file version: " + version);
}

View File

@ -26,12 +26,12 @@ import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.JPanel;
import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.Lookup;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
/**

View File

@ -4,7 +4,7 @@
CTLicenseDialog.title=Add a License...
CTLicenseDialog.licenseNumberLabel.text=License Number:
CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CTLicenseDialog.licenseNumberTextField.text=
CTLicenseDialog.cancelButton.text=Cancel
CTLicenseDialog.okButton.text=Ok
CTLicenseDialog.warningLabel.text=
@ -25,3 +25,4 @@ EULADialog.title=Cyber Triage End User License Agreement
CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
CTMalwareScannerOptionsPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html>
CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit
CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

View File

@ -4,11 +4,11 @@
CTLicenseDialog.title=Add a License...
CTLicenseDialog.licenseNumberLabel.text=License Number:
CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CTLicenseDialog.licenseNumberTextField.text=
CTLicenseDialog.cancelButton.text=Cancel
CTLicenseDialog.okButton.text=Ok
CTLicenseDialog.warningLabel.text=
CTLicenseDialog_verifyInput_licenseNumberError=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html>
CTLicenseDialog_verifyInput_licenseNumberError=<html>Please enter a license number</html>
CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text=
CTMalwareScannerOptionsPanel.countersResetLabel.text=
CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text=
@ -31,6 +31,8 @@ CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title=License Number Alr
CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_desc=Please verify that license number is of format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_title=Invalid License Number
CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error
# {0} - licenseCode
CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0}
CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.
CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error
# {0} - expiresDate
@ -63,3 +65,4 @@ EULADialog.title=Cyber Triage End User License Agreement
CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text=
CTMalwareScannerOptionsPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html>
CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit
CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

View File

@ -127,6 +127,9 @@
<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>
<Property name="toolTipText" 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.toolTipText" 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">

View File

@ -18,18 +18,20 @@
*/
package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud;
import java.awt.Color;
import java.util.regex.Pattern;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.corecomponents.TextPrompt;
/**
* License dialog
*/
class CTLicenseDialog extends javax.swing.JDialog {
private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9\\-]+?\\s*$");
private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9-_]+?\\s*$");
private String licenseString = null;
/**
@ -38,6 +40,7 @@ class CTLicenseDialog extends javax.swing.JDialog {
public CTLicenseDialog(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
configureHintText();
this.licenseNumberTextField.getDocument().putProperty("filterNewlines", Boolean.TRUE);
this.licenseNumberTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
@ -56,13 +59,23 @@ class CTLicenseDialog extends javax.swing.JDialog {
}
});
}
private void configureHintText() {
TextPrompt textPrompt = new TextPrompt(
StringUtils.defaultString(this.licenseNumberTextField.getToolTipText()),
this.licenseNumberTextField);
textPrompt.setForeground(Color.LIGHT_GRAY);
float alpha = 0.9f; // Mostly opaque
textPrompt.changeAlpha(alpha);
}
String getValue() {
return licenseString;
}
@Messages({
"CTLicenseDialog_verifyInput_licenseNumberError=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html>"
"CTLicenseDialog_verifyInput_licenseNumberError=<html>Please enter a license number</html>"
})
private void verifyInput() {
String licenseInput = StringUtils.defaultString(this.licenseNumberTextField.getText());
@ -165,6 +178,7 @@ class CTLicenseDialog extends javax.swing.JDialog {
getContentPane().add(cancelButton, gridBagConstraints);
licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N
licenseNumberTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.toolTipText")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
@ -177,7 +191,8 @@ class CTLicenseDialog extends javax.swing.JDialog {
}// </editor-fold>//GEN-END:initComponents
private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
this.licenseString = this.licenseNumberTextField.getText();
String inputText = this.licenseNumberTextField.getText();
this.licenseString = inputText == null ? null : inputText.trim();
this.dispose();
}//GEN-LAST:event_okButtonActionPerformed

View File

@ -40,7 +40,6 @@ import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
@ -51,6 +50,7 @@ import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Options panel to be displayed in the CTOptionsPanel for settings regarding
@ -608,6 +608,8 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
@NbBundle.Messages({
"CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error",
"CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error",
"# {0} - licenseCode",
"CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0}",
"CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.",})
private class LicenseFetcher extends SwingWorker<LicenseResponse, Void> {
@ -629,10 +631,9 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
protected void done() {
try {
LicenseResponse licenseResponse = get();
if (licenseResponse != null && licenseResponse.isSuccess()) {
SwingUtilities.invokeLater(() -> acceptEula(licenseResponse));
} else {
logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch was not successful");
// if no result, show unauthorized
if (licenseResponse == null) {
logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch returned no result.");
JOptionPane.showMessageDialog(
CTMalwareScannerOptionsPanel.this,
CTCloudException.ErrorCode.UN_AUTHORIZED.getDescription(),
@ -640,7 +641,30 @@ public class CTMalwareScannerOptionsPanel extends CTOptionsSubPanel {
JOptionPane.ERROR_MESSAGE);
setLicenseDisplay(licenseInfo, null);
loadMalwareScansInfo(licenseInfo);
return;
}
// if not successful response
if (!Boolean.TRUE.equals(licenseResponse.isSuccess())) {
logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch was not successful");
// use default message unless error message specified
String message = Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc(licenseText);
if (!StringUtils.isBlank(licenseResponse.getErrorMsg())) {
message = licenseResponse.getErrorMsg();
}
JOptionPane.showMessageDialog(
CTMalwareScannerOptionsPanel.this,
message,
Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title(),
JOptionPane.ERROR_MESSAGE);
setLicenseDisplay(licenseInfo, null);
loadMalwareScansInfo(licenseInfo);
return;
}
// otherwise, load
SwingUtilities.invokeLater(() -> acceptEula(licenseResponse));
} catch (InterruptedException | CancellationException ex) {
// ignore cancellation; just load current license
setLicenseDisplay(licenseInfo, null);

View File

@ -12,3 +12,5 @@ CTIncidentImportOptionsPanel.border.title_1=Incident Importer
CTIncidentImportOptionsPanel.incidentTextLabel.text=The Cyber Triage Incident Import module allows you to open data collected by Cyber Triage in Autopsy. To use this feature you must install the Cyber Triage Import Module.
CTincidentImportOptionsPanel.instructionsTextLabel.text=
CTIncidentImportOptionsPanel.instructionsTextLabel.text=For instructions on obtaining the module refer to:
CTIncidentImportOptionsPanel.importModule.text=Cyber Triage Import Module:
CTIncidentImportOptionsPanel.importModuleDetected.text=

View File

@ -12,3 +12,7 @@ CTIncidentImportOptionsPanel.border.title_1=Incident Importer
CTIncidentImportOptionsPanel.incidentTextLabel.text=The Cyber Triage Incident Import module allows you to open data collected by Cyber Triage in Autopsy. To use this feature you must install the Cyber Triage Import Module.
CTincidentImportOptionsPanel.instructionsTextLabel.text=
CTIncidentImportOptionsPanel.instructionsTextLabel.text=For instructions on obtaining the module refer to:
CTIncidentImportOptionsPanel.importModule.text=Cyber Triage Import Module:
CTIncidentImportOptionsPanel.importModuleDetected.text=
CTIncidentImportOptionsPanel_setModuleDetected_detected=Detected
CTIncidentImportOptionsPanel_setModuleDetected_notDetected=Not Detected

View File

@ -42,7 +42,35 @@
</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="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="0" gridWidth="2" 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="importModule">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties" key="CTIncidentImportOptionsPanel.importModule.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="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="3" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="importModuleDetected">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="com/basistech/df/cybertriage/autopsy/incidentoptions/Bundle.properties" key="CTIncidentImportOptionsPanel.importModuleDetected.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="0" insetsBottom="5" insetsRight="5" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -68,7 +96,7 @@
</AccessibilityProperties>
<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="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
@ -86,7 +114,7 @@
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" 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"/>
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="5" insetsBottom="5" insetsRight="5" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>

View File

@ -1,16 +1,21 @@
/** *************************************************************************
** This data and information is proprietary to, and a valuable trade secret
** of, Sleuth Kit Labs. It is given in confidence by Sleuth Kit Labs
** and may only be used as permitted under the license agreement under which
** it has been distributed, and in no other way.
**
** Copyright (c) 2023 Sleuth Kit Labs, LLC. All rights reserved
**
** The technical data and information provided herein are provided with
** `limited rights', and the computer software provided herein is provided
** with `restricted rights' as those terms are defined in DAR and ASPR
** 7-104.9(a).
************************************************************************** */
/*
* 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.incidentoptions;
import com.basistech.df.cybertriage.autopsy.ctoptions.subpanel.CTOptionsSubPanel;
@ -19,15 +24,20 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JFileChooser;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.AutopsyContentProvider;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
@ -41,6 +51,8 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
private static final String CT_IMPORTER_DOC_LINK = "https://docs.cybertriage.com/en/latest/chapters/integrations/autopsy.html";
private static final String CT_STANDARD_CONTENT_PROVIDER_NAME = "CTStandardContentProvider";
private final JFileChooserFactory fileRepoChooserFactory = new JFileChooserFactory();
private final CTSettingsPersistence ctPersistence = CTSettingsPersistence.getInstance();
@ -88,8 +100,23 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
public synchronized void loadSettings() {
CTSettings ctSettings = ctPersistence.loadCTSettings();
setCTSettingsDisplay(ctSettings);
setModuleDetected();
setEnabledItems(Case.isCaseOpen());
}
@Messages({
"CTIncidentImportOptionsPanel_setModuleDetected_detected=Detected",
"CTIncidentImportOptionsPanel_setModuleDetected_notDetected=Not Detected"
})
private void setModuleDetected() {
Collection<? extends AutopsyContentProvider> contentProviders = Lookup.getDefault().lookupAll(AutopsyContentProvider.class);
boolean detected = ((Collection<? extends AutopsyContentProvider>) (contentProviders != null ? contentProviders : Collections.emptyList())).stream()
.anyMatch(p -> p != null && StringUtils.defaultString(p.getName()).toUpperCase().startsWith(CT_STANDARD_CONTENT_PROVIDER_NAME.toUpperCase()));
this.importModuleDetected.setText(detected
? Bundle.CTIncidentImportOptionsPanel_setModuleDetected_detected()
: Bundle.CTIncidentImportOptionsPanel_setModuleDetected_notDetected());
}
private void setEnabledItems(boolean caseOpen) {
this.caseOpenWarningLabel.setVisible(caseOpen);
@ -122,6 +149,8 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
incidentTextPanel = new javax.swing.JPanel();
incidentTextLabel = new javax.swing.JLabel();
javax.swing.JLabel importModule = new javax.swing.JLabel();
importModuleDetected = new javax.swing.JLabel();
instructionsPanel = new javax.swing.JPanel();
instructionsTextLabel = new javax.swing.JLabel();
instructionsLinkLabel = new javax.swing.JLabel();
@ -140,10 +169,28 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
incidentTextPanel.add(incidentTextLabel, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(importModule, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.importModule.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 3);
incidentTextPanel.add(importModule, gridBagConstraints);
org.openide.awt.Mnemonics.setLocalizedText(importModuleDetected, org.openide.util.NbBundle.getMessage(CTIncidentImportOptionsPanel.class, "CTIncidentImportOptionsPanel.importModuleDetected.text")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(5, 0, 5, 5);
incidentTextPanel.add(importModuleDetected, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
@ -156,7 +203,7 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 0);
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 0);
instructionsPanel.add(instructionsTextLabel, gridBagConstraints);
instructionsTextLabel.getAccessibleContext().setAccessibleName("For instructions on obtaining the module refer to:");
@ -171,7 +218,7 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5);
gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
instructionsPanel.add(instructionsLinkLabel, gridBagConstraints);
gridBagConstraints = new java.awt.GridBagConstraints();
@ -278,6 +325,7 @@ public class CTIncidentImportOptionsPanel extends CTOptionsSubPanel {
private javax.swing.JLabel caseOpenWarningLabel;
private javax.swing.JButton fileRepoBrowseButton;
private javax.swing.JTextField fileRepoPathField;
private javax.swing.JLabel importModuleDetected;
private javax.swing.JLabel incidentTextLabel;
private javax.swing.JPanel incidentTextPanel;
private javax.swing.JLabel instructionsLinkLabel;

View File

@ -1,16 +1,21 @@
/** *************************************************************************
** This data and information is proprietary to, and a valuable trade secret
** of, Sleuth Kit Labs. It is given in confidence by Sleuth Kit Labs
** and may only be used as permitted under the license agreement under which
** it has been distributed, and in no other way.
**
** Copyright (c) 2023 Sleuth Kit Labs, LLC. All rights reserved
**
** The technical data and information provided herein are provided with
** `limited rights', and the computer software provided herein is provided
** with `restricted rights' as those terms are defined in DAR and ASPR
** 7-104.9(a).
************************************************************************** */
/*
* 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.incidentoptions;
import java.io.IOException;
@ -19,35 +24,60 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.apache.commons.lang3.StringUtils;
import org.openide.modules.Places;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
/**
* CT settings that don't include license information. This code must be kept in-sync with code in
* CT Autopsy Importer NBM.
* CT settings that don't include license information. This code must be kept
* in-sync with code in CT Autopsy Importer NBM.
*/
public class CTSettings {
private static final Logger LOGGER = Logger.getLogger(CTSettings.class.getCanonicalName());
private static final String DEFAULT_FILE_REPO_PATH = getAppDataLocalDirectory();
// taken from com.basistech.df.cybertriage.utils.SystemProperties
private static String getAppDataLocalDirectory() {
private static final String CYBERTRIAGE_FOLDER = "cybertriage";
private static final String CYBERTRIAGE_DOT_FOLDER = "." + CYBERTRIAGE_FOLDER;
Logger LOGGER = java.util.logging.Logger.getLogger(CTSettings.class.getCanonicalName());
// based on com.basistech.df.cybertriage.utils.SystemProperties
private static String getAppDataLocalDirectory() {
if (Objects.nonNull(Places.getUserDirectory()) && Places.getUserDirectory().getAbsolutePath().endsWith("testuserdir")) { // APP is in testing .. this should return the test path
LOGGER.log(Level.INFO, "Application Data (test mode) Path: " + Places.getUserDirectory().getAbsolutePath());
return Places.getUserDirectory().getAbsolutePath();
} else {
Path localAppPath = Paths.get(System.getenv("LOCALAPPDATA"), "cybertriage");
}
// try to use LOCALAPPDATA on windows
String localDataStr = System.getenv("LOCALAPPDATA");
if (StringUtils.isNotBlank(localDataStr)) {
Path localAppPath = Paths.get(localDataStr, CYBERTRIAGE_FOLDER);
try {
Files.createDirectories(localAppPath);
LOGGER.log(Level.INFO, "Application Data Path: " + localAppPath.toString());
return localAppPath.toString();
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "IO Error, defaulting to user dir", ex);
return Places.getUserDirectory().getAbsolutePath(); // In case of an IO Error
LOGGER.log(Level.SEVERE, "IO Error using " + localAppPath.toString(), ex);
}
}
// try to use ~/.cybertriage anywhere else
if (!PlatformUtil.isWindowsOS()) {
String homePathStr = System.getenv("HOME");
if (StringUtils.isNotBlank(homePathStr)) {
Path localAppPath = Paths.get(homePathStr, CYBERTRIAGE_DOT_FOLDER);
try {
Files.createDirectories(localAppPath);
LOGGER.log(Level.INFO, "Non-windows Application Data Path: " + localAppPath.toString());
return localAppPath.toString();
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "IO Error using " + localAppPath.toString(), ex);
}
}
}
// defer to user directory otherwise
return Places.getUserDirectory().getAbsolutePath(); // In case of an IO Error
}
public static String getDefaultFileRepoPath() {

View File

@ -1,16 +1,21 @@
/** *************************************************************************
** This data and information is proprietary to, and a valuable trade secret
** of, Sleuth Kit Labs. It is given in confidence by Sleuth Kit Labs
** and may only be used as permitted under the license agreement under which
** it has been distributed, and in no other way.
**
** Copyright (c) 2023 Sleuth Kit Labs, LLC. All rights reserved
**
** The technical data and information provided herein are provided with
** `limited rights', and the computer software provided herein is provided
** with `restricted rights' as those terms are defined in DAR and ASPR
** 7-104.9(a).
************************************************************************** */
/*
* 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.incidentoptions;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@ -23,6 +23,7 @@ import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException;
import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData;
import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
import com.basistech.df.cybertriage.autopsy.ctapi.json.FileUploadRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MalwareResultBean.Status;
import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest;
@ -112,8 +113,10 @@ class MalwareScanIngestModule implements FileIngestModule {
//minimum file uploads left before issuing warning
private static final long LOW_UPLOADS_REMAINING = 25;
// min and max upload size in bytes
private static final long MIN_UPLOAD_SIZE = 1;
private static final long MAX_UPLOAD_SIZE = 1_000_000_000;
private static final long MAX_UPLOAD_SIZE = 100_000_000; // 100MB
private static final int NUM_FILE_UPLOAD_RETRIES = 7;
private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000;
@ -131,8 +134,7 @@ class MalwareScanIngestModule implements FileIngestModule {
"application/x-msdos-program"//NON-NLS
).collect(Collectors.toSet());
private static final String MALWARE_TYPE_NAME = "TSK_MALWARE";
private static final String MALWARE_CONFIG = "Cyber Triage Cloud";
private static final String MALWARE_CONFIG = ""; // NOTE: Adding a configuration complicates NTL branch UI
private static final Logger logger = Logger.getLogger(MalwareScanIngestModule.class.getName());
@ -232,18 +234,13 @@ class MalwareScanIngestModule implements FileIngestModule {
// setup necessary variables for processing
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
BlackboardArtifact.Type malwareType = tskCase.getBlackboard().getOrAddArtifactType(
MALWARE_TYPE_NAME,
Bundle.MalwareScanIngestModule_malwareTypeDisplayName(),
BlackboardArtifact.Category.ANALYSIS_RESULT);
return new IngestJobState(
context,
tskCase,
new PathNormalizer(tskCase),
new FileTypeDetector(),
licenseInfoOpt.get(),
malwareType,
BlackboardArtifact.Type.TSK_MALWARE,
uploadFiles,
true
);
@ -640,7 +637,7 @@ class MalwareScanIngestModule implements FileIngestModule {
}
// get auth token / file upload url
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(ingestJobState.getLicenseInfo().getDecryptedLicense(), true);
AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(ingestJobState.getLicenseInfo().getDecryptedLicense(), af.getSize());
if (StringUtils.isBlank(authTokenResponse.getFileUploadUrl())) {
throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR);
} else if (remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount()) <= 0) {
@ -658,7 +655,13 @@ class MalwareScanIngestModule implements FileIngestModule {
// upload bytes
ReadContentInputStream fileInputStream = new ReadContentInputStream(af);
ctApiDAO.uploadFile(authTokenResponse.getFileUploadUrl(), af.getName(), fileInputStream);
ctApiDAO.uploadFile(new FileUploadRequest()
.setContentLength(af.getSize())
.setFileInputStream(fileInputStream)
.setFileName(af.getName())
.setFullUrlPath(authTokenResponse.getFileUploadUrl())
);
// upload metadata
MetadataUploadRequest metaRequest = new MetadataUploadRequest()
@ -700,6 +703,7 @@ class MalwareScanIngestModule implements FileIngestModule {
Bundle.MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_title(),
Bundle.MalwareScanIngestModule_longPollForNotFound_fileLookupPolling_desc()
);
logger.log(Level.INFO, "Begin polling for malware status of file uploads.");
Map<String, List<Long>> remaining = new HashMap<>(ingestJobState.getUnidentifiedHashes());
@ -735,6 +739,9 @@ class MalwareScanIngestModule implements FileIngestModule {
// exponential backoff before trying again
long waitMultiplier = ((long) Math.pow(2, retry));
logger.log(Level.INFO, MessageFormat.format("Waiting {0} milliseconds before polling again for malware status of file uploads.", (waitMultiplier * FILE_UPLOAD_RETRY_SLEEP_MILLIS)));
for (int i = 0; i < waitMultiplier; i++) {
if (!ingestJobState.isDoFileLookups() || ingestJobState.getIngestJobContext().fileIngestIsCancelled()) {
return;

View File

@ -66,8 +66,8 @@ Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the
Case.open.msgDlg.updated.title=Case Database Schema Update
Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
Previously, the image was located at:\n\
{0}\n\
Previously, the image with host, {0}, was located at:\n\
{1}\n\
Please note that you will still be able to browse directories and generate reports\n\
if you choose No, but you will not be able to view file content or run the ingest process.
Case.checkImgExist.confDlg.doesntExist.title=Missing Image

View File

@ -253,8 +253,8 @@ Case.open.msgDlg.updated.msg=Updated case database schema.\nA backup copy of the
Case.open.msgDlg.updated.title=Case Database Schema Update
Case.checkImgExist.confDlg.doesntExist.msg=One of the images associated with \n\
this case are missing. Would you like to search for them now?\n\
Previously, the image was located at:\n\
{0}\n\
Previously, the image with host, {0}, was located at:\n\
{1}\n\
Please note that you will still be able to browse directories and generate reports\n\
if you choose No, but you will not be able to view file content or run the ingest process.
Case.checkImgExist.confDlg.doesntExist.title=Missing Image

View File

@ -194,6 +194,8 @@ public class Case {
private final SleuthkitEventListener sleuthkitEventListener;
private CollaborationMonitor collaborationMonitor;
private Services caseServices;
// matches something like '\\.\PHYSICALDRIVE0'
private static final String PLACEHOLDER_DS_PATH_REGEX = "^\\s*\\\\\\\\\\.\\\\PHYSICALDRIVE\\d*\\s*$";
private volatile boolean hasDataSource = false;
private volatile boolean hasData = false;
@ -1303,9 +1305,18 @@ public class Case {
for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
long obj_id = entry.getKey();
String path = entry.getValue();
boolean fileExists = (new File(path).isFile() || DriveUtils.driveExists(path));
boolean fileExists = (new File(path).exists()|| DriveUtils.driveExists(path));
if (!fileExists) {
// CT-7336: ignore relocating datasources if file provider is present and placeholder path is used.
if (newCurrentCase.getMetadata() != null
&& !StringUtils.isBlank(newCurrentCase.getMetadata().getContentProviderName())
&& (path == null || path.matches(PLACEHOLDER_DS_PATH_REGEX))) {
continue;
}
try {
DataSource ds = newCurrentCase.getSleuthkitCase().getDataSource(obj_id);
String hostName = StringUtils.defaultString(ds.getHost() == null ? "" : ds.getHost().getName());
// Using invokeAndWait means that the dialog will
// open on the EDT but this thread will wait for an
// answer. Using invokeLater would cause this loop to
@ -1315,7 +1326,7 @@ public class Case {
public void run() {
int response = JOptionPane.showConfirmDialog(
mainFrame,
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", path),
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", hostName, path),
NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
@ -1327,7 +1338,7 @@ public class Case {
}
});
} catch (InterruptedException | InvocationTargetException ex) {
} catch (InterruptedException | InvocationTargetException | TskCoreException | TskDataException ex) {
logger.log(Level.SEVERE, "Failed to show missing image confirmation dialog", ex); //NON-NLS
}
}

View File

@ -64,6 +64,7 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_INTERESTING_IT
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_TL_EVENT;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_ASSOCIATED_OBJECT;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_KEYWORD_HIT;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_MALWARE;
/**
* Classes for creating nodes for BlackboardArtifacts.
@ -73,10 +74,6 @@ public class Artifacts {
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST
= EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
// this is currently a custom TSK artifact type, created in MalwareScanIngestModule
private static BlackboardArtifact.Type MALWARE_ARTIFACT_TYPE = null;
private static final String MALWARE_HITS = "TSK_MALWARE";
/**
* Base class for a parent node of artifacts.
*/
@ -247,15 +244,6 @@ public class Artifacts {
@SuppressWarnings("deprecation")
private static TypeNodeKey getTypeKey(BlackboardArtifact.Type type, SleuthkitCase skCase, long dsObjId) {
// Get the custom TSK_MALWARE artifact type from case database
if (MALWARE_ARTIFACT_TYPE == null) {
try {
MALWARE_ARTIFACT_TYPE = skCase.getArtifactType(MALWARE_HITS);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get TSK_MALWARE artifact type from database : ", ex); //NON-NLS
}
}
int typeId = type.getTypeID();
if (TSK_EMAIL_MSG.getTypeID() == typeId) {
EmailExtracted.RootNode emailNode = new EmailExtracted(skCase, dsObjId).new RootNode();
@ -281,9 +269,9 @@ public class Artifacts {
} else if (TSK_HASHSET_HIT.getTypeID() == typeId) {
HashsetHits.RootNode hashsetHits = new HashsetHits(skCase, dsObjId).new RootNode();
return new TypeNodeKey(hashsetHits, TSK_HASHSET_HIT);
} else if (MALWARE_ARTIFACT_TYPE != null && MALWARE_ARTIFACT_TYPE.getTypeID() == typeId) {
} else if (TSK_MALWARE.getTypeID() == typeId) {
MalwareHits.RootNode malwareHits = new MalwareHits(skCase, dsObjId).new RootNode();
return new TypeNodeKey(malwareHits, MALWARE_ARTIFACT_TYPE);
return new TypeNodeKey(malwareHits, TSK_MALWARE);
} else {
return new TypeNodeKey(type, dsObjId);
}

View File

@ -44,12 +44,12 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
import org.sleuthkit.datamodel.AnalysisResult;
import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_MALWARE;
import org.sleuthkit.datamodel.Score;
/**
@ -57,9 +57,6 @@ import org.sleuthkit.datamodel.Score;
*/
public class MalwareHits implements AutopsyVisitableItem {
private static final String MALWARE_HITS = "TSK_MALWARE"; // this is currently a custom TSK artifact type, created in MalwareScanIngestModule
private static BlackboardArtifact.Type MALWARE_ARTIFACT_TYPE = null;
private static String DISPLAY_NAME;
private static final Logger logger = Logger.getLogger(MalwareHits.class.getName());
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.COMPLETED, IngestManager.IngestJobEvent.CANCELLED);
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.DATA_ADDED);
@ -126,20 +123,9 @@ public class MalwareHits implements AutopsyVisitableItem {
return;
}
// Get the custom TSK_MALWARE artifact type from case database
if (MALWARE_ARTIFACT_TYPE == null) {
try {
MALWARE_ARTIFACT_TYPE = skCase.getArtifactType(MALWARE_HITS);
DISPLAY_NAME = MALWARE_ARTIFACT_TYPE.getDisplayName();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get TSK_MALWARE artifact type from database : ", ex); //NON-NLS
return;
}
}
String query = "SELECT blackboard_artifacts.artifact_obj_id " //NON-NLS
+ "FROM blackboard_artifacts,tsk_analysis_results WHERE " //NON-NLS
+ "blackboard_artifacts.artifact_type_id=" + MALWARE_ARTIFACT_TYPE.getTypeID() //NON-NLS
+ "blackboard_artifacts.artifact_type_id=" + TSK_MALWARE.getTypeID() //NON-NLS
+ " AND tsk_analysis_results.artifact_obj_id=blackboard_artifacts.artifact_obj_id" //NON-NLS
+ " AND (tsk_analysis_results.significance=" + Score.Significance.NOTABLE.getId() //NON-NLS
+ " OR tsk_analysis_results.significance=" + Score.Significance.LIKELY_NOTABLE.getId() + " )"; //NON-NLS
@ -182,7 +168,7 @@ public class MalwareHits implements AutopsyVisitableItem {
* oldValue if the event is a remote event.
*/
ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == MALWARE_ARTIFACT_TYPE.getTypeID()) {
if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == TSK_MALWARE.getTypeID()) {
malwareResults.update();
}
} catch (NoCurrentCaseException notUsed) {
@ -248,13 +234,13 @@ public class MalwareHits implements AutopsyVisitableItem {
public class RootNode extends UpdatableCountTypeNode {
public RootNode() {
super(Children.create(new HitFactory(DISPLAY_NAME), true),
Lookups.singleton(DISPLAY_NAME),
DISPLAY_NAME,
super(Children.create(new HitFactory(TSK_MALWARE.getDisplayName()), true),
Lookups.singleton(TSK_MALWARE.getDisplayName()),
TSK_MALWARE.getDisplayName(),
filteringDSObjId,
MALWARE_ARTIFACT_TYPE);
TSK_MALWARE);
super.setName(MALWARE_HITS);
super.setName(TSK_MALWARE.getTypeName());
// TODO make an icon
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/artifact-icon.png");
}
@ -297,7 +283,7 @@ public class MalwareHits implements AutopsyVisitableItem {
*/
@Override
void updateDisplayName() {
super.setDisplayName(DISPLAY_NAME + " (" + malwareResults.getArtifactIds().size() + ")");
super.setDisplayName(TSK_MALWARE.getDisplayName() + " (" + malwareResults.getArtifactIds().size() + ")");
}
}

View File

@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.corelibs/3
OpenIDE-Module-Implementation-Version: 7
OpenIDE-Module-Implementation-Version: 8
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/corelibs/Bundle.properties
OpenIDE-Module-Specification-Version: 1.4
AutoUpdate-Show-In-Client: true

View File

@ -144,7 +144,7 @@
<compile-dependency/>
<run-dependency>
<release-version>10</release-version>
<specification-version>10.24</specification-version>
<specification-version>10.25</specification-version>
</run-dependency>
</dependency>
<dependency>
@ -162,7 +162,7 @@
<compile-dependency/>
<run-dependency>
<release-version>6</release-version>
<specification-version>6.6</specification-version>
<specification-version>6.7</specification-version>
</run-dependency>
</dependency>
</module-dependencies>

View File

@ -127,7 +127,7 @@
<compile-dependency/>
<run-dependency>
<release-version>10</release-version>
<specification-version>10.24</specification-version>
<specification-version>10.25</specification-version>
</run-dependency>
</dependency>
<dependency>

View File

@ -1,7 +1,7 @@
Manifest-Version: 1.0
AutoUpdate-Show-In-Client: true
OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/6
OpenIDE-Module-Implementation-Version: 23
OpenIDE-Module-Implementation-Version: 24
OpenIDE-Module-Install: org/sleuthkit/autopsy/keywordsearch/Installer.class
OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties

View File

@ -48,4 +48,4 @@ javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/autopsy/
nbm.needs.restart=true
spec.version.base=6.6
spec.version.base=6.7

View File

@ -128,7 +128,7 @@
<compile-dependency/>
<run-dependency>
<release-version>10</release-version>
<specification-version>10.24</specification-version>
<specification-version>10.25</specification-version>
</run-dependency>
</dependency>
<dependency>

View File

@ -9,7 +9,7 @@ Library Updates
Ingest Module Updates:
- Recent Activity checks for malicious Chrome extensions from list provided by https://github.com/randomaccess3/detections
- Keyword Search module now can search without needing to index text into Solr.
- New Cyber Triage Malware Scanner module that uses Reversing Labs (requires license)
- New Cyber Triage Malware Scanner module that uses Reversing Labs (requires license). https://www.cybertriage.com/autopsy-malware-module/
Add Data Source Updates:
- Timestamps for logical files can be added. Issue https://github.com/sleuthkit/autopsy/issues/5852, https://github.com/sleuthkit/autopsy/issues/1788
@ -24,7 +24,7 @@ Bugs:
- Fix exporting of CSV files. Issue https://github.com/sleuthkit/autopsy/issues/6717
Misc:
- Added File Repository concept for data source files that are in a central location
- Added File Repository concept for data source files that are in a central location. Required for Cyber Triage import feature.
- Added Spanish language support, contributor https://github.com/AburtoArielPM
---------------- VERSION 4.20.0 --------------

View File

@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.recentactivity/6
OpenIDE-Module-Implementation-Version: 19
OpenIDE-Module-Implementation-Version: 20
OpenIDE-Module-Layer: org/sleuthkit/autopsy/recentactivity/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/recentactivity/Bundle.properties
OpenIDE-Module-Requires:

View File

@ -1,3 +1,3 @@
<project name="TSK_VERSION">
<property name="TSK_VERSION" value="4.12.0"/>
<property name="TSK_VERSION" value="4.12.1"/>
</project>

View File

@ -47,7 +47,7 @@
<compile-dependency/>
<run-dependency>
<release-version>10</release-version>
<specification-version>10.24</specification-version>
<specification-version>10.25</specification-version>
</run-dependency>
</dependency>
<dependency>
@ -73,7 +73,7 @@
<compile-dependency/>
<run-dependency>
<release-version>6</release-version>
<specification-version>6.6</specification-version>
<specification-version>6.7</specification-version>
</run-dependency>
</dependency>
</module-dependencies>

View File

@ -9,18 +9,23 @@ The Cyber Triage Malware Scanner module will use the malware scanning infrastruc
This module requires a commercial license from Cyber Triage.
For more information on obtaining a license, refer to [CyberTriage.com](https://cybertriage.com/autopsy-malware-module). The remainder of this page is about the use of the module once it is licensed.
For more information on what the module does or obtaining a license, refer to [CyberTriage.com](https://cybertriage.com/autopsy-malware-module). The remainder of this page is about the use of the module once it is licensed.
Configuration
=======
==============
Once you have a license, you must add it on the Options panel. Choose the 'Cyber Triage' tab and choose 'Add License'.
You will need to first get a paid or eval license from the above URL. The code will come in via email. Example license formats include:
- AUT-8ed86eb5-17fc-4b3a-9b75-ce638c11b070
- b826a555-951f-42ca-86ce-439a81106688
\image html Malware-scanner-global-options-panel-no-license.png
Once you have a license, you must add it on the Autopsy Options panel.
Choose the 'Cyber Triage' tab and choose 'Add License'.
After you enter the license number that you should have received from your email, you will then need to review and agree to the license terms.
\image html malware-scanner-global-options-panel-no-license.png
After you enter the license number from your email, you will then need to review and agree to the license terms.
The options panel should now display information about the lookup limits. You can always refer back to here about what your limits are and when they reset.
@ -35,7 +40,7 @@ Using the Module
Ingest Settings
------
For each data source, you select if you want files to be uploaded if they have not already been analyzed. By default, they are uploaded. You can choose to not upload them though.
For each data source, you select if you want files to be uploaded if they have not already been analyzed. By default, they are uploaded. You can choose to not upload them though. Refer to the main [website](https://cybertriage.com/autopsy-malware-module) for details on what happens when files are uploaded.
\image html malware-scanner-ingest-panel.png

View File

@ -1,5 +1,5 @@
<hr/>
<p><i>Copyright &#169; 2012-2022 Basis Technology. Generated on $date<br/>
<p><i>Copyright &#169; 2012-2023 BasisTech. Generated on $date<br/>
This work is licensed under a
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/">Creative Commons Attribution-Share Alike 3.0 United States License</a>.
</i></p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -4,7 +4,7 @@ app.title=Autopsy
### lowercase version of above
app.name=${branding.token}
### if left unset, version will default to today's date
app.version=4.20.0
app.version=4.21.0
### build.type must be one of: DEVELOPMENT, RELEASE
#build.type=RELEASE
build.type=DEVELOPMENT

View File

@ -33,7 +33,7 @@ sub main {
update_core_project_xml();
update_unix_setup();
print "Files updated. You need to commit and push them\n";
print "Files updated and added to git. You need to commit (no -a) and push.\n";
}

View File

@ -54,7 +54,7 @@
<compile-dependency/>
<run-dependency>
<release-version>10</release-version>
<specification-version>10.24</specification-version>
<specification-version>10.25</specification-version>
</run-dependency>
</dependency>
<dependency>

View File

@ -5,7 +5,7 @@
# NOTE: update_sleuthkit_version.pl updates this value and relies
# on it keeping the same name and whitespace. Don't change it.
TSK_VERSION=4.12.0
TSK_VERSION=4.12.1
usage() {