From b871974e7b254ff308aa277cb7a69002d1e0585f Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 12:58:11 -0500 Subject: [PATCH 01/11] Crypto module created for detecting encrypted files. --- Core/nbproject/project.xml | 1 + .../CryptoDetectionFileIngestModule.java | 242 ++++++++++++++++++ .../crypto/CryptoDetectionModuleFactory.java | 73 ++++++ 3 files changed, 316 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java create mode 100755 Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 987b2ffe78..e5778c0955 100755 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -313,6 +313,7 @@ org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions + org.sleuthkit.autopsy.modules.crypto org.sleuthkit.autopsy.modules.filetypeid org.sleuthkit.autopsy.modules.hashdatabase org.sleuthkit.autopsy.modules.vmextractor diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java new file mode 100755 index 0000000000..93db6697e7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java @@ -0,0 +1,242 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 Basis Technology Corp. + * Contact: carrier sleuthkit 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 org.sleuthkit.autopsy.modules.crypto; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +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.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * File ingest module to detect encryption. + */ +final class CryptoDetectionFileIngestModule implements FileIngestModule { + + private static final double ENTROPY_FACTOR = 1.4426950408889634073599246810019; // (1 / log(2)) + + private static final Logger LOGGER = Logger.getLogger(CryptoDetectionFileIngestModule.class.getName()); + private final IngestServices SERVICES = IngestServices.getInstance(); + private long jobId; + private static final IngestModuleReferenceCounter REF_COUNTER = new IngestModuleReferenceCounter(); + private FileTypeDetector fileTypeDetector; + private Blackboard blackboard; + + /** + * Create a CryptoDetectionFileIngestModule object that will detect files + * that are encrypted and create blackboard artifacts as appropriate. + */ + CryptoDetectionFileIngestModule() { + } + + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + jobId = context.getJobId(); + REF_COUNTER.incrementAndGet(jobId); + try { + fileTypeDetector = new FileTypeDetector(); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + throw new IngestModule.IngestModuleException("Failed to create file type detector", ex); + } + } + + @Override + public IngestModule.ProcessResult process(AbstractFile content) { + blackboard = Case.getCurrentCase().getServices().getBlackboard(); + + if (isFileSupported(content)) { + return processFile(content); + } + + return IngestModule.ProcessResult.OK; + } + + /** + * Process the file. If the file has an entropy value greater than seven, + * create a blackboard artifact. + * + * @param The file to be processed. + * + * @return 'OK' if the file was processed successfully, or 'ERROR' if there + * was a problem. + */ + private IngestModule.ProcessResult processFile(AbstractFile f) { + try { + double entropy = calculateEntropy(f); + if (entropy > 7.5) { + BlackboardArtifact artifact = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + MessageNotifyUtil.Notify.error("Failed to index encryption detected artifact for keyword search.", artifact.getDisplayName()); + } + + /* + * Send an event to update the view with the new result. + */ + SERVICES.fireModuleDataEvent(new ModuleDataEvent(CryptoDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Collections.singletonList(artifact))); + } + + return IngestModule.ProcessResult.OK; + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Failed to create blackboard artifact ({0}).", ex.getLocalizedMessage()); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } catch (IOException ex) { + LOGGER.log(Level.WARNING, String.format("Failed to calculate the entropy for '%s'.", Paths.get(f.getParentPath(), f.getName())), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + + /** + * Calculate the entropy of the file. The result is used to qualify the file + * as an encrypted file. + * + * @param file The file to be calculated against. + * + * @return The entropy of the file. + * + * @throws IOException If there is a failure closing or reading from the + * InputStream. + */ + private double calculateEntropy(AbstractFile file) throws IOException { + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + + /* + * Determine the number of times each byte value appears. + */ + int[] byteOccurences = new int[256]; + int mostRecentByte = 0; + int readByte; + while ((readByte = bin.read()) != -1) { + byteOccurences[readByte]++; + mostRecentByte = readByte; + } + byteOccurences[mostRecentByte]--; + + /* + * Calculate the entropy based on the byte occurence counts. + */ + long dataLength = file.getSize() - 1; + double entropy = 0; + for (int i = 0; i < 256; i++) { + if (byteOccurences[i] > 0) { + double byteProbability = (double) byteOccurences[i] / (double) dataLength; + entropy += (byteProbability * Math.log(byteProbability) * ENTROPY_FACTOR); + } + } + + return -entropy; + + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "IOException occurred while trying to read data from InputStream.", ex); //NON-NLS + throw ex; + } finally { + try { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Failed to close InputStream.", ex); //NON-NLS + throw ex; + } + } + } + + /** + * This method checks if the AbstractFile input is supported. To qualify, it + * must be an actual file that is not known, has a size that's evenly + * divisible by 512 and a minimum size of 5MB, and has a MIME type of + * 'application/octet-stream'. + * + * @param file AbstractFile to be checked. + * + * @return True if the AbstractFile qualifies. + */ + private boolean isFileSupported(AbstractFile file) { + boolean supported = false; + + /* + * Qualify the file type. + */ + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) && + !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) && + !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) && + !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { + /* + * Qualify the file against hash databases. + */ + if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + /* + * Qualify the size. + */ + long contentSize = file.getSize(); + if (contentSize >= 0x500000 && (contentSize & 511) == 0) { + /* + * Qualify the MIME type. + */ + try { + String mimeType = fileTypeDetector.getFileType(file); + if (mimeType != null && mimeType.equals("application/octet-stream")) { + supported = true; + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to detect file type", ex); //NON-NLS + } + } + } + } + + return supported; + } + + @Override + public void shutDown() { + REF_COUNTER.decrementAndGet(jobId); + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java new file mode 100755 index 0000000000..de3d518ee2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 Basis Technology Corp. + * Contact: carrier sleuthkit 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 org.sleuthkit.autopsy.modules.crypto; + +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * A factory that creates file ingest modules that detect encryption. + */ +@ServiceProvider(service = IngestModuleFactory.class) +@Messages({ + "CryptoDetectionFileIngestModule.moduleName.text=Crypto Detection", + "CryptoDetectionFileIngestModule.getDesc.text=Looks for files that are encrypted and have an entropy greater than seven." +}) +public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + /** + * Get the name of the module. + * + * @return The module name. + */ + static String getModuleName() { + return NbBundle.getMessage(CryptoDetectionFileIngestModule.class, "CryptoDetectionFileIngestModule.moduleName.text"); + } + + @Override + public String getModuleDescription() { + return NbBundle.getMessage(CryptoDetectionFileIngestModule.class, "CryptoDetectionFileIngestModule.getDesc.text"); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { + return new CryptoDetectionFileIngestModule(); + } +} \ No newline at end of file From 39cd68aca65ee573b54ba6c4612889ef6b9c48eb Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 13:38:55 -0500 Subject: [PATCH 02/11] Minor tweaks. --- .../crypto/CryptoDetectionFileIngestModule.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java index 93db6697e7..ea0e806b14 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java @@ -137,6 +137,10 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * InputStream. */ private double calculateEntropy(AbstractFile file) throws IOException { + /* + * Logic in this method is based on + * https://github.com/willjasen/entropy/blob/master/entropy.java + */ InputStream in = null; BufferedInputStream bin = null; @@ -200,6 +204,11 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { */ private boolean isFileSupported(AbstractFile file) { boolean supported = false; + + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ /* * Qualify the file type. @@ -216,7 +225,7 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * Qualify the size. */ long contentSize = file.getSize(); - if (contentSize >= 0x500000 && (contentSize & 511) == 0) { + if (contentSize >= 5242880 && (contentSize % 512) == 0) { /* * Qualify the MIME type. */ From 6bb2701776b781dfb26d0e074f1b8078bf9e01c3 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 14:02:08 -0500 Subject: [PATCH 03/11] Minor tweaks. --- .../CryptoDetectionFileIngestModule.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java index ea0e806b14..60c1040359 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java @@ -46,7 +46,7 @@ import org.sleuthkit.datamodel.TskData; */ final class CryptoDetectionFileIngestModule implements FileIngestModule { - private static final double ENTROPY_FACTOR = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) private static final Logger LOGGER = Logger.getLogger(CryptoDetectionFileIngestModule.class.getName()); private final IngestServices SERVICES = IngestServices.getInstance(); @@ -152,13 +152,10 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * Determine the number of times each byte value appears. */ int[] byteOccurences = new int[256]; - int mostRecentByte = 0; int readByte; while ((readByte = bin.read()) != -1) { byteOccurences[readByte]++; - mostRecentByte = readByte; } - byteOccurences[mostRecentByte]--; /* * Calculate the entropy based on the byte occurence counts. @@ -168,7 +165,7 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { for (int i = 0; i < 256; i++) { if (byteOccurences[i] > 0) { double byteProbability = (double) byteOccurences[i] / (double) dataLength; - entropy += (byteProbability * Math.log(byteProbability) * ENTROPY_FACTOR); + entropy += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); } } @@ -203,20 +200,20 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * @return True if the AbstractFile qualifies. */ private boolean isFileSupported(AbstractFile file) { - boolean supported = false; - /* * Criteria for the checks in this method are partially based on * http://www.forensicswiki.org/wiki/TrueCrypt#Detection */ + boolean supported = false; + /* * Qualify the file type. */ - if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) && - !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) && - !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) && - !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { /* * Qualify the file against hash databases. */ @@ -248,4 +245,4 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { public void shutDown() { REF_COUNTER.decrementAndGet(jobId); } -} \ No newline at end of file +} From 7c0cbe098ebbbbf9757f41b0f8c0f8b253286abd Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 14:11:56 -0500 Subject: [PATCH 04/11] Typo fixed. --- .../autopsy/modules/crypto/CryptoDetectionModuleFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java index de3d518ee2..2b6e7c25f8 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; @ServiceProvider(service = IngestModuleFactory.class) @Messages({ "CryptoDetectionFileIngestModule.moduleName.text=Crypto Detection", - "CryptoDetectionFileIngestModule.getDesc.text=Looks for files that are encrypted and have an entropy greater than seven." + "CryptoDetectionFileIngestModule.getDesc.text=Looks for files that are encrypted and have an entropy greater than 7.5." }) public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { From 7a55b65989b1c112d09ea279414eddc52727f669 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 14:23:23 -0500 Subject: [PATCH 05/11] Updated description to be more generic. --- .../autopsy/modules/crypto/CryptoDetectionModuleFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java index 2b6e7c25f8..f40b2a3490 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java @@ -33,7 +33,7 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; @ServiceProvider(service = IngestModuleFactory.class) @Messages({ "CryptoDetectionFileIngestModule.moduleName.text=Crypto Detection", - "CryptoDetectionFileIngestModule.getDesc.text=Looks for files that are encrypted and have an entropy greater than 7.5." + "CryptoDetectionFileIngestModule.getDesc.text=Looks for large files with high entropy." }) public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { From eef2e81246dc256ae2a05945d25114f10074078c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Fri, 17 Nov 2017 16:04:24 -0500 Subject: [PATCH 06/11] Added message inbox integration and modified logging. --- .../CryptoDetectionFileIngestModule.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java index 60c1040359..f3f821d2e1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java @@ -30,6 +30,7 @@ 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.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModule; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; @@ -48,8 +49,8 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) - private static final Logger LOGGER = Logger.getLogger(CryptoDetectionFileIngestModule.class.getName()); private final IngestServices SERVICES = IngestServices.getInstance(); + private final Logger LOGGER = SERVICES.getLogger(CryptoDetectionModuleFactory.getModuleName()); private long jobId; private static final IngestModuleReferenceCounter REF_COUNTER = new IngestModuleReferenceCounter(); private FileTypeDetector fileTypeDetector; @@ -106,13 +107,26 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { blackboard.indexArtifact(artifact); } catch (Blackboard.BlackboardException ex) { LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS - MessageNotifyUtil.Notify.error("Failed to index encryption detected artifact for keyword search.", artifact.getDisplayName()); + MessageNotifyUtil.Notify.show("Failed to index encryption detected artifact for keyword search.", artifact.getDisplayName(), MessageNotifyUtil.MessageType.ERROR); } /* * Send an event to update the view with the new result. */ SERVICES.fireModuleDataEvent(new ModuleDataEvent(CryptoDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(); + detailsSb.append("File: " + f.getParentPath() + f.getName() + "
\n"); + detailsSb.append("Entropy: " + entropy); + + SERVICES.postMessage(IngestMessage.createDataMessage(CryptoDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + f.getName(), + detailsSb.toString(), + f.getName(), + artifact)); } return IngestModule.ProcessResult.OK; From 0f315a5bf2836e49432b1ac3a9e76413f64b6960 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Wed, 22 Nov 2017 13:16:35 -0500 Subject: [PATCH 07/11] Cleanup. --- Core/nbproject/project.xml | 2 +- .../EncryptionDetectionFileIngestModule.java} | 235 +++++++++--------- .../EncryptionDetectionModuleFactory.java} | 14 +- 3 files changed, 126 insertions(+), 125 deletions(-) rename Core/src/org/sleuthkit/autopsy/modules/{crypto/CryptoDetectionFileIngestModule.java => encryptiondetection/EncryptionDetectionFileIngestModule.java} (59%) rename Core/src/org/sleuthkit/autopsy/modules/{crypto/CryptoDetectionModuleFactory.java => encryptiondetection/EncryptionDetectionModuleFactory.java} (74%) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index e5778c0955..0e6b98892f 100755 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -313,7 +313,7 @@ org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions - org.sleuthkit.autopsy.modules.crypto + org.sleuthkit.autopsy.modules.encryptiondetection org.sleuthkit.autopsy.modules.filetypeid org.sleuthkit.autopsy.modules.hashdatabase org.sleuthkit.autopsy.modules.vmextractor diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java similarity index 59% rename from Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java rename to Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index f3f821d2e1..f18911c016 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.crypto; +package org.sleuthkit.autopsy.modules.encryptiondetection; import java.io.BufferedInputStream; import java.io.IOException; @@ -27,12 +27,10 @@ import java.util.logging.Level; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModule; -import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; @@ -45,28 +43,30 @@ import org.sleuthkit.datamodel.TskData; /** * File ingest module to detect encryption. */ -final class CryptoDetectionFileIngestModule implements FileIngestModule { +final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { + private static final double ENTROPY_THRESHOLD = 7.5; + private static final int FILE_SIZE_THRESHOLD = 5242880; // 5MB + private static final int FILE_SIZE_MODULUS = 512; private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; private final IngestServices SERVICES = IngestServices.getInstance(); - private final Logger LOGGER = SERVICES.getLogger(CryptoDetectionModuleFactory.getModuleName()); - private long jobId; - private static final IngestModuleReferenceCounter REF_COUNTER = new IngestModuleReferenceCounter(); + private final Logger LOGGER = SERVICES.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; private Blackboard blackboard; + private double entropy; /** - * Create a CryptoDetectionFileIngestModule object that will detect files + * Create a EncryptionDetectionFileIngestModule object that will detect files * that are encrypted and create blackboard artifacts as appropriate. */ - CryptoDetectionFileIngestModule() { + EncryptionDetectionFileIngestModule() { } @Override public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { - jobId = context.getJobId(); - REF_COUNTER.incrementAndGet(jobId); + blackboard = Case.getCurrentCase().getServices().getBlackboard(); try { fileTypeDetector = new FileTypeDetector(); } catch (FileTypeDetector.FileTypeDetectorInitException ex) { @@ -75,70 +75,129 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { } @Override - public IngestModule.ProcessResult process(AbstractFile content) { - blackboard = Case.getCurrentCase().getServices().getBlackboard(); + public IngestModule.ProcessResult process(AbstractFile file) { - if (isFileSupported(content)) { - return processFile(content); + try { + if (isFileEncrypted(file)) { + return flagFile(file); + } + } catch (IOException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to process file '%s'", Paths.get(file.getParentPath(), file.getName())), ex); + return IngestModule.ProcessResult.ERROR; } return IngestModule.ProcessResult.OK; } /** - * Process the file. If the file has an entropy value greater than seven, - * create a blackboard artifact. + * Create a blackboard artifact. * * @param The file to be processed. * * @return 'OK' if the file was processed successfully, or 'ERROR' if there * was a problem. */ - private IngestModule.ProcessResult processFile(AbstractFile f) { + private IngestModule.ProcessResult flagFile(AbstractFile file) { try { - double entropy = calculateEntropy(f); - if (entropy > 7.5) { - BlackboardArtifact artifact = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); - - try { - /* - * Index the artifact for keyword search. - */ - blackboard.indexArtifact(artifact); - } catch (Blackboard.BlackboardException ex) { - LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS - MessageNotifyUtil.Notify.show("Failed to index encryption detected artifact for keyword search.", artifact.getDisplayName(), MessageNotifyUtil.MessageType.ERROR); - } + BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + try { /* - * Send an event to update the view with the new result. + * Index the artifact for keyword search. */ - SERVICES.fireModuleDataEvent(new ModuleDataEvent(CryptoDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Collections.singletonList(artifact))); - - /* - * Make an ingest inbox message. - */ - StringBuilder detailsSb = new StringBuilder(); - detailsSb.append("File: " + f.getParentPath() + f.getName() + "
\n"); - detailsSb.append("Entropy: " + entropy); - - SERVICES.postMessage(IngestMessage.createDataMessage(CryptoDetectionModuleFactory.getModuleName(), - "Encryption Detected Match: " + f.getName(), - detailsSb.toString(), - f.getName(), - artifact)); + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS } + /* + * Send an event to update the view with the new result. + */ + SERVICES.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(); + detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("
\n"); + detailsSb.append("Entropy: ").append(entropy); + + SERVICES.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + file.getName(), + detailsSb.toString(), + file.getName(), + artifact)); + return IngestModule.ProcessResult.OK; } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "Failed to create blackboard artifact ({0}).", ex.getLocalizedMessage()); //NON-NLS - return IngestModule.ProcessResult.ERROR; - } catch (IOException ex) { - LOGGER.log(Level.WARNING, String.format("Failed to calculate the entropy for '%s'.", Paths.get(f.getParentPath(), f.getName())), ex); //NON-NLS + LOGGER.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", Paths.get(file.getParentPath(), file.getName())), ex); //NON-NLS return IngestModule.ProcessResult.ERROR; } } + /** + * This method checks if the AbstractFile input is encrypted. Initial + * qualifications require that it be an actual file that is not known, meets + * file size requirements, and has a MIME type of + * 'application/octet-stream'. + * + * @param file AbstractFile to be checked. + * + * @return True if the AbstractFile is encrypted. + */ + private boolean isFileEncrypted(AbstractFile file) throws IOException, TskCoreException { + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ + + boolean possiblyEncrypted = false; + + /* + * Qualify the file type. + */ + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { + /* + * Qualify the file against hash databases. + */ + if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + /* + * Qualify the size. + */ + long contentSize = file.getSize(); + if (contentSize >= FILE_SIZE_THRESHOLD && (contentSize % FILE_SIZE_MODULUS) == 0) { + /* + * Qualify the MIME type. + */ + try { + String mimeType = fileTypeDetector.getFileType(file); + if (mimeType != null && mimeType.equals("application/octet-stream")) { + possiblyEncrypted = true; + } + } catch (TskCoreException ex) { + throw new TskCoreException("Failed to detect the file type.", ex); + } + } + } + } + + if (possiblyEncrypted) { + try { + entropy = calculateEntropy(file); + if (entropy > ENTROPY_THRESHOLD) { + return true; + } + } catch (IOException ex) { + throw new IOException("Unable to calculate the entropy.", ex); + } + } + + return false; + } + /** * Calculate the entropy of the file. The result is used to qualify the file * as an encrypted file. @@ -155,6 +214,7 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * Logic in this method is based on * https://github.com/willjasen/entropy/blob/master/entropy.java */ + InputStream in = null; BufferedInputStream bin = null; @@ -165,7 +225,7 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { /* * Determine the number of times each byte value appears. */ - int[] byteOccurences = new int[256]; + int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; int readByte; while ((readByte = bin.read()) != -1) { byteOccurences[readByte]++; @@ -175,19 +235,18 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { * Calculate the entropy based on the byte occurence counts. */ long dataLength = file.getSize() - 1; - double entropy = 0; - for (int i = 0; i < 256; i++) { + double entropyAccumulator = 0; + for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { if (byteOccurences[i] > 0) { double byteProbability = (double) byteOccurences[i] / (double) dataLength; - entropy += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); + entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); } } - return -entropy; + return -entropyAccumulator; } catch (IOException ex) { - LOGGER.log(Level.WARNING, "IOException occurred while trying to read data from InputStream.", ex); //NON-NLS - throw ex; + throw new IOException("IOException occurred while trying to read data from InputStream.", ex); } finally { try { if (in != null) { @@ -197,66 +256,8 @@ final class CryptoDetectionFileIngestModule implements FileIngestModule { bin.close(); } } catch (IOException ex) { - LOGGER.log(Level.WARNING, "Failed to close InputStream.", ex); //NON-NLS - throw ex; + throw new IOException("Failed to close InputStream.", ex); } } } - - /** - * This method checks if the AbstractFile input is supported. To qualify, it - * must be an actual file that is not known, has a size that's evenly - * divisible by 512 and a minimum size of 5MB, and has a MIME type of - * 'application/octet-stream'. - * - * @param file AbstractFile to be checked. - * - * @return True if the AbstractFile qualifies. - */ - private boolean isFileSupported(AbstractFile file) { - /* - * Criteria for the checks in this method are partially based on - * http://www.forensicswiki.org/wiki/TrueCrypt#Detection - */ - - boolean supported = false; - - /* - * Qualify the file type. - */ - if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { - /* - * Qualify the file against hash databases. - */ - if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { - /* - * Qualify the size. - */ - long contentSize = file.getSize(); - if (contentSize >= 5242880 && (contentSize % 512) == 0) { - /* - * Qualify the MIME type. - */ - try { - String mimeType = fileTypeDetector.getFileType(file); - if (mimeType != null && mimeType.equals("application/octet-stream")) { - supported = true; - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to detect file type", ex); //NON-NLS - } - } - } - } - - return supported; - } - - @Override - public void shutDown() { - REF_COUNTER.decrementAndGet(jobId); - } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java similarity index 74% rename from Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java rename to Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java index f40b2a3490..53eca1aec6 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/crypto/CryptoDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.crypto; +package org.sleuthkit.autopsy.modules.encryptiondetection; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -32,10 +32,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; */ @ServiceProvider(service = IngestModuleFactory.class) @Messages({ - "CryptoDetectionFileIngestModule.moduleName.text=Crypto Detection", - "CryptoDetectionFileIngestModule.getDesc.text=Looks for large files with high entropy." + "EncryptionDetectionFileIngestModule.moduleName.text=Encryption Detection", + "EncryptionDetectionFileIngestModule.getDesc.text=Looks for large files with high entropy." }) -public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { +public class EncryptionDetectionModuleFactory extends IngestModuleFactoryAdapter { @Override public String getModuleDisplayName() { @@ -48,12 +48,12 @@ public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { * @return The module name. */ static String getModuleName() { - return NbBundle.getMessage(CryptoDetectionFileIngestModule.class, "CryptoDetectionFileIngestModule.moduleName.text"); + return NbBundle.getMessage(EncryptionDetectionFileIngestModule.class, "EncryptionDetectionFileIngestModule.moduleName.text"); } @Override public String getModuleDescription() { - return NbBundle.getMessage(CryptoDetectionFileIngestModule.class, "CryptoDetectionFileIngestModule.getDesc.text"); + return NbBundle.getMessage(EncryptionDetectionFileIngestModule.class, "EncryptionDetectionFileIngestModule.getDesc.text"); } @Override @@ -68,6 +68,6 @@ public class CryptoDetectionModuleFactory extends IngestModuleFactoryAdapter { @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { - return new CryptoDetectionFileIngestModule(); + return new EncryptionDetectionFileIngestModule(); } } \ No newline at end of file From 605994d64174e64520e45938fbf95f36a4bc415a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Nov 2017 18:17:09 -0500 Subject: [PATCH 08/11] 3224 add Databases node to File Types by Extension tree --- .../org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java | 5 +++++ .../sleuthkit/autopsy/datamodel/FileTypesByExtension.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java index 4195b33008..eafa8377b2 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java @@ -38,6 +38,7 @@ public class FileTypeExtensions { private final static List WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS private final static List PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS private final static List ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz", ".bz2"); //NON-NLS + private final static List DATABASE_EXTENSIONS = Arrays.asList(".db", ".db3", ".sqlite", ".sqlite3"); //NON-NLS public static List getImageExtensions() { return IMAGE_EXTENSIONS; @@ -75,6 +76,10 @@ public class FileTypeExtensions { return ARCHIVE_EXTENSIONS; } + public static List getDatabaseExtensions() { + return DATABASE_EXTENSIONS; + } + private FileTypeExtensions() { } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 305da8e953..3c79626e81 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -34,6 +34,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; @@ -423,6 +424,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } // root node filters + @Messages({"FileTypeExtensionFilters.tskDatabaseFilter.text=Databases"}) public static enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface { TSK_IMAGE_FILTER(0, "TSK_IMAGE_FILTER", //NON-NLS @@ -437,6 +439,9 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { TSK_ARCHIVE_FILTER(3, "TSK_ARCHIVE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskArchiveFilter.text"), FileTypeExtensions.getArchiveExtensions()), + TSK_DATABASE_FILTER(3, "TSK_DATABASE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDatabaseFilter.text"), + FileTypeExtensions.getDatabaseExtensions()), TSK_DOCUMENT_FILTER(3, "TSK_DOCUMENT_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDocumentFilter.text"), Arrays.asList(".htm", ".html", ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf")), //NON-NLS From d7c59a0356ce7c329bf388a7fa93b0bc5061abb1 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Nov 2017 18:19:34 -0500 Subject: [PATCH 09/11] 3224 fix IDs for FileTypesByExtension filter enum --- .../sleuthkit/autopsy/datamodel/FileTypesByExtension.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 3c79626e81..2341fff12c 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -439,13 +439,13 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { TSK_ARCHIVE_FILTER(3, "TSK_ARCHIVE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskArchiveFilter.text"), FileTypeExtensions.getArchiveExtensions()), - TSK_DATABASE_FILTER(3, "TSK_DATABASE_FILTER", //NON-NLS + TSK_DATABASE_FILTER(4, "TSK_DATABASE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDatabaseFilter.text"), FileTypeExtensions.getDatabaseExtensions()), - TSK_DOCUMENT_FILTER(3, "TSK_DOCUMENT_FILTER", //NON-NLS + TSK_DOCUMENT_FILTER(5, "TSK_DOCUMENT_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDocumentFilter.text"), Arrays.asList(".htm", ".html", ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf")), //NON-NLS - TSK_EXECUTABLE_FILTER(3, "TSK_EXECUTABLE_FILTER", //NON-NLS + TSK_EXECUTABLE_FILTER(6, "TSK_EXECUTABLE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskExecFilter.text"), FileTypeExtensions.getExecutableExtensions()); //NON-NLS From a6d312663c4bc4cd598efe730aac6c8bd9b384ed Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 28 Nov 2017 15:44:31 -0500 Subject: [PATCH 10/11] 3158 Agency Logo preview now present in options panel --- .../corecomponents/AutopsyOptionsPanel.form | 543 ++++++++++-------- .../corecomponents/AutopsyOptionsPanel.java | 332 +++++++---- .../autopsy/corecomponents/Bundle.properties | 3 +- 3 files changed, 539 insertions(+), 339 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index 06607d7d9b..2e618379e4 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -22,12 +22,18 @@ - + + + + - + + + + @@ -46,247 +52,336 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + - - - - - - - - + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 4f95087f66..4862846b13 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -18,9 +18,18 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import javax.swing.JFileChooser; +import javax.swing.JOptionPane; import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.ModuleSettings; @@ -29,16 +38,23 @@ import org.sleuthkit.autopsy.report.ReportBranding; /** * Options panel that allow users to set application preferences. */ +@Messages({"AutopsyOptionsPanel.agencyLogoPreview.text=
No logo
selected
", + "AutopsyOptionsPanel.logoPanel.border.title=Logo", + "AutopsyOptionsPanel.viewPanel.border.title=View", + "AutopsyOptionsPanel.invalidImageFile.msg=The selected file was not able to be used as an agency logo.", + "AutopsyOptionsPanel.invalidImageFile.title=Invalid Image File"}) final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private final JFileChooser fc; + private static final Logger logger = Logger.getLogger(AutopsyOptionsPanel.class.getName()); AutopsyOptionsPanel() { initComponents(); fc = new JFileChooser(); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(false); + fc.setAcceptAllFileFilterUsed(false); fc.setFileFilter(new GeneralFilter(GeneralFilter.GRAPHIC_IMAGE_EXTS, GeneralFilter.GRAPHIC_IMG_DECR)); } @@ -53,7 +69,28 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { boolean useLocalTime = UserPreferences.displayTimesInLocalTime(); useLocalTimeRB.setSelected(useLocalTime); useGMTTimeRB.setSelected(!useLocalTime); - agencyLogoPathField.setText(ModuleSettings.getConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP)); + String path = ModuleSettings.getConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP); + try { + updateAgencyLogo(path); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex); + } + } + + private void updateAgencyLogo(String path) throws IOException { + agencyLogoPathField.setText(path); + ImageIcon agencyLogoIcon = new ImageIcon(); + agencyLogoPreview.setText(Bundle.AutopsyOptionsPanel_agencyLogoPreview_text()); + if (!agencyLogoPathField.getText().isEmpty()) { + File file = new File(agencyLogoPathField.getText()); + if (file.exists()) { + BufferedImage image = ImageIO.read(file); //create it as an image first to support BMP files + agencyLogoIcon = new ImageIcon(image.getScaledInstance(64, 64, 4)); + agencyLogoPreview.setText(""); + } + } + agencyLogoPreview.setIcon(agencyLogoIcon); + agencyLogoPreview.repaint(); } void store() { @@ -64,8 +101,8 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCB.isSelected()); UserPreferences.setDisplayTimesInLocalTime(useLocalTimeRB.isSelected()); if (!agencyLogoPathField.getText().isEmpty()) { - File image = new File(agencyLogoPathField.getText()); - if (image.exists()) { + File file = new File(agencyLogoPathField.getText()); + if (file.exists()) { ModuleSettings.setConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP, agencyLogoPathField.getText()); } } @@ -87,24 +124,87 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { buttonGroup3 = new javax.swing.ButtonGroup(); jScrollPane1 = new javax.swing.JScrollPane(); jPanel1 = new javax.swing.JPanel(); - useBestViewerRB = new javax.swing.JRadioButton(); - keepCurrentViewerRB = new javax.swing.JRadioButton(); - jLabelSelectFile = new javax.swing.JLabel(); - jLabelTimeDisplay = new javax.swing.JLabel(); - useLocalTimeRB = new javax.swing.JRadioButton(); - useGMTTimeRB = new javax.swing.JRadioButton(); - jLabelHideKnownFiles = new javax.swing.JLabel(); - dataSourcesHideKnownCB = new javax.swing.JCheckBox(); - viewsHideKnownCB = new javax.swing.JCheckBox(); - dataSourcesHideSlackCB = new javax.swing.JCheckBox(); - viewsHideSlackCB = new javax.swing.JCheckBox(); - jLabelHideSlackFiles = new javax.swing.JLabel(); + logoPanel = new javax.swing.JPanel(); agencyLogoImageLabel = new javax.swing.JLabel(); agencyLogoPathField = new javax.swing.JTextField(); browseLogosButton = new javax.swing.JButton(); + agencyLogoPreview = new javax.swing.JLabel(); + viewPanel = new javax.swing.JPanel(); + jLabelSelectFile = new javax.swing.JLabel(); + useBestViewerRB = new javax.swing.JRadioButton(); + keepCurrentViewerRB = new javax.swing.JRadioButton(); + jLabelHideKnownFiles = new javax.swing.JLabel(); + dataSourcesHideKnownCB = new javax.swing.JCheckBox(); + viewsHideKnownCB = new javax.swing.JCheckBox(); + jLabelHideSlackFiles = new javax.swing.JLabel(); + dataSourcesHideSlackCB = new javax.swing.JCheckBox(); + viewsHideSlackCB = new javax.swing.JCheckBox(); + jLabelTimeDisplay = new javax.swing.JLabel(); + useLocalTimeRB = new javax.swing.JRadioButton(); + useGMTTimeRB = new javax.swing.JRadioButton(); jScrollPane1.setBorder(null); + logoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logoPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(agencyLogoImageLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoImageLabel.text")); // NOI18N + + agencyLogoPathField.setEditable(false); + agencyLogoPathField.setBackground(new java.awt.Color(255, 255, 255)); + agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N + agencyLogoPathField.setFocusable(false); + agencyLogoPathField.setRequestFocusEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(browseLogosButton, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.browseLogosButton.text")); // NOI18N + browseLogosButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseLogosButtonActionPerformed(evt); + } + }); + + agencyLogoPreview.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + org.openide.awt.Mnemonics.setLocalizedText(agencyLogoPreview, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPreview.text")); // NOI18N + agencyLogoPreview.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + agencyLogoPreview.setMaximumSize(new java.awt.Dimension(64, 64)); + agencyLogoPreview.setMinimumSize(new java.awt.Dimension(64, 64)); + agencyLogoPreview.setPreferredSize(new java.awt.Dimension(64, 64)); + + javax.swing.GroupLayout logoPanelLayout = new javax.swing.GroupLayout(logoPanel); + logoPanel.setLayout(logoPanelLayout); + logoPanelLayout.setHorizontalGroup( + logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, logoPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(agencyLogoImageLabel) + .addGroup(logoPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(agencyLogoPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 259, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseLogosButton))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(agencyLogoPreview, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(149, Short.MAX_VALUE)) + ); + logoPanelLayout.setVerticalGroup( + logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(logoPanelLayout.createSequentialGroup() + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(agencyLogoPreview, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(logoPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(agencyLogoImageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(agencyLogoPathField) + .addComponent(browseLogosButton)))) + .addGap(0, 0, 0)) + ); + + viewPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectFile, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSelectFile.text")); // NOI18N + buttonGroup1.add(useBestViewerRB); org.openide.awt.Mnemonics.setLocalizedText(useBestViewerRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.text")); // NOI18N useBestViewerRB.setToolTipText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.toolTipText")); // NOI18N @@ -123,7 +223,37 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectFile, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSelectFile.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabelHideKnownFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideKnownFiles.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideKnownCB.text")); // NOI18N + dataSourcesHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourcesHideKnownCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(viewsHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideKnownCB.text")); // NOI18N + viewsHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewsHideKnownCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelHideSlackFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideSlackFiles.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideSlackCB.text")); // NOI18N + dataSourcesHideSlackCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourcesHideSlackCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(viewsHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideSlackCB.text")); // NOI18N + viewsHideSlackCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewsHideSlackCBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(jLabelTimeDisplay, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelTimeDisplay.text")); // NOI18N @@ -143,93 +273,34 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabelHideKnownFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideKnownFiles.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideKnownCB.text")); // NOI18N - dataSourcesHideKnownCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - dataSourcesHideKnownCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(viewsHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideKnownCB.text")); // NOI18N - viewsHideKnownCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - viewsHideKnownCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideSlackCB.text")); // NOI18N - dataSourcesHideSlackCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - dataSourcesHideSlackCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(viewsHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideSlackCB.text")); // NOI18N - viewsHideSlackCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - viewsHideSlackCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jLabelHideSlackFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideSlackFiles.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(agencyLogoImageLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoImageLabel.text")); // NOI18N - - agencyLogoPathField.setEditable(false); - agencyLogoPathField.setBackground(new java.awt.Color(255, 255, 255)); - agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N - agencyLogoPathField.setFocusable(false); - agencyLogoPathField.setRequestFocusEnabled(false); - - org.openide.awt.Mnemonics.setLocalizedText(browseLogosButton, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.browseLogosButton.text")); // NOI18N - browseLogosButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - browseLogosButtonActionPerformed(evt); - } - }); - - javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + javax.swing.GroupLayout viewPanelLayout = new javax.swing.GroupLayout(viewPanel); + viewPanel.setLayout(viewPanelLayout); + viewPanelLayout.setHorizontalGroup( + viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, viewPanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabelTimeDisplay) - .addComponent(jLabelHideKnownFiles) - .addComponent(jLabelSelectFile) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(useLocalTimeRB) - .addComponent(useGMTTimeRB) - .addComponent(keepCurrentViewerRB) - .addComponent(useBestViewerRB) - .addComponent(dataSourcesHideKnownCB) - .addComponent(viewsHideKnownCB)))) - .addContainerGap(140, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabelHideSlackFiles) - .addComponent(agencyLogoImageLabel) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(agencyLogoPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 259, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseLogosButton)) - .addComponent(dataSourcesHideSlackCB) - .addComponent(viewsHideSlackCB)))) - .addGap(0, 0, Short.MAX_VALUE)))) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(viewPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(useGMTTimeRB) + .addComponent(keepCurrentViewerRB) + .addComponent(useBestViewerRB) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(useLocalTimeRB) + .addComponent(dataSourcesHideSlackCB) + .addComponent(viewsHideSlackCB) + .addComponent(dataSourcesHideKnownCB) + .addComponent(viewsHideKnownCB)))) + .addComponent(jLabelHideSlackFiles) + .addComponent(jLabelTimeDisplay) + .addComponent(jLabelHideKnownFiles) + .addComponent(jLabelSelectFile)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + viewPanelLayout.setVerticalGroup( + viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, viewPanelLayout.createSequentialGroup() .addContainerGap() .addComponent(jLabelSelectFile) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -253,14 +324,28 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(useLocalTimeRB) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(useGMTTimeRB) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(agencyLogoImageLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(agencyLogoPathField) - .addComponent(browseLogosButton)) - .addGap(35, 35, 35)) + .addComponent(useGMTTimeRB)) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(logoPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) ); jScrollPane1.setViewportView(jPanel1); @@ -269,11 +354,15 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 672, Short.MAX_VALUE) + .addGap(0, 0, 0)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 489, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -310,17 +399,32 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { }//GEN-LAST:event_viewsHideSlackCBActionPerformed private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed + String oldLogoPath = agencyLogoPathField.getText(); int returnState = fc.showOpenDialog(this); if (returnState == JFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); - agencyLogoPathField.setText(path); - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + try { + updateAgencyLogo(path); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } catch (IOException | IndexOutOfBoundsException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(this.getClass(), + "AutopsyOptionsPanel.invalidImageFile.msg"), + NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), + JOptionPane.ERROR_MESSAGE); + try { + updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid + } catch (IOException ex1) { + logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); + } + } } }//GEN-LAST:event_browseLogosButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel agencyLogoImageLabel; private javax.swing.JTextField agencyLogoPathField; + private javax.swing.JLabel agencyLogoPreview; private javax.swing.JButton browseLogosButton; private javax.swing.ButtonGroup buttonGroup1; private javax.swing.ButtonGroup buttonGroup3; @@ -333,9 +437,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JRadioButton keepCurrentViewerRB; + private javax.swing.JPanel logoPanel; private javax.swing.JRadioButton useBestViewerRB; private javax.swing.JRadioButton useGMTTimeRB; private javax.swing.JRadioButton useLocalTimeRB; + private javax.swing.JPanel viewPanel; private javax.swing.JCheckBox viewsHideKnownCB; private javax.swing.JCheckBox viewsHideSlackCB; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 5ac6cbd626..26a2cc03e0 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -1,7 +1,7 @@ CTL_DataContentAction=DataContent CTL_DataContentTopComponent=Data Content CTL_CustomAboutAction=About -OptionsCategory_Name_General=View +OptionsCategory_Name_General=Application OptionsCategory_Keywords_General=Autopsy Options HINT_DataContentTopComponent=This is a DataContent window HINT_NodeTableTopComponent=This is a DataResult window @@ -198,7 +198,6 @@ AutopsyOptionsPanel.agencyLogoPathField.text= SortChooserDialog.label=remove SortChooser.addCriteriaButton.text=Add Sort Criteria DataResultViewerThumbnail.sortButton.text=Sort - CriterionChooser.ascendingRadio.text=\u25b2 Ascending\n CriterionChooser.removeButton.text=Remove CriterionChooser.descendingRadio.text=\u25bc Descending From 3d306b31baa5966db98acd6ecaacd572a7d0edc2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 28 Nov 2017 16:30:32 -0500 Subject: [PATCH 11/11] 3158 fix NPE when some invalid image files are tried as agency logo --- .../sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 4862846b13..435bdc99b4 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -85,6 +85,9 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { File file = new File(agencyLogoPathField.getText()); if (file.exists()) { BufferedImage image = ImageIO.read(file); //create it as an image first to support BMP files + if (image == null) { + throw new IOException("Unable to read file as a BufferedImage for file " + file.toString()); + } agencyLogoIcon = new ImageIcon(image.getScaledInstance(64, 64, 4)); agencyLogoPreview.setText(""); }