diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 47fff7320f..0147f31750 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -2060,6 +2060,7 @@ public class Case { private final SleuthkitCase tskCase; private final String caseName; + private final long MAX_IMAGE_THRESHOLD = 100; private final ProgressIndicator progressIndicator; /** @@ -2117,28 +2118,18 @@ public class Case { * event that the operation is * cancelled prior to completion. */ - private void openFileSystems(List images) throws InterruptedException { + private void openFileSystems(List images) throws TskCoreException, InterruptedException { byte[] tempBuff = new byte[512]; - + for (Image image : images) { String imageStr = image.getName(); progressIndicator.progress(Bundle.Case_openFileSystems_openingImage(imageStr)); - Collection fileSystems = this.tskCase.getFileSystems(image); + Collection fileSystems = this.tskCase.getImageFileSystems(image); checkIfCancelled(); for (FileSystem fileSystem : fileSystems) { - try { - fileSystem.read(tempBuff, 0, 512); - } catch (TskCoreException ex) { - String fileSysStr = fileSystem.getName(); - - logger.log( - Level.WARNING, - String.format("Could not open filesystem: %s in image: %s for case: %s.", fileSysStr, imageStr, caseName), - ex); - } - + fileSystem.read(tempBuff, 0, 512); checkIfCancelled(); } @@ -2153,6 +2144,14 @@ public class Case { if (images == null) { return; } + + if (images.size() > MAX_IMAGE_THRESHOLD) { + // If we have a large number of images, don't try to preload anything + logger.log( + Level.INFO, + String.format("Skipping background load of file systems due to large number of images in case (%d)", images.size())); + return; + } checkIfCancelled(); openFileSystems(images); @@ -2160,6 +2159,9 @@ public class Case { logger.log( Level.INFO, String.format("Background operation opening all file systems in %s has been cancelled.", caseName)); + } catch (Exception ex) { + // Exception firewall + logger.log(Level.WARNING, "Error while opening file systems in background", ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 49388d619e..8af7cd7efa 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -146,6 +146,12 @@ public class TagsManager implements Closeable { return tagDisplayNames; } + /** + * Gets the set of display names of notable (TskData.FileKnown.BAD) tag types. + * If a case is not open the list will only include only the user defined + * custom tags. Otherwise the list will include all notable tags. + * @return + */ public static List getNotableTagDisplayNames() { List tagDisplayNames = new ArrayList<>(); for (TagNameDefinition tagDef : TagNameDefinition.getTagNameDefinitions()) { @@ -153,6 +159,22 @@ public class TagsManager implements Closeable { tagDisplayNames.add(tagDef.getDisplayName()); } } + + try { + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + for (TagName tagName : tagsManager.getAllTagNames()) { + if(tagName.getKnownStatus() == TskData.FileKnown.BAD && + !tagDisplayNames.contains(tagName.getDisplayName())) { + tagDisplayNames.add(tagName.getDisplayName()); + } + } + } catch (NoCurrentCaseException ignored) { + /* + * No current case, nothing more to add to the set. + */ + } catch(TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get list of TagNames from TagsManager.", ex); + } return tagDisplayNames; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index b71606594f..a4ce71cb36 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -26,6 +26,7 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.datamodel.HashHitInfo; /** * Main interface for interacting with the database @@ -553,6 +554,22 @@ public interface CentralRepository { */ public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; + + /** + * Retrieves the given file HashHitInfo if the given file hash is in this + * reference set. Only searches the reference_files table. + * + * @param hash The hash to find in a search. + * @param referenceSetID The referenceSetID within which the file should exist. + * + * @return The HashHitInfo if found or null if not found. + * + * @throws CentralRepoException + * @throws CorrelationAttributeNormalizationException + */ + HashHitInfo lookupHash(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException; + + /** * Check if the given value is in a specific reference set * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index de88396de7..2afda97c05 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; +import org.sleuthkit.datamodel.HashHitInfo; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskData; @@ -2307,6 +2308,45 @@ abstract class RdbmsCentralRepo implements CentralRepository { return isValueInReferenceSet(hash, referenceSetID, CorrelationAttributeInstance.FILES_TYPE_ID); } + @Override + public HashHitInfo lookupHash(String hash, int referenceSetID) throws CentralRepoException, CorrelationAttributeNormalizationException { + int correlationTypeID = CorrelationAttributeInstance.FILES_TYPE_ID; + String normalizeValued = CorrelationAttributeNormalizer.normalize(this.getCorrelationTypeById(correlationTypeID), hash); + + Connection conn = connect(); + + PreparedStatement preparedStatement = null; + ResultSet resultSet = null; + String sql = "SELECT value,comment FROM %s WHERE value=? AND reference_set_id=?"; + + String fileTableName = CentralRepoDbUtil.correlationTypeToReferenceTableName(getCorrelationTypeById(correlationTypeID)); + + try { + preparedStatement = conn.prepareStatement(String.format(sql, fileTableName)); + preparedStatement.setString(1, normalizeValued); + preparedStatement.setInt(2, referenceSetID); + resultSet = preparedStatement.executeQuery(); + if (resultSet.next()) { + String comment = resultSet.getString("comment"); + String hashFound = resultSet.getString("value"); + HashHitInfo found = new HashHitInfo(hashFound, "", ""); + found.addComment(comment); + return found; + } + else { + return null; + } + } catch (SQLException ex) { + throw new CentralRepoException("Error determining if value (" + normalizeValued + ") is in reference set " + referenceSetID, ex); // NON-NLS + } finally { + CentralRepoDbUtil.closeStatement(preparedStatement); + CentralRepoDbUtil.closeResultSet(resultSet); + CentralRepoDbUtil.closeConnection(conn); + } + } + + + /** * Check if the given value is in a specific reference set * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java index d8bd1c43c3..67c194fda7 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/CaseEventListener.java @@ -55,6 +55,7 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.events.AutopsyEvent; /** * Listen for case events and update entries in the Central Repository database @@ -86,6 +87,10 @@ final class CaseEventListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { + if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { + return; + } + CentralRepository dbManager; try { dbManager = CentralRepository.getInstance(); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java index c5c92b11c0..a12675994e 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/AnnotationsContentViewer.java @@ -20,11 +20,19 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.Function; import java.util.logging.Level; -import org.apache.commons.lang3.StringEscapeUtils; +import java.util.stream.Collectors; +import javax.swing.JLabel; +import javax.swing.text.EditorKit; +import javax.swing.text.html.HTMLEditorKit; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.tuple.Pair; -import org.openide.util.NbBundle; +import static org.openide.util.NbBundle.Messages; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.nodes.Node; import org.openide.util.lookup.ServiceProvider; @@ -32,8 +40,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase; -import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource; import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; @@ -46,26 +52,190 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; /** * Annotations view of file contents. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @ServiceProvider(service = DataContentViewer.class, position = 8) -@NbBundle.Messages({ +@Messages({ "AnnotationsContentViewer.title=Annotations", - "AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content." + "AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.", + "AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:", + "AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:", + "AnnotationsContentViewer.tagEntry.title=Tags", + "AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:", + "AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner:", + "AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:", + "AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment", + "AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments", + "AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments", + "AnnotationsContentViewer.fileHitEntry.setName=Set Name:", + "AnnotationsContentViewer.fileHitEntry.comment=Comment:", + "AnnotationsContentViewer.sourceFile.title=Source File", + "AnnotationsContentViewer.onEmpty=No annotations were found for this particular item." }) public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer { + /** + * Describes a key value pair for an item of type T where the key is the + * field name to display and the value is retrieved from item of type T + * using a provided Function. + * + * @param The item type. + */ + private static class ItemEntry { + + private final String itemName; + private final Function valueRetriever; + + ItemEntry(String itemName, Function valueRetriever) { + this.itemName = itemName; + this.valueRetriever = valueRetriever; + } + + String getItemName() { + return itemName; + } + + Function getValueRetriever() { + return valueRetriever; + } + + String retrieveValue(T object) { + return valueRetriever.apply(object); + } + } + + /** + * Describes a section that will be appended to the annotations view panel. + * + * @param The item type for items to display. + */ + private static class SectionConfig { + + private final String title; + private final List> attributes; + + SectionConfig(String title, List> attributes) { + this.title = title; + this.attributes = attributes; + } + + /** + * @return The title for the section. + */ + String getTitle() { + return title; + } + + /** + * @return Describes key-value pairs on the object to display to the + * user. + */ + List> getAttributes() { + return attributes; + } + } + private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName()); + private static final String EMPTY_HTML = ""; + + private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize(); + + // how big the subheader should be + private static final int SUBHEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 12 / 11; + + // how big the header should be + private static final int HEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 14 / 11; + + // the subsection indent + private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE; + + // spacing occurring after an item + private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE; + private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE * 2; + private static final int DEFAULT_SUBSECTION_SPACING = DEFAULT_FONT_SIZE / 2; + private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2; + + // html stylesheet classnames for components + private static final String MESSAGE_CLASSNAME = "message"; + private static final String SUBSECTION_CLASSNAME = "subsection"; + private static final String SUBHEADER_CLASSNAME = "subheader"; + private static final String SECTION_CLASSNAME = "section"; + private static final String HEADER_CLASSNAME = "header"; + private static final String VERTICAL_TABLE_CLASSNAME = "vertical-table"; + + // additional styling for components + private static final String STYLE_SHEET_RULE + = String.format(" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE) + + String.format(" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ", + SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING) + + String.format(" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", HEADER_CLASSNAME, HEADER_FONT_SIZE) + + String.format(" td { vertical-align: top; font-size:%dpx; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px;} ", DEFAULT_FONT_SIZE, CELL_SPACING) + + String.format(" th { vertical-align: top; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px} ", DEFAULT_FONT_SIZE, CELL_SPACING) + + String.format(" .%s { margin: %dpx 0px; padding-left: %dpx; } ", SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD) + + String.format(" .%s { margin-bottom: %dpx; } ", SECTION_CLASSNAME, DEFAULT_SECTION_SPACING); + + // describing table values for a tag + private static final List> TAG_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(), + (tag) -> (tag.getName() != null) ? tag.getName().getDisplayName() : null), + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), (tag) -> tag.getUserName()), + new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), (tag) -> tag.getComment()) + ); + + private static final SectionConfig TAG_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_tagEntry_title(), TAG_ENTRIES); + + // file set attributes and table configurations + private static final List> FILESET_HIT_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_setName(), + (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)), + new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_comment(), + (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)) + ); + + private static final SectionConfig INTERESTING_FILE_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_interestingFileHitTitle(), FILESET_HIT_ENTRIES); + + private static final SectionConfig HASHSET_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_hashSetHitTitle(), FILESET_HIT_ENTRIES); + + private static final SectionConfig ARTIFACT_COMMENT_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_artifactCommentTitle(), FILESET_HIT_ENTRIES); + + // central repository attributes and table configuration + private static final List> CR_COMMENTS_ENTRIES = Arrays.asList( + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(), + cai -> (cai.getCorrelationCase() != null) ? cai.getCorrelationCase().getDisplayName() : null), + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), cai -> cai.getComment()), + new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), cai -> cai.getFilePath()) + ); + + private static final SectionConfig CR_COMMENTS_CONFIG + = new SectionConfig<>(Bundle.AnnotationsContentViewer_centralRepositoryEntry_title(), CR_COMMENTS_ENTRIES); + /** * Creates an instance of AnnotationsContentViewer. */ public AnnotationsContentViewer() { initComponents(); Utilities.configureTextPaneAsHtml(jTextPane1); + // get html editor kit and apply additional style rules + EditorKit editorKit = jTextPane1.getEditorKit(); + if (editorKit instanceof HTMLEditorKit) { + HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit; + htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE); + } } @Override @@ -75,7 +245,8 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data return; } - StringBuilder html = new StringBuilder(); + Document html = Jsoup.parse(EMPTY_HTML); + Element body = html.getElementsByTag("body").first(); BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); Content sourceFile = null; @@ -101,279 +272,439 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data artifact.getDisplayName(), artifact.getArtifactID()), ex); } + boolean somethingWasRendered = false; if (artifact != null) { - populateTagData(html, artifact, sourceFile); + somethingWasRendered = renderArtifact(body, artifact, sourceFile); } else { - populateTagData(html, sourceFile); + somethingWasRendered = renderContent(body, sourceFile, false); } - if (sourceFile instanceof AbstractFile) { - populateCentralRepositoryData(html, artifact, (AbstractFile) sourceFile); + if (!somethingWasRendered) { + appendMessage(body, Bundle.AnnotationsContentViewer_onEmpty()); } - setText(html.toString()); + jTextPane1.setText(html.html()); jTextPane1.setCaretPosition(0); } /** - * Populate the "Selected Item" sections with tag data for the supplied - * content. + * Renders annotations for an artifact. * - * @param html The HTML text to update. - * @param content Selected content. + * @param parent The html element to render content int. + * @param bba The blackboard artifact to render. + * @param sourceContent The content from which the blackboard artifact + * comes. + * + * @return If any content was actually rendered. */ - private void populateTagData(StringBuilder html, Content content) { + private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) { + boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false); + + if (CentralRepository.isEnabled()) { + List centralRepoComments = getCentralRepositoryData(bba); + boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false); + contentRendered = contentRendered || crRendered; + } + + // if artifact is a hashset hit or interesting file and has a non-blank comment + if ((ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == bba.getArtifactTypeID() + || ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID()) + && (hasTskComment(bba))) { + + boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false); + contentRendered = contentRendered || filesetRendered; + } + + Element sourceFileSection = appendSection(parent, Bundle.AnnotationsContentViewer_sourceFile_title()); + boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent, true); + + if (!sourceFileRendered) { + sourceFileSection.remove(); + } + + return contentRendered || sourceFileRendered; + } + + /** + * Renders annotations for a content item. + * + * @param parent The parent within which to render. + * @param sourceContent The content for which annotations will be gathered. + * @param isSubheader True if this section should be rendered as a + * subheader as opposed to a top-level header. + * + * @return If any content was actually rendered. + */ + private static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader) { + boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader); + + if (sourceContent instanceof AbstractFile) { + AbstractFile sourceFile = (AbstractFile) sourceContent; + + if (CentralRepository.isEnabled()) { + List centralRepoComments = getCentralRepositoryData(sourceFile); + boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader); + contentRendered = contentRendered || crRendered; + } + + boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG, + getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_HASHSET_HIT), + isSubheader); + + boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG, + getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT), + isSubheader); + + contentRendered = contentRendered || hashsetRendered || interestingFileRendered; + } + return contentRendered; + } + + /** + * Retrieves tags associated with a content item. + * + * @param sourceContent The content for which to gather content. + * + * @return The Tags associated with this item. + */ + private static List getTags(Content sourceContent) { try { SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - startSection(html, "Selected Item"); - List fileTagsList = tskCase.getContentTagsByContent(content); - if (fileTagsList.isEmpty()) { - addMessage(html, "There are no tags for the selected content."); - } else { - for (ContentTag tag : fileTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); + return tskCase.getContentTagsByContent(sourceContent); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS } + return new ArrayList<>(); } /** - * Populate the "Selected Item" and "Source File" sections with tag data for - * a supplied artifact. + * Retrieves tags for blackboard artifact tags. * - * @param html The HTML text to update. - * @param artifact A selected artifact. - * @param sourceFile The source content of the selected artifact. + * @param bba The blackboard artifact for which to retrieve tags. + * + * @return The found tags. */ - private void populateTagData(StringBuilder html, BlackboardArtifact artifact, Content sourceFile) { + private static List getTags(BlackboardArtifact bba) { try { SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - startSection(html, "Selected Item"); - List artifactTagsList = tskCase.getBlackboardArtifactTagsByArtifact(artifact); - if (artifactTagsList.isEmpty()) { - addMessage(html, "There are no tags for the selected artifact."); - } else { - for (BlackboardArtifactTag tag : artifactTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); - - if (sourceFile != null) { - startSection(html, "Source File"); - List fileTagsList = tskCase.getContentTagsByContent(sourceFile); - if (fileTagsList.isEmpty()) { - addMessage(html, "There are no tags for the source content."); - } else { - for (ContentTag tag : fileTagsList) { - addTagEntry(html, tag); - } - } - endSection(html); - } + return tskCase.getBlackboardArtifactTagsByArtifact(bba); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS } + return new ArrayList<>(); } /** - * Populate the "Central Repository Comments" section with data. + * Retrieves the blackboard artifacts for a source file matching a certain + * type that have a non-blank TSK_COMMENT. + * + * @param sourceFile The source file for which to fetch artifacts. + * @param type The type of blackboard artifact to fetch. + * + * @return The artifacts found matching this type. + */ + private static List getFileSetHits(AbstractFile sourceFile, ARTIFACT_TYPE type) { + try { + SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + return tskCase.getBlackboardArtifacts(type, sourceFile.getId()).stream() + .filter((bba) -> hasTskComment(bba)) + .collect(Collectors.toList()); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception while getting file set hits from the case database.", ex); //NON-NLS + } + return new ArrayList<>(); + } + + /** + * Returns true if the artifact contains a non-blank TSK_COMMENT attribute. + * + * @param artifact The artifact to check. + * + * @return True if it has a non-blank TSK_COMMENT. + */ + private static boolean hasTskComment(BlackboardArtifact artifact) { + return StringUtils.isNotBlank(tryGetAttribute(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)); + } + + /** + * Attempts to retrieve the attribute of a particular type from a blackboard + * artifact. + * + * @param artifact The artifact from which to retrieve the information. + * @param attributeType The attribute type to retrieve from the artifact. + * + * @return The string value of the attribute or null if not found. + */ + private static String tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) { + if (artifact == null) { + return null; + } + + BlackboardAttribute attr = null; + try { + attr = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Unable to fetch attribute of type %s for artifact %s", attributeType, artifact), ex); + } + + if (attr == null) { + return null; + } + + return attr.getValueString(); + } + + /** + * Gets the "Central Repository Comments" section with data for the + * blackboard artifact. + * + * @param artifact The selected artifact. + * + * @return The Correlation Attribute Instances associated with the artifact + * that have comments. + */ + private static List getCentralRepositoryData(BlackboardArtifact artifact) { + if (artifact == null) { + return new ArrayList<>(); + } + + List> lookupKeys = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact) + .stream() + .map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue())) + .collect(Collectors.toList()); + + return getCorrelationAttributeComments(lookupKeys); + } + + /** + * Gets the "Central Repository Comments" section with data. * - * @param html The HTML text to update. - * @param artifact A selected artifact (can be null). * @param sourceFile A selected file, or a source file of the selected * artifact. + * + * @return The Correlation Attribute Instances associated with the + * sourcefile that have comments. */ - private void populateCentralRepositoryData(StringBuilder html, BlackboardArtifact artifact, AbstractFile sourceFile) { - if (CentralRepository.isEnabled()) { - startSection(html, "Central Repository Comments"); - List instancesList = new ArrayList<>(); - if (artifact != null) { - instancesList.addAll(CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact)); - } - try { - List artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); - String md5 = sourceFile.getMd5Hash(); - if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) { - for (CorrelationAttributeInstance.Type attributeType : artifactTypes) { - if (attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) { - CorrelationCase correlationCase = CentralRepository.getInstance().getCase(Case.getCurrentCase()); - instancesList.add(new CorrelationAttributeInstance( - attributeType, - md5, - correlationCase, - CorrelationDataSource.fromTSKDataSource(correlationCase, sourceFile.getDataSource()), - sourceFile.getParentPath() + sourceFile.getName(), - "", - sourceFile.getKnown(), - sourceFile.getId())); - break; - } - } - } - - boolean commentDataFound = false; - - for (CorrelationAttributeInstance instance : instancesList) { - List correlatedInstancesList - = CentralRepository.getInstance().getArtifactInstancesByTypeValue(instance.getCorrelationType(), instance.getCorrelationValue()); - for (CorrelationAttributeInstance correlatedInstance : correlatedInstancesList) { - if (correlatedInstance.getComment() != null && correlatedInstance.getComment().isEmpty() == false) { - commentDataFound = true; - addCentralRepositoryEntry(html, correlatedInstance); - } - } - } - - if (commentDataFound == false) { - addMessage(html, "There is no comment data for the selected content in the Central Repository."); - } - } catch (CentralRepoException | TskCoreException ex) { - logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS - } catch (CorrelationAttributeNormalizationException ex) { - logger.log(Level.SEVERE, "Error normalizing instance from Central Repository database.", ex); // NON-NLS - } - endSection(html); + private static List getCentralRepositoryData(AbstractFile sourceFile) { + if (sourceFile == null || StringUtils.isEmpty(sourceFile.getMd5Hash())) { + return new ArrayList<>(); } + + List artifactTypes = null; + try { + artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes(); + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS + } + + if (artifactTypes == null || artifactTypes.isEmpty()) { + return new ArrayList<>(); + } + + String md5 = sourceFile.getMd5Hash(); + + // get key lookups for a file attribute types and the md5 hash + List> lookupKeys = artifactTypes.stream() + .filter((attributeType) -> attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) + .map((attributeType) -> Pair.of(attributeType, md5)) + .collect(Collectors.toList()); + + return getCorrelationAttributeComments(lookupKeys); } /** - * Set the text of the text panel. + * Given a type and a value for that type, does a lookup in the Central + * Repository for matching values that have comments. * - * @param text The text to set to the text panel. + * @param lookupKeys The type and value to lookup. + * + * @return The found correlation attribute instances. */ - private void setText(String text) { - jTextPane1.setText("" + text + ""); //NON-NLS + private static List getCorrelationAttributeComments(List> lookupKeys) { + List instancesToRet = new ArrayList<>(); + + try { + // use lookup instances to find the actual correlation attributes for the items selected + for (Pair typeVal : lookupKeys) { + instancesToRet.addAll(CentralRepository.getInstance() + .getArtifactInstancesByTypeValue(typeVal.getKey(), typeVal.getValue()) + .stream() + // for each one found, if it has a comment, return + .filter((cai) -> StringUtils.isNotBlank(cai.getComment())) + .collect(Collectors.toList())); + } + + } catch (CentralRepoException ex) { + logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS + } catch (CorrelationAttributeNormalizationException ex) { + logger.log(Level.SEVERE, "Error normalizing instance from Central Repository database.", ex); // NON-NLS + } + + return instancesToRet; } /** - * Start a new data section. + * Append entries to the parent element in the annotations viewer. Entries + * will be formatted as a table in the format specified in the + * SectionConfig. * - * @param html The HTML text to add the section to. - * @param sectionName The name of the section. + * @param parent The parent element for which the entries will be + * attached. + * @param config The display configuration for this entry type (i.e. + * table type, name, if data is not present). + * @param items The items to display. + * @param isSubsection Whether or not this should be displayed as a + * subsection. If not displayed as a top-level section. + * + * @return If there was actual content rendered for this set of entries. */ - private void startSection(StringBuilder html, String sectionName) { - html.append("

") - .append(sectionName) - .append("


"); //NON-NLS + private static boolean appendEntries(Element parent, SectionConfig config, List items, + boolean isSubsection) { + if (items == null || items.isEmpty()) { + return false; + } + + Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle()); + appendVerticalEntryTables(sectionDiv, items, config.getAttributes()); + return true; } /** - * Add a message. + * Appends a table where items are displayed in rows of key-value pairs. * - * @param html The HTML text to add the message to. - * @param message The message text. + * @param parent The parent to append the table. + * @param items The items to process into a series of tables. + * @param rowHeaders The keys and the means to process items in order to get + * key-value pairs. + * + * @return The parent element provided as parameter. */ - private void addMessage(StringBuilder html, String message) { - html.append("

") - .append(message) - .append("


"); //NON-NLS + private static Element appendVerticalEntryTables(Element parent, List items, List> rowHeaders) { + boolean isFirst = true; + for (T item : items) { + if (item == null) { + continue; + } + + List> tableData = rowHeaders.stream() + .map(row -> Arrays.asList(row.getItemName(), row.retrieveValue(item))) + .collect(Collectors.toList()); + + Element childTable = appendTable(parent, 2, tableData, null); + childTable.attr("class", VERTICAL_TABLE_CLASSNAME); + + if (isFirst) { + isFirst = false; + } else { + childTable.attr("style", String.format("margin-top: %dpx;", DEFAULT_TABLE_SPACING)); + } + } + + return parent; } /** - * Add a data table containing information about a tag. + * Appends a generic table to the parent element. * - * @param html The HTML text to add the table to. - * @param tag The tag whose information will be used to populate the table. + * @param parent The parent element that will have a table appended + * to it. + * @param columnNumber The number of columns to append. + * @param content The content in content.get(row).get(column) format. + * @param columnHeaders The column headers or null if no column headers + * should be created. + * + * @return The created table. */ - @NbBundle.Messages({ - "AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:", - "AnnotationsContentViewer.tagEntryDataLabel.tagUser=Tag User:", - "AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:" - }) - private void addTagEntry(StringBuilder html, Tag tag) { - startTable(html); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(), tag.getName().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), tag.getUserName()); - addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), formatHtmlString(tag.getComment())); - endTable(html); + private static Element appendTable(Element parent, int columnNumber, List> content, List columnHeaders) { + Element table = parent.appendElement("table"); + if (columnHeaders != null && !columnHeaders.isEmpty()) { + Element header = table.appendElement("thead"); + appendRow(header, columnHeaders, columnNumber, true); + } + Element tableBody = table.appendElement("tbody"); + + content.forEach((rowData) -> appendRow(tableBody, rowData, columnNumber, false)); + return table; } /** - * Add a data table containing information about a correlation attribute - * instance in the Central Repository. + * Appends a row to the parent element (should be thead or tbody). * - * @param html The HTML text to add the table to. - * @param attributeInstance The attribute instance whose information will be - * used to populate the table. + * @param rowParent The parent table element. + * @param data The data to place in columns within the table. + * @param columnNumber The number of columns to append. + * @param isHeader Whether or not this should have header cells ('th') + * instead of regular cells ('td'). + * + * @return The row created. */ - @NbBundle.Messages({ - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:", - "AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:" - }) - private void addCentralRepositoryEntry(StringBuilder html, CorrelationAttributeInstance attributeInstance) { - startTable(html); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(), attributeInstance.getCorrelationCase().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_type(), attributeInstance.getCorrelationType().getDisplayName()); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), formatHtmlString(attributeInstance.getComment())); - addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), attributeInstance.getFilePath()); - endTable(html); + private static Element appendRow(Element rowParent, List data, int columnNumber, boolean isHeader) { + String cellType = isHeader ? "th" : "td"; + Element row = rowParent.appendElement("tr"); + for (int i = 0; i < columnNumber; i++) { + Element cell = row.appendElement(cellType); + if (data != null && i < data.size()) { + cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i)); + } + } + return row; } /** - * Start a data table. + * Appends a new section with a section header to the parent element. * - * @param html The HTML text to add the table to. + * @param parent The element to append this section to. + * @param headerText The text for the section. + * + * @return The div for the new section. */ - private void startTable(StringBuilder html) { - html.append(""); //NON-NLS + private static Element appendSection(Element parent, String headerText) { + Element sectionDiv = parent.appendElement("div"); + sectionDiv.attr("class", SECTION_CLASSNAME); + Element header = sectionDiv.appendElement("h1"); + header.text(headerText); + header.attr("class", HEADER_CLASSNAME); + return sectionDiv; } /** - * Add a data row to a table. + * Appends a new subsection with a subsection header to the parent element. * - * @param html The HTML text to add the row to. - * @param key The key for the left column of the data row. - * @param value The value for the right column of the data row. + * @param parent The element to append this subsection to. + * @param headerText The text for the subsection. + * + * @return The div for the new subsection. */ - private void addRow(StringBuilder html, String key, String value) { - html.append(""); //NON-NLS + private static Element appendSubsection(Element parent, String headerText) { + Element subsectionDiv = parent.appendElement("div"); + subsectionDiv.attr("class", SUBSECTION_CLASSNAME); + Element header = subsectionDiv.appendElement("h2"); + header.text(headerText); + header.attr("class", SUBHEADER_CLASSNAME); + return subsectionDiv; } /** - * End a data table. + * Appends a message to the parent element. This is typically used in the + * event that no data exists for a certain type. * - * @param html The HTML text on which to end a table. + * @param parent The parent element that will have this message appended to + * it. + * @param message The message to append. + * + * @return The paragraph element for the new message. */ - private void endTable(StringBuilder html) { - html.append("
"); //NON-NLS - html.append(key); - html.append(""); //NON-NLS - html.append(value); - html.append("


"); //NON-NLS - } - - /** - * End a data section. - * - * @param html The HTML text on which to end a section. - */ - private void endSection(StringBuilder html) { - html.append("
"); //NON-NLS - } - - /** - * Apply escape sequence to special characters. Line feed and carriage - * return character combinations will be converted to HTML line breaks. - * - * @param text The text to format. - * - * @return The formatted text. - */ - private String formatHtmlString(String text) { - String formattedString = StringEscapeUtils.escapeHtml4(text); - return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "
"); + private static Element appendMessage(Element parent, String message) { + Element messageEl = parent.appendElement("p"); + messageEl.text(message); + messageEl.attr("class", MESSAGE_CLASSNAME); + return messageEl; } /** @@ -462,6 +793,6 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data @Override public void resetComponent() { - setText(""); + jTextPane1.setText(EMPTY_HTML); } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index fd9d2d345e..a0526646bb 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -15,13 +15,22 @@ # governing permissions and limitations under the License. # +AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case: AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment: AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path: AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type: +AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment +AnnotationsContentViewer.fileHitEntry.comment=Comment: +AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments +AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments +AnnotationsContentViewer.fileHitEntry.setName=Set Name: +AnnotationsContentViewer.onEmpty=No annotations were found for this particular item. +AnnotationsContentViewer.sourceFile.title=Source File +AnnotationsContentViewer.tagEntry.title=Tags AnnotationsContentViewer.tagEntryDataLabel.comment=Comment: AnnotationsContentViewer.tagEntryDataLabel.tag=Tag: -AnnotationsContentViewer.tagEntryDataLabel.tagUser=Tag User: +AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner: AnnotationsContentViewer.title=Annotations AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content. ApplicationContentViewer.title=Application diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form index 37ea0f00fa..eb1f729611 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.form @@ -45,40 +45,16 @@ - - + - - - - - + - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java index 660f67a516..d77ca887d8 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/DefaultArtifactContentViewer.java @@ -199,8 +199,6 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements rightClickMenu = new javax.swing.JPopupMenu(); copyMenuItem = new javax.swing.JMenuItem(); selectAllMenuItem = new javax.swing.JMenuItem(); - jScrollPane1 = new javax.swing.JScrollPane(); - jPanel1 = new javax.swing.JPanel(); resultsTableScrollPane = new javax.swing.JScrollPane(); copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DefaultArtifactContentViewer.class, "DefaultArtifactContentViewer.copyMenuItem.text")); // NOI18N @@ -211,13 +209,6 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements setPreferredSize(new java.awt.Dimension(100, 58)); - jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); - - jPanel1.setPreferredSize(new java.awt.Dimension(620, 58)); - jPanel1.setLayout(new java.awt.GridBagLayout()); - jScrollPane1.setViewportView(jPanel1); - resultsTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); resultsTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); resultsTableScrollPane.setPreferredSize(new java.awt.Dimension(620, 34)); @@ -226,22 +217,16 @@ public class DefaultArtifactContentViewer extends javax.swing.JPanel implements this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(resultsTableScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 58, Short.MAX_VALUE) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem copyMenuItem; - private javax.swing.JPanel jPanel1; - private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane resultsTableScrollPane; private javax.swing.JPopupMenu rightClickMenu; private javax.swing.JMenuItem selectAllMenuItem; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java index 99eecf50d2..73d1a02314 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddHashValuesToDatabaseProgressDialog.java @@ -30,6 +30,7 @@ import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingWorker; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.datamodel.HashEntry; @@ -46,7 +47,14 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { private final HashDb hashDb; private final List hashes; private final List invalidHashes; - private final Pattern md5Pattern; + + // Matches hash with optional comma separated comment. + private static final Pattern HASH_LINE_PATTERN = Pattern.compile("^([a-fA-F0-9]{32})(,(.*))?$"); + // The regex group for the hash. + private static final int HASH_GROUP = 1; + // The regex group for the comment. + private static final int COMMENT_GROUP = 3; + private String errorTitle; private String errorMessage; private final String text; @@ -64,7 +72,6 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { display(parent); this.hashes = new ArrayList<>(); this.invalidHashes = new ArrayList<>(); - this.md5Pattern = Pattern.compile("^([a-fA-F0-9]{32})"); // NON-NLS this.parentRef = parent; this.hashDb = hashDb; this.text = text; @@ -161,17 +168,15 @@ public class AddHashValuesToDatabaseProgressDialog extends javax.swing.JDialog { // These entries may be of or format for (String hashEntry : linesInTextArea) { hashEntry = hashEntry.trim(); - Matcher m = md5Pattern.matcher(hashEntry); + Matcher m = HASH_LINE_PATTERN.matcher(hashEntry); if (m.find()) { - // Is there any text left on this line? If so, treat it as a comment. - String comment = hashEntry.substring(m.end()).trim(); - if (comment.length() > 0) { - comment = (comment.charAt(0) == ',') ? comment.substring(1) : comment; - hashes.add(new HashEntry(null, m.group(0), null, null, comment)); - } else { - // more information can be added to the HashEntry - sha-1, sha-512, comment - hashes.add(new HashEntry(null, m.group(0), null, null, null)); - } + String hash = m.group(HASH_GROUP); + + // if there was a match and the match is not empty, assign to comment + String comment = StringUtils.isNotBlank(m.group(COMMENT_GROUP)) ? + m.group(COMMENT_GROUP).trim() : null; + + hashes.add(new HashEntry(null, hash, null, null, comment)); } else { if (!hashEntry.isEmpty()) { invalidHashes.add(hashEntry); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties index d9fb142cf4..e7ed2dedd7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties @@ -94,8 +94,6 @@ HashDbIngestModule.fileReadErrorMsg=Read Error: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0} ({1}). HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error: {0} HashDbIngestModule.settingKnownBadStateErr=Error encountered while setting notable state for {0}. -HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. -HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. HashDbIngestModule.postToBB.fileName=File Name HashDbIngestModule.postToBB.md5Hash=MD5 Hash HashDbIngestModule.postToBB.hashsetName=Hash Set Name @@ -145,8 +143,6 @@ HashDbManager.hashDbFileExistsExceptionMsg=A file already exists at\n{0} HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash set at\n{0}\nhas already been created or imported. HashDbManager.illegalHashDbFileNameExtensionMsg=The hash set file name must have a .{0} extension. HashDbManager.moduleErr=Module Error -HashDbManager.knownBad.text=Notable -HashDbManager.known.text=Known HashDbManager.fileNameExtensionFilter.title=Hash Set File HashDbSearchAction.dlgMsg.title=File Search by MD5 Hash HashDbSearchAction.getName.text=Hash Search @@ -162,8 +158,6 @@ AddContentToHashDbAction.multipleSelectionNameEmpty=Add Files to Hash Set (Empty HashDbManager.ingestRunningExceptionMsg=Ingest is ongoing; this service will be unavailable until it finishes. HashDbManager.saveErrorExceptionMsg=Error saving hash configuration HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash set is selected -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash sets to use: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets to use: AddContentToHashDbAction.addFilesToHashSet.files=files AddContentToHashDbAction.addFilesToHashSet.file=file HashDbManager.errCreatingIndex.title=Error creating index @@ -241,3 +235,7 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=Copy hash set into user configuration folder HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=In Live Triage situations, this option ensures that path to the hash set will be valid HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=Select hash sets to use: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=No Change +HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText= +HashDbImportDatabaseDialog.noChangeRadioButton.text=No Change diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED index 44057d0016..853a23d0db 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties-MERGED @@ -9,6 +9,7 @@ HashDbImportDatabaseDialog.missingOrg=An organization must be selected HashDbImportDatabaseDialog.missingVersion=A version must be entered HashDbImportDatabaseDialog.mustEnterHashSetNameMsg=A hash set name must be entered. HashDbImportDatabaseDialog.populateOrgsError.message=Failure loading organizations. +HashDbIngestModule.complete.noChangesFound=No Change items found: # {0} - File name HashDbIngestModule.dialogTitle.errorFindingArtifacts=Error Finding Artifacts: {0} # {0} - File name @@ -16,10 +17,21 @@ HashDbIngestModule.errorMessage.lookingForFileArtifacts=Error encountered while HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search. HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed. HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed. +# {0} - fileName +HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. +# {0} - fileName +HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. +# {0} - fileName +HashDbIngestModule.lookingUpNoChangeHashValueErr=Error encountered while looking up no change hash value for {0}. +HashDbIngestModule.noChangeFileSearchWillNotExecuteWarn='No Change' file search will not be executed. +HashDbIngestModule.noChangeHashDbSetMsg=No 'No Change' hash set. HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set. HashDbIngestModule.noKnownHashDbSetMsg=No known hash set. HashDbManager.CentralRepoHashDb.orgError=Error loading organization HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets +HashDbManager.known.text=Known +HashDbManager.knownBad.text=Notable +HashDbManager.noChange.text=No Change # {0} - hash set name HashDbManager.noDbPath.message=Couldn't get valid hash set path for: {0} HashDbSearchAction.noOpenCase.errMsg=No open case available. @@ -49,7 +61,10 @@ ImportCentralRepoDbProgressDialog.errorParsingFile.message=Error parsing hash se ImportCentralRepoDbProgressDialog.linesProcessed.message=\ hashes processed ImportCentralRepoDbProgressDialog.title.text=Central Repository Import Progress OpenIDE-Module-Display-Category=Ingest Module -OpenIDE-Module-Long-Description=Hash Set ingest module. \n\nThe ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\nThe module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. +OpenIDE-Module-Long-Description=\ + Hash Set ingest module. \n\n\ + The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\ + The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration. OpenIDE-Module-Name=HashDatabases OptionsCategory_Name_HashDatabase=Hash Sets OptionsCategory_Keywords_HashDatabase=Hash Sets @@ -141,8 +156,6 @@ HashDbIngestModule.fileReadErrorMsg=Read Error: {0} HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0} ({1}). HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error: {0} HashDbIngestModule.settingKnownBadStateErr=Error encountered while setting notable state for {0}. -HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}. -HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}. HashDbIngestModule.postToBB.fileName=File Name HashDbIngestModule.postToBB.md5Hash=MD5 Hash HashDbIngestModule.postToBB.hashsetName=Hash Set Name @@ -178,7 +191,10 @@ HashDbSearchThread.name.searching=Searching HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found. ModalNoButtons.indexingDbsTitle=Indexing hash sets ModalNoButtons.indexingDbTitle=Indexing hash set -ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \nThe generated index will be left unusable. If you choose to continue,\nplease delete the corresponding -md5.idx file in the hash folder.\nExit indexing? +ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\ +The generated index will be left unusable. If you choose to continue,\n\ + please delete the corresponding -md5.idx file in the hash folder.\n\ + Exit indexing? ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0} @@ -189,8 +205,6 @@ HashDbManager.hashDbFileExistsExceptionMsg=A file already exists at\n{0} HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash set at\n{0}\nhas already been created or imported. HashDbManager.illegalHashDbFileNameExtensionMsg=The hash set file name must have a .{0} extension. HashDbManager.moduleErr=Module Error -HashDbManager.knownBad.text=Notable -HashDbManager.known.text=Known HashDbManager.fileNameExtensionFilter.title=Hash Set File HashDbSearchAction.dlgMsg.title=File Search by MD5 Hash HashDbSearchAction.getName.text=Hash Search @@ -205,13 +219,7 @@ AddContentToHashDbAction.singleSelectionNameEmpty=Add File to Hash Set (Empty Fi AddContentToHashDbAction.multipleSelectionNameEmpty=Add Files to Hash Set (Empty File) HashDbManager.ingestRunningExceptionMsg=Ingest is ongoing; this service will be unavailable until it finishes. HashDbManager.saveErrorExceptionMsg=Error saving hash configuration -HashLookupSettingsPanel.jButton3.text=Import Hash Set -HashLookupSettingsPanel.jLabel6.text=Type: -HashLookupSettingsPanel.jLabel4.text=Location: -HashLookupSettingsPanel.jLabel2.text=Name: HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash set is selected -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash sets to use: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets to use: AddContentToHashDbAction.addFilesToHashSet.files=files AddContentToHashDbAction.addFilesToHashSet.file=file HashDbManager.errCreatingIndex.title=Error creating index @@ -289,3 +297,7 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=Copy hash set into user configuration folder HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=In Live Triage situations, this option ensures that path to the hash set will be valid HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=Select hash sets to use: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=No Change +HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText= +HashDbImportDatabaseDialog.noChangeRadioButton.text=No Change diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties index 3f5ea43a61..c800aca094 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/Bundle_ja.properties @@ -216,8 +216,6 @@ HashLookupSettingsPanel.jLabel6.text=\u30bf\u30a4\u30d7: HashLookupSettingsPanel.jLabel4.text=\u5834\u6240: HashLookupSettingsPanel.jLabel2.text=\u540d\u524d: HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u304c\u9078\u629e\u3055\u308c\u3066\u3044\u306a\u3044\u5834\u5408\u3067\u3082MD5\u3092\u8a08\u7b97 -HashLookupModuleSettingsPanel.knownHashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u65e2\u77e5\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: -HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u9855\u8457\u306a\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: AddContentToHashDbAction.addFilesToHashSet.files=\u30d5\u30a1\u30a4\u30eb AddContentToHashDbAction.addFilesToHashSet.file=\u30d5\u30a1\u30a4\u30eb HashDbManager.errCreatingIndex.title=\u7d22\u5f15\u306e\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f @@ -295,3 +293,6 @@ AddHashValuesToDatabaseDialog.okButton.text_2=OK HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u30e6\u30fc\u30b6\u30fc\u69cb\u6210\u30d5\u30a1\u30a4\u30eb\u306b\u30b3\u30d4\u30fc HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=\u30e9\u30a4\u30d6\u30c8\u30ea\u30a2\u30fc\u30b8\u306e\u72b6\u6cc1\u3067\u306f\u3001\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u3088\u3063\u3066\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3078\u306e\u30d1\u30b9\u304c\u6709\u52b9\u3067\u3042\u308b\u3053\u3068\u304c\u4fdd\u8a3c\u3055\u308c\u307e\u3059\u3002 HashLookupSettingsPanel.indexPathLabel.text= +HashLookupModuleSettingsPanel.hashDbsLabel.text=\u4f7f\u7528\u3059\u308b\u65e2\u77e5\u30cf\u30c3\u30b7\u30e5\u30bb\u30c3\u30c8\u3092\u9078\u629e: +HashDbCreateDatabaseDialog.noChangeRadioButton.text=\u9855\u8457 +HashDbImportDatabaseDialog.noChangeRadioButton.text=\u9855\u8457 diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form index 7c74b4b583..b274f93d3c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.form @@ -78,6 +78,7 @@ + @@ -125,19 +126,21 @@ - + - - - - + + + + + + @@ -313,5 +316,18 @@ + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 5470b385fb..63a8c84e93 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -198,6 +198,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { lbOrg = new javax.swing.JLabel(); orgComboBox = new javax.swing.JComboBox<>(); orgButton = new javax.swing.JButton(); + noChangeRadioButton = new javax.swing.JRadioButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -292,6 +293,14 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { } }); + buttonGroup1.add(noChangeRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(noChangeRadioButton, org.openide.util.NbBundle.getMessage(HashDbCreateDatabaseDialog.class, "HashDbCreateDatabaseDialog.noChangeRadioButton.text")); // NOI18N + noChangeRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + noChangeRadioButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -334,7 +343,8 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { .addGap(32, 32, 32) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(knownRadioButton) - .addComponent(knownBadRadioButton))) + .addComponent(knownBadRadioButton) + .addComponent(noChangeRadioButton))) .addGroup(layout.createSequentialGroup() .addGap(12, 12, 12) .addComponent(jLabel2)) @@ -374,16 +384,18 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { .addComponent(knownRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(knownBadRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(sendIngestMessagesCheckbox) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) - .addComponent(okButton)))) + .addComponent(okButton))) + .addGroup(layout.createSequentialGroup() + .addComponent(noChangeRadioButton) + .addGap(24, 24, 24) + .addComponent(sendIngestMessagesCheckbox) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); @@ -391,13 +403,13 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { }// //GEN-END:initComponents private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(false); - sendIngestMessagesCheckbox.setEnabled(false); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN.isInboxMessagesAllowed()); }//GEN-LAST:event_knownRadioButtonActionPerformed private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(true); - sendIngestMessagesCheckbox.setEnabled(true); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN_BAD.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN_BAD.isInboxMessagesAllowed()); }//GEN-LAST:event_knownBadRadioButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed @@ -476,15 +488,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { } KnownFilesType type; - TskData.FileKnown fileKnown; + if (knownRadioButton.isSelected()) { type = KnownFilesType.KNOWN; - fileKnown = TskData.FileKnown.KNOWN; + } else if (noChangeRadioButton.isSelected()) { + type = KnownFilesType.NO_CHANGE; } else { type = KnownFilesType.KNOWN_BAD; - fileKnown = TskData.FileKnown.BAD; } + TskData.FileKnown fileKnown = type.getFileKnown(); + String errorMessage = NbBundle .getMessage(this.getClass(), "HashDbCreateDatabaseDialog.errMsg.hashDbCreationErr"); @@ -586,6 +600,11 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { enableComponents(); }//GEN-LAST:event_centralRepoRadioButtonActionPerformed + private void noChangeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noChangeRadioButtonActionPerformed + sendIngestMessagesCheckbox.setSelected(KnownFilesType.NO_CHANGE.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.NO_CHANGE.isInboxMessagesAllowed()); + }//GEN-LAST:event_noChangeRadioButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JButton cancelButton; @@ -600,6 +619,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private javax.swing.JRadioButton knownBadRadioButton; private javax.swing.JRadioButton knownRadioButton; private javax.swing.JLabel lbOrg; + private javax.swing.JRadioButton noChangeRadioButton; private javax.swing.JButton okButton; private javax.swing.JButton orgButton; private javax.swing.JComboBox orgComboBox; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form index e285e99a12..13ed7fbf57 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.form @@ -29,7 +29,7 @@ - + @@ -54,10 +54,6 @@ - - - - @@ -76,7 +72,16 @@ - + + + + + + + + + + @@ -86,16 +91,17 @@ - - + + + @@ -113,52 +119,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -367,5 +375,21 @@ + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index a08f324a44..c854cb165d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -182,6 +182,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { centralRepoRadioButton = new javax.swing.JRadioButton(); jLabel4 = new javax.swing.JLabel(); saveInUserConfigFolderCheckbox = new javax.swing.JCheckBox(); + noChangeRadioButton = new javax.swing.JRadioButton(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); @@ -291,6 +292,15 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { org.openide.awt.Mnemonics.setLocalizedText(saveInUserConfigFolderCheckbox, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text")); // NOI18N saveInUserConfigFolderCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText")); // NOI18N + buttonGroup1.add(noChangeRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(noChangeRadioButton, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.noChangeRadioButton.text")); // NOI18N + noChangeRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.noChangeRadioButton.toolTipText")); // NOI18N + noChangeRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + noChangeRadioButtonActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( @@ -315,9 +325,6 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(openButton)))) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(sendIngestMessagesCheckbox) - .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addComponent(lbOrg) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -331,7 +338,13 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addGap(40, 40, 40) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(versionTextField) - .addComponent(hashSetNameTextField))) + .addComponent(hashSetNameTextField)))) + .addGap(81, 81, 81)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(sendIngestMessagesCheckbox) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addComponent(okButton))) @@ -339,14 +352,15 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(cancelButton)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(saveInUserConfigFolderCheckbox) .addComponent(jLabel2) - .addComponent(readOnlyCheckbox) .addGroup(layout.createSequentialGroup() .addGap(19, 19, 19) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(knownRadioButton) - .addComponent(knownBadRadioButton)))) + .addComponent(knownBadRadioButton) + .addComponent(noChangeRadioButton))) + .addComponent(saveInUserConfigFolderCheckbox) + .addComponent(readOnlyCheckbox)) .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); @@ -361,44 +375,46 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { .addComponent(databasePathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel3) .addComponent(openButton)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fileTypeRadioButton) + .addComponent(centralRepoRadioButton) + .addComponent(jLabel4)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbVersion) + .addComponent(versionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(orgButton) + .addComponent(orgComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbOrg)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(knownBadRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(noChangeRadioButton) + .addGap(5, 5, 5) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(fileTypeRadioButton) - .addComponent(centralRepoRadioButton) - .addComponent(jLabel4)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(hashSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbVersion) - .addComponent(versionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(orgButton) - .addComponent(orgComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbOrg)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(knownRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(knownBadRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(readOnlyCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(sendIngestMessagesCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(saveInUserConfigFolderCheckbox) - .addGap(0, 29, Short.MAX_VALUE)) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) + .addGap(81, 81, 81) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cancelButton) .addComponent(okButton)))) - .addContainerGap()) + .addGap(18, 18, 18)) ); pack(); @@ -436,13 +452,13 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { }//GEN-LAST:event_openButtonActionPerformed private void knownRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(false); - sendIngestMessagesCheckbox.setEnabled(false); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN.isInboxMessagesAllowed()); }//GEN-LAST:event_knownRadioButtonActionPerformed private void knownBadRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownBadRadioButtonActionPerformed - sendIngestMessagesCheckbox.setSelected(true); - sendIngestMessagesCheckbox.setEnabled(true); + sendIngestMessagesCheckbox.setSelected(KnownFilesType.KNOWN_BAD.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.KNOWN_BAD.isInboxMessagesAllowed()); }//GEN-LAST:event_knownBadRadioButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed @@ -531,6 +547,8 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { KnownFilesType type; if (knownRadioButton.isSelected()) { type = KnownFilesType.KNOWN; + } else if (noChangeRadioButton.isSelected()) { + type = KnownFilesType.NO_CHANGE; } else { type = KnownFilesType.KNOWN_BAD; } @@ -629,6 +647,11 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { enableComponents(); }//GEN-LAST:event_readOnlyCheckboxActionPerformed + private void noChangeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_noChangeRadioButtonActionPerformed + sendIngestMessagesCheckbox.setSelected(KnownFilesType.NO_CHANGE.isDefaultInboxMessages()); + sendIngestMessagesCheckbox.setEnabled(KnownFilesType.NO_CHANGE.isInboxMessagesAllowed()); + }//GEN-LAST:event_noChangeRadioButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JButton cancelButton; @@ -644,6 +667,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { private javax.swing.JRadioButton knownRadioButton; private javax.swing.JLabel lbOrg; private javax.swing.JLabel lbVersion; + private javax.swing.JRadioButton noChangeRadioButton; private javax.swing.JButton okButton; private javax.swing.JButton openButton; private javax.swing.JButton orgButton; diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index a562ab2a22..873f891ad0 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -24,7 +24,9 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; import java.util.logging.Level; +import java.util.stream.Stream; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; @@ -57,12 +59,26 @@ import org.sleuthkit.datamodel.TskException; @Messages({ "HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.", "HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.", + "HashDbIngestModule.noChangeHashDbSetMsg=No 'No Change' hash set.", + "HashDbIngestModule.noChangeFileSearchWillNotExecuteWarn='No Change' file search will not be executed.", "HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.", - "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed." -}) + "HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed.", + "# {0} - fileName", "HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking up notable hash value for {0}.", + "# {0} - fileName", "HashDbIngestModule.lookingUpNoChangeHashValueErr=Error encountered while looking up no change hash value for {0}.", + "# {0} - fileName", "HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}.",}) public class HashDbIngestModule implements FileIngestModule { private static final Logger logger = Logger.getLogger(HashDbIngestModule.class.getName()); + + private final Function knownBadLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpKnownBadHashValueErr(file.getName()); + + private final Function noChangeLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpNoChangeHashValueErr(file.getName()); + + private final Function knownLookupError + = (file) -> Bundle.HashDbIngestModule_lookingUpKnownHashValueErr(file.getName()); + private static final int MAX_COMMENT_SIZE = 500; private final IngestServices services = IngestServices.getInstance(); private final SleuthkitCase skCase; @@ -70,6 +86,7 @@ public class HashDbIngestModule implements FileIngestModule { private final HashLookupModuleSettings settings; private final List knownBadHashSets = new ArrayList<>(); private final List knownHashSets = new ArrayList<>(); + private final List noChangeHashSets = new ArrayList<>(); private long jobId; private static final HashMap totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); @@ -81,6 +98,7 @@ public class HashDbIngestModule implements FileIngestModule { private static class IngestJobTotals { private final AtomicLong totalKnownBadCount = new AtomicLong(0); + private final AtomicLong totalNoChangeCount = new AtomicLong(0); private final AtomicLong totalCalctime = new AtomicLong(0); private final AtomicLong totalLookuptime = new AtomicLong(0); } @@ -114,8 +132,8 @@ public class HashDbIngestModule implements FileIngestModule { if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) { throw new IngestModuleException("Could not load all hash sets"); } - updateEnabledHashSets(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets); - updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets); + + initializeHashsets(hashDbManager.getAllHashSets()); if (refCounter.incrementAndGet(jobId) == 1) { // initialize job totals @@ -128,6 +146,13 @@ public class HashDbIngestModule implements FileIngestModule { Bundle.HashDbIngestModule_noKnownBadHashDbSetMsg(), Bundle.HashDbIngestModule_knownBadFileSearchWillNotExecuteWarn())); } + + if (noChangeHashSets.isEmpty()) { + services.postMessage(IngestMessage.createWarningMessage( + HashLookupModuleFactory.getModuleName(), + Bundle.HashDbIngestModule_noChangeHashDbSetMsg(), + Bundle.HashDbIngestModule_noChangeFileSearchWillNotExecuteWarn())); + } if (knownHashSets.isEmpty()) { services.postMessage(IngestMessage.createWarningMessage( @@ -139,18 +164,29 @@ public class HashDbIngestModule implements FileIngestModule { } /** - * Cycle through list of hashsets and return the subset that is enabled. + * Cycle through list of hashsets and place each HashDB in the appropriate + * list based on KnownFilesType. * - * @param allHashSets List of all hashsets from DB manager - * @param enabledHashSets List of enabled ones to return. + * @param allHashSets List of all hashsets from DB manager */ - private void updateEnabledHashSets(List allHashSets, List enabledHashSets) { - enabledHashSets.clear(); + private void initializeHashsets(List allHashSets) { for (HashDb db : allHashSets) { if (settings.isHashSetEnabled(db)) { try { if (db.isValid()) { - enabledHashSets.add(db); + switch (db.getKnownFilesType()) { + case KNOWN: + knownHashSets.add(db); + break; + case KNOWN_BAD: + knownBadHashSets.add(db); + break; + case NO_CHANGE: + noChangeHashSets.add(db); + break; + default: + throw new TskCoreException("Unknown KnownFilesType: " + db.getKnownFilesType()); + } } } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName() + " hash set", ex); //NON-NLS @@ -174,128 +210,37 @@ public class HashDbIngestModule implements FileIngestModule { return ProcessResult.ERROR; } - // Skip unallocated space files. - if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { - return ProcessResult.OK; - } - - /* - * Skip directories. One reason for this is because we won't accurately - * calculate hashes of NTFS directories that have content that spans the - * IDX_ROOT and IDX_ALLOC artifacts. So we disable that until a solution - * for it is developed. - */ - if (file.isDir()) { - return ProcessResult.OK; - } - - // bail out if we have no hashes set - if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) { + if (shouldSkip(file)) { return ProcessResult.OK; } // Safely get a reference to the totalsForIngestJobs object IngestJobTotals totals = getTotalsForIngestJobs(jobId); - // calc hash value - String name = file.getName(); - long fileId = file.getId(); - String md5Hash = file.getMd5Hash(); - if (md5Hash == null || md5Hash.isEmpty()) { - try { - TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); - long calcstart = System.currentTimeMillis(); - md5Hash = HashUtility.calculateMd5Hash(file); - if (file.getSize() > 0) { - // Surprisingly, the hash calculation does not seem to be correlated that - // strongly with file size until the files get large. - // Only normalize if the file size is greater than ~1MB. - if (file.getSize() < 1000000) { - HealthMonitor.submitTimingMetric(metric); - } else { - // In testing, this normalization gave reasonable resuls - HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); - } - } - file.setMd5Hash(md5Hash); - long delta = (System.currentTimeMillis() - calcstart); - totals.totalCalctime.addAndGet(delta); - - } catch (IOException ex) { - logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr", - file.getParentPath() + file.getName(), - file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)?"Allocated File" : "Deleted File"))); - return ProcessResult.ERROR; - } + // calc hash value + String md5Hash = getHash(file, totals); + if (md5Hash == null) { + return ProcessResult.ERROR; } - // look up in notable first - boolean foundBad = false; + // the processing result of handling this file ProcessResult ret = ProcessResult.OK; - for (HashDb db : knownBadHashSets) { - try { - long lookupstart = System.currentTimeMillis(); - HashHitInfo hashInfo = db.lookupMD5(file); - if (null != hashInfo) { - foundBad = true; - totals.totalKnownBadCount.incrementAndGet(); - file.setKnown(TskData.FileKnown.BAD); + // look up in notable first + FindInHashsetsResult knownBadResult = findInHashsets(file, totals.totalKnownBadCount, + totals.totalLookuptime, knownBadHashSets, TskData.FileKnown.BAD, knownBadLookupError); - String hashSetName = db.getDisplayName(); + boolean foundBad = knownBadResult.isFound(); + if (knownBadResult.isError()) { + ret = ProcessResult.ERROR; + } - String comment = ""; - ArrayList comments = hashInfo.getComments(); - int i = 0; - for (String c : comments) { - if (++i > 1) { - comment += " "; - } - comment += c; - if (comment.length() > MAX_COMMENT_SIZE) { - comment = comment.substring(0, MAX_COMMENT_SIZE) + "..."; - break; - } - } + // look up no change items next + FindInHashsetsResult noChangeResult = findInHashsets(file, totals.totalNoChangeCount, + totals.totalLookuptime, noChangeHashSets, TskData.FileKnown.UNKNOWN, noChangeLookupError); - /* - * We have a match. Now create an artifact if it is - * determined that one hasn't been created yet. - */ - List attributesList = new ArrayList<>(); - attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, HashLookupModuleFactory.getModuleName(), hashSetName)); - try { - org.sleuthkit.datamodel.Blackboard tskBlackboard = skCase.getBlackboard(); - if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) == false) { - postHashSetHitToBlackboard(file, md5Hash, hashSetName, comment, db.getSendIngestMessages()); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, String.format( - "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(name), - Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(name))); - ret = ProcessResult.ERROR; - } - } - long delta = (System.currentTimeMillis() - lookupstart); - totals.totalLookuptime.addAndGet(delta); - - } catch (TskException ex) { - logger.log(Level.WARNING, String.format( - "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownBadHashValueErr", name))); - ret = ProcessResult.ERROR; - } + if (noChangeResult.isError()) { + ret = ProcessResult.ERROR; } // If the file is not in the notable sets, search for it in the known sets. @@ -313,12 +258,7 @@ public class HashDbIngestModule implements FileIngestModule { totals.totalLookuptime.addAndGet(delta); } catch (TskException ex) { - logger.log(Level.WARNING, String.format( - "Couldn't lookup known hash for file '%s' (id=%d) - see sleuthkit log for details", name, fileId), ex); //NON-NLS - services.postMessage(IngestMessage.createErrorMessage( - HashLookupModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", name), - NbBundle.getMessage(this.getClass(), "HashDbIngestModule.lookingUpKnownHashValueErr", name))); + reportLookupError(ex, file, knownLookupError); ret = ProcessResult.ERROR; } } @@ -327,6 +267,245 @@ public class HashDbIngestModule implements FileIngestModule { return ret; } + /** + * Returns true if this file should be skipped for processing. + * + * @param file The file to potentially skip. + * + * @return True if this file should be skipped. + */ + private boolean shouldSkip(AbstractFile file) { + // Skip unallocated space files. + if ((file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + || file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + return true; + } + + /* + * Skip directories. One reason for this is because we won't accurately + * calculate hashes of NTFS directories that have content that spans the + * IDX_ROOT and IDX_ALLOC artifacts. So we disable that until a solution + * for it is developed. + */ + if (file.isDir()) { + return true; + } + + // bail out if we have no hashes set + if ((knownHashSets.isEmpty()) && (knownBadHashSets.isEmpty()) && (!settings.shouldCalculateHashes())) { + return true; + } + + return false; + } + + /** + * Reports an error when an issue is encountered looking up a file. + * + * @param ex The exception thrown in the error. + * @param file The file for which this error applies. + * @param lookupErrorMessage The function that generates an error message + * specific to which piece of the ingest + * processing failed. + */ + private void reportLookupError(TskException ex, AbstractFile file, Function lookupErrorMessage) { + logger.log(Level.WARNING, String.format( + "Couldn't lookup notable hash for file '%s' (id=%d) - see sleuthkit log for details", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.hashLookupErrorMsg", file.getName()), + lookupErrorMessage.apply(file))); + } + + /** + * The result of attempting to find a file in a list of HashDB objects. + */ + private static class FindInHashsetsResult { + + private final boolean found; + private final boolean error; + + FindInHashsetsResult(boolean found, boolean error) { + this.found = found; + this.error = error; + } + + /** + * Returns true if the file was found in the HashDB. + * + * @return True if the file was found in the HashDB. + */ + boolean isFound() { + return found; + } + + /** + * Returns true if there was an error in the process of finding a file + * in a HashDB. + * + * @return True if there was an error in the process of finding a file + * in a HashDB. + */ + boolean isError() { + return error; + } + } + + /** + * Attempts to find an abstract file in a list of HashDB objects. + * + * @param file The file to find. + * @param totalCount The total cound of files found in this type + * @param totalLookupTime The counter tracking the total amount of run + * time for this operation. + * @param hashSets The HashDB objects to cycle through looking for + * a hash hit. + * @param statusIfFound The FileKnown status to set on the file if the + * file is found in the hashSets. + * @param lookupErrorMessage The function that generates a message should + * there be an error in looking up the file in the + * hashSets. + * + * @return Whether or not the file was found and whether or not there was an + * error during the operation. + */ + private FindInHashsetsResult findInHashsets(AbstractFile file, AtomicLong totalCount, AtomicLong totalLookupTime, + List hashSets, TskData.FileKnown statusIfFound, Function lookupErrorMessage) { + + boolean found = false; + boolean wasError = false; + for (HashDb db : hashSets) { + try { + long lookupstart = System.currentTimeMillis(); + HashHitInfo hashInfo = db.lookupMD5(file); + if (null != hashInfo) { + found = true; + + totalCount.incrementAndGet(); + file.setKnown(statusIfFound); + String hashSetName = db.getDisplayName(); + String comment = generateComment(hashInfo); + if (!createArtifactIfNotExists(hashSetName, file, comment, db)) { + wasError = true; + } + } + long delta = (System.currentTimeMillis() - lookupstart); + totalLookupTime.addAndGet(delta); + + } catch (TskException ex) { + reportLookupError(ex, file, lookupErrorMessage); + wasError = true; + } + } + + return new FindInHashsetsResult(found, wasError); + } + + /** + * Generates a formatted comment. + * + * @param hashInfo The HashHitInfo. + * + * @return The formatted comment. + */ + private String generateComment(HashHitInfo hashInfo) { + String comment = ""; + ArrayList comments = hashInfo.getComments(); + int i = 0; + for (String c : comments) { + if (++i > 1) { + comment += " "; + } + comment += c; + if (comment.length() > MAX_COMMENT_SIZE) { + comment = comment.substring(0, MAX_COMMENT_SIZE) + "..."; + break; + } + } + return comment; + } + + /** + * Creates a BlackboardArtifact if artifact does not already exist. + * + * @param hashSetName The name of the hashset found. + * @param file The file that had a hash hit. + * @param comment The comment to associate with this artifact. + * @param db the database in which this file was found. + * + * @return True if the operation occurred successfully and without error. + */ + private boolean createArtifactIfNotExists(String hashSetName, AbstractFile file, String comment, HashDb db) { + /* + * We have a match. Now create an artifact if it is determined that one + * hasn't been created yet. + */ + List attributesList = new ArrayList<>(); + attributesList.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, HashLookupModuleFactory.getModuleName(), hashSetName)); + try { + Blackboard tskBlackboard = skCase.getBlackboard(); + if (tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, attributesList) == false) { + postHashSetHitToBlackboard(file, file.getMd5Hash(), hashSetName, comment, db.getSendIngestMessages()); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format( + "A problem occurred while checking for existing artifacts for file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + Bundle.HashDbIngestModule_dialogTitle_errorFindingArtifacts(file.getName()), + Bundle.HashDbIngestModule_errorMessage_lookingForFileArtifacts(file.getName()))); + return false; + } + return true; + } + + /** + * Retrieves the md5 hash for a file or generates one if no one exists on + * the file. + * + * @param file The file in order to determine the hash. + * @param totals The timing metrics for this process. + * + * @return The found or determined md5 hash or null if none could be + * determined. + */ + private String getHash(AbstractFile file, IngestJobTotals totals) { + String md5Hash = file.getMd5Hash(); + if (md5Hash != null && md5Hash.isEmpty()) { + return md5Hash; + } + + try { + TimingMetric metric = HealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); + long calcstart = System.currentTimeMillis(); + md5Hash = HashUtility.calculateMd5Hash(file); + if (file.getSize() > 0) { + // Surprisingly, the hash calculation does not seem to be correlated that + // strongly with file size until the files get large. + // Only normalize if the file size is greater than ~1MB. + if (file.getSize() < 1000000) { + HealthMonitor.submitTimingMetric(metric); + } else { + // In testing, this normalization gave reasonable resuls + HealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + } + } + file.setMd5Hash(md5Hash); + long delta = (System.currentTimeMillis() - calcstart); + totals.totalCalctime.addAndGet(delta); + return md5Hash; + } catch (IOException ex) { + logger.log(Level.WARNING, String.format("Error calculating hash of file '%s' (id=%d).", file.getName(), file.getId()), ex); //NON-NLS + services.postMessage(IngestMessage.createErrorMessage( + HashLookupModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.fileReadErrorMsg", file.getName()), + NbBundle.getMessage(this.getClass(), "HashDbIngestModule.calcHashValueErr", + file.getParentPath() + file.getName(), + file.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC) ? "Allocated File" : "Deleted File"))); + return null; + } + } + /** * Post a hash set hit to the blackboard. * @@ -413,35 +592,35 @@ public class HashDbIngestModule implements FileIngestModule { * @param knownBadHashSets The list of hash sets for "known bad" files. * @param knownHashSets The list of hash sets for "known" files. */ - private static synchronized void postSummary(long jobId, - List knownBadHashSets, List knownHashSets) { + @Messages("HashDbIngestModule.complete.noChangesFound=No Change items found:") + private static synchronized void postSummary(long jobId, List knownBadHashSets, + List noChangeHashSets, List knownHashSets) { + IngestJobTotals jobTotals = getTotalsForIngestJobs(jobId); totalsForIngestJobs.remove(jobId); - if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty())) { + if ((!knownBadHashSets.isEmpty()) || (!knownHashSets.isEmpty()) || (!noChangeHashSets.isEmpty())) { StringBuilder detailsSb = new StringBuilder(); //details - detailsSb.append(""); //NON-NLS + detailsSb.append( + "
" + + "" + + "" + + + "" + + "" + + + "\n" + + + "\n
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.knownBadsFound") + "" + jobTotals.totalKnownBadCount.get() + "
" + Bundle.HashDbIngestModule_complete_noChangesFound() + "" + jobTotals.totalNoChangeCount.get() + "
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalCalcTime") + + "" + jobTotals.totalCalctime.get() + "
" + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalLookupTime") + + "" + jobTotals.totalLookuptime.get() + "
" + - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.knownBadsFound")) - .append(""); //NON-NLS - detailsSb.append("").append(jobTotals.totalKnownBadCount.get()).append(""); //NON-NLS - - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalCalcTime")) - .append("").append(jobTotals.totalCalctime.get()).append("\n"); //NON-NLS - detailsSb.append("") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.totalLookupTime")) - .append("").append(jobTotals.totalLookuptime.get()).append("\n"); //NON-NLS - detailsSb.append(""); //NON-NLS - - detailsSb.append("

") //NON-NLS - .append(NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.databasesUsed")) - .append("

\n
    "); //NON-NLS - for (HashDb db : knownBadHashSets) { - detailsSb.append("
  • ").append(db.getHashSetName()).append("
  • \n"); //NON-NLS - } + "

    " + NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.complete.databasesUsed") + "

    \n
      "); //NON-NLS + + Stream.concat(knownBadHashSets.stream(), noChangeHashSets.stream()).forEach((db) -> { + detailsSb.append("
    • " + db.getHashSetName() + "
    • \n"); //NON-NLS + }); detailsSb.append("
    "); //NON-NLS @@ -456,7 +635,7 @@ public class HashDbIngestModule implements FileIngestModule { @Override public void shutDown() { if (refCounter.decrementAndGet(jobId) == 0) { - postSummary(jobId, knownBadHashSets, knownHashSets); + postSummary(jobId, knownBadHashSets, noChangeHashSets, knownHashSets); } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java index c6ae0d5ef4..7559649689 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbManager.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2011-2018 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. @@ -23,7 +23,6 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.io.IOException; -import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -31,6 +30,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Stream; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.SwingWorker; @@ -58,6 +58,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; /** * This class implements a singleton that manages the set of hash databases used @@ -103,8 +104,8 @@ public class HashDbManager implements PropertyChangeListener { public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } - - synchronized boolean verifyAllDatabasesLoadedCorrectly(){ + + synchronized boolean verifyAllDatabasesLoadedCorrectly() { return allDatabasesLoadedCorrectly; } @@ -238,7 +239,7 @@ public class HashDbManager implements PropertyChangeListener { } return hashDb; } - + private SleuthkitHashSet addHashDatabase(int handle, String hashSetName, boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType) throws TskCoreException { // Wrap an object around the handle. SleuthkitHashSet hashDb = new SleuthkitHashSet(handle, hashSetName, searchDuringIngest, sendIngestMessages, knownFilesType); @@ -273,22 +274,22 @@ public class HashDbManager implements PropertyChangeListener { } return hashDb; } - - CentralRepoHashSet addExistingCentralRepoHashSet(String hashSetName, String version, int referenceSetID, - boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType, - boolean readOnly) throws TskCoreException{ - - if(! CentralRepository.isEnabled()){ + + CentralRepoHashSet addExistingCentralRepoHashSet(String hashSetName, String version, int referenceSetID, + boolean searchDuringIngest, boolean sendIngestMessages, HashDb.KnownFilesType knownFilesType, + boolean readOnly) throws TskCoreException { + + if (!CentralRepository.isEnabled()) { throw new TskCoreException("Could not load central repository hash set " + hashSetName + " - central repository is not enabled"); } - + CentralRepoHashSet db = new CentralRepoHashSet(hashSetName, version, referenceSetID, searchDuringIngest, - sendIngestMessages, knownFilesType, readOnly); - - if(! db.isValid()){ + sendIngestMessages, knownFilesType, readOnly); + + if (!db.isValid()) { throw new TskCoreException("Error finding hash set " + hashSetName + " in central repository"); } - + // Add the hash database to the collection hashSets.add(db); @@ -302,8 +303,8 @@ public class HashDbManager implements PropertyChangeListener { NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErrorListeningToUpdatesMsg"), MessageNotifyUtil.MessageType.ERROR); } - return db; - + return db; + } synchronized void indexHashDatabase(SleuthkitHashSet hashDb) { @@ -341,7 +342,7 @@ public class HashDbManager implements PropertyChangeListener { this.removeHashDatabaseNoSave(hashDb); this.save(); } - + public synchronized void removeHashDatabaseNoSave(HashDb hashDb) throws HashDbManagerException { // Don't remove a database if ingest is running boolean ingestIsRunning = IngestManager.getInstance().isIngestRunning(); @@ -357,17 +358,16 @@ public class HashDbManager implements PropertyChangeListener { hashSets.remove(hashDb); // Now undertake the operations that could throw. - // Indexing is only relevanet for sleuthkit hashsets - if(hashDb instanceof SleuthkitHashSet){ - SleuthkitHashSet hashDatabase = (SleuthkitHashSet)hashDb; + if (hashDb instanceof SleuthkitHashSet) { + SleuthkitHashSet hashDatabase = (SleuthkitHashSet) hashDb; try { - if(hashDatabase.hasIndex()){ + if (hashDatabase.hasIndex()) { hashSetPaths.remove(hashDatabase.getIndexPath()); } } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS - } + } try { if (!hashDatabase.hasIndexOnly()) { @@ -376,7 +376,7 @@ public class HashDbManager implements PropertyChangeListener { } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS } - + try { hashDatabase.close(); } catch (TskCoreException ex) { @@ -405,7 +405,7 @@ public class HashDbManager implements PropertyChangeListener { throw new HashDbManagerException(NbBundle.getMessage(this.getClass(), "HashDbManager.saveErrorExceptionMsg")); } } - + /** * Gets all of the hash databases used to classify files as known or known * bad. Will add any new central repository databases to the list before @@ -414,12 +414,12 @@ public class HashDbManager implements PropertyChangeListener { * @return A list, possibly empty, of hash databases. */ public synchronized List getAllHashSets() { - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } - + List hashDbs = new ArrayList<>(); hashDbs.addAll(this.hashSets); return hashDbs; @@ -432,9 +432,9 @@ public class HashDbManager implements PropertyChangeListener { */ public synchronized List getKnownFileHashSets() { List hashDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } this.hashSets.stream().filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN)).forEach((db) -> { @@ -450,9 +450,9 @@ public class HashDbManager implements PropertyChangeListener { */ public synchronized List getKnownBadFileHashSets() { List hashDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } this.hashSets.stream().filter((db) -> (db.getKnownFilesType() == HashDb.KnownFilesType.KNOWN_BAD)).forEach((db) -> { @@ -472,9 +472,9 @@ public class HashDbManager implements PropertyChangeListener { private List getUpdateableHashSets(List hashDbs) { ArrayList updateableDbs = new ArrayList<>(); - try{ + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } for (HashDb db : hashDbs) { @@ -488,34 +488,27 @@ public class HashDbManager implements PropertyChangeListener { } return updateableDbs; } - - private List getCentralRepoHashSetsFromDatabase(){ + + private List getCentralRepoHashSetsFromDatabase() { List crHashSets = new ArrayList<>(); - if(CentralRepository.isEnabled()){ - try{ + if (CentralRepository.isEnabled()) { + try { List crSets = CentralRepository.getInstance().getAllReferenceSets(CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - for(CentralRepoFileSet globalSet:crSets){ - + for (CentralRepoFileSet globalSet : crSets) { + // Defaults for fields not stored in the central repository: // searchDuringIngest: false // sendIngestMessages: true if the hash set is notable - boolean sendIngestMessages = convertFileKnown(globalSet.getFileKnownStatus()).equals(HashDb.KnownFilesType.KNOWN_BAD); + boolean sendIngestMessages = KnownFilesType.fromFileKnown(globalSet.getFileKnownStatus()).equals(HashDb.KnownFilesType.KNOWN_BAD); crHashSets.add(new HashDbInfo(globalSet.getSetName(), globalSet.getVersion(), - globalSet.getGlobalSetID(), convertFileKnown(globalSet.getFileKnownStatus()), globalSet.isReadOnly(), false, sendIngestMessages)); - } - } catch (CentralRepoException ex){ + globalSet.getGlobalSetID(), KnownFilesType.fromFileKnown(globalSet.getFileKnownStatus()), globalSet.isReadOnly(), false, sendIngestMessages)); + } + } catch (CentralRepoException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error loading central repository hash sets", ex); //NON-NLS } } return crHashSets; } - - private static HashDb.KnownFilesType convertFileKnown(TskData.FileKnown fileKnown){ - if(fileKnown.equals(TskData.FileKnown.BAD)){ - return HashDb.KnownFilesType.KNOWN_BAD; - } - return HashDb.KnownFilesType.KNOWN; - } /** * Restores the last saved hash sets configuration. This supports @@ -531,9 +524,9 @@ public class HashDbManager implements PropertyChangeListener { private void closeHashDatabases(List hashDatabases) { for (HashDb database : hashDatabases) { - if(database instanceof SleuthkitHashSet){ + if (database instanceof SleuthkitHashSet) { try { - ((SleuthkitHashSet)database).close(); + ((SleuthkitHashSet) database).close(); } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + database.getHashSetName() + " hash set", ex); //NON-NLS } @@ -558,13 +551,13 @@ public class HashDbManager implements PropertyChangeListener { * @param settings The settings to configure. */ @Messages({"# {0} - hash set name", "HashDbManager.noDbPath.message=Couldn't get valid hash set path for: {0}", - "HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets"}) + "HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets"}) private void configureSettings(HashLookupSettings settings) { allDatabasesLoadedCorrectly = true; List hashDbInfoList = settings.getHashDbInfo(); for (HashDbInfo hashDbInfo : hashDbInfoList) { try { - if(hashDbInfo.isFileDatabaseType()){ + if (hashDbInfo.isFileDatabaseType()) { String dbPath = this.getValidFilePath(hashDbInfo.getHashSetName(), hashDbInfo.getPath()); if (dbPath != null) { addHashDatabase(SleuthkitJNI.openHashDatabase(dbPath), hashDbInfo.getHashSetName(), hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType()); @@ -573,10 +566,10 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } else { - if(CentralRepository.isEnabled()){ - addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), - hashDbInfo.getReferenceSetID(), - hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), + if (CentralRepository.isEnabled()) { + addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), + hashDbInfo.getReferenceSetID(), + hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), hashDbInfo.isReadOnly()); } } @@ -590,13 +583,13 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } - - if(CentralRepository.isEnabled()){ - try{ + + if (CentralRepository.isEnabled()) { + try { updateHashSetsFromCentralRepository(); - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS - + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), Bundle.HashDbManager_centralRepoLoadError_message(), NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"), @@ -604,14 +597,17 @@ public class HashDbManager implements PropertyChangeListener { allDatabasesLoadedCorrectly = false; } } - - /* NOTE: When RuntimeProperties.coreComponentsAreActive() is "false", - I don't think we should overwrite hash db settings file because we - were unable to load a database. The user should have to fix the issue or - remove the database from settings. Overwiting the settings effectively removes - the database from HashLookupSettings and the user may not know about this - because the dialogs are not being displayed. The next time user starts Autopsy, HashDB - will load without errors and the user may think that the problem was solved.*/ + + /* + * NOTE: When RuntimeProperties.coreComponentsAreActive() is "false", I + * don't think we should overwrite hash db settings file because we were + * unable to load a database. The user should have to fix the issue or + * remove the database from settings. Overwiting the settings + * effectively removes the database from HashLookupSettings and the user + * may not know about this because the dialogs are not being displayed. + * The next time user starts Autopsy, HashDB will load without errors + * and the user may think that the problem was solved. + */ if (!allDatabasesLoadedCorrectly && RuntimeProperties.runningWithGUI()) { try { HashLookupSettings.writeSettings(new HashLookupSettings(HashLookupSettings.convertHashSetList(this.hashSets))); @@ -622,31 +618,31 @@ public class HashDbManager implements PropertyChangeListener { } } } - + private void updateHashSetsFromCentralRepository() throws TskCoreException { - if(CentralRepository.isEnabled()){ + if (CentralRepository.isEnabled()) { List crHashDbInfoList = getCentralRepoHashSetsFromDatabase(); - for(HashDbInfo hashDbInfo : crHashDbInfoList) { - if(hashDbInfoIsNew(hashDbInfo)){ - addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), - hashDbInfo.getReferenceSetID(), - hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), - hashDbInfo.isReadOnly()); + for (HashDbInfo hashDbInfo : crHashDbInfoList) { + if (hashDbInfoIsNew(hashDbInfo)) { + addExistingCentralRepoHashSet(hashDbInfo.getHashSetName(), hashDbInfo.getVersion(), + hashDbInfo.getReferenceSetID(), + hashDbInfo.getSearchDuringIngest(), hashDbInfo.getSendIngestMessages(), hashDbInfo.getKnownFilesType(), + hashDbInfo.isReadOnly()); } } } } - - private boolean hashDbInfoIsNew(HashDbInfo dbInfo){ - for(HashDb db:this.hashSets){ - if(dbInfo.matches(db)){ + + private boolean hashDbInfoIsNew(HashDbInfo dbInfo) { + for (HashDb db : this.hashSets) { + if (dbInfo.matches(db)) { return false; } } return true; } - private String getValidFilePath(String hashSetName, String configuredPath) { + private String getValidFilePath(String hashSetName, String configuredPath) { // Check the configured path. File database = new File(configuredPath); if (database.exists()) { @@ -655,12 +651,12 @@ public class HashDbManager implements PropertyChangeListener { // Give the user an opportunity to find the desired file. String newPath = null; - if (RuntimeProperties.runningWithGUI() && - JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), - NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", - hashSetName, configuredPath), - NbBundle.getMessage(this.getClass(), "HashDbManager.dlgTitle.MissingDb"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + if (RuntimeProperties.runningWithGUI() + && JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc", + hashSetName, configuredPath), + NbBundle.getMessage(this.getClass(), "HashDbManager.dlgTitle.MissingDb"), + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { newPath = searchForFile(); if (null != newPath && !newPath.isEmpty()) { database = new File(newPath); @@ -692,26 +688,89 @@ public class HashDbManager implements PropertyChangeListener { } return filePath; } - + public static abstract class HashDb { - + /** * Indicates how files with hashes stored in a particular hash database * object should be classified. */ + @Messages({ + "HashDbManager.noChange.text=No Change", + "HashDbManager.known.text=Known", + "HashDbManager.knownBad.text=Notable" + }) public enum KnownFilesType { - KNOWN(NbBundle.getMessage(HashDbManager.class, "HashDbManager.known.text")), - KNOWN_BAD(NbBundle.getMessage(HashDbManager.class, "HashDbManager.knownBad.text")); - private final String displayName; + KNOWN(Bundle.HashDbManager_known_text(), TskData.FileKnown.KNOWN, false, false), + KNOWN_BAD(Bundle.HashDbManager_knownBad_text(), TskData.FileKnown.BAD, true, true), + NO_CHANGE(Bundle.HashDbManager_noChange_text(), TskData.FileKnown.UNKNOWN, true, false); - private KnownFilesType(String displayName) { + private final String displayName; + private final TskData.FileKnown fileKnown; + private final boolean allowSendInboxMessages; + private final boolean defaultSendInboxMessages; + + KnownFilesType(String displayName, TskData.FileKnown fileKnown, boolean allowSendInboxMessages, boolean defaultSendInboxMessages) { this.displayName = displayName; + this.fileKnown = fileKnown; + this.allowSendInboxMessages = allowSendInboxMessages; + this.defaultSendInboxMessages = defaultSendInboxMessages; + } + + /** + * Returns whether or not it is allowable to send inbox messages + * with this known files type. + * + * @return Whether or not it is allowable to send inbox messages + * with this known files type. + */ + boolean isInboxMessagesAllowed() { + return allowSendInboxMessages; + } + + /** + * Returns whether or not by default for this type is to send inbox + * messages. + * + * @return Whether or not by default for this type is to send inbox + * messages. + */ + boolean isDefaultInboxMessages() { + return defaultSendInboxMessages; } public String getDisplayName() { return this.displayName; } + + /** + * Retrieves the corresponding TskData.FileKnown enum type that + * relates to this. + * + * @return The corresponding TskData.FileKnown. + */ + TskData.FileKnown getFileKnown() { + return this.fileKnown; + } + + /** + * Converts a TskData.FileKnown to the corresponding KnownFilesType. + * + * @param fileKnown The TskData.FileKnown type. + * + * @return The corresponding KnownFilesType. + */ + static KnownFilesType fromFileKnown(TskData.FileKnown fileKnown) { + if (fileKnown == null) { + return null; + } + + return Stream.of(KnownFilesType.values()) + .filter((type) -> type.getFileKnown() == fileKnown) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown TskData.FileKnown type: " + fileKnown)); + } } /** @@ -721,9 +780,9 @@ public class HashDbManager implements PropertyChangeListener { INDEXING_DONE } - + public abstract String getHashSetName(); - + abstract String getDisplayName(); public abstract String getDatabasePath() throws TskCoreException; @@ -731,7 +790,7 @@ public class HashDbManager implements PropertyChangeListener { public abstract HashDb.KnownFilesType getKnownFilesType(); public abstract boolean getSearchDuringIngest(); - + abstract void setSearchDuringIngest(boolean useForIngest); public abstract boolean getSendIngestMessages(); @@ -764,28 +823,30 @@ public class HashDbManager implements PropertyChangeListener { public abstract boolean lookupMD5Quick(Content content) throws TskCoreException; public abstract HashHitInfo lookupMD5(Content content) throws TskCoreException; - + /** - * Returns whether this database can be enabled. - * For file type, this is the same as checking that it has an index + * Returns whether this database can be enabled. For file type, this is + * the same as checking that it has an index + * * @return true if is valid, false otherwise - * @throws TskCoreException + * + * @throws TskCoreException */ abstract boolean isValid() throws TskCoreException; - + public abstract String getIndexPath() throws TskCoreException; - + public abstract boolean hasIndexOnly() throws TskCoreException; - + public abstract void firePropertyChange(String propertyName, Object oldValue, Object newValue); - + public abstract void addPropertyChangeListener(PropertyChangeListener pcl); - + public abstract void removePropertyChangeListener(PropertyChangeListener pcl); - + @Override public abstract String toString(); - + } /** @@ -793,13 +854,13 @@ public class HashDbManager implements PropertyChangeListener { * as known or know bad. */ class SleuthkitHashSet extends HashDb { - + private static final long serialVersionUID = 1L; private final int handle; private final String hashSetName; private boolean searchDuringIngest; private boolean sendIngestMessages; - private final HashDb.KnownFilesType knownFilesType; + private final HashDb.KnownFilesType knownFilesType; private boolean indexing; private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); @@ -813,8 +874,8 @@ public class HashDbManager implements PropertyChangeListener { } /** - * Adds a listener for the events defined in HashDb.Event. - * Listeners are used during indexing. + * Adds a listener for the events defined in HashDb.Event. Listeners are + * used during indexing. * * @param pcl */ @@ -832,8 +893,8 @@ public class HashDbManager implements PropertyChangeListener { public void removePropertyChangeListener(PropertyChangeListener pcl) { propertyChangeSupport.removePropertyChangeListener(pcl); } - - int getHandle(){ + + int getHandle() { return handle; } @@ -841,9 +902,9 @@ public class HashDbManager implements PropertyChangeListener { public String getHashSetName() { return hashSetName; } - + @Override - String getDisplayName(){ + String getDisplayName() { return getHashSetName(); } @@ -851,9 +912,9 @@ public class HashDbManager implements PropertyChangeListener { public String getDatabasePath() throws TskCoreException { return SleuthkitJNI.getHashDatabasePath(handle); } - - public void setIndexing(boolean indexing){ - this.indexing = indexing; + + public void setIndexing(boolean indexing) { + this.indexing = indexing; } @Override @@ -989,12 +1050,14 @@ public class HashDbManager implements PropertyChangeListener { } return result; } - + /** - * Returns whether this database can be enabled. - * For file type, this is the same as checking that it has an index + * Returns whether this database can be enabled. For file type, this is + * the same as checking that it has an index + * * @return true if is valid, false otherwise - * @throws TskCoreException + * + * @throws TskCoreException */ @Override boolean isValid() throws TskCoreException { @@ -1017,21 +1080,20 @@ public class HashDbManager implements PropertyChangeListener { boolean isIndexing() { return indexing; } - + @Override - public void firePropertyChange(String propertyName, Object oldValue, Object newValue){ + public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } private void close() throws TskCoreException { SleuthkitJNI.closeHashDatabase(handle); } - + @Override - public String toString(){ + public String toString() { return getHashSetName(); } - @Override public int hashCode() { @@ -1066,13 +1128,13 @@ public class HashDbManager implements PropertyChangeListener { * Instances of this class represent hash databases used to classify files * as known or know bad. */ - class CentralRepoHashSet extends HashDb{ + class CentralRepoHashSet extends HashDb { private static final long serialVersionUID = 1L; private final String hashSetName; private boolean searchDuringIngest; private boolean sendIngestMessages; - private final HashDb.KnownFilesType knownFilesType; + private final HashDb.KnownFilesType knownFilesType; private final int referenceSetID; private final String version; private String orgName; @@ -1080,10 +1142,10 @@ public class HashDbManager implements PropertyChangeListener { private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); @Messages({"HashDbManager.CentralRepoHashDb.orgError=Error loading organization"}) - private CentralRepoHashSet(String hashSetName, String version, int referenceSetID, - boolean useForIngest, boolean sendHitMessages, HashDb.KnownFilesType knownFilesType, + private CentralRepoHashSet(String hashSetName, String version, int referenceSetID, + boolean useForIngest, boolean sendHitMessages, HashDb.KnownFilesType knownFilesType, boolean readOnly) - throws TskCoreException{ + throws TskCoreException { this.hashSetName = hashSetName; this.version = version; this.referenceSetID = referenceSetID; @@ -1091,18 +1153,18 @@ public class HashDbManager implements PropertyChangeListener { this.sendIngestMessages = sendHitMessages; this.knownFilesType = knownFilesType; this.readOnly = readOnly; - - try{ + + try { orgName = CentralRepository.getInstance().getReferenceSetOrganization(referenceSetID).getName(); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error looking up central repository organization for reference set " + referenceSetID, ex); //NON-NLS orgName = Bundle.HashDbManager_CentralRepoHashDb_orgError(); } } /** - * Adds a listener for the events defined in HashDb.Event. - * Listeners are used during indexing. + * Adds a listener for the events defined in HashDb.Event. Listeners are + * used during indexing. * * @param pcl */ @@ -1120,9 +1182,9 @@ public class HashDbManager implements PropertyChangeListener { public void removePropertyChangeListener(PropertyChangeListener pcl) { propertyChangeSupport.removePropertyChangeListener(pcl); } - + @Override - public boolean hasIndexOnly() throws TskCoreException{ + public boolean hasIndexOnly() throws TskCoreException { return true; } @@ -1130,25 +1192,25 @@ public class HashDbManager implements PropertyChangeListener { public String getHashSetName() { return hashSetName; } - + @Override - public String getDisplayName(){ - if(! getVersion().isEmpty()){ + public String getDisplayName() { + if (!getVersion().isEmpty()) { return getHashSetName() + " " + getVersion() + " (remote)"; } else { return getHashSetName() + " (remote)"; } } - - String getVersion(){ + + String getVersion() { return version; } - - String getOrgName(){ + + String getOrgName() { return orgName; } - - int getReferenceSetID(){ + + int getReferenceSetID() { return referenceSetID; } @@ -1196,7 +1258,7 @@ public class HashDbManager implements PropertyChangeListener { */ @Override public boolean isUpdateable() throws TskCoreException { - return (! readOnly); + return (!readOnly); } /** @@ -1229,18 +1291,13 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - TskData.FileKnown type; - if(knownFilesType.equals(HashDb.KnownFilesType.KNOWN_BAD)){ - type = TskData.FileKnown.BAD; - } else { - type = TskData.FileKnown.KNOWN; - } - - try{ + TskData.FileKnown type = knownFilesType.getFileKnown(); + + try { CentralRepoFileInstance fileInstance = new CentralRepoFileInstance(referenceSetID, file.getMd5Hash(), - type, comment); - CentralRepository.getInstance().addReferenceInstance(fileInstance,CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + type, comment); + CentralRepository.getInstance().addReferenceInstance(fileInstance, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); //NON-NLS } } @@ -1257,24 +1314,20 @@ public class HashDbManager implements PropertyChangeListener { @Override public void addHashes(List hashes) throws TskCoreException { Set globalFileInstances = new HashSet<>(); - for(HashEntry hashEntry:hashes){ - TskData.FileKnown type; - if(knownFilesType.equals(HashDb.KnownFilesType.KNOWN_BAD)){ - type = TskData.FileKnown.BAD; - } else { - type = TskData.FileKnown.KNOWN; - } + for (HashEntry hashEntry : hashes) { + TskData.FileKnown type = knownFilesType.getFileKnown(); + try { globalFileInstances.add(new CentralRepoFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment())); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); } } - - try{ - CentralRepository.getInstance().bulkInsertReferenceTypeEntries(globalFileInstances, + + try { + CentralRepository.getInstance().bulkInsertReferenceTypeEntries(globalFileInstances, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); } } @@ -1295,9 +1348,9 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - try{ + try { return CentralRepository.getInstance().isFileHashInReferenceSet(file.getMd5Hash(), this.referenceSetID); - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash " + file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS throw new TskCoreException("Error performing central reposiotry hash lookup", ex); @@ -1324,12 +1377,9 @@ public class HashDbManager implements PropertyChangeListener { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; if (null != file.getMd5Hash()) { - try{ - if(CentralRepository.getInstance().isFileHashInReferenceSet(file.getMd5Hash(), this.referenceSetID)){ - // Make a bare-bones HashHitInfo for now - result = new HashHitInfo(file.getMd5Hash(), "", ""); - } - } catch (CentralRepoException | CorrelationAttributeNormalizationException ex){ + try { + return CentralRepository.getInstance().lookupHash(file.getMd5Hash(), referenceSetID); + } catch (CentralRepoException | CorrelationAttributeNormalizationException ex) { Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash " + file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS throw new TskCoreException("Error performing central reposiotry hash lookup", ex); @@ -1338,35 +1388,34 @@ public class HashDbManager implements PropertyChangeListener { } return result; } - + /** * Returns whether this database can be enabled. - * + * * @return true if is valid, false otherwise */ @Override boolean isValid() { - if(! CentralRepository.isEnabled()) { + if (!CentralRepository.isEnabled()) { return false; } - try{ + try { return CentralRepository.getInstance().referenceSetIsValid(this.referenceSetID, this.hashSetName, this.version); - } catch (CentralRepoException ex){ + } catch (CentralRepoException ex) { Logger.getLogger(CentralRepoHashSet.class.getName()).log(Level.SEVERE, "Error validating hash set " + hashSetName, ex); //NON-NLS return false; } } - + @Override - public void firePropertyChange(String propertyName, Object oldValue, Object newValue){ + public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } - + @Override - public String toString(){ + public String toString() { return getDisplayName(); } - @Override public int hashCode() { @@ -1398,8 +1447,8 @@ public class HashDbManager implements PropertyChangeListener { } return true; } - } - + } + /** * Worker thread to make an index of a database */ diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form index c0a486fce9..3e2e525560 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.form @@ -25,16 +25,12 @@ - + - - - - - + @@ -46,22 +42,25 @@ - + - + - - - - - + - + + + + + + + + @@ -75,7 +74,7 @@ - + @@ -86,20 +85,6 @@ - - - - - - - - - - - - - - @@ -121,39 +106,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java index 6dcd567226..e804e0bea7 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupModuleSettingsPanel.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2011-2018 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. @@ -27,13 +27,13 @@ import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; +import org.apache.commons.lang.StringUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; - /** * Ingest job settings panel for hash lookup file ingest modules. */ @@ -42,10 +42,8 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe private static final long serialVersionUID = 1L; private final HashDbManager hashDbManager = HashDbManager.getInstance(); - private final List knownHashSetModels = new ArrayList<>(); - private final HashSetsTableModel knownHashSetsTableModel = new HashSetsTableModel(knownHashSetModels); - private final List knownBadHashSetModels = new ArrayList<>(); - private final HashSetsTableModel knownBadHashSetsTableModel = new HashSetsTableModel(knownBadHashSetModels); + private final List hashSetModels = new ArrayList<>(); + private final HashSetsTableModel hashSetsTableModel = new HashSetsTableModel(hashSetModels); HashLookupModuleSettingsPanel(HashLookupModuleSettings settings) { initializeHashSetModels(settings); @@ -54,11 +52,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe } private void initializeHashSetModels(HashLookupModuleSettings settings) { - initializeHashSetModels(settings, validSetsOnly(hashDbManager.getKnownFileHashSets()), knownHashSetModels); - initializeHashSetModels(settings, validSetsOnly(hashDbManager.getKnownBadFileHashSets()), knownBadHashSetModels); - } - - private void initializeHashSetModels(HashLookupModuleSettings settings, List hashDbs, List hashSetModels) { + List hashDbs = validSetsOnly(hashDbManager.getAllHashSets()); hashSetModels.clear(); for (HashDb db : hashDbs) { hashSetModels.add(new HashSetModel(db, settings.isHashSetEnabled(db), isHashDbValid(db))); @@ -66,8 +60,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe } private void customizeComponents(HashLookupModuleSettings settings) { - customizeHashSetsTable(jScrollPane1, knownHashTable, knownHashSetsTableModel); - customizeHashSetsTable(jScrollPane2, knownBadHashTable, knownBadHashSetsTableModel); + customizeHashSetsTable(hashDbsScrollPane, hashTable, hashSetsTableModel); alwaysCalcHashesCheckbox.setSelected(settings.shouldCalculateHashes()); hashDbManager.addPropertyChangeListener(this); alwaysCalcHashesCheckbox.setText("" + org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text") + ""); // NOI18N NON-NLS @@ -78,7 +71,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe table.setTableHeader(null); table.setRowSelectionAllowed(false); final int width1 = scrollPane.getPreferredSize().width; - knownHashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + hashTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); TableColumn column; for (int i = 0; i < table.getColumnCount(); i++) { column = table.getColumnModel().getColumn(i); @@ -103,8 +96,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe public IngestModuleIngestJobSettings getSettings() { List enabledHashSets = new ArrayList<>(); List disabledHashSets = new ArrayList<>(); - addHashSets(knownHashSetModels, enabledHashSets, disabledHashSets); - addHashSets(knownBadHashSetModels, enabledHashSets, disabledHashSets); + addHashSets(hashSetModels, enabledHashSets, disabledHashSets); return new HashLookupModuleSettings(alwaysCalcHashesCheckbox.isSelected(), enabledHashSets, disabledHashSets); } @@ -121,46 +113,41 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe void update() { updateHashSetModels(); - knownHashSetsTableModel.fireTableDataChanged(); - knownBadHashSetsTableModel.fireTableDataChanged(); + hashSetsTableModel.fireTableDataChanged(); } - private void updateHashSetModels() { - updateHashSetModels(validSetsOnly(hashDbManager.getKnownFileHashSets()), knownHashSetModels); - updateHashSetModels(validSetsOnly(hashDbManager.getKnownBadFileHashSets()), knownBadHashSetModels); - } - - private List validSetsOnly(List hashDbs){ + private List validSetsOnly(List hashDbs) { List validDbs = new ArrayList<>(); - for(HashDb db:hashDbs){ - try{ - if(db.isValid()){ + for (HashDb db : hashDbs) { + try { + if (db.isValid()) { validDbs.add(db); } - } catch (TskCoreException ex){ + } catch (TskCoreException ex) { Logger.getLogger(HashLookupModuleSettingsPanel.class.getName()).log(Level.SEVERE, "Error checking validity for hash set (name = " + db.getHashSetName() + ")", ex); //NON-NLS } } return validDbs; } - void updateHashSetModels(List hashDbs, List hashSetModels) { - + void updateHashSetModels() { + List hashDbs = validSetsOnly(hashDbManager.getAllHashSets()); + List hashDatabases = new ArrayList<>(hashDbs); - + // Update the hash sets and detect deletions. List deletedHashSetModels = new ArrayList<>(); for (HashSetModel model : hashSetModels) { boolean foundDatabase = false; - for(HashDb db : hashDatabases){ - if(model.getDatabase().equals(db)){ + for (HashDb db : hashDatabases) { + if (model.getDatabase().equals(db)) { model.setValid(isHashDbValid(db)); hashDatabases.remove(db); foundDatabase = true; break; } } - if(! foundDatabase){ + if (!foundDatabase) { deletedHashSetModels.add(model); } } @@ -179,8 +166,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe void reset(HashLookupModuleSettings newSettings) { initializeHashSetModels(newSettings); alwaysCalcHashesCheckbox.setSelected(newSettings.shouldCalculateHashes()); - knownHashSetsTableModel.fireTableDataChanged(); - knownBadHashSetsTableModel.fireTableDataChanged(); + hashSetsTableModel.fireTableDataChanged(); } private boolean isHashDbValid(HashDb hashDb) { @@ -204,8 +190,8 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe this.enabled = enabled; this.valid = valid; } - - HashDb getDatabase(){ + + HashDb getDatabase() { return db; } @@ -213,6 +199,16 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe return db.getDisplayName(); } + String getFormattedName() { + String knownTypeName = (db != null && db.getKnownFilesType() != null) ? db.getKnownFilesType().getDisplayName() : ""; + if (!StringUtils.isBlank(knownTypeName)) { + knownTypeName = String.format(" (%s)", knownTypeName); + } + + String displayName = db != null ? db.getDisplayName() : ""; + return displayName + knownTypeName; + } + void setEnabled(boolean enabled) { this.enabled = enabled; } @@ -254,7 +250,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe if (columnIndex == 0) { return hashSets.get(rowIndex).isEnabled(); } else { - return hashSets.get(rowIndex).getName(); + return hashSets.get(rowIndex).getFormattedName(); } } @@ -285,26 +281,21 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe // //GEN-BEGIN:initComponents private void initComponents() { - jScrollPane1 = new javax.swing.JScrollPane(); - knownHashTable = new javax.swing.JTable(); - knownBadHashDbsLabel = new javax.swing.JLabel(); - knownHashDbsLabel = new javax.swing.JLabel(); + hashDbsLabel = new javax.swing.JLabel(); + hashDbsScrollPane = new javax.swing.JScrollPane(); + hashTable = new javax.swing.JTable(); alwaysCalcHashesCheckbox = new javax.swing.JCheckBox(); - jScrollPane2 = new javax.swing.JScrollPane(); - knownBadHashTable = new javax.swing.JTable(); setPreferredSize(new java.awt.Dimension(292, 150)); - jScrollPane1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + hashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.hashDbsLabel.text")); // NOI18N - knownHashTable.setBackground(new java.awt.Color(240, 240, 240)); - knownHashTable.setShowHorizontalLines(false); - knownHashTable.setShowVerticalLines(false); - jScrollPane1.setViewportView(knownHashTable); + hashDbsScrollPane.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - knownBadHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text")); // NOI18N - - knownHashDbsLabel.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.knownHashDbsLabel.text")); // NOI18N + hashTable.setBackground(new java.awt.Color(240, 240, 240)); + hashTable.setShowHorizontalLines(false); + hashTable.setShowVerticalLines(false); + hashDbsScrollPane.setViewportView(hashTable); alwaysCalcHashesCheckbox.setText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text")); // NOI18N alwaysCalcHashesCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashLookupModuleSettingsPanel.class, "HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.toolTipText")); // NOI18N @@ -314,21 +305,6 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe alwaysCalcHashesCheckbox.setVerticalAlignment(javax.swing.SwingConstants.TOP); alwaysCalcHashesCheckbox.setVerticalTextPosition(javax.swing.SwingConstants.TOP); - jScrollPane2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - - knownBadHashTable.setBackground(new java.awt.Color(240, 240, 240)); - knownBadHashTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - - } - )); - knownBadHashTable.setShowHorizontalLines(false); - knownBadHashTable.setShowVerticalLines(false); - jScrollPane2.setViewportView(knownBadHashTable); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -337,14 +313,11 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(knownHashDbsLabel) + .addComponent(hashDbsLabel) .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(knownBadHashDbsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 290, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addGap(10, 10, 10) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))) + .addComponent(hashDbsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 494, Short.MAX_VALUE)) .addComponent(alwaysCalcHashesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); @@ -352,26 +325,19 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(2, 2, 2) - .addComponent(knownHashDbsLabel) + .addComponent(hashDbsLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 29, Short.MAX_VALUE) + .addComponent(hashDbsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 207, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(knownBadHashDbsLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 29, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(alwaysCalcHashesCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0)) + .addContainerGap()) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox alwaysCalcHashesCheckbox; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JScrollPane jScrollPane2; - private javax.swing.JLabel knownBadHashDbsLabel; - private javax.swing.JTable knownBadHashTable; - private javax.swing.JLabel knownHashDbsLabel; - private javax.swing.JTable knownHashTable; + private javax.swing.JLabel hashDbsLabel; + private javax.swing.JScrollPane hashDbsScrollPane; + private javax.swing.JTable hashTable; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java index 0f1f97ba97..fade3e47c5 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashLookupSettingsPanel.java @@ -49,7 +49,6 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SleuthkitHashSet; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.CentralRepoHashSet; -import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.SetEvt; @@ -95,16 +94,16 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan } } }); - + HashDbManager.getInstance().addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String propName = evt.getPropertyName(); - if(propName.equals(SetEvt.DB_ADDED.toString()) || - propName.equals(SetEvt.DB_DELETED.toString())) { + if (propName.equals(SetEvt.DB_ADDED.toString()) + || propName.equals(SetEvt.DB_DELETED.toString())) { hashSetTableModel.refreshModel(); } - } + } }); } @@ -282,7 +281,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan // Update ingest option components. sendIngestMessagesCheckBox.setSelected(db.getSendIngestMessages()); - sendIngestMessagesCheckBox.setEnabled(!ingestIsRunning && db.getKnownFilesType().equals(KnownFilesType.KNOWN_BAD)); + sendIngestMessagesCheckBox.setEnabled(!ingestIsRunning && db.getKnownFilesType().isInboxMessagesAllowed()); // Update database action buttons. createDatabaseButton.setEnabled(true); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java index 8a7a3ae034..85d28231a9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashSetParser.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.modules.hashdatabase; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.HashEntry; interface HashSetParser { @@ -26,7 +27,8 @@ interface HashSetParser { * Get the next hash to import * * @return The hash as a string, or null if the end of file was reached - * without error + * without error + * * @throws TskCoreException */ String getNextHash() throws TskCoreException; @@ -50,4 +52,20 @@ interface HashSetParser { * Closes the import file */ void close(); + + /** + * Get the next hash to import as a HashEntry object. + * + * @return A new hash entry for the next item parsed. + * + * @throws TskCoreException + */ + default HashEntry getNextHashEntry() throws TskCoreException { + String next = getNextHash(); + if (next == null) { + return null; + } + + return new HashEntry(null, next, null, null, null); + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java index f25471b0e0..d868b39189 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/ImportCentralRepoDbProgressDialog.java @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.datamodel.HashEntry; /** * Imports a hash set into the central repository and updates a progress dialog @@ -186,7 +187,7 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P * Get the newly created database * * @return the imported database. May be null if an error occurred or - * the user canceled + * the user canceled */ synchronized HashDbManager.CentralRepoHashSet getDatabase() { return newHashDb; @@ -205,7 +206,7 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P * Check if the import was successful or if there was an error. * * @return true if the import process completed without error, false - * otherwise + * otherwise */ boolean getImportSuccess() { return importSuccess.get(); @@ -231,16 +232,11 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P try { // Conver to the FileKnown enum used by EamGlobalSet - TskData.FileKnown knownStatus; - if (knownFilesType.equals(HashDbManager.HashDb.KnownFilesType.KNOWN)) { - knownStatus = TskData.FileKnown.KNOWN; - } else { - knownStatus = TskData.FileKnown.BAD; - } + TskData.FileKnown knownStatus = knownFilesType.getFileKnown(); // Create an empty hashset in the central repository CentralRepository dbManager = CentralRepository.getInstance(); - referenceSetID.set(dbManager.newReferenceSet(new CentralRepoFileSet(orgId, hashSetName, version, knownStatus, + referenceSetID.set(dbManager.newReferenceSet(new CentralRepoFileSet(orgId, hashSetName, version, knownStatus, readOnly, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID)))); // Get the "FILES" content type. This is a database lookup so we @@ -255,14 +251,14 @@ class ImportCentralRepoDbProgressDialog extends javax.swing.JDialog implements P return null; } - String newHash = hashSetParser.getNextHash(); + HashEntry newHash = hashSetParser.getNextHashEntry(); if (newHash != null) { CentralRepoFileInstance eamGlobalFileInstance = new CentralRepoFileInstance( referenceSetID.get(), - newHash, + newHash.getMd5Hash(), knownStatus, - ""); + newHash.getComment()); globalInstances.add(eamGlobalFileInstance); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java index 94d2724995..55bd974be4 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/KdbHashSetParser.java @@ -25,6 +25,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.HashEntry; import org.sleuthkit.datamodel.TskCoreException; /** @@ -68,7 +69,9 @@ public class KdbHashSetParser implements HashSetParser { } // Get the hashes - resultSet = statement.executeQuery("SELECT md5 FROM hashes"); + resultSet = statement.executeQuery("SELECT h.md5 as md5, " + + " (SELECT group_concat(c.comment, ' ') FROM comments c WHERE h.id = c.hash_id) as comment " + + " from hashes h"); // At this point, getNextHash can read each hash from the result set } catch (ClassNotFoundException | SQLException ex) { @@ -77,15 +80,21 @@ public class KdbHashSetParser implements HashSetParser { } + /** * Get the next hash to import * * @return The hash as a string + * * @throws TskCoreException */ @Override public String getNextHash() throws TskCoreException { + return getNextHashEntry().getMd5Hash(); + } + @Override + public HashEntry getNextHashEntry() throws TskCoreException { try { if (resultSet.next()) { byte[] hashBytes = resultSet.getBytes("md5"); @@ -98,13 +107,15 @@ public class KdbHashSetParser implements HashSetParser { throw new TskCoreException("Hash has incorrect length: " + sb.toString()); } + String md5Hash = sb.toString(); + String comment = resultSet.getString("comment"); totalHashesRead++; - return sb.toString(); + return new HashEntry(null, md5Hash, null, null, comment); } else { throw new TskCoreException("Could not read expected number of hashes from hash set " + filename); } } catch (SQLException ex) { - throw new TskCoreException("Error reading hash from result set for hash set " + filename, ex); + throw new TskCoreException("Error opening/reading hash set " + filename, ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties new file mode 100644 index 0000000000..508c0d1370 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties @@ -0,0 +1,15 @@ +CTL_OpenPersonas=Personas +CTL_PersonasTopComponentAction=PersonasTopComponent +CTL_PersonasTopComponent=Personas +PersonaDetailsTopComponent.resultNameLbl.text=Name: +PersonaDetailsTopComponent.resultCasesLbl.text=Cases found in: +PersonaDetailsTopComponent.resultAccountsLbl.text=Accounts: +PersonaDetailsTopComponent.resultAliasesLbl.text=Aliases: +PersonaDetailsTopComponent.resultNameField.text=Johnathan Dough +PersonaSearchTopComponent.searchAccountRadio.text=Account +PersonaSearchTopComponent.searchNameRadio.text=Name +PersonaSearchTopComponent.searchField.text= +PersonaSearchTopComponent.searchBtn.text=Search +PersonaSearchTopComponent.filterResultsTable.columnModel.title1=Name +PersonaSearchTopComponent.filterResultsTable.columnModel.title0=ID +PersonaSearchTopComponent.filterResultsTable.toolTipText= diff --git a/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties-MERGED new file mode 100644 index 0000000000..1e9b1769d2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/Bundle.properties-MERGED @@ -0,0 +1,5 @@ +CTL_OpenPersonas=Personas +CTL_PersonasTopComponentAction=PersonasTopComponent +CTL_PersonasTopComponent=Personas +OpenPersonasAction.displayName=Personas +PTopComponent_Name=Personas diff --git a/Core/src/org/sleuthkit/autopsy/persona/OpenPersonasAction.java b/Core/src/org/sleuthkit/autopsy/persona/OpenPersonasAction.java new file mode 100644 index 0000000000..157b25d1cc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/OpenPersonasAction.java @@ -0,0 +1,93 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.persona; + +import javax.swing.JMenuItem; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; + +/** + * An Action that opens the Personas window. + */ + +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.persona.Personas") +@ActionRegistration(displayName = "#CTL_OpenPersonas", lazy = false) +@ActionReferences(value = { + @ActionReference(path = "Menu/Tools", position = 105) +}) +public final class OpenPersonasAction extends CallableSystemAction { + + private static final long serialVersionUID = 1L; + + private final JMenuItem menuItem; + + + public OpenPersonasAction() { + menuItem = super.getMenuPresenter(); + this.setEnabled(true); + } + + @Override + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public void performAction() { + final TopComponent topComponent = WindowManager.getDefault().findTopComponent("PersonasTopComponent"); + if (topComponent != null) { + if (topComponent.isOpened() == false) { + topComponent.open(); + } + topComponent.toFront(); + topComponent.requestActive(); + } + } + + @Override + @NbBundle.Messages("OpenPersonasAction.displayName=Personas") + public String getName() { + return Bundle.OpenPersonasAction_displayName(); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } + + @Override + public void setEnabled(boolean enable) { + super.setEnabled(enable); + menuItem.setEnabled(enable); + } + + @Override + public JMenuItem getMenuPresenter() { + return menuItem; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form new file mode 100644 index 0000000000..fbfd7ded76 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.form @@ -0,0 +1,230 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java new file mode 100644 index 0000000000..fdc0af6e0d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaDetailsTopComponent.java @@ -0,0 +1,209 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.persona; + +import org.openide.windows.TopComponent; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.RetainLocation; +import org.openide.windows.WindowManager; + +/** + * Top component for persona details + */ +@TopComponent.Description(preferredID = "PersonasTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "personadetails", openAtStartup = false) +@RetainLocation("personadetails") +@SuppressWarnings("PMD.SingularField") +public final class PersonaDetailsTopComponent extends TopComponent { + + @Messages({ + "PDTopComponent_Name=Persona Details" + }) + public PersonaDetailsTopComponent() { + initComponents(); + setName(Bundle.PDTopComponent_Name()); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + detailsPanel = new javax.swing.JPanel(); + resultNameLbl = new javax.swing.JLabel(); + resultNameField = new javax.swing.JTextField(); + resultAliasesLbl = new javax.swing.JLabel(); + resultAccountsLbl = new javax.swing.JLabel(); + accountsTablePane = new javax.swing.JScrollPane(); + accountsTable = new javax.swing.JTable(); + resultCasesLbl = new javax.swing.JLabel(); + casesListPane = new javax.swing.JScrollPane(); + casesList = new javax.swing.JList<>(); + aliasesListPane = new javax.swing.JScrollPane(); + aliasesList = new javax.swing.JList<>(); + + org.openide.awt.Mnemonics.setLocalizedText(resultNameLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultNameLbl.text")); // NOI18N + + resultNameField.setEditable(false); + resultNameField.setText(org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultNameField.text")); // NOI18N + resultNameField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + resultNameFieldActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(resultAliasesLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultAliasesLbl.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(resultAccountsLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultAccountsLbl.text")); // NOI18N + + accountsTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + {"Email", "jdb@yahoo.com"}, + {"Phone", "865-555-5555"}, + {"Twitter", "@jd93.bread"}, + {null, null} + }, + new String [] { + "Type", "Data" + } + ) { + Class[] types = new Class [] { + java.lang.String.class, java.lang.String.class + }; + boolean[] canEdit = new boolean [] { + false, false + }; + + public Class getColumnClass(int columnIndex) { + return types [columnIndex]; + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return canEdit [columnIndex]; + } + }); + accountsTablePane.setViewportView(accountsTable); + + org.openide.awt.Mnemonics.setLocalizedText(resultCasesLbl, org.openide.util.NbBundle.getMessage(PersonaDetailsTopComponent.class, "PersonaDetailsTopComponent.resultCasesLbl.text")); // NOI18N + + casesList.setModel(new javax.swing.AbstractListModel() { + String[] strings = { "Investigation 13", "Scene 5" }; + public int getSize() { return strings.length; } + public String getElementAt(int i) { return strings[i]; } + }); + casesListPane.setViewportView(casesList); + + aliasesList.setModel(new javax.swing.AbstractListModel() { + String[] strings = { "J.D.", "Fred Smidge", "Ethan Roseman" }; + public int getSize() { return strings.length; } + public String getElementAt(int i) { return strings[i]; } + }); + aliasesListPane.setViewportView(aliasesList); + + javax.swing.GroupLayout detailsPanelLayout = new javax.swing.GroupLayout(detailsPanel); + detailsPanel.setLayout(detailsPanelLayout); + detailsPanelLayout.setHorizontalGroup( + detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, detailsPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(accountsTablePane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(aliasesListPane) + .addComponent(casesListPane) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, detailsPanelLayout.createSequentialGroup() + .addComponent(resultNameLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(resultNameField, javax.swing.GroupLayout.DEFAULT_SIZE, 447, Short.MAX_VALUE)) + .addComponent(resultAliasesLbl, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultAccountsLbl, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultCasesLbl, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + detailsPanelLayout.setVerticalGroup( + detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(detailsPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(detailsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(resultNameLbl) + .addComponent(resultNameField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addComponent(resultAliasesLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(aliasesListPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(resultAccountsLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(accountsTablePane, javax.swing.GroupLayout.PREFERRED_SIZE, 153, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(resultCasesLbl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(casesListPane, javax.swing.GroupLayout.DEFAULT_SIZE, 118, Short.MAX_VALUE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 505, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(detailsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 555, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(0, 0, Short.MAX_VALUE) + .addComponent(detailsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + ); + }// //GEN-END:initComponents + + private void resultNameFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resultNameFieldActionPerformed + + }//GEN-LAST:event_resultNameFieldActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTable accountsTable; + private javax.swing.JScrollPane accountsTablePane; + private javax.swing.JList aliasesList; + private javax.swing.JScrollPane aliasesListPane; + private javax.swing.JList casesList; + private javax.swing.JScrollPane casesListPane; + private javax.swing.JPanel detailsPanel; + private javax.swing.JLabel resultAccountsLbl; + private javax.swing.JLabel resultAliasesLbl; + private javax.swing.JLabel resultCasesLbl; + private javax.swing.JTextField resultNameField; + private javax.swing.JLabel resultNameLbl; + // End of variables declaration//GEN-END:variables + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form new file mode 100644 index 0000000000..ec91c8bb53 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.form @@ -0,0 +1,178 @@ + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonaSearchTopComponent.filterResultsTable.columnModel.title0" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + <ResourceString bundle="org/sleuthkit/autopsy/persona/Bundle.properties" key="PersonaSearchTopComponent.filterResultsTable.columnModel.title1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java new file mode 100644 index 0000000000..7cf22279db --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/persona/PersonaSearchTopComponent.java @@ -0,0 +1,217 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.persona; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Collections; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableModel; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.RetainLocation; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; +import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; + +/** + * Top component for the Personas tool + * + */ +@TopComponent.Description(preferredID = "PersonasTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "personasearch", openAtStartup = false) +@RetainLocation("personasearch") +@SuppressWarnings("PMD.SingularField") +public final class PersonaSearchTopComponent extends TopComponent { + + @Messages({ + "PSTopComponent_Name=Persona Search" + }) + public PersonaSearchTopComponent() { + initComponents(); + setName(Bundle.PSTopComponent_Name()); + executeSearch(); + + searchBtn.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + executeSearch(); + } + }); + } + + /** + * Table model for the persona search results + */ + final class PersonaFilterTableModel extends DefaultTableModel { + private static final long serialVersionUID = 1L; + + PersonaFilterTableModel(Object[][] rows, String[] colNames) { + super(rows, colNames); + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + } + + private void updateResultsTable(Collection results) { + Object[][] rows = new Object[results.size()][2]; + int i = 0; + for (Persona result : results) { + rows[i] = new String[]{String.valueOf(result.getId()), result.getName()}; + i++; + } + DefaultTableModel updatedTableModel = new PersonaFilterTableModel( + rows, + new String[]{"ID", "Name"} + ); + + filterResultsTable.setModel(updatedTableModel); + + // Formatting + filterResultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + filterResultsTable.getColumnModel().getColumn(0).setMaxWidth(100); + filterResultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + } + + private void executeSearch() { + Collection results = Collections.EMPTY_LIST; + try { + results = Persona.getPersonaByName(searchField.getText()); + } catch (CentralRepoException ex) { + Exceptions.printStackTrace(ex); + } + updateResultsTable(results); + } + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + searchButtonGroup = new javax.swing.ButtonGroup(); + searchPanel = new javax.swing.JPanel(); + searchField = new javax.swing.JTextField(); + searchNameRadio = new javax.swing.JRadioButton(); + searchAccountRadio = new javax.swing.JRadioButton(); + filterResultsPane = new javax.swing.JScrollPane(); + filterResultsTable = new javax.swing.JTable(); + searchBtn = new javax.swing.JButton(); + + setMinimumSize(new java.awt.Dimension(400, 400)); + + searchField.setText(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchField.text")); // NOI18N + + searchButtonGroup.add(searchNameRadio); + searchNameRadio.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(searchNameRadio, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchNameRadio.text")); // NOI18N + searchNameRadio.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + searchNameRadioActionPerformed(evt); + } + }); + + searchButtonGroup.add(searchAccountRadio); + org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchAccountRadio.text")); // NOI18N + + filterResultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.toolTipText")); // NOI18N + filterResultsTable.getTableHeader().setReorderingAllowed(false); + filterResultsPane.setViewportView(filterResultsTable); + if (filterResultsTable.getColumnModel().getColumnCount() > 0) { + filterResultsTable.getColumnModel().getColumn(0).setMaxWidth(25); + filterResultsTable.getColumnModel().getColumn(0).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.columnModel.title0")); // NOI18N + filterResultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.filterResultsTable.columnModel.title1")); // NOI18N + } + + org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonaSearchTopComponent.class, "PersonaSearchTopComponent.searchBtn.text")); // NOI18N + + javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); + searchPanel.setLayout(searchPanelLayout); + searchPanelLayout.setHorizontalGroup( + searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(searchPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(filterResultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(searchField) + .addGroup(searchPanelLayout.createSequentialGroup() + .addComponent(searchNameRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(searchAccountRadio) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 155, Short.MAX_VALUE) + .addComponent(searchBtn))) + .addContainerGap()) + ); + searchPanelLayout.setVerticalGroup( + searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(searchPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(searchField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(searchNameRadio) + .addComponent(searchAccountRadio) + .addComponent(searchBtn)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(filterResultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 320, Short.MAX_VALUE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(searchPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(searchPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void searchNameRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchNameRadioActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_searchNameRadioActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane filterResultsPane; + private javax.swing.JTable filterResultsTable; + private javax.swing.JRadioButton searchAccountRadio; + private javax.swing.JButton searchBtn; + private javax.swing.ButtonGroup searchButtonGroup; + private javax.swing.JTextField searchField; + private javax.swing.JRadioButton searchNameRadio; + private javax.swing.JPanel searchPanel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index 046331ca9f..8c88f29f53 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -65,6 +65,7 @@ import org.sleuthkit.datamodel.Pool; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TaggingManager.ContentTagChange; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.TskData; @@ -740,13 +741,13 @@ public class PortableCaseReportModule implements ReportModule { if (! oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } - ContentTag newContentTag = portableSkCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset()); + ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset()); // Get the image tag data associated with this tag (empty string if there is none) // and save it if present String appData = getImageTagDataForContentTag(tag); if (! appData.isEmpty()) { - addImageTagToPortableCase(newContentTag, appData); + addImageTagToPortableCase(newContentTag.getAddedTag(), appData); } } } @@ -847,7 +848,7 @@ public class PortableCaseReportModule implements ReportModule { if (! oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } - portableSkCase.addBlackboardArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment()); + portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment()); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index 5dcd725381..d01cea5c08 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -61,7 +61,7 @@ -