mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge pull request #6079 from dannysmyda/6474-Update-Case-Report-Module
6474 update case report module
This commit is contained in:
commit
1cd48f4480
@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2018-2020 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.report.modules.caseuco;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Writes Autopsy DataModel objects to Case UCO format.
|
||||
*
|
||||
* Clients are expected to add the Case first. Then they should add each data
|
||||
* source before adding any files for that data source.
|
||||
*
|
||||
* Here is an example, where we add everything:
|
||||
*
|
||||
* Path directory = Paths.get("C:", "Reports");
|
||||
* CaseUcoReportGenerator caseUco = new CaseUcoReportGenerator(directory, "my-report");
|
||||
*
|
||||
* Case caseObj = Case.getCurrentCase();
|
||||
* caseUco.addCase(caseObj);
|
||||
* List<Content> dataSources = caseObj.getDataSources();
|
||||
* for(Content dataSource : dataSources) {
|
||||
* caseUco.addDataSource(dataSource, caseObj);
|
||||
* List<AbstractFile> files = getAllFilesInDataSource(dataSource);
|
||||
* for(AbstractFile file : files) {
|
||||
* caseUco.addFile(file, dataSource);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Path reportOutput = caseUco.generateReport();
|
||||
* //Done. Report at - "C:\Reports\my-report.json-ld"
|
||||
*
|
||||
* Please note that the life cycle for this class ends with generateReport().
|
||||
* The underlying file handle to 'my-report.json-ld' will be closed. Any further
|
||||
* calls to addX() will result in an IOException.
|
||||
*/
|
||||
public final class CaseUcoReportGenerator {
|
||||
|
||||
private static final String EXTENSION = "json-ld";
|
||||
|
||||
private final TimeZone timeZone;
|
||||
private final Path reportPath;
|
||||
private final JsonGenerator reportGenerator;
|
||||
|
||||
/**
|
||||
* Creates a CaseUCO Report Generator that writes a report in the specified
|
||||
* directory.
|
||||
*
|
||||
* TimeZone is assumed to be GMT+0 for formatting file creation time,
|
||||
* accessed time and modified time.
|
||||
*
|
||||
* @param directory Directory to write the CaseUCO report file. Assumes the
|
||||
* calling thread has write access to the directory and that the directory
|
||||
* exists.
|
||||
* @param reportName Name of the CaseUCO report file.
|
||||
* @throws IOException If an I/O error occurs
|
||||
*/
|
||||
public CaseUcoReportGenerator(Path directory, String reportName) throws IOException {
|
||||
this.reportPath = directory.resolve(reportName + "." + EXTENSION);
|
||||
|
||||
JsonFactory jsonGeneratorFactory = new JsonFactory();
|
||||
reportGenerator = jsonGeneratorFactory.createGenerator(reportPath.toFile(), JsonEncoding.UTF8);
|
||||
// Puts a newline between each Key, Value pair for readability.
|
||||
reportGenerator.setPrettyPrinter(new DefaultPrettyPrinter()
|
||||
.withObjectIndenter(new DefaultIndenter(" ", "\n")));
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
|
||||
|
||||
reportGenerator.setCodec(mapper);
|
||||
|
||||
reportGenerator.writeStartObject();
|
||||
reportGenerator.writeFieldName("@graph");
|
||||
reportGenerator.writeStartArray();
|
||||
|
||||
//Assume GMT+0
|
||||
this.timeZone = new SimpleTimeZone(0, "GMT");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an AbstractFile instance to the Case UCO report.
|
||||
*
|
||||
* @param file AbstractFile instance to write
|
||||
* @param parentDataSource The parent data source for this abstract file. It
|
||||
* is assumed that this parent has been written to the report (via
|
||||
* addDataSource) prior to this call. Otherwise, the report may be invalid.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public void addFile(AbstractFile file, Content parentDataSource) throws IOException, TskCoreException {
|
||||
addFile(file, parentDataSource, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an AbstractFile instance to the Case UCO report.
|
||||
*
|
||||
* @param file AbstractFile instance to write
|
||||
* @param parentDataSource The parent data source for this abstract file. It
|
||||
* is assumed that this parent has been written to the report (via
|
||||
* addDataSource) prior to this call. Otherwise, the report may be invalid.
|
||||
* @param localPath The location of the file on secondary storage, somewhere
|
||||
* other than the case. Example: local disk. This value will be ignored if
|
||||
* it is null.
|
||||
* @throws IOException
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public void addFile(AbstractFile file, Content parentDataSource, Path localPath) throws IOException, TskCoreException {
|
||||
String fileTraceId = getFileTraceId(file);
|
||||
|
||||
//Create the Trace CASE node, which will contain attributes about some evidence.
|
||||
//Trace is the standard term for evidence. For us, this means file system files.
|
||||
CASENode fileTrace = new CASENode(fileTraceId, "Trace");
|
||||
|
||||
//The bits of evidence for each Trace node are contained within Property
|
||||
//Bundles. There are a number of Property Bundles available in the CASE ontology.
|
||||
|
||||
//Build up the File Property Bundle, as the name implies - properties of
|
||||
//the file itself.
|
||||
CASEPropertyBundle filePropertyBundle = createFileBundle(file);
|
||||
fileTrace.addBundle(filePropertyBundle);
|
||||
|
||||
//Build up the ContentData Property Bundle, as the name implies - properties of
|
||||
//the File data itself.
|
||||
CASEPropertyBundle contentDataPropertyBundle = createContentDataBundle(file);
|
||||
fileTrace.addBundle(contentDataPropertyBundle);
|
||||
|
||||
if(localPath != null) {
|
||||
String urlTraceId = getURLTraceId(file);
|
||||
CASENode urlTrace = new CASENode(urlTraceId, "Trace");
|
||||
CASEPropertyBundle urlPropertyBundle = new CASEPropertyBundle("URL");
|
||||
urlPropertyBundle.addProperty("fullValue", localPath.toString());
|
||||
urlTrace.addBundle(urlPropertyBundle);
|
||||
|
||||
contentDataPropertyBundle.addProperty("dataPayloadReferenceUrl", urlTraceId);
|
||||
reportGenerator.writeObject(urlTrace);
|
||||
}
|
||||
|
||||
//Create the Relationship CASE node. This defines how the Trace CASE node described above
|
||||
//is related to another CASE node (in this case, the parent data source).
|
||||
String relationshipID = getRelationshipId(file);
|
||||
CASENode relationship = createRelationshipNode(relationshipID,
|
||||
fileTraceId, getDataSourceTraceId(parentDataSource));
|
||||
|
||||
//Build up the PathRelation bundle for the relationship node,
|
||||
//as the name implies - the Path of the Trace in the data source.
|
||||
CASEPropertyBundle pathRelationPropertyBundle = new CASEPropertyBundle("PathRelation");
|
||||
pathRelationPropertyBundle.addProperty("path", file.getUniquePath());
|
||||
relationship.addBundle(pathRelationPropertyBundle);
|
||||
|
||||
//This completes the triage, write them to JSON.
|
||||
reportGenerator.writeObject(fileTrace);
|
||||
reportGenerator.writeObject(relationship);
|
||||
}
|
||||
|
||||
private String getURLTraceId(Content content) {
|
||||
return "url-" + content.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* All relationship nodes will be the same within our context. Namely, contained-within
|
||||
* and isDirectional as true.
|
||||
*/
|
||||
private CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID) {
|
||||
CASENode relationship = new CASENode(relationshipID, "Relationship");
|
||||
relationship.addProperty("source", sourceID);
|
||||
relationship.addProperty("target", targetID);
|
||||
relationship.addProperty("kindOfRelationship", "contained-within");
|
||||
relationship.addProperty("isDirectional", true);
|
||||
return relationship;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a File Property Bundle with a selection of file attributes.
|
||||
*/
|
||||
private CASEPropertyBundle createFileBundle(AbstractFile file) throws TskCoreException {
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
String createdTime = ContentUtils.getStringTimeISO8601(file.getCrtime(), timeZone);
|
||||
String accessedTime = ContentUtils.getStringTimeISO8601(file.getAtime(), timeZone);
|
||||
String modifiedTime = ContentUtils.getStringTimeISO8601(file.getMtime(), timeZone);
|
||||
filePropertyBundle.addProperty("createdTime", createdTime);
|
||||
filePropertyBundle.addProperty("accessedTime", accessedTime);
|
||||
filePropertyBundle.addProperty("modifiedTime", modifiedTime);
|
||||
if (!Strings.isNullOrEmpty(file.getNameExtension())) {
|
||||
filePropertyBundle.addProperty("extension", file.getNameExtension());
|
||||
}
|
||||
filePropertyBundle.addProperty("fileName", file.getName());
|
||||
filePropertyBundle.addProperty("filePath", file.getUniquePath());
|
||||
filePropertyBundle.addProperty("isDirectory", file.isDir());
|
||||
filePropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
|
||||
return filePropertyBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Content Data Property Bundle with a selection of file attributes.
|
||||
*/
|
||||
private CASEPropertyBundle createContentDataBundle(AbstractFile file) {
|
||||
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
|
||||
if (!Strings.isNullOrEmpty(file.getMIMEType())) {
|
||||
contentDataPropertyBundle.addProperty("mimeType", file.getMIMEType());
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(file.getMd5Hash())) {
|
||||
List<CASEPropertyBundle> hashPropertyBundles = new ArrayList<>();
|
||||
CASEPropertyBundle md5HashPropertyBundle = new CASEPropertyBundle("Hash");
|
||||
md5HashPropertyBundle.addProperty("hashMethod", "MD5");
|
||||
md5HashPropertyBundle.addProperty("hashValue", file.getMd5Hash());
|
||||
hashPropertyBundles.add(md5HashPropertyBundle);
|
||||
contentDataPropertyBundle.addProperty("hash", hashPropertyBundles);
|
||||
}
|
||||
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
|
||||
return contentDataPropertyBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique CASE Node file trace id.
|
||||
*/
|
||||
private String getFileTraceId(AbstractFile file) {
|
||||
return "file-" + file.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique CASE Node relationship id value.
|
||||
*/
|
||||
private String getRelationshipId(Content content) {
|
||||
return "relationship-" + content.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Content instance (which is known to be a DataSource) to the CASE
|
||||
* report. This means writing a selection of attributes to a CASE or UCO
|
||||
* object.
|
||||
*
|
||||
* @param dataSource Datasource content to write
|
||||
* @param parentCase The parent case that this data source belongs in. It is
|
||||
* assumed that this parent has been written to the report (via addCase)
|
||||
* prior to this call. Otherwise, the report may be invalid.
|
||||
*/
|
||||
public void addDataSource(Content dataSource, Case parentCase) throws IOException, TskCoreException {
|
||||
String dataSourceTraceId = this.getDataSourceTraceId(dataSource);
|
||||
|
||||
CASENode dataSourceTrace = new CASENode(dataSourceTraceId, "Trace");
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
|
||||
String dataSourcePath = getDataSourcePath(dataSource);
|
||||
|
||||
filePropertyBundle.addProperty("filePath", dataSourcePath);
|
||||
dataSourceTrace.addBundle(filePropertyBundle);
|
||||
|
||||
if (dataSource.getSize() > 0) {
|
||||
CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
|
||||
contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(dataSource.getSize()));
|
||||
dataSourceTrace.addBundle(contentDataPropertyBundle);
|
||||
}
|
||||
|
||||
// create a "relationship" entry between the case and the data source
|
||||
String caseTraceId = getCaseTraceId(parentCase);
|
||||
String relationshipTraceId = getRelationshipId(dataSource);
|
||||
CASENode relationship = createRelationshipNode(relationshipTraceId,
|
||||
dataSourceTraceId, caseTraceId);
|
||||
|
||||
CASEPropertyBundle pathRelationBundle = new CASEPropertyBundle("PathRelation");
|
||||
pathRelationBundle.addProperty("path", dataSourcePath);
|
||||
relationship.addBundle(pathRelationBundle);
|
||||
|
||||
//This completes the triage, write them to JSON.
|
||||
reportGenerator.writeObject(dataSourceTrace);
|
||||
reportGenerator.writeObject(relationship);
|
||||
}
|
||||
|
||||
private String getDataSourcePath(Content dataSource) {
|
||||
String dataSourcePath = "";
|
||||
if (dataSource instanceof Image) {
|
||||
String[] paths = ((Image) dataSource).getPaths();
|
||||
if (paths.length > 0) {
|
||||
//Get the first data source in the path, as this will
|
||||
//be reflected in each file's uniquePath.
|
||||
dataSourcePath = paths[0];
|
||||
}
|
||||
} else {
|
||||
dataSourcePath = dataSource.getName();
|
||||
}
|
||||
dataSourcePath = dataSourcePath.replaceAll("\\\\", "/");
|
||||
return dataSourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique Case UCO trace id for a data source.
|
||||
*
|
||||
* @param dataSource
|
||||
* @return
|
||||
*/
|
||||
private String getDataSourceTraceId(Content dataSource) {
|
||||
return "data-source-" + dataSource.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Case instance to the Case UCO report. This means writing a
|
||||
* selection of Case attributes to a CASE/UCO object.
|
||||
*
|
||||
* @param caseObj Case instance to include in the report.
|
||||
* @throws IOException If an I/O error is encountered.
|
||||
*/
|
||||
public void addCase(Case caseObj) throws IOException {
|
||||
SleuthkitCase skCase = caseObj.getSleuthkitCase();
|
||||
|
||||
String caseDirPath = skCase.getDbDirPath();
|
||||
String caseTraceId = getCaseTraceId(caseObj);
|
||||
CASENode caseTrace = new CASENode(caseTraceId, "Trace");
|
||||
CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
|
||||
|
||||
// replace double slashes with single ones
|
||||
caseDirPath = caseDirPath.replaceAll("\\\\", "/");
|
||||
|
||||
Case.CaseType caseType = caseObj.getCaseType();
|
||||
if (caseType.equals(CaseType.SINGLE_USER_CASE)) {
|
||||
filePropertyBundle.addProperty("filePath", caseDirPath + "/" + skCase.getDatabaseName());
|
||||
filePropertyBundle.addProperty("isDirectory", false);
|
||||
} else {
|
||||
filePropertyBundle.addProperty("filePath", caseDirPath);
|
||||
filePropertyBundle.addProperty("isDirectory", true);
|
||||
}
|
||||
|
||||
caseTrace.addBundle(filePropertyBundle);
|
||||
reportGenerator.writeObject(caseTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unique Case UCO trace id for a Case.
|
||||
*
|
||||
* @param caseObj
|
||||
* @return
|
||||
*/
|
||||
private String getCaseTraceId(Case caseObj) {
|
||||
return "case-" + caseObj.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Path to the completed Case UCO report file.
|
||||
*
|
||||
* This marks the end of the CaseUcoReportGenerator's life cycle. This
|
||||
* function will close an underlying file handles, meaning any subsequent
|
||||
* calls to addX() will result in an IOException.
|
||||
*
|
||||
* @return The Path to the finalized report.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
public Path generateReport() throws IOException {
|
||||
//Finalize the report.
|
||||
reportGenerator.writeEndArray();
|
||||
reportGenerator.writeEndObject();
|
||||
reportGenerator.close();
|
||||
|
||||
return reportPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A CASE or UCO object. CASE objects can have properties and
|
||||
* property bundles.
|
||||
*/
|
||||
private final class CASENode {
|
||||
|
||||
private final String id;
|
||||
private final String type;
|
||||
|
||||
//Dynamic properties added to this CASENode.
|
||||
private final Map<String, Object> properties;
|
||||
private final List<CASEPropertyBundle> propertyBundle;
|
||||
|
||||
public CASENode(String id, String type) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
properties = new LinkedHashMap<>();
|
||||
propertyBundle = new ArrayList<>();
|
||||
}
|
||||
|
||||
@JsonProperty("@id")
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@JsonProperty("@type")
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@JsonProperty("propertyBundle")
|
||||
public List<CASEPropertyBundle> getPropertyBundle() {
|
||||
return propertyBundle;
|
||||
}
|
||||
|
||||
public void addProperty(String key, Object val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
|
||||
public void addBundle(CASEPropertyBundle bundle) {
|
||||
propertyBundle.add(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains CASE or UCO properties.
|
||||
*/
|
||||
private final class CASEPropertyBundle {
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
public CASEPropertyBundle(String type) {
|
||||
properties = new LinkedHashMap<>();
|
||||
addProperty("@type", type);
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void addProperty(String key, Object val) {
|
||||
properties.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.report.modules.caseuco;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -39,29 +47,37 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.report.GeneralReportModule;
|
||||
import org.sleuthkit.autopsy.report.GeneralReportSettings;
|
||||
import org.sleuthkit.autopsy.report.ReportProgressPanel;
|
||||
import org.sleuthkit.caseuco.CaseUcoExporter;
|
||||
import org.sleuthkit.caseuco.ContentNotExportableException;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
|
||||
|
||||
/**
|
||||
* CaseUcoReportModule generates a report in CASE-UCO format. This module will
|
||||
* write all files and data sources to the report.
|
||||
* Exports an Autopsy case to a CASE-UCO report file. This module will write all
|
||||
* files and artifacts from the selected data sources.
|
||||
*/
|
||||
public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CaseUcoReportModule.class.getName());
|
||||
private static final CaseUcoReportModule SINGLE_INSTANCE = new CaseUcoReportModule();
|
||||
|
||||
//Supported types of TSK_FS_FILES
|
||||
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {{
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
|
||||
}};
|
||||
|
||||
private static final String REPORT_FILE_NAME = "CASE_UCO_output";
|
||||
private static final String EXTENSION = "json-ld";
|
||||
//Supported types of TSK_FS_FILES
|
||||
private static final Set<Short> SUPPORTED_TYPES = new HashSet<Short>() {
|
||||
{
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue());
|
||||
add(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
private static final String REPORT_FILE_NAME = "CASE_UCO_output";
|
||||
private static final String EXTENSION = "jsonld";
|
||||
|
||||
// Hidden constructor for the report
|
||||
private CaseUcoReportModule() {
|
||||
@ -76,7 +92,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
public String getName() {
|
||||
return NbBundle.getMessage(this.getClass(), "CaseUcoReportModule.getName.text");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JPanel getConfigurationPanel() {
|
||||
return null; // No configuration panel
|
||||
@ -84,7 +100,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
|
||||
@Override
|
||||
public String getRelativeFilePath() {
|
||||
return REPORT_FILE_NAME + "." + EXTENSION;
|
||||
return REPORT_FILE_NAME + "." + EXTENSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -100,7 +116,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
public static String getReportFileName() {
|
||||
return REPORT_FILE_NAME;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsDataSourceSelection() {
|
||||
return true;
|
||||
@ -109,7 +125,7 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
/**
|
||||
* Generates a CASE-UCO format report for all files in the Case.
|
||||
*
|
||||
* @param settings Report settings.
|
||||
* @param settings Report settings.
|
||||
* @param progressPanel panel to update the report's progress
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
@ -128,74 +144,123 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
try {
|
||||
// Check if ingest has finished
|
||||
warnIngest(progressPanel);
|
||||
|
||||
|
||||
//Create report paths if they don't already exist.
|
||||
Path reportDirectory = Paths.get(settings.getReportDirectoryPath());
|
||||
try {
|
||||
Files.createDirectories(reportDirectory);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Unable to create directory for CASE-UCO report.", ex);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
Bundle.CaseUcoReportModule_unableToCreateDirectories());
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
Bundle.CaseUcoReportModule_unableToCreateDirectories());
|
||||
return;
|
||||
}
|
||||
|
||||
CaseUcoReportGenerator generator =
|
||||
new CaseUcoReportGenerator(reportDirectory, REPORT_FILE_NAME);
|
||||
|
||||
//First write the Case to the report file.
|
||||
Case caseObj = Case.getCurrentCaseThrows();
|
||||
generator.addCase(caseObj);
|
||||
|
||||
List<Content> dataSources = caseObj.getDataSources().stream()
|
||||
.filter((dataSource) -> {
|
||||
if(settings.getSelectedDataSources() == null) {
|
||||
// Assume all data sources if list is null.
|
||||
return true;
|
||||
|
||||
Case currentCase = Case.getCurrentCaseThrows();
|
||||
|
||||
Path caseJsonReportFile = reportDirectory.resolve(REPORT_FILE_NAME + "." + EXTENSION);
|
||||
|
||||
try (OutputStream stream = new FileOutputStream(caseJsonReportFile.toFile());
|
||||
JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
reportWriter.setIndent(" ");
|
||||
reportWriter.beginObject();
|
||||
reportWriter.name("@graph");
|
||||
reportWriter.beginArray();
|
||||
|
||||
CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
|
||||
for (JsonElement element : exporter.exportSleuthkitCase()) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
|
||||
// Get a list of selected data sources to process.
|
||||
List<DataSource> dataSources = getSelectedDataSources(currentCase, settings);
|
||||
|
||||
progressPanel.setIndeterminate(false);
|
||||
progressPanel.setMaximumProgress(dataSources.size());
|
||||
progressPanel.start();
|
||||
|
||||
// First stage of reporting is for files and data sources.
|
||||
// Iterate through each data source and dump all files contained
|
||||
// in that data source.
|
||||
for (int i = 0; i < dataSources.size(); i++) {
|
||||
DataSource dataSource = dataSources.get(i);
|
||||
progressPanel.updateStatusLabel(String.format(
|
||||
Bundle.CaseUcoReportModule_processingDataSource(),
|
||||
dataSource.getName()));
|
||||
// Add the data source export.
|
||||
for (JsonElement element : exporter.exportDataSource(dataSource)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
// Search all children of the data source.
|
||||
performDepthFirstSearch(dataSource, gson, exporter, reportWriter);
|
||||
progressPanel.setProgress(i + 1);
|
||||
}
|
||||
|
||||
// Second stage of reporting handles artifacts.
|
||||
Set<Long> dataSourceIds = dataSources.stream()
|
||||
.map((datasource) -> datasource.getId())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Write all standard artifacts that are contained within the
|
||||
// selected data sources.
|
||||
for (ARTIFACT_TYPE artType : currentCase.getSleuthkitCase().getBlackboardArtifactTypesInUse()) {
|
||||
for (BlackboardArtifact artifact : currentCase.getSleuthkitCase().getBlackboardArtifacts(artType)) {
|
||||
if (dataSourceIds.contains(artifact.getDataSource().getId())) {
|
||||
|
||||
try {
|
||||
for (JsonElement element : exporter.exportBlackboardArtifact(artifact)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
} catch (ContentNotExportableException | BlackboardJsonAttrUtil.InvalidJsonException ex) {
|
||||
logger.log(Level.WARNING, String.format("Unable to export blackboard artifact (id: %d) to CASE/UCO. "
|
||||
+ "The artifact type is either not supported or the artifact instance does not have any "
|
||||
+ "exportable attributes.", artifact.getId()));
|
||||
}
|
||||
}
|
||||
return settings.getSelectedDataSources().contains(dataSource.getId());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
progressPanel.setIndeterminate(false);
|
||||
progressPanel.setMaximumProgress(dataSources.size());
|
||||
progressPanel.start();
|
||||
|
||||
//Then search each data source for file content.
|
||||
for(int i = 0; i < dataSources.size(); i++) {
|
||||
Content dataSource = dataSources.get(i);
|
||||
progressPanel.updateStatusLabel(String.format(
|
||||
Bundle.CaseUcoReportModule_processingDataSource(),
|
||||
dataSource.getName()));
|
||||
//Add the data source and then all children.
|
||||
generator.addDataSource(dataSource, caseObj);
|
||||
performDepthFirstSearch(dataSource, generator);
|
||||
progressPanel.setProgress(i+1);
|
||||
}
|
||||
}
|
||||
|
||||
reportWriter.endArray();
|
||||
reportWriter.endObject();
|
||||
}
|
||||
|
||||
//Complete the report.
|
||||
Path reportPath = generator.generateReport();
|
||||
caseObj.addReport(reportPath.toString(),
|
||||
Bundle.CaseUcoReportModule_srcModuleName(),
|
||||
|
||||
currentCase.addReport(caseJsonReportFile.toString(),
|
||||
Bundle.CaseUcoReportModule_srcModuleName(),
|
||||
REPORT_FILE_NAME);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "I/O error encountered while generating the report.", ex);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
Bundle.CaseUcoReportModule_ioError());
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.WARNING, "No case open.", ex);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
Bundle.CaseUcoReportModule_noCaseOpen());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "TskCoreException encounted while generating the report.", ex);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR,
|
||||
String.format(Bundle.CaseUcoReportModule_tskCoreException(), ex.toString()));
|
||||
}
|
||||
|
||||
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the selected data sources from the settings instance.
|
||||
*/
|
||||
private List<DataSource> getSelectedDataSources(Case currentCase, GeneralReportSettings settings) throws TskCoreException {
|
||||
return currentCase.getSleuthkitCase().getDataSources().stream()
|
||||
.filter((dataSource) -> {
|
||||
if (settings.getSelectedDataSources() == null) {
|
||||
// Assume all data sources if list is null.
|
||||
return true;
|
||||
}
|
||||
return settings.getSelectedDataSources().contains(dataSource.getId());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Warn the user if ingest is still ongoing.
|
||||
*/
|
||||
@ -204,30 +269,32 @@ public final class CaseUcoReportModule implements GeneralReportModule {
|
||||
progressPanel.updateStatusLabel(Bundle.CaseUcoReportModule_ingestWarning());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform DFS on the data sources tree, which will search it in entirety.
|
||||
* This traversal is more memory efficient than BFS (Breadth first search).
|
||||
* Perform DFS on the data sources tree, which will search it in entirety.
|
||||
*/
|
||||
private void performDepthFirstSearch(Content dataSource,
|
||||
CaseUcoReportGenerator generator) throws IOException, TskCoreException {
|
||||
|
||||
private void performDepthFirstSearch(DataSource dataSource,
|
||||
Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter) throws IOException, TskCoreException {
|
||||
|
||||
Deque<Content> stack = new ArrayDeque<>();
|
||||
stack.addAll(dataSource.getChildren());
|
||||
|
||||
//Depth First Search the data source tree.
|
||||
while(!stack.isEmpty()) {
|
||||
while (!stack.isEmpty()) {
|
||||
Content current = stack.pop();
|
||||
if(current instanceof AbstractFile) {
|
||||
AbstractFile f = (AbstractFile) (current);
|
||||
if(SUPPORTED_TYPES.contains(f.getMetaType().getValue())) {
|
||||
generator.addFile(f, dataSource);
|
||||
if (current instanceof AbstractFile) {
|
||||
AbstractFile file = (AbstractFile) (current);
|
||||
if (SUPPORTED_TYPES.contains(file.getMetaType().getValue())) {
|
||||
|
||||
for (JsonElement element : exporter.exportAbstractFile(file)) {
|
||||
gson.toJson(element, reportWriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Content child : current.getChildren()) {
|
||||
for (Content child : current.getChildren()) {
|
||||
stack.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user