diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 987b2ffe78..0e6b98892f 100755 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -313,6 +313,7 @@ org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions + org.sleuthkit.autopsy.modules.encryptiondetection org.sleuthkit.autopsy.modules.filetypeid org.sleuthkit.autopsy.modules.hashdatabase org.sleuthkit.autopsy.modules.vmextractor diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index 06607d7d9b..2e618379e4 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -22,12 +22,18 @@ - + + + + - + + + + @@ -46,247 +52,336 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - + - - - - - - - - + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 4f95087f66..435bdc99b4 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -18,9 +18,18 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import javax.swing.JFileChooser; +import javax.swing.JOptionPane; import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.GeneralFilter; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.ModuleSettings; @@ -29,16 +38,23 @@ import org.sleuthkit.autopsy.report.ReportBranding; /** * Options panel that allow users to set application preferences. */ +@Messages({"AutopsyOptionsPanel.agencyLogoPreview.text=
No logo
selected
", + "AutopsyOptionsPanel.logoPanel.border.title=Logo", + "AutopsyOptionsPanel.viewPanel.border.title=View", + "AutopsyOptionsPanel.invalidImageFile.msg=The selected file was not able to be used as an agency logo.", + "AutopsyOptionsPanel.invalidImageFile.title=Invalid Image File"}) final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private final JFileChooser fc; + private static final Logger logger = Logger.getLogger(AutopsyOptionsPanel.class.getName()); AutopsyOptionsPanel() { initComponents(); fc = new JFileChooser(); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(false); + fc.setAcceptAllFileFilterUsed(false); fc.setFileFilter(new GeneralFilter(GeneralFilter.GRAPHIC_IMAGE_EXTS, GeneralFilter.GRAPHIC_IMG_DECR)); } @@ -53,7 +69,31 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { boolean useLocalTime = UserPreferences.displayTimesInLocalTime(); useLocalTimeRB.setSelected(useLocalTime); useGMTTimeRB.setSelected(!useLocalTime); - agencyLogoPathField.setText(ModuleSettings.getConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP)); + String path = ModuleSettings.getConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP); + try { + updateAgencyLogo(path); + } catch (IOException ex) { + logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex); + } + } + + private void updateAgencyLogo(String path) throws IOException { + agencyLogoPathField.setText(path); + ImageIcon agencyLogoIcon = new ImageIcon(); + agencyLogoPreview.setText(Bundle.AutopsyOptionsPanel_agencyLogoPreview_text()); + if (!agencyLogoPathField.getText().isEmpty()) { + File file = new File(agencyLogoPathField.getText()); + if (file.exists()) { + BufferedImage image = ImageIO.read(file); //create it as an image first to support BMP files + if (image == null) { + throw new IOException("Unable to read file as a BufferedImage for file " + file.toString()); + } + agencyLogoIcon = new ImageIcon(image.getScaledInstance(64, 64, 4)); + agencyLogoPreview.setText(""); + } + } + agencyLogoPreview.setIcon(agencyLogoIcon); + agencyLogoPreview.repaint(); } void store() { @@ -64,8 +104,8 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { UserPreferences.setHideSlackFilesInViewsTree(viewsHideSlackCB.isSelected()); UserPreferences.setDisplayTimesInLocalTime(useLocalTimeRB.isSelected()); if (!agencyLogoPathField.getText().isEmpty()) { - File image = new File(agencyLogoPathField.getText()); - if (image.exists()) { + File file = new File(agencyLogoPathField.getText()); + if (file.exists()) { ModuleSettings.setConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP, agencyLogoPathField.getText()); } } @@ -87,24 +127,87 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { buttonGroup3 = new javax.swing.ButtonGroup(); jScrollPane1 = new javax.swing.JScrollPane(); jPanel1 = new javax.swing.JPanel(); - useBestViewerRB = new javax.swing.JRadioButton(); - keepCurrentViewerRB = new javax.swing.JRadioButton(); - jLabelSelectFile = new javax.swing.JLabel(); - jLabelTimeDisplay = new javax.swing.JLabel(); - useLocalTimeRB = new javax.swing.JRadioButton(); - useGMTTimeRB = new javax.swing.JRadioButton(); - jLabelHideKnownFiles = new javax.swing.JLabel(); - dataSourcesHideKnownCB = new javax.swing.JCheckBox(); - viewsHideKnownCB = new javax.swing.JCheckBox(); - dataSourcesHideSlackCB = new javax.swing.JCheckBox(); - viewsHideSlackCB = new javax.swing.JCheckBox(); - jLabelHideSlackFiles = new javax.swing.JLabel(); + logoPanel = new javax.swing.JPanel(); agencyLogoImageLabel = new javax.swing.JLabel(); agencyLogoPathField = new javax.swing.JTextField(); browseLogosButton = new javax.swing.JButton(); + agencyLogoPreview = new javax.swing.JLabel(); + viewPanel = new javax.swing.JPanel(); + jLabelSelectFile = new javax.swing.JLabel(); + useBestViewerRB = new javax.swing.JRadioButton(); + keepCurrentViewerRB = new javax.swing.JRadioButton(); + jLabelHideKnownFiles = new javax.swing.JLabel(); + dataSourcesHideKnownCB = new javax.swing.JCheckBox(); + viewsHideKnownCB = new javax.swing.JCheckBox(); + jLabelHideSlackFiles = new javax.swing.JLabel(); + dataSourcesHideSlackCB = new javax.swing.JCheckBox(); + viewsHideSlackCB = new javax.swing.JCheckBox(); + jLabelTimeDisplay = new javax.swing.JLabel(); + useLocalTimeRB = new javax.swing.JRadioButton(); + useGMTTimeRB = new javax.swing.JRadioButton(); jScrollPane1.setBorder(null); + logoPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.logoPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(agencyLogoImageLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoImageLabel.text")); // NOI18N + + agencyLogoPathField.setEditable(false); + agencyLogoPathField.setBackground(new java.awt.Color(255, 255, 255)); + agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N + agencyLogoPathField.setFocusable(false); + agencyLogoPathField.setRequestFocusEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(browseLogosButton, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.browseLogosButton.text")); // NOI18N + browseLogosButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseLogosButtonActionPerformed(evt); + } + }); + + agencyLogoPreview.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + org.openide.awt.Mnemonics.setLocalizedText(agencyLogoPreview, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPreview.text")); // NOI18N + agencyLogoPreview.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + agencyLogoPreview.setMaximumSize(new java.awt.Dimension(64, 64)); + agencyLogoPreview.setMinimumSize(new java.awt.Dimension(64, 64)); + agencyLogoPreview.setPreferredSize(new java.awt.Dimension(64, 64)); + + javax.swing.GroupLayout logoPanelLayout = new javax.swing.GroupLayout(logoPanel); + logoPanel.setLayout(logoPanelLayout); + logoPanelLayout.setHorizontalGroup( + logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, logoPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(agencyLogoImageLabel) + .addGroup(logoPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(agencyLogoPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 259, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseLogosButton))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(agencyLogoPreview, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(149, Short.MAX_VALUE)) + ); + logoPanelLayout.setVerticalGroup( + logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(logoPanelLayout.createSequentialGroup() + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(agencyLogoPreview, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(logoPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(agencyLogoImageLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(agencyLogoPathField) + .addComponent(browseLogosButton)))) + .addGap(0, 0, 0)) + ); + + viewPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectFile, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSelectFile.text")); // NOI18N + buttonGroup1.add(useBestViewerRB); org.openide.awt.Mnemonics.setLocalizedText(useBestViewerRB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.text")); // NOI18N useBestViewerRB.setToolTipText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.useBestViewerRB.toolTipText")); // NOI18N @@ -123,7 +226,37 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectFile, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelSelectFile.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabelHideKnownFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideKnownFiles.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideKnownCB.text")); // NOI18N + dataSourcesHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourcesHideKnownCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(viewsHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideKnownCB.text")); // NOI18N + viewsHideKnownCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewsHideKnownCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelHideSlackFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideSlackFiles.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideSlackCB.text")); // NOI18N + dataSourcesHideSlackCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dataSourcesHideSlackCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(viewsHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideSlackCB.text")); // NOI18N + viewsHideSlackCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + viewsHideSlackCBActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(jLabelTimeDisplay, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelTimeDisplay.text")); // NOI18N @@ -143,93 +276,34 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { } }); - org.openide.awt.Mnemonics.setLocalizedText(jLabelHideKnownFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideKnownFiles.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideKnownCB.text")); // NOI18N - dataSourcesHideKnownCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - dataSourcesHideKnownCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(viewsHideKnownCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideKnownCB.text")); // NOI18N - viewsHideKnownCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - viewsHideKnownCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(dataSourcesHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.dataSourcesHideSlackCB.text")); // NOI18N - dataSourcesHideSlackCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - dataSourcesHideSlackCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(viewsHideSlackCB, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.viewsHideSlackCB.text")); // NOI18N - viewsHideSlackCB.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - viewsHideSlackCBActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(jLabelHideSlackFiles, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.jLabelHideSlackFiles.text")); // NOI18N - - org.openide.awt.Mnemonics.setLocalizedText(agencyLogoImageLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoImageLabel.text")); // NOI18N - - agencyLogoPathField.setEditable(false); - agencyLogoPathField.setBackground(new java.awt.Color(255, 255, 255)); - agencyLogoPathField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.agencyLogoPathField.text")); // NOI18N - agencyLogoPathField.setFocusable(false); - agencyLogoPathField.setRequestFocusEnabled(false); - - org.openide.awt.Mnemonics.setLocalizedText(browseLogosButton, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.browseLogosButton.text")); // NOI18N - browseLogosButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - browseLogosButtonActionPerformed(evt); - } - }); - - javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); - jPanel1.setLayout(jPanel1Layout); - jPanel1Layout.setHorizontalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + javax.swing.GroupLayout viewPanelLayout = new javax.swing.GroupLayout(viewPanel); + viewPanel.setLayout(viewPanelLayout); + viewPanelLayout.setHorizontalGroup( + viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, viewPanelLayout.createSequentialGroup() .addContainerGap() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabelTimeDisplay) - .addComponent(jLabelHideKnownFiles) - .addComponent(jLabelSelectFile) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(useLocalTimeRB) - .addComponent(useGMTTimeRB) - .addComponent(keepCurrentViewerRB) - .addComponent(useBestViewerRB) - .addComponent(dataSourcesHideKnownCB) - .addComponent(viewsHideKnownCB)))) - .addContainerGap(140, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabelHideSlackFiles) - .addComponent(agencyLogoImageLabel) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(agencyLogoPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 259, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseLogosButton)) - .addComponent(dataSourcesHideSlackCB) - .addComponent(viewsHideSlackCB)))) - .addGap(0, 0, Short.MAX_VALUE)))) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(viewPanelLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(useGMTTimeRB) + .addComponent(keepCurrentViewerRB) + .addComponent(useBestViewerRB) + .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(useLocalTimeRB) + .addComponent(dataSourcesHideSlackCB) + .addComponent(viewsHideSlackCB) + .addComponent(dataSourcesHideKnownCB) + .addComponent(viewsHideKnownCB)))) + .addComponent(jLabelHideSlackFiles) + .addComponent(jLabelTimeDisplay) + .addComponent(jLabelHideKnownFiles) + .addComponent(jLabelSelectFile)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - jPanel1Layout.setVerticalGroup( - jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() + viewPanelLayout.setVerticalGroup( + viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, viewPanelLayout.createSequentialGroup() .addContainerGap() .addComponent(jLabelSelectFile) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -253,14 +327,28 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(useLocalTimeRB) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(useGMTTimeRB) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(agencyLogoImageLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(agencyLogoPathField) - .addComponent(browseLogosButton)) - .addGap(35, 35, 35)) + .addComponent(useGMTTimeRB)) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(logoPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) ); jScrollPane1.setViewportView(jPanel1); @@ -269,11 +357,15 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 672, Short.MAX_VALUE) + .addGap(0, 0, 0)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1) + .addGroup(layout.createSequentialGroup() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 489, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -310,17 +402,32 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { }//GEN-LAST:event_viewsHideSlackCBActionPerformed private void browseLogosButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseLogosButtonActionPerformed + String oldLogoPath = agencyLogoPathField.getText(); int returnState = fc.showOpenDialog(this); if (returnState == JFileChooser.APPROVE_OPTION) { String path = fc.getSelectedFile().getPath(); - agencyLogoPathField.setText(path); - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + try { + updateAgencyLogo(path); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } catch (IOException | IndexOutOfBoundsException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(this.getClass(), + "AutopsyOptionsPanel.invalidImageFile.msg"), + NbBundle.getMessage(this.getClass(), "AutopsyOptionsPanel.invalidImageFile.title"), + JOptionPane.ERROR_MESSAGE); + try { + updateAgencyLogo(oldLogoPath); //restore previous setting if new one is invalid + } catch (IOException ex1) { + logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex1); + } + } } }//GEN-LAST:event_browseLogosButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel agencyLogoImageLabel; private javax.swing.JTextField agencyLogoPathField; + private javax.swing.JLabel agencyLogoPreview; private javax.swing.JButton browseLogosButton; private javax.swing.ButtonGroup buttonGroup1; private javax.swing.ButtonGroup buttonGroup3; @@ -333,9 +440,11 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JRadioButton keepCurrentViewerRB; + private javax.swing.JPanel logoPanel; private javax.swing.JRadioButton useBestViewerRB; private javax.swing.JRadioButton useGMTTimeRB; private javax.swing.JRadioButton useLocalTimeRB; + private javax.swing.JPanel viewPanel; private javax.swing.JCheckBox viewsHideKnownCB; private javax.swing.JCheckBox viewsHideSlackCB; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 5ac6cbd626..26a2cc03e0 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -1,7 +1,7 @@ CTL_DataContentAction=DataContent CTL_DataContentTopComponent=Data Content CTL_CustomAboutAction=About -OptionsCategory_Name_General=View +OptionsCategory_Name_General=Application OptionsCategory_Keywords_General=Autopsy Options HINT_DataContentTopComponent=This is a DataContent window HINT_NodeTableTopComponent=This is a DataResult window @@ -198,7 +198,6 @@ AutopsyOptionsPanel.agencyLogoPathField.text= SortChooserDialog.label=remove SortChooser.addCriteriaButton.text=Add Sort Criteria DataResultViewerThumbnail.sortButton.text=Sort - CriterionChooser.ascendingRadio.text=\u25b2 Ascending\n CriterionChooser.removeButton.text=Remove CriterionChooser.descendingRadio.text=\u25bc Descending diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java index 4195b33008..eafa8377b2 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeExtensions.java @@ -38,6 +38,7 @@ public class FileTypeExtensions { private final static List WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS private final static List PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS private final static List ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz", ".bz2"); //NON-NLS + private final static List DATABASE_EXTENSIONS = Arrays.asList(".db", ".db3", ".sqlite", ".sqlite3"); //NON-NLS public static List getImageExtensions() { return IMAGE_EXTENSIONS; @@ -75,6 +76,10 @@ public class FileTypeExtensions { return ARCHIVE_EXTENSIONS; } + public static List getDatabaseExtensions() { + return DATABASE_EXTENSIONS; + } + private FileTypeExtensions() { } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 305da8e953..2341fff12c 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -34,6 +34,7 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; @@ -423,6 +424,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } // root node filters + @Messages({"FileTypeExtensionFilters.tskDatabaseFilter.text=Databases"}) public static enum RootFilter implements AutopsyVisitableItem, SearchFilterInterface { TSK_IMAGE_FILTER(0, "TSK_IMAGE_FILTER", //NON-NLS @@ -437,10 +439,13 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { TSK_ARCHIVE_FILTER(3, "TSK_ARCHIVE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskArchiveFilter.text"), FileTypeExtensions.getArchiveExtensions()), - TSK_DOCUMENT_FILTER(3, "TSK_DOCUMENT_FILTER", //NON-NLS + TSK_DATABASE_FILTER(4, "TSK_DATABASE_FILTER", //NON-NLS + NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDatabaseFilter.text"), + FileTypeExtensions.getDatabaseExtensions()), + TSK_DOCUMENT_FILTER(5, "TSK_DOCUMENT_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskDocumentFilter.text"), Arrays.asList(".htm", ".html", ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".rtf")), //NON-NLS - TSK_EXECUTABLE_FILTER(3, "TSK_EXECUTABLE_FILTER", //NON-NLS + TSK_EXECUTABLE_FILTER(6, "TSK_EXECUTABLE_FILTER", //NON-NLS NbBundle.getMessage(FileTypesByExtension.class, "FileTypeExtensionFilters.tskExecFilter.text"), FileTypeExtensions.getExecutableExtensions()); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java new file mode 100755 index 0000000000..f18911c016 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -0,0 +1,263 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.FileIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * File ingest module to detect encryption. + */ +final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { + + private static final double ENTROPY_THRESHOLD = 7.5; + private static final int FILE_SIZE_THRESHOLD = 5242880; // 5MB + private static final int FILE_SIZE_MODULUS = 512; + private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; + + private final IngestServices SERVICES = IngestServices.getInstance(); + private final Logger LOGGER = SERVICES.getLogger(EncryptionDetectionModuleFactory.getModuleName()); + private FileTypeDetector fileTypeDetector; + private Blackboard blackboard; + private double entropy; + + /** + * Create a EncryptionDetectionFileIngestModule object that will detect files + * that are encrypted and create blackboard artifacts as appropriate. + */ + EncryptionDetectionFileIngestModule() { + } + + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + blackboard = Case.getCurrentCase().getServices().getBlackboard(); + try { + fileTypeDetector = new FileTypeDetector(); + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { + throw new IngestModule.IngestModuleException("Failed to create file type detector", ex); + } + } + + @Override + public IngestModule.ProcessResult process(AbstractFile file) { + + try { + if (isFileEncrypted(file)) { + return flagFile(file); + } + } catch (IOException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Unable to process file '%s'", Paths.get(file.getParentPath(), file.getName())), ex); + return IngestModule.ProcessResult.ERROR; + } + + return IngestModule.ProcessResult.OK; + } + + /** + * Create a blackboard artifact. + * + * @param The file to be processed. + * + * @return 'OK' if the file was processed successfully, or 'ERROR' if there + * was a problem. + */ + private IngestModule.ProcessResult flagFile(AbstractFile file) { + try { + BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + /* + * Send an event to update the view with the new result. + */ + SERVICES.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(); + detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("
\n"); + detailsSb.append("Entropy: ").append(entropy); + + SERVICES.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + file.getName(), + detailsSb.toString(), + file.getName(), + artifact)); + + return IngestModule.ProcessResult.OK; + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", Paths.get(file.getParentPath(), file.getName())), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + + /** + * This method checks if the AbstractFile input is encrypted. Initial + * qualifications require that it be an actual file that is not known, meets + * file size requirements, and has a MIME type of + * 'application/octet-stream'. + * + * @param file AbstractFile to be checked. + * + * @return True if the AbstractFile is encrypted. + */ + private boolean isFileEncrypted(AbstractFile file) throws IOException, TskCoreException { + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ + + boolean possiblyEncrypted = false; + + /* + * Qualify the file type. + */ + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR)) { + /* + * Qualify the file against hash databases. + */ + if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + /* + * Qualify the size. + */ + long contentSize = file.getSize(); + if (contentSize >= FILE_SIZE_THRESHOLD && (contentSize % FILE_SIZE_MODULUS) == 0) { + /* + * Qualify the MIME type. + */ + try { + String mimeType = fileTypeDetector.getFileType(file); + if (mimeType != null && mimeType.equals("application/octet-stream")) { + possiblyEncrypted = true; + } + } catch (TskCoreException ex) { + throw new TskCoreException("Failed to detect the file type.", ex); + } + } + } + } + + if (possiblyEncrypted) { + try { + entropy = calculateEntropy(file); + if (entropy > ENTROPY_THRESHOLD) { + return true; + } + } catch (IOException ex) { + throw new IOException("Unable to calculate the entropy.", ex); + } + } + + return false; + } + + /** + * Calculate the entropy of the file. The result is used to qualify the file + * as an encrypted file. + * + * @param file The file to be calculated against. + * + * @return The entropy of the file. + * + * @throws IOException If there is a failure closing or reading from the + * InputStream. + */ + private double calculateEntropy(AbstractFile file) throws IOException { + /* + * Logic in this method is based on + * https://github.com/willjasen/entropy/blob/master/entropy.java + */ + + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + + /* + * Determine the number of times each byte value appears. + */ + int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; + int readByte; + while ((readByte = bin.read()) != -1) { + byteOccurences[readByte]++; + } + + /* + * Calculate the entropy based on the byte occurence counts. + */ + long dataLength = file.getSize() - 1; + double entropyAccumulator = 0; + for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { + if (byteOccurences[i] > 0) { + double byteProbability = (double) byteOccurences[i] / (double) dataLength; + entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); + } + } + + return -entropyAccumulator; + + } catch (IOException ex) { + throw new IOException("IOException occurred while trying to read data from InputStream.", ex); + } finally { + try { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } catch (IOException ex) { + throw new IOException("Failed to close InputStream.", ex); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java new file mode 100755 index 0000000000..53eca1aec6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2017 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * A factory that creates file ingest modules that detect encryption. + */ +@ServiceProvider(service = IngestModuleFactory.class) +@Messages({ + "EncryptionDetectionFileIngestModule.moduleName.text=Encryption Detection", + "EncryptionDetectionFileIngestModule.getDesc.text=Looks for large files with high entropy." +}) +public class EncryptionDetectionModuleFactory extends IngestModuleFactoryAdapter { + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + /** + * Get the name of the module. + * + * @return The module name. + */ + static String getModuleName() { + return NbBundle.getMessage(EncryptionDetectionFileIngestModule.class, "EncryptionDetectionFileIngestModule.moduleName.text"); + } + + @Override + public String getModuleDescription() { + return NbBundle.getMessage(EncryptionDetectionFileIngestModule.class, "EncryptionDetectionFileIngestModule.getDesc.text"); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { + return new EncryptionDetectionFileIngestModule(); + } +} \ No newline at end of file