updates for auto ingest

This commit is contained in:
Greg DiCristofaro 2025-01-15 16:41:50 -05:00
parent b0a26d14c6
commit f875da5c12
No known key found for this signature in database
12 changed files with 348 additions and 77 deletions

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.UUID; import java.util.UUID;
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileFilter;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders; import org.openide.util.lookup.ServiceProviders;
@ -184,7 +185,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
*/ */
@Override @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
run(null, progressMonitor, callback); run(null, null, progressMonitor, callback);
} }
/** /**
@ -204,9 +205,16 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
*/ */
@Override @Override
public void run(Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { 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(); ingestStream = new DefaultIngestStream();
readConfigSettings(); readConfigSettings();
this.host = host; this.host = host;
this.password = StringUtils.defaultString(password, this.password);
try { try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.password, this.host); new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.password, this.host);
@ -221,6 +229,46 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, this.password, progressMonitor, callback); 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
* separate thread and the given settings instead of those provided by the
* selection and configuration panel. Returns as soon as the background task
* is started and uses the callback object to signal task completion and
* return results.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
* intended to be unique across multiple cases
* (e.g., a UUID).
* @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
* @param progressMonitor Progress monitor for reporting progress
* during processing.
* @param callback Callback to call when processing is done.
*/
public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
ingestStream = new DefaultIngestStream();
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
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<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
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 * Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and * separate thread and the settings provided by the selection and
@ -241,7 +289,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
@Override @Override
public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, public void runWithIngestStream(IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
DataSourceProcessorCallback callBack) { DataSourceProcessorCallback callBack) {
runWithIngestStream(null, settings, progress, callBack); runWithIngestStream(null, null, settings, progress, callBack);
} }
/** /**
@ -265,10 +313,18 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
@Override @Override
public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress, public void runWithIngestStream(Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
DataSourceProcessorCallback callBack) { 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 // Read the settings from the wizard
readConfigSettings(); readConfigSettings();
this.host = host; this.host = host;
this.password = StringUtils.defaultIfEmpty(password, this.password);
// Set up the data source before creating the ingest stream // Set up the data source before creating the ingest stream
try { try {
@ -296,6 +352,7 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, this.password, progress, callBack); doAddImageProcess(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, this.password, progress, callBack);
} }
/** /**
* Store the options from the config panel. * Store the options from the config panel.
*/ */
@ -334,42 +391,6 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
return true; 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
* selection and configuration panel. Returns as soon as the background task
* is started and uses the callback object to signal task completion and
* return results.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
* intended to be unique across multiple cases
* (e.g., a UUID).
* @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
* @param progressMonitor Progress monitor for reporting progress
* during processing.
* @param callback Callback to call when processing is done.
*/
public void run(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
ingestStream = new DefaultIngestStream();
try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, this.password, null);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>();
errors.add(ex.getMessage());
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errors, new ArrayList<>());
return;
}
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 * Adds a data source to the case database using a background task in a
@ -472,6 +493,13 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
@Override @Override
public int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException { 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 // check file extension for supported types
if (!isAcceptedByFiler(dataSourcePath.toFile(), filtersList)) { if (!isAcceptedByFiler(dataSourcePath.toFile(), filtersList)) {
@ -504,23 +532,29 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
@Override @Override
public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
process(deviceId, dataSourcePath, null, progressMonitor, callBack); process(deviceId, dataSourcePath, this.password, null, progressMonitor, callBack);
} }
@Override @Override
public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { public void process(String deviceId, Path dataSourcePath, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
process(deviceId, dataSourcePath, this.password, 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 method does not use the config panel
this.deviceId = deviceId; this.deviceId = deviceId;
this.imagePath = dataSourcePath.toString(); this.imagePath = dataSourcePath.toString();
this.sectorSize = 0; this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID(); this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.password = password;
this.host = host; this.host = host;
this.ignoreFatOrphanFiles = false; this.ignoreFatOrphanFiles = false;
ingestStream = new DefaultIngestStream(); ingestStream = new DefaultIngestStream();
try { try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, this.password, host); new String[]{imagePath}, sectorSize, timeZone, "", "", "", deviceId, password, host);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>(); final List<String> errors = new ArrayList<>();
@ -529,28 +563,36 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
return; return;
} }
doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, null, progressMonitor, callBack); doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, password, progressMonitor, callBack);
} }
@Override @Override
public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { 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, this.password, null, settings, progressMonitor, callBack);
} }
@Override @Override
public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) { public IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
return processWithIngestStream(deviceId, dataSourcePath, this.password, 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 method does not use the config panel
this.deviceId = deviceId; this.deviceId = deviceId;
this.imagePath = dataSourcePath.toString(); this.imagePath = dataSourcePath.toString();
this.sectorSize = 0; this.sectorSize = 0;
this.timeZone = Calendar.getInstance().getTimeZone().getID(); this.timeZone = Calendar.getInstance().getTimeZone().getID();
this.host = host; this.host = host;
this.password = password;
this.ignoreFatOrphanFiles = false; this.ignoreFatOrphanFiles = false;
// Set up the data source before creating the ingest stream // Set up the data source before creating the ingest stream
try { try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, this.password, host); new String[]{imagePath}, sectorSize, timeZone, md5, sha1, sha256, deviceId, password, host);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex); logger.log(Level.SEVERE, "Error adding data source with path " + imagePath + " to database", ex);
final List<String> errors = new ArrayList<>(); final List<String> errors = new ArrayList<>();
@ -570,8 +612,10 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
return null; return null;
} }
doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, null, progressMonitor, callBack); doAddImageProcess(deviceId, dataSourcePath.toString(), sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, password, progressMonitor, callBack);
return ingestStream; return ingestStream;
} }
} }

View File

@ -136,7 +136,12 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
*/ */
@Override @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { 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);
} }
/** /**
@ -155,7 +160,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
* to return results. * to return results.
*/ */
@Override @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(); deviceId = UUID.randomUUID().toString();
drivePath = configPanel.getContentPath(); drivePath = configPanel.getContentPath();
sectorSize = configPanel.getSectorSize(); sectorSize = configPanel.getSectorSize();
@ -168,12 +173,13 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
this.host = host; this.host = host;
this.password = password;
Image image; Image image;
try { try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{drivePath}, sectorSize, new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId, this.host); timeZone, null, null, null, deviceId, this.password, this.host);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
final List<String> errors = new ArrayList<>(); final List<String> errors = new ArrayList<>();
@ -183,7 +189,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
addDiskTask = new AddImageTask( addDiskTask = new AddImageTask(
new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, password), new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, this.password),
progressMonitor, progressMonitor,
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));
@ -242,7 +248,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
try { try {
image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(), image = SleuthkitJNI.addImageToDatabase(Case.getCurrentCase().getSleuthkitCase(),
new String[]{drivePath}, sectorSize, new String[]{drivePath}, sectorSize,
timeZone, null, null, null, deviceId); timeZone, null, null, null, deviceId, this.password, null);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex); logger.log(Level.SEVERE, "Error adding local disk with path " + drivePath + " to database", ex);
final List<String> errors = new ArrayList<>(); final List<String> errors = new ArrayList<>();
@ -251,7 +257,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
return; return;
} }
addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, password), addDiskTask = new AddImageTask(new AddImageTask.ImageDetails(deviceId, image, sectorSize, timeZone, ignoreFatOrphanFiles, null, null, null, imageWriterSettings, this.password),
progressMonitor, progressMonitor,
new StreamingAddDataSourceCallbacks(new DefaultIngestStream()), new StreamingAddDataSourceCallbacks(new DefaultIngestStream()),
new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback)); new StreamingAddImageTaskCallback(new DefaultIngestStream(), callback));

View File

@ -49,7 +49,9 @@ class CommandLineCommand {
DATA_SOURCE_PATH, DATA_SOURCE_PATH,
DATA_SOURCE_ID, DATA_SOURCE_ID,
INGEST_PROFILE_NAME, INGEST_PROFILE_NAME,
REPORT_PROFILE_NAME; REPORT_PROFILE_NAME,
BITLOCKER_KEY,
;
} }
private final CommandType type; private final CommandType type;

View File

@ -198,7 +198,8 @@ public class CommandLineIngestManager extends CommandLineManager {
} }
String dataSourcePath = inputs.get(CommandLineCommand.InputType.DATA_SOURCE_PATH.name()); 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); runDataSourceProcessor(caseForJob, dataSource);
String outputDirPath = getOutputDirPath(caseForJob); String outputDirPath = getOutputDirPath(caseForJob);
@ -406,7 +407,7 @@ public class CommandLineIngestManager extends CommandLineManager {
// Get an ordered list of data source processors to try // Get an ordered list of data source processors to try
List<AutoIngestDataSourceProcessor> validDataSourceProcessors; List<AutoIngestDataSourceProcessor> validDataSourceProcessors;
try { try {
validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath()); validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath(), dataSource.getPassword());
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) {
LOGGER.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath()); LOGGER.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath());
// rethrow the exception. // rethrow the exception.
@ -429,7 +430,7 @@ public class CommandLineIngestManager extends CommandLineManager {
DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, ingestLock); DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, ingestLock);
caseForJob.notifyAddingDataSource(taskId); caseForJob.notifyAddingDataSource(taskId);
LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{dataSource.getPath(), selectedProcessor.getDataSourceType()}); 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(); ingestLock.wait();
// at this point we got the content object(s) from the current DSP. // at this point we got the content object(s) from the current DSP.

View File

@ -30,6 +30,7 @@ import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.ArrayUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.netbeans.api.sendopts.CommandException; import org.netbeans.api.sendopts.CommandException;
import org.netbeans.spi.sendopts.Env; 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 dataSourcePathOption = Option.requiredArgument('s', "dataSourcePath");
private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId"); private final Option dataSourceObjectIdOption = Option.requiredArgument('i', "dataSourceObjectId");
private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource"); private final Option addDataSourceCommandOption = Option.withoutArgument('a', "addDataSource");
private final Option bitlockerKeyCommandOption = Option.withoutArgument('k', "key");
private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest"); private final Option runIngestCommandOption = Option.optionalArgument('r', "runIngest");
private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources"); private final Option listAllDataSourcesCommandOption = Option.withoutArgument('l', "listAllDataSources");
private final Option generateReportsOption = Option.optionalArgument('g', "generateReports"); private final Option generateReportsOption = Option.optionalArgument('g', "generateReports");
@ -81,20 +83,21 @@ public class CommandLineOptionProcessor extends OptionProcessor {
@Override @Override
protected Set<Option> getOptions() { protected Set<Option> getOptions() {
Set<Option> set = new HashSet<>(); return Set.of(
set.add(createCaseCommandOption); createCaseCommandOption,
set.add(caseNameOption); caseNameOption,
set.add(caseTypeOption); caseTypeOption,
set.add(caseBaseDirOption); caseBaseDirOption,
set.add(dataSourcePathOption); dataSourcePathOption,
set.add(addDataSourceCommandOption); addDataSourceCommandOption,
set.add(dataSourceObjectIdOption); bitlockerKeyCommandOption,
set.add(runIngestCommandOption); dataSourceObjectIdOption,
set.add(listAllDataSourcesCommandOption); runIngestCommandOption,
set.add(generateReportsOption); listAllDataSourcesCommandOption,
set.add(listAllIngestProfileOption); generateReportsOption,
set.add(defaultArgument); listAllIngestProfileOption,
return set; defaultArgument
);
} }
@Override @Override
@ -203,6 +206,9 @@ public class CommandLineOptionProcessor extends OptionProcessor {
} }
} }
String[] keysArgs = values.get(bitlockerKeyCommandOption);
String bitlockerKey = ArrayUtils.isNotEmpty(keysArgs) ? keysArgs[0] : null;
String dataSourceId = ""; String dataSourceId = "";
if (values.containsKey(dataSourceObjectIdOption)) { if (values.containsKey(dataSourceObjectIdOption)) {
@ -247,6 +253,11 @@ public class CommandLineOptionProcessor extends OptionProcessor {
newCommand.addInputValue(CommandLineCommand.InputType.CASE_NAME.name(), inputCaseName); newCommand.addInputValue(CommandLineCommand.InputType.CASE_NAME.name(), inputCaseName);
newCommand.addInputValue(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name(), caseBaseDir); newCommand.addInputValue(CommandLineCommand.InputType.CASES_BASE_DIR_PATH.name(), caseBaseDir);
newCommand.addInputValue(CommandLineCommand.InputType.DATA_SOURCE_PATH.name(), dataSourcePath); newCommand.addInputValue(CommandLineCommand.InputType.DATA_SOURCE_PATH.name(), dataSourcePath);
if (bitlockerKey != null) {
newCommand.addInputValue(CommandLineCommand.InputType.BITLOCKER_KEY.name(), bitlockerKey);
}
commands.add(newCommand); commands.add(newCommand);
runFromCommandLine(true); runFromCommandLine(true);
} }

View File

@ -129,6 +129,27 @@ public interface DataSourceProcessor {
run(progressMonitor, callback); run(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. 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.
*
* @param password The password
* @param host Host for the data source.
* @param progressMonitor 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.
*/
default void run(String password, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
run(host, progressMonitor, callback);
}
/** /**
* Adds a data source to the case database using a background task in a * Adds a data source to the case database using a background task in a
* separate thread and the settings provided by the selection and * separate thread and the settings provided by the selection and
@ -176,6 +197,31 @@ public interface DataSourceProcessor {
runWithIngestStream(settings, progress, callBack); runWithIngestStream(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. The ingest
* settings must be complete before calling this method.
*
* @param password The password to decrypt the data source.
* @param host 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.
*/
default void runWithIngestStream(String password, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progress,
DataSourceProcessorCallback callBack) {
runWithIngestStream(host, settings, progress, callBack);
}
/** /**
* Check if this DSP supports ingest streams. * Check if this DSP supports ingest streams.
* *

View File

@ -30,13 +30,20 @@ public class AutoIngestDataSource {
private final String deviceId; private final String deviceId;
private final Path path; private final Path path;
private final String password;
private DataSourceProcessorResult resultCode; private DataSourceProcessorResult resultCode;
private List<String> errorMessages; private List<String> errorMessages;
private List<Content> content; private List<Content> content;
public AutoIngestDataSource(String deviceId, Path path) {
public AutoIngestDataSource(String deviceId, Path path, String password) {
this.deviceId = deviceId; this.deviceId = deviceId;
this.path = path; this.path = path;
this.password = password;
}
public AutoIngestDataSource(String deviceId, Path path) {
this(deviceId, path, null);
} }
public String getDeviceId() { public String getDeviceId() {
@ -47,6 +54,13 @@ public class AutoIngestDataSource {
return this.path; return this.path;
} }
/**
* @return The password to decrypt the data source.
*/
public String getPassword() {
return password;
}
public synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List<String> errorMessages, List<Content> content) { public synchronized void setDataSourceProcessorOutput(DataSourceProcessorResult result, List<String> errorMessages, List<Content> content) {
this.resultCode = result; this.resultCode = result;
this.errorMessages = new ArrayList<>(errorMessages); this.errorMessages = new ArrayList<>(errorMessages);

View File

@ -50,6 +50,25 @@ public interface AutoIngestDataSourceProcessor extends DataSourceProcessor {
*/ */
int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException; int canProcess(Path dataSourcePath) throws AutoIngestDataSourceProcessorException;
/**
* Indicates whether the DataSourceProcessor is capable of processing the
* data source. Returns a confidence value. Method can throw an exception
* for a system level problem. The exception should not be thrown for an issue
* related to bad input data.
*
* @param dataSourcePath Path to the data source.
* @param password The password to decrypt the data source.
* @return Confidence value. Values between 0 and 100 are recommended. Zero
* or less means the data source is not supported by the
* DataSourceProcessor. Value of 100 indicates high certainty in
* being able to process the data source.
* @throws org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
default int canProcess(Path dataSourcePath, String password) throws AutoIngestDataSourceProcessorException {
return canProcess(dataSourcePath);
}
/** /**
* Adds a data source to the case database using a background task in a * Adds a data source to the case database using a background task in a
* separate thread by calling DataSourceProcessor.run() method. Returns as * separate thread by calling DataSourceProcessor.run() method. Returns as
@ -91,6 +110,30 @@ public interface AutoIngestDataSourceProcessor extends DataSourceProcessor {
process(deviceId, dataSourcePath, progressMonitor, callBack); process(deviceId, dataSourcePath, progressMonitor, callBack);
} }
/**
* Adds a data source to the case database using a background task in a
* separate thread by calling DataSourceProcessor.run() method. Returns as
* soon as the background task is started. The background task uses a
* callback object to signal task completion and return results. Method can
* throw an exception for a system level problem. The exception should not
* be thrown for an issue related to bad input data.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param dataSourcePath Path to the data source.
* @param password The password to decrypt the datasource.
* @param host Host for this data source.
* @param progressMonitor 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.
*/
default void process(String deviceId, Path dataSourcePath, String password, Host host, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
process(deviceId, dataSourcePath, progressMonitor, callBack);
}
/** /**
* Adds a data source to the case database using a background task in a * Adds a data source to the case database using a background task in a
* separate thread by calling DataSourceProcessor.run() method. Returns as * separate thread by calling DataSourceProcessor.run() method. Returns as
@ -140,6 +183,33 @@ public interface AutoIngestDataSourceProcessor extends DataSourceProcessor {
return processWithIngestStream(deviceId, dataSourcePath, settings, progressMonitor, callBack); return processWithIngestStream(deviceId, dataSourcePath, settings, progressMonitor, callBack);
} }
/**
* Adds a data source to the case database using a background task in a
* separate thread by calling DataSourceProcessor.run() method. Returns as
* soon as the background task is started. The background task uses a
* callback object to signal task completion and return results. Method can
* throw an exception for a system level problem. The exception should not
* be thrown for an issue related to bad input data.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is intended
* to be unique across multiple cases (e.g., a UUID).
* @param dataSourcePath Path to the data source.
* @param password The password to decrypt the datasource.
* @param host The host for this data source.
* @param settings The ingest job settings.
* @param progressMonitor 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.
*
* @return The new ingest stream or null if an error occurred. Errors will be handled by the callback.
*/
default IngestStream processWithIngestStream(String deviceId, Path dataSourcePath, String password, Host host, IngestJobSettings settings, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) {
return processWithIngestStream(deviceId, dataSourcePath, settings, progressMonitor, callBack);
}
/** /**
* A custom exception for the use of AutomatedIngestDataSourceProcessor. * A custom exception for the use of AutomatedIngestDataSourceProcessor.
*/ */

View File

@ -49,9 +49,27 @@ public class DataSourceProcessorUtility {
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException * org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/ */
public static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException { public static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
return getDataSourceProcessorForFile(dataSourcePath, null, processorCandidates);
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used.
*
* @param dataSourcePath Full path to the data source
* @param password The password to decrypt the data source.
* @param processorCandidates Possible DSPs that can handle the data source
*
* @return Hash map of all DSPs that can process the data source along with
* their confidence score
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
public static Map<AutoIngestDataSourceProcessor, Integer> getDataSourceProcessorForFile(Path dataSourcePath, String password, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = new HashMap<>(); Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = new HashMap<>();
for (AutoIngestDataSourceProcessor processor : processorCandidates) { for (AutoIngestDataSourceProcessor processor : processorCandidates) {
int confidence = processor.canProcess(dataSourcePath); int confidence = processor.canProcess(dataSourcePath, password);
if (confidence > 0) { if (confidence > 0) {
validDataSourceProcessorsMap.put(processor, confidence); validDataSourceProcessorsMap.put(processor, confidence);
} }
@ -76,9 +94,29 @@ public class DataSourceProcessorUtility {
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException * org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/ */
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath) throws AutoIngestDataSourceProcessorException { public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath) throws AutoIngestDataSourceProcessorException {
return getOrderedListOfDataSourceProcessors(dataSourcePath, (String) null);
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used. Returns ordered list of
* data source processors. DSPs are ordered in descending order from highest
* confidence to lowest.
*
* @param dataSourcePath Full path to the data source
* @param password The password to decrypt the data source.
*
* @return Ordered list of data source processors. DSPs are ordered in
* descending order from highest confidence to lowest.
*
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, String password) throws AutoIngestDataSourceProcessorException {
// lookup all AutomatedIngestDataSourceProcessors // lookup all AutomatedIngestDataSourceProcessors
Collection<? extends AutoIngestDataSourceProcessor> processorCandidates = Lookup.getDefault().lookupAll(AutoIngestDataSourceProcessor.class); Collection<? extends AutoIngestDataSourceProcessor> processorCandidates = Lookup.getDefault().lookupAll(AutoIngestDataSourceProcessor.class);
return getOrderedListOfDataSourceProcessors(dataSourcePath, processorCandidates); return getOrderedListOfDataSourceProcessors(dataSourcePath, password, processorCandidates);
} }
/** /**
@ -98,6 +136,27 @@ public class DataSourceProcessorUtility {
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException * org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/ */
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException { public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
return getOrderedListOfDataSourceProcessors(dataSourcePath, null, processorCandidates);
}
/**
* A utility method to find all Data Source Processors (DSP) that are able
* to process the input data source. Only the DSPs that implement
* AutoIngestDataSourceProcessor interface are used. Returns ordered list of
* data source processors. DSPs are ordered in descending order from highest
* confidence to lowest.
*
* @param dataSourcePath Full path to the data source
* @param password The password to decrypt the data source.
* @param processorCandidates Collection of AutoIngestDataSourceProcessor objects to use
*
* @return Ordered list of data source processors. DSPs are ordered in
* descending order from highest confidence to lowest.
*
* @throws
* org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException
*/
public static List<AutoIngestDataSourceProcessor> getOrderedListOfDataSourceProcessors(Path dataSourcePath, String password, Collection<? extends AutoIngestDataSourceProcessor> processorCandidates) throws AutoIngestDataSourceProcessorException {
Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = getDataSourceProcessorForFile(dataSourcePath, processorCandidates); Map<AutoIngestDataSourceProcessor, Integer> validDataSourceProcessorsMap = getDataSourceProcessorForFile(dataSourcePath, processorCandidates);
return orderDataSourceProcessorsByConfidence(validDataSourceProcessorsMap); return orderDataSourceProcessorsByConfidence(validDataSourceProcessorsMap);
} }

View File

@ -2573,7 +2573,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
return null; return null;
} }
String deviceId = manifest.getDeviceId(); String deviceId = manifest.getDeviceId();
return new AutoIngestDataSource(deviceId, dataSourcePath); String password = manifest.getPassword();
return new AutoIngestDataSource(deviceId, dataSourcePath, password);
} }
/** /**
@ -2603,7 +2604,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
// Get an ordered list of data source processors to try // Get an ordered list of data source processors to try
List<AutoIngestDataSourceProcessor> validDataSourceProcessors; List<AutoIngestDataSourceProcessor> validDataSourceProcessors;
try { try {
validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath()); validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath(), dataSource.getPassword());
} catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) {
sysLogger.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath()); sysLogger.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath());
// rethrow the exception. It will get caught & handled upstream and will result in AIM auto-pause. // rethrow the exception. It will get caught & handled upstream and will result in AIM auto-pause.
@ -2641,7 +2642,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
jobLogger.logIngestJobSettingsErrors(); jobLogger.logIngestJobSettingsErrors();
throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Error(s) in ingest job settings for " + manifestPath); throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Error(s) in ingest job settings for " + manifestPath);
} }
currentIngestStream = selectedProcessor.processWithIngestStream(dataSource.getDeviceId(), dataSource.getPath(), ingestJobSettings, progressMonitor, callBack); currentIngestStream = selectedProcessor.processWithIngestStream(dataSource.getDeviceId(), dataSource.getPath(), dataSource.getPassword(), null, ingestJobSettings, progressMonitor, callBack);
if (currentIngestStream == null) { if (currentIngestStream == null) {
// Either there was a failure to add the data source object to the database or the ingest settings were bad. // Either there was a failure to add the data source object to the database or the ingest settings were bad.
// An error in the ingest settings is the more likely scenario. // An error in the ingest settings is the more likely scenario.
@ -2651,7 +2652,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Error initializing processing for " + manifestPath + ", probably due to an ingest settings error"); throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Error initializing processing for " + manifestPath + ", probably due to an ingest settings error");
} }
} else { } else {
selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack); selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), dataSource.getPassword(), null, progressMonitor, callBack);
} }
ingestLock.wait(); ingestLock.wait();

View File

@ -45,6 +45,7 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
private static final String MANIFEST_FILE_NAME_SIGNATURE = "_MANIFEST.XML"; private static final String MANIFEST_FILE_NAME_SIGNATURE = "_MANIFEST.XML";
private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest"; private static final String ROOT_ELEM_TAG_NAME = "AutopsyManifest";
private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()"; private static final String CASE_NAME_XPATH = "/AutopsyManifest/CaseName/text()";
private static final String PASSWORD_XPATH = "/AutopsyManifest/Password/text()";
private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()"; private static final String DEVICE_ID_XPATH = "/AutopsyManifest/DeviceId/text()";
private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSource/text()"; private static final String DATA_SOURCE_NAME_XPATH = "/AutopsyManifest/DataSource/text()";
private static final Logger logger = Logger.getLogger(AutopsyManifestFileParser.class.getName()); private static final Logger logger = Logger.getLogger(AutopsyManifestFileParser.class.getName());
@ -102,7 +103,10 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
} }
Path dataSourcePath = filePath.getParent().resolve(dataSourceName); Path dataSourcePath = filePath.getParent().resolve(dataSourceName);
return new Manifest(filePath, dateFileCreated, caseName, deviceId, dataSourcePath, new HashMap<>()); expr = xpath.compile(PASSWORD_XPATH);
String password = (String) expr.evaluate(doc, XPathConstants.STRING);
return new Manifest(filePath, dateFileCreated, caseName, deviceId, dataSourcePath, password, new HashMap<>());
} catch (Exception ex) { } catch (Exception ex) {
throw new ManifestFileParserException(String.format("Error parsing manifest %s", filePath), ex); throw new ManifestFileParserException(String.format("Error parsing manifest %s", filePath), ex);
} finally { } finally {

View File

@ -42,9 +42,14 @@ public final class Manifest implements Serializable {
private final String caseName; private final String caseName;
private final String deviceId; private final String deviceId;
private final String dataSourceFileName; private final String dataSourceFileName;
private final String password;
private final Map<String, String> manifestProperties; private final Map<String, String> manifestProperties;
public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, Map<String, String> manifestProperties) { public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, Map<String, String> manifestProperties) {
this(manifestFilePath, dateFileCreated, caseName, deviceId, dataSourcePath, null, manifestProperties);
}
public Manifest(Path manifestFilePath, Date dateFileCreated, String caseName, String deviceId, Path dataSourcePath, String password, Map<String, String> manifestProperties) {
this.filePathString = manifestFilePath.toString(); this.filePathString = manifestFilePath.toString();
this.filePath = Paths.get(filePathString); this.filePath = Paths.get(filePathString);
@ -60,6 +65,7 @@ public final class Manifest implements Serializable {
this.dataSourcePath = Paths.get(""); this.dataSourcePath = Paths.get("");
dataSourceFileName = ""; dataSourceFileName = "";
} }
this.password = password;
this.manifestProperties = new HashMap<>(manifestProperties); this.manifestProperties = new HashMap<>(manifestProperties);
} }
@ -91,6 +97,13 @@ public final class Manifest implements Serializable {
return dataSourcePath; return dataSourcePath;
} }
/**
* @return The password to decrypt the data source (may be null).
*/
public String getPassword() {
return password;
}
public String getDataSourceFileName() { public String getDataSourceFileName() {
return dataSourceFileName; return dataSourceFileName;
} }