diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java index 310051b7f5..793d90bae3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageTask.java @@ -107,7 +107,7 @@ class AddImageTask implements Runnable { try { synchronized (tskAddImageProcessLock) { if (!tskAddImageProcessStopped) { - tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(imageDetails.timeZone, true, imageDetails.ignoreFatOrphanFiles, imageWriterPath); + tskAddImageProcess = currentCase.getSleuthkitCase().makeAddImageProcess(imageDetails.timeZone, true, imageDetails.ignoreFatOrphanFiles, imageWriterPath, imageDetails.password); } else { return; } @@ -319,8 +319,9 @@ class AddImageTask implements Runnable { String sha1; String sha256; ImageWriterSettings imageWriterSettings; + String password; - ImageDetails(String deviceId, Image image, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings) { + ImageDetails(String deviceId, Image image, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, ImageWriterSettings imageWriterSettings, String password) { this.deviceId = deviceId; this.image = image; this.sectorSize = sectorSize; @@ -330,6 +331,7 @@ class AddImageTask implements Runnable { this.sha1 = sha1; this.sha256 = sha256; this.imageWriterSettings = imageWriterSettings; + this.password = password; } String getImagePath() { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java index 320a2200f7..52393fee8f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardDataSourceSettingsVisual.java @@ -49,6 +49,8 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { private JPanel currentPanel; private final Map datasourceProcessorsMap = new HashMap<>(); + + private final PanelUpdateListener panelUpdateListener; private String currentDsp; @@ -60,6 +62,7 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { AddImageWizardDataSourceSettingsVisual(AddImageWizardDataSourceSettingsPanel wizPanel) { initComponents(); this.wizPanel = wizPanel; + this.panelUpdateListener = new PanelUpdateListener(); typePanel.setLayout(new BorderLayout()); discoverDataSourceProcessors(); currentDsp = ImageDSProcessor.getType(); //default value to the ImageDSProcessor @@ -107,24 +110,37 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { */ @SuppressWarnings("deprecation") private void updateCurrentPanel(JPanel panel) { + cleanupUpdateListener(currentPanel); currentPanel = panel; typePanel.removeAll(); typePanel.add(currentPanel, BorderLayout.CENTER); typePanel.validate(); typePanel.repaint(); - currentPanel.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString())) { - wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); - } - if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString())) { - wizPanel.moveFocusToNext(); - } - } - }); + cleanupUpdateListener(currentPanel); + currentPanel.addPropertyChangeListener(panelUpdateListener); this.wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); } + + /** + * Removes PanelUpdateListener from panel if found. + * @param panel The panel from which to remove the listener. + */ + private void cleanupUpdateListener(JPanel panel) { + if (panel == null) { + return; + } + + PropertyChangeListener[] listeners = panel.getPropertyChangeListeners(); + if (listeners == null) { + return; + } + + for (PropertyChangeListener listener: listeners) { + if (listener instanceof PanelUpdateListener) { + panel.removePropertyChangeListener(listener); + } + } + } /** * Returns the currently selected DS Processor @@ -221,4 +237,20 @@ final class AddImageWizardDataSourceSettingsVisual extends JPanel { protected abstract boolean addSeparatorAfter(JList list, Object value, int index); } + + /** + * A property change listener that responds to update events. + */ + private class PanelUpdateListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString())) { + wizPanel.enableNextButton(getCurrentDSProcessor().isPanelValid()); + } + if (evt.getPropertyName().equals(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString())) { + wizPanel.moveFocusToNext(); + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 92b64b840b..9fc3d3315e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -270,3 +270,6 @@ LocalFilesPanel.timestampToIncludeLabel.text=Timestamps To Include: LocalFilesPanel.accessTimeCheckBox.text=Access Time - Can be changed when the file is opened LocalFilesPanel.timeStampToIncludeLabel.text=Timestamps To Include: LocalFilesPanel.timeStampNoteLabel.text=NOTE: Time stamps may have changed when the files were copied to the current location. +ImageFilePanel.passwordLabel.text=Bitlocker Password (optional): +ImageFilePanel.passwordTextField.text= +ImageFilePanel.loadingLabel.text=loading... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED index 14465bba03..f711125611 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties-MERGED @@ -151,7 +151,7 @@ GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) GeneralFilter.executableDesc.text=Executables (*.exe) GeneralFilter.graphicImageDesc.text=Images (*.png, *.jpg, *.jpeg, *.gif, *.bmp) GeneralFilter.rawImageDesc.text=Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin) -GeneralFilter.virtualMachineImageDesc.text=Virtual Machines (*.vmdk, *.vhd) +GeneralFilter.virtualMachineImageDesc.text=Virtual Machines (*.vmdk, *.vhd, *.vhdx) ImageFilePanel.000.confirmationMessage=The selected file has extenson .001 but there is a .000 file in the sequence of raw images.\nShould the .000 file be used as the start, instead of the selected .001 file?\n ImageFilePanel.moduleErr=Module Error ImageFilePanel.moduleErr.msg=A module caused an error listening to ImageFilePanel updates. See log to determine which module. Some data could be incomplete.\n @@ -159,6 +159,10 @@ ImageFilePanel.validatePanel.dataSourceOnCDriveError=Warning: Path to multi-user ImageFilePanel.validatePanel.invalidMD5=Invalid MD5 hash ImageFilePanel.validatePanel.invalidSHA1=Invalid SHA1 hash ImageFilePanel.validatePanel.invalidSHA256=Invalid SHA256 hash +# {0} - imageOpenError +ImageFilePanel_validatePanel_imageOpenError=

An error occurred while opening the image:{0}

+ImageFilePanel_validatePanel_unknownError=

An unknown error occurred while attempting to validate the image

+ImageFilePanel_validatePanel_unknownErrorMsg= IngestJobInfoPanel.IngestJobTableModel.EndTime.header=End Time IngestJobInfoPanel.IngestJobTableModel.IngestStatus.header=Ingest Status IngestJobInfoPanel.IngestJobTableModel.StartTime.header=Start Time @@ -504,3 +508,6 @@ LocalFilesPanel.timestampToIncludeLabel.text=Timestamps To Include: LocalFilesPanel.accessTimeCheckBox.text=Access Time - Can be changed when the file is opened LocalFilesPanel.timeStampToIncludeLabel.text=Timestamps To Include: LocalFilesPanel.timeStampNoteLabel.text=NOTE: Time stamps may have changed when the files were copied to the current location. +ImageFilePanel.passwordLabel.text=Bitlocker Password (optional): +ImageFilePanel.passwordTextField.text= +ImageFilePanel.loadingLabel.text=loading... diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java index 9b174f28f7..fb0c529a4e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageDSProcessor.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.logging.Level; import java.util.UUID; import javax.swing.filechooser.FileFilter; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProviders; @@ -82,6 +83,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour private String sha1; private String sha256; private Host host = null; + private String password; static { filtersList.add(allFilter); @@ -162,7 +164,9 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ @Override public boolean isPanelValid() { - return configPanel.validatePanel(); + // before attempting to validate the panel (a potentially long running operation), + // check if the validation is loading or on delay. + return !configPanel.isValidationLoading() && configPanel.validatePanel(); } /** @@ -181,7 +185,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - run(null, progressMonitor, callback); + run(null, null, progressMonitor, callback); } /** @@ -201,12 +205,19 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour */ @Override public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + run(null, host, progressMonitor, callback); + } + + + @Override + public void run(String password, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { ingestStream = new DefaultIngestStream(); readConfigSettings(); this.host = host; + this.password = StringUtils.defaultString(password, this.password); try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host); + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.password, this.host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -215,117 +226,9 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour return; } - doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progressMonitor, callback); - } - - /** - * Adds a data source to the case database using a background task in a - * separate thread and the settings provided by the selection and - * configuration panel. Files found during ingest will be sent directly to - * the IngestStream provided. Returns as soon as the background task is - * started. The background task uses a callback object to signal task - * completion and return results. - * - * This method should not be called unless isPanelValid returns true, and - * should only be called for DSPs that support ingest streams. - * - * @param settings The ingest job settings. - * @param progress Progress monitor that will be used by the background task - * to report progress. - * @param callBack Callback that will be used by the background task to - * return results. - */ - @Override - public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, - DataSourceProcessorCallback callBack) { - runWithIngestStream(null, settings, progress, callBack); - } - - /** - * Adds a data source to the case database using a background task in a - * separate thread and the settings provided by the selection and - * configuration panel. Files found during ingest will be sent directly to - * the IngestStream provided. Returns as soon as the background task is - * started. The background task uses a callback object to signal task - * completion and return results. - * - * This method should not be called unless isPanelValid returns true, and - * should only be called for DSPs that support ingest streams. - * - * @param host The host for this data source. - * @param settings The ingest job settings. - * @param progress Progress monitor that will be used by the background task - * to report progress. - * @param callBack Callback that will be used by the background task to - * return results. - */ - @Override - public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, - DataSourceProcessorCallback callBack) { - - // Read the settings from the wizard - readConfigSettings(); - this.host = host; - - // Set up the data source before creating the ingest stream - try { - image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.host); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); - final List errors = new ArrayList<>(); - errors.add(ex.getMessage()); - callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); - return; - } - - // Now initialize the ingest stream - try { - ingestStream = IngestManager.getInstance().openIngestStream(image, settings); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error starting ingest modules", ex); - // There was an error with ingest, but the data source has already been added - // so proceed with the defaultIngestStream. Code in openIngestStream - // should have caused a dialog to popup with the errors. - ingestStream = new DefaultIngestStream(); - } - - doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, progress, callBack); - } - - /** - * Store the options from the config panel. - */ - private void readConfigSettings() { - configPanel.storeSettings(); - deviceId = UUID.randomUUID().toString(); - imagePath = configPanel.getContentPaths(); - sectorSize = configPanel.getSectorSize(); - timeZone = configPanel.getTimeZone(); - ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); - md5 = configPanel.getMd5(); - if (md5.isEmpty()) { - md5 = null; - } - sha1 = configPanel.getSha1(); - if (sha1.isEmpty()) { - sha1 = null; - } - sha256 = configPanel.getSha256(); - if (sha256.isEmpty()) { - sha256 = null; - } - } - - /** - * Check if this DSP supports ingest streams. - * - * @return True if this DSP supports an ingest stream, false otherwise. - */ - @Override - public boolean supportsIngestStream() { - return true; + doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, this.password, progressMonitor, callback); } + /** * Adds a data source to the case database using a background task in a @@ -352,7 +255,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour ingestStream = new DefaultIngestStream(); try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId); + new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, null, null); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -361,9 +264,134 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour return; } - doAddImageProcess(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callback); + doAddImageProcess(deviceId, imagePath, 0, timeZone, ignoreFatOrphanFiles, null, null, null, this.password, progressMonitor, callback); + } + + + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Files found during ingest will be sent directly to + * the IngestStream provided. Returns as soon as the background task is + * started. The background task uses a callback object to signal task + * completion and return results. + * + * This method should not be called unless isPanelValid returns true, and + * should only be called for DSPs that support ingest streams. + * + * @param settings The ingest job settings. + * @param progress Progress monitor that will be used by the background task + * to report progress. + * @param callBack Callback that will be used by the background task to + * return results. + */ + @Override + public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, + DataSourceProcessorCallback callBack) { + runWithIngestStream(null, null, settings, progress, callBack); } + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Files found during ingest will be sent directly to + * the IngestStream provided. Returns as soon as the background task is + * started. The background task uses a callback object to signal task + * completion and return results. + * + * This method should not be called unless isPanelValid returns true, and + * should only be called for DSPs that support ingest streams. + * + * @param host The host for this data source. + * @param settings The ingest job settings. + * @param progress Progress monitor that will be used by the background task + * to report progress. + * @param callBack Callback that will be used by the background task to + * return results. + */ + @Override + public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, + DataSourceProcessorCallback callBack) { + runWithIngestStream(null, host, settings, progress, callBack); + } + + + @Override + public void runWithIngestStream(String password, Host host, IngestJobSettings settings, + DataSourceProcessorProgressMonitor progress, DataSourceProcessorCallback callBack) { + + // Read the settings from the wizard + readConfigSettings(); + this.host = host; + this.password = StringUtils.defaultString(password, this.password); + + // Set up the data source before creating the ingest stream + try { + image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.password, this.host); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); + final List errors = new ArrayList<>(); + errors.add(ex.getMessage()); + callBack.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>()); + return; + } + + // Now initialize the ingest stream + try { + ingestStream = IngestManager.getInstance().openIngestStream(image, settings); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error starting ingest modules", ex); + // There was an error with ingest, but the data source has already been added + // so proceed with the defaultIngestStream. Code in openIngestStream + // should have caused a dialog to popup with the errors. + ingestStream = new DefaultIngestStream(); + } + + doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, this.password, progress, callBack); + } + + + /** + * Store the options from the config panel. + */ + private void readConfigSettings() { + configPanel.storeSettings(); + deviceId = UUID.randomUUID().toString(); + imagePath = configPanel.getContentPaths(); + sectorSize = configPanel.getSectorSize(); + timeZone = configPanel.getTimeZone(); + ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); + md5 = configPanel.getMd5(); + if (md5.isEmpty()) { + md5 = null; + } + sha1 = configPanel.getSha1(); + if (sha1.isEmpty()) { + sha1 = null; + } + sha256 = configPanel.getSha256(); + if (sha256.isEmpty()) { + sha256 = null; + } + this.password = configPanel.getPassword(); + if (this.password.isEmpty()) { + password = null; + } + } + + /** + * Check if this DSP supports ingest streams. + * + * @return True if this DSP supports an ingest stream, false otherwise. + */ + @Override + public boolean supportsIngestStream() { + return true; + } + + /** * Adds a data source to the case database using a background task in a * separate thread and the given settings instead of those provided by the @@ -389,11 +417,12 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour * @param md5 The MD5 hash of the image, may be null. * @param sha1 The SHA-1 hash of the image, may be null. * @param sha256 The SHA-256 hash of the image, may be null. + * @param password Password for image decryption. May be null. * @param progressMonitor Progress monitor for reporting progress * during processing. * @param callback Callback to call when processing is done. */ - private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + private void doAddImageProcess(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, String password, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { // If the data source or ingest stream haven't been initialized, stop processing if (ingestStream == null) { @@ -413,7 +442,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour return; } - AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null); + AddImageTask.ImageDetails imageDetails = new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, password); addImageTask = new AddImageTask(imageDetails, progressMonitor, new StreamingAddDataSourceCallbacks(ingestStream), @@ -449,6 +478,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour timeZone = null; ignoreFatOrphanFiles = false; host = null; + password = null; configPanel.reset(); } @@ -463,6 +493,13 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour @Override public int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException { + return canProcess(dataSourcePath, null); + } + + + + @Override + public int canProcess(Path dataSourcePath, String password) throws AutoIngestDataSourceProcessorException { // check file extension for supported types if (!isAcceptedByFiler(dataSourcePath.toFile(), filtersList)) { @@ -470,10 +507,20 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour } try { - // verify that the image has a file system that TSK can process - if (!DataSourceUtils.imageHasFileSystem(dataSourcePath)) { - // image does not have a file system that TSK can process - return 0; + if (password == null) { + + // verify that the image has a file system that TSK can process + if (!DataSourceUtils.imageHasFileSystem(dataSourcePath)) { + // image does not have a file system that TSK can process + return 0; + } + } else { + // verify that the image has a file system that TSK can process + if (!DataSourceUtils.imageHasFileSystem(dataSourcePath, password)) { + // image does not have a file system that TSK can process + return 0; + } + } } catch (Exception ex) { throw new AutoIngestDataSourceProcessorException("Exception inside canProcess() method", ex); @@ -485,23 +532,29 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour @Override public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { - process(deviceId, dataSourcePath, null, progressMonitor, callBack); + process(deviceId, dataSourcePath, null, null, progressMonitor, callBack); } @Override public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + process(deviceId, dataSourcePath, null, host, progressMonitor, callBack); + } + + @Override + public void process(String deviceId, Path dataSourcePath, String password, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { // this method does not use the config panel this.deviceId = deviceId; this.imagePath = dataSourcePath.toString(); this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); + this.password = StringUtils.defaultString(password, this.password); this.host = host; this.ignoreFatOrphanFiles = false; ingestStream = new DefaultIngestStream(); try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, host); + new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, this.password, host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -510,28 +563,36 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour return; } - doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack); + doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, this.password, progressMonitor, callBack); } + + @Override public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { - return processWithIngestStream(deviceId, dataSourcePath, null, settings, progressMonitor, callBack); + return processWithIngestStream(deviceId, dataSourcePath, null, null, settings, progressMonitor, callBack); } @Override public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { + return processWithIngestStream(deviceId, dataSourcePath, null, host, settings, progressMonitor, callBack); + } + + @Override + public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, String password, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { // this method does not use the config panel this.deviceId = deviceId; this.imagePath = dataSourcePath.toString(); this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); this.host = host; + this.password = password; this.ignoreFatOrphanFiles = false; // Set up the data source before creating the ingest stream try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), - new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, host); + new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, password, host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); final List errors = new ArrayList<>(); @@ -551,8 +612,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour return null; } - doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, progressMonitor, callBack); + doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, password, progressMonitor, callBack); return ingestStream; } + + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form index 0c634afc24..6e3633641d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.form @@ -19,108 +19,10 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -128,6 +30,11 @@ + + + + + @@ -138,6 +45,11 @@ + + + + + @@ -145,6 +57,11 @@ + + + + + @@ -152,6 +69,11 @@ + + + + + @@ -163,6 +85,11 @@ + + + + + @@ -173,6 +100,11 @@ + + + + + @@ -182,7 +114,22 @@ + + + + + + + + + + + + + + + @@ -190,6 +137,11 @@ + + + + + @@ -200,6 +152,11 @@ + + + + + @@ -208,6 +165,11 @@ + + + + + @@ -216,6 +178,11 @@ + + + + + @@ -224,6 +191,11 @@ + + + + + @@ -232,6 +204,11 @@ + + + + + @@ -240,6 +217,11 @@ + + + + + @@ -248,6 +230,11 @@ + + + + + @@ -256,6 +243,11 @@ + + + + + @@ -264,6 +256,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java index eda7149f66..7213838357 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ImageFilePanel.java @@ -18,9 +18,16 @@ */ package org.sleuthkit.autopsy.casemodule; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.io.File; import java.util.Calendar; import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; +import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -32,11 +39,14 @@ import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.DriveUtils; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; import org.sleuthkit.autopsy.coreutils.TimeZoneUtils; import org.sleuthkit.autopsy.guiutils.JFileChooserFactory; import org.sleuthkit.datamodel.HashUtility; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.SleuthkitJNI.TestOpenImageResult; /** * Panel for adding an image file such as .img, .E0x, .00x, etc. Allows the user @@ -44,8 +54,10 @@ import org.sleuthkit.datamodel.HashUtility; * files in FAT32. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -public class ImageFilePanel extends JPanel implements DocumentListener { +public class ImageFilePanel extends JPanel { + private static final Logger logger = Logger.getLogger(AddImageTask.class.getName()); + private static final long serialVersionUID = 1L; private static final String PROP_LASTIMAGE_PATH = "LBL_LastImage_PATH"; //NON-NLS private static final String[] SECTOR_SIZE_CHOICES = {"Auto Detect", "512", "1024", "2048", "4096"}; @@ -53,6 +65,15 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private JFileChooser fileChooser; private final String contextName; private final List fileChooserFilters; + + private static int VALIDATE_TIMEOUT_MILLIS = 1000; + static ScheduledThreadPoolExecutor delayedValidationService = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("ImageFilePanel delayed validation").build()); + + private final ReentrantLock validationWaitingLock = new ReentrantLock(); + private final ReentrantLock validationLock = new ReentrantLock(); + + private Runnable validateAction = null; + private Future validateFuture = null; /** * Creates new form ImageFilePanel @@ -76,6 +97,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { sectorSizeComboBox.setSelectedIndex(0); errorLabel.setVisible(false); + loadingLabel.setVisible(false); this.fileChooserFilters = fileChooserFilters; } @@ -105,11 +127,17 @@ public class ImageFilePanel extends JPanel implements DocumentListener { */ public static synchronized ImageFilePanel createInstance(String context, List fileChooserFilters) { ImageFilePanel instance = new ImageFilePanel(context, fileChooserFilters); + DocumentListener delayedValidationListener = instance.new DelayedValidationDocListener(); + // post-constructor initialization of listener support without leaking references of uninitialized objects - instance.getPathTextField().getDocument().addDocumentListener(instance); - instance.getMd5TextFieldField().getDocument().addDocumentListener(instance); - instance.getSha1TextField().getDocument().addDocumentListener(instance); - instance.getSha256TextField().getDocument().addDocumentListener(instance); + for (JTextField textField: List.of( + instance.getPathTextField(), + instance.getMd5TextFieldField(), + instance.getSha1TextField(), + instance.getSha256TextField(), + instance.getPasswordTextField())) { + textField.getDocument().addDocumentListener(delayedValidationListener); + } return instance; } @@ -129,6 +157,10 @@ public class ImageFilePanel extends JPanel implements DocumentListener { return sha256HashTextField; } + private JTextField getPasswordTextField() { + return passwordTextField; + } + private JFileChooser getChooser() { if(fileChooser == null) { fileChooser = fileChooserHelper.getChooser(); @@ -151,6 +183,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener { */ // //GEN-BEGIN:initComponents private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; pathLabel = new javax.swing.JLabel(); browseButton = new javax.swing.JButton(); @@ -169,11 +202,25 @@ public class ImageFilePanel extends JPanel implements DocumentListener { md5HashLabel = new javax.swing.JLabel(); hashValuesLabel = new javax.swing.JLabel(); hashValuesNoteLabel = new javax.swing.JLabel(); + passwordLabel = new javax.swing.JLabel(); + passwordTextField = new javax.swing.JTextField(); + javax.swing.JPanel spacer = new javax.swing.JPanel(); + loadingLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(0, 65)); setPreferredSize(new java.awt.Dimension(403, 65)); + setLayout(new java.awt.GridBagLayout()); org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.pathLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + add(pathLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.browseButton.text")); // NOI18N browseButton.addActionListener(new java.awt.event.ActionListener() { @@ -181,125 +228,227 @@ public class ImageFilePanel extends JPanel implements DocumentListener { browseButtonActionPerformed(evt); } }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(browseButton, gridBagConstraints); pathTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.pathTextField.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(pathTextField, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.timeZoneLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(timeZoneLabel, gridBagConstraints); timeZoneComboBox.setMaximumRowCount(30); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(timeZoneComboBox, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.noFatOrphansCheckbox.text")); // NOI18N noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.noFatOrphansCheckbox.toolTipText")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(noFatOrphansCheckbox, gridBagConstraints); errorLabel.setForeground(new java.awt.Color(255, 0, 0)); org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.errorLabel.text")); // NOI18N + errorLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); + errorLabel.setMaximumSize(new java.awt.Dimension(500, 60)); + errorLabel.setMinimumSize(new java.awt.Dimension(200, 20)); + errorLabel.setPreferredSize(new java.awt.Dimension(200, 60)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 11; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + add(errorLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(sectorSizeLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.sectorSizeLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(sectorSizeLabel, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(sectorSizeComboBox, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(sha256HashLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.sha256HashLabel.text")); // NOI18N sha256HashLabel.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 9; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(sha256HashLabel, gridBagConstraints); sha256HashTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.sha256HashTextField.text")); // NOI18N sha256HashTextField.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 9; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(sha256HashTextField, gridBagConstraints); sha1HashTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.sha1HashTextField.text")); // NOI18N sha1HashTextField.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 8; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(sha1HashTextField, gridBagConstraints); md5HashTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.md5HashTextField.text")); // NOI18N md5HashTextField.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 7; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(md5HashTextField, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(sha1HashLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.sha1HashLabel.text")); // NOI18N sha1HashLabel.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 8; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(sha1HashLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(md5HashLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.md5HashLabel.text")); // NOI18N md5HashLabel.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 7; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(md5HashLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(hashValuesLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.hashValuesLabel.text")); // NOI18N hashValuesLabel.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 6; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + add(hashValuesLabel, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(hashValuesNoteLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.hashValuesNoteLabel.text")); // NOI18N hashValuesNoteLabel.setEnabled(false); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 10; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(hashValuesNoteLabel, gridBagConstraints); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pathTextField) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(browseButton) - .addGap(2, 2, 2)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(pathLabel) - .addComponent(noFatOrphansCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 262, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 368, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(errorLabel) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 455, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(sectorSizeLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 455, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(md5HashLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(md5HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 455, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(sha1HashLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(sha1HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 455, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(sha256HashLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(sha256HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 455, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addComponent(hashValuesNoteLabel) - .addComponent(hashValuesLabel)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + org.openide.awt.Mnemonics.setLocalizedText(passwordLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.passwordLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 5; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + add(passwordLabel, gridBagConstraints); + + passwordTextField.setText(org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.passwordTextField.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 5; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 5, 5); + add(passwordTextField, gridBagConstraints); + + javax.swing.GroupLayout spacerLayout = new javax.swing.GroupLayout(spacer); + spacer.setLayout(spacerLayout); + spacerLayout.setHorizontalGroup( + spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pathLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(browseButton) - .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(noFatOrphansCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(timeZoneLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(sectorSizeLabel)) - .addGap(39, 39, 39) - .addComponent(hashValuesLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(md5HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(md5HashLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(sha1HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(sha1HashLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(sha256HashTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(sha256HashLabel)) - .addGap(18, 18, 18) - .addComponent(hashValuesNoteLabel) - .addGap(18, 18, 18) - .addComponent(errorLabel) - .addContainerGap(51, Short.MAX_VALUE)) + spacerLayout.setVerticalGroup( + spacerLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 12; + gridBagConstraints.weighty = 1.0; + add(spacer, gridBagConstraints); + + loadingLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/working_spinner.gif"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(loadingLabel, org.openide.util.NbBundle.getMessage(ImageFilePanel.class, "ImageFilePanel.loadingLabel.text")); // NOI18N + loadingLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); + loadingLabel.setMaximumSize(new java.awt.Dimension(500, 60)); + loadingLabel.setMinimumSize(new java.awt.Dimension(200, 20)); + loadingLabel.setPreferredSize(new java.awt.Dimension(200, 60)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 11; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + add(loadingLabel, gridBagConstraints); }// //GEN-END:initComponents @NbBundle.Messages({"ImageFilePanel.000.confirmationMessage=The selected file" @@ -349,9 +498,12 @@ public class ImageFilePanel extends JPanel implements DocumentListener { private javax.swing.JLabel errorLabel; private javax.swing.JLabel hashValuesLabel; private javax.swing.JLabel hashValuesNoteLabel; + private javax.swing.JLabel loadingLabel; private javax.swing.JLabel md5HashLabel; private javax.swing.JTextField md5HashTextField; private javax.swing.JCheckBox noFatOrphansCheckbox; + private javax.swing.JLabel passwordLabel; + private javax.swing.JTextField passwordTextField; private javax.swing.JLabel pathLabel; private javax.swing.JTextField pathTextField; private javax.swing.JComboBox sectorSizeComboBox; @@ -436,6 +588,10 @@ public class ImageFilePanel extends JPanel implements DocumentListener { String getSha256() { return this.sha256HashTextField.getText(); } + + String getPassword() { + return this.passwordTextField.getText(); + } public void reset() { //reset the UI elements to default @@ -443,6 +599,24 @@ public class ImageFilePanel extends JPanel implements DocumentListener { this.md5HashTextField.setText(null); this.sha1HashTextField.setText(null); this.sha256HashTextField.setText(null); + this.passwordTextField.setText(null); + } + + /** + * Sets UI enabled state. + * + * @param enabled True + */ + private void setUIEnabled(boolean enabled, boolean validNonE01) { + this.browseButton.setEnabled(enabled); + this.noFatOrphansCheckbox.setEnabled(enabled); + this.passwordTextField.setEnabled(enabled); + this.pathTextField.setEnabled(enabled); + this.sectorSizeComboBox.setEnabled(enabled); + this.md5HashTextField.setEnabled(enabled && validNonE01); + this.sha1HashTextField.setEnabled(enabled && validNonE01); + this.sha256HashTextField.setEnabled(enabled && validNonE01); + this.timeZoneComboBox.setEnabled(enabled); } /** @@ -454,49 +628,113 @@ public class ImageFilePanel extends JPanel implements DocumentListener { "ImageFilePanel.validatePanel.dataSourceOnCDriveError=Warning: Path to multi-user data source is on \"C:\" drive", "ImageFilePanel.validatePanel.invalidMD5=Invalid MD5 hash", "ImageFilePanel.validatePanel.invalidSHA1=Invalid SHA1 hash", - "ImageFilePanel.validatePanel.invalidSHA256=Invalid SHA256 hash",}) + "ImageFilePanel.validatePanel.invalidSHA256=Invalid SHA256 hash", + "# {0} - imageOpenError", + "ImageFilePanel_validatePanel_imageOpenError=

An error occurred while opening the image:{0}

", + "ImageFilePanel_validatePanel_unknownErrorMsg=", + "ImageFilePanel_validatePanel_unknownError=

An unknown error occurred while attempting to validate the image

" + }) public boolean validatePanel() { - errorLabel.setVisible(false); + return runWithLock(this.validationLock, () -> { + boolean validNonE01 = true; + try { - String path = getContentPaths(); - if (!isImagePathValid()) { - return false; - } + // acquire field values at the beginning to minimize chance of changing while validating. + String path = getContentPaths(); - if (!StringUtils.isBlank(getMd5()) && !HashUtility.isValidMd5Hash(getMd5())) { + validNonE01 = isValidNonE01(path); + setUIEnabled(false, validNonE01); + + String md5 = getMd5(); + String sha1 = getSha1(); + String sha256 = getSha256(); + String password = getPassword(); + + if (!isImagePathValid(path)) { + showError(null); + return false; + } + + if (!StringUtils.isBlank(md5) && !HashUtility.isValidMd5Hash(md5)) { + showError(Bundle.ImageFilePanel_validatePanel_invalidMD5()); + return false; + } + + if (!StringUtils.isBlank(sha1) && !HashUtility.isValidSha1Hash(sha1)) { + showError(Bundle.ImageFilePanel_validatePanel_invalidSHA1()); + return false; + } + + if (!StringUtils.isBlank(sha256) && !HashUtility.isValidSha256Hash(sha256)) { + showError(Bundle.ImageFilePanel_validatePanel_invalidSHA256()); + return false; + } + + try { + TestOpenImageResult testResult = SleuthkitJNI.testOpenImage(path, password); + if (!testResult.wasSuccessful()) { + showError(Bundle.ImageFilePanel_validatePanel_imageOpenError( + StringUtils.defaultIfBlank( + testResult.getMessage(), + Bundle.ImageFilePanel_validatePanel_unknownErrorMsg()))); + return false; + } + } catch (Throwable t) { + logger.log(Level.SEVERE, "An unknown error occurred test opening image: " + path, t); + showError(Bundle.ImageFilePanel_validatePanel_unknownError()); + return false; + } + + if (!PathValidator.isValidForCaseType(path, Case.getCurrentCase().getCaseType())) { + showError(Bundle.ImageFilePanel_validatePanel_dataSourceOnCDriveError()); + } else { + showError(null); + } + return true; + } finally { + setUIEnabled(true, validNonE01); + } + }); + } + + /** + * Show an error message if error message is non-empty. Otherwise, hide + * error message. Either way, hide loading label. + * + * @param errorMessage The error message to show or null for no error. + */ + private void showError(String errorMessage) { + loadingLabel.setVisible(false); + if (StringUtils.isNotBlank(errorMessage)) { errorLabel.setVisible(true); - errorLabel.setText(Bundle.ImageFilePanel_validatePanel_invalidMD5()); - return false; + errorLabel.setText(errorMessage); + } else { + errorLabel.setVisible(false); + errorLabel.setText(""); } + } - if (!StringUtils.isBlank(getSha1()) && !HashUtility.isValidSha1Hash(getSha1())) { - errorLabel.setVisible(true); - errorLabel.setText(Bundle.ImageFilePanel_validatePanel_invalidSHA1()); + /** + * Returns true if path is valid for processing. + * + * @param path The path. + * @return True if valid for processing. + */ + private boolean isImagePathValid(String path) { + if (StringUtils.isBlank(path) || (!(new File(path).isFile() || DriveUtils.isPhysicalDrive(path) || DriveUtils.isPartition(path)))) { return false; } - if (!StringUtils.isBlank(getSha256()) && !HashUtility.isValidSha256Hash(getSha256())) { - errorLabel.setVisible(true); - errorLabel.setText(Bundle.ImageFilePanel_validatePanel_invalidSHA256()); - return false; - } - - if (!PathValidator.isValidForCaseType(path, Case.getCurrentCase().getCaseType())) { - errorLabel.setVisible(true); - errorLabel.setText(Bundle.ImageFilePanel_validatePanel_dataSourceOnCDriveError()); - } - return true; } - private boolean isImagePathValid() { - String path = getContentPaths(); - - if (StringUtils.isBlank(path) || (!(new File(path).isFile() || DriveUtils.isPhysicalDrive(path) || DriveUtils.isPartition(path)))) { - return false; - } - - return true; + /** + * Returns true if the path is a valid image that is not an E01. + * @param path The path. + * @return True if valid image and not E01. + */ + private boolean isValidNonE01(String path) { + return StringUtils.isNotBlank(path) && isImagePathValid(path) && !path.toLowerCase().endsWith(".e01"); } public void storeSettings() { @@ -514,21 +752,6 @@ public class ImageFilePanel extends JPanel implements DocumentListener { } } - @Override - public void insertUpdate(DocumentEvent e) { - updateHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateHelper(); - } - /** * Update functions are called by the pathTextField which has this set as * it's DocumentEventListener. Each update function fires a property change @@ -539,12 +762,13 @@ public class ImageFilePanel extends JPanel implements DocumentListener { "ImageFilePanel.moduleErr.msg=A module caused an error listening to ImageFilePanel updates." + " See log to determine which module. Some data could be incomplete.\n"}) private void updateHelper() { - if (isImagePathValid() && !getContentPaths().toLowerCase().endsWith(".e01")) { + String path = getContentPaths(); + if (isValidNonE01(path)) { setHashValuesComponentsEnabled(true); } else { setHashValuesComponentsEnabled(false); } - + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); } @@ -554,4 +778,119 @@ public class ImageFilePanel extends JPanel implements DocumentListener { public void select() { pathTextField.requestFocusInWindow(); } + + /** + * Runs the supplier action with the reentrant lock or blocks until + * acquired. + * + * @param + * @param lock The reentrant lock. + * @param action The action to run. + * @return The value of the supplier. + */ + private T runWithLock(ReentrantLock lock, Supplier action) { + try { + lock.lock(); + return action.get(); + } finally { + lock.unlock(); + } + } + + /** + * @return True if the panel is on a delay for validating (i.e. typing a + * password for bitlocker). + */ + public boolean isValidationLoading() { + return runWithLock(this.validationWaitingLock, () -> this.validateFuture != null + && !this.validateFuture.isCancelled() + && !this.validateFuture.isDone()); + } + + /** + * This class validates on a delay canceling any tasks previously scheduled + * so that password validation doesn't lock up the system. + */ + private class DelayedValidationDocListener implements DocumentListener { + + @Override + public void insertUpdate(DocumentEvent e) { + delayValidate(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + delayValidate(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + delayValidate(); + } + + /** + * Run validation on a delay to avoid password checking too many times + * while typing. + */ + private void delayValidate() { + + boolean triggerUpdate = runWithLock(validationWaitingLock, () -> { + + boolean toRetTriggerUpdate = false; + if (isValidationLoading()) { + validateFuture.cancel(true); + toRetTriggerUpdate = true; + } + + validateAction = new ValidationRunnable(); + + validateFuture = delayedValidationService.schedule( + validateAction, + VALIDATE_TIMEOUT_MILLIS, + TimeUnit.MILLISECONDS); + + return toRetTriggerUpdate; + }); + + errorLabel.setVisible(false); + loadingLabel.setVisible(true); + + // trigger invalidation after setting up new runnable if not already triggered + if (triggerUpdate) { + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + } + + /** + * Runnable to run the updateHelper if the validation action remains + * this runnable. + */ + private class ValidationRunnable implements Runnable { + + @Override + public void run() { + + boolean isRunningAction = runWithLock(validationWaitingLock, () -> { + if (validateAction != this) { + return false; + } + + // set the validation action to null to indicate that this is done running and can be validated. + validateAction = null; + validateFuture = null; + + return true; + }); + + if (!isRunningAction) { + return; + } else if (Thread.interrupted()) { + return; + } + + ImageFilePanel.this.updateHelper(); + } + + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index c659727d74..463d5c9bcd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -59,6 +59,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { private Host host; private ImageWriterSettings imageWriterSettings; private boolean ignoreFatOrphanFiles; + private String password; /** * Constructs a local drive data source processor that implements the @@ -135,9 +136,14 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { */ @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - run(null, progressMonitor, callback); + run(null, null, progressMonitor, callback); } + @Override + public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + run(null, host, progressMonitor, callback); + } + /** * Adds a data source to the case database using a background task in a * separate thread and the settings provided by the selection and @@ -154,7 +160,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { * to return results. */ @Override - public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + public void run(String password, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { deviceId = UUID.randomUUID().toString(); drivePath = configPanel.getContentPath(); sectorSize = configPanel.getSectorSize(); @@ -167,12 +173,13 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { } this.host = host; + this.password = password; Image image; try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), new String[]{drivePath}, sectorSize, - timeZone, null, null, null, deviceId, this.host); + timeZone, null, null, null, deviceId, this.password, this.host); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); final List errors = new ArrayList<>(); @@ -182,7 +189,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { } addDiskTask = new AddImageTask( - new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), + new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, this.password), progressMonitor, new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); @@ -241,7 +248,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { try { image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), new String[]{drivePath}, sectorSize, - timeZone, null, null, null, deviceId); + timeZone, null, null, null, deviceId, this.password, null); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); final List errors = new ArrayList<>(); @@ -250,7 +257,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor { return; } - addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings), + addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, this.password), progressMonitor, new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java index 1cacabe67c..378af77b75 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineCommand.java @@ -49,7 +49,9 @@ class CommandLineCommand { DATA_SOURCE_PATH, DATA_SOURCE_ID, INGEST_PROFILE_NAME, - REPORT_PROFILE_NAME; + REPORT_PROFILE_NAME, + BITLOCKER_KEY, + ; } private final CommandType type; diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java index ffc31fff1c..64940a022d 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineIngestManager.java @@ -198,7 +198,8 @@ public class CommandLineIngestManager extends CommandLineManager { } String dataSourcePath = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); - dataSource = new AutoIngestDataSource(UUID.randomUUID().toString(), Paths.get(dataSourcePath)); + String password = inputs.get(CommandLineCommand.InputType.BITLOCKER_KEY.name()); + dataSource = new AutoIngestDataSource(UUID.randomUUID().toString(), Paths.get(dataSourcePath), password); runDataSourceProcessor(caseForJob, dataSource); String outputDirPath = getOutputDirPath(caseForJob); @@ -406,7 +407,7 @@ public class CommandLineIngestManager extends CommandLineManager { // Get an ordered list of data source processors to try List validDataSourceProcessors; try { - validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath()); + validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath(), dataSource.getPassword()); } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { LOGGER.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath()); // rethrow the exception. @@ -429,7 +430,7 @@ public class CommandLineIngestManager extends CommandLineManager { DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, ingestLock); caseForJob.notifyAddingDataSource(taskId); LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{dataSource.getPath(), selectedProcessor.getDataSourceType()}); - selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack); + selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), dataSource.getPassword(), null, progressMonitor, callBack); ingestLock.wait(); // at this point we got the content object(s) from the current DSP. diff --git a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java index 921c2e0a65..dc2a84e727 100755 --- a/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/commandlineingest/CommandLineOptionProcessor.java @@ -30,6 +30,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.ArrayUtils; import org.sleuthkit.autopsy.coreutils.Logger; import org.netbeans.api.sendopts.CommandException; import org.netbeans.spi.sendopts.Env; @@ -52,6 +53,7 @@ public class CommandLineOptionProcessor extends OptionProcessor { private final Option dataSourcePathOption = Option.requiredArgument('s', "dataSourcePath"); private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId"); private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource"); + private final Option bitlockerKeyCommandOption = Option.requiredArgument('k', "key"); private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest"); private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources"); private final Option generateReportsOption = Option.optionalArgument('g', "generateReports"); @@ -81,20 +83,21 @@ public class CommandLineOptionProcessor extends OptionProcessor { @Override protected Set