From 2ef53e6a302ae528f7d1773a81c199917acd00e9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 14 Jul 2020 11:05:36 -0400 Subject: [PATCH 1/3] Updated autopsy to use the new CaseUcoExporter tool --- .../caseuco/CaseUcoReportGenerator.java | 466 ------------- .../modules/caseuco/CaseUcoReportModule.java | 186 ++++-- .../PortableCaseReportModule.java | 614 +++++++++--------- 3 files changed, 435 insertions(+), 831 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java deleted file mode 100755 index 9cc4467835..0000000000 --- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportGenerator.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018-2020 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.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 dataSources = caseObj.getDataSources(); - * for(Content dataSource : dataSources) { - * caseUco.addDataSource(dataSource, caseObj); - * List 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 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 properties; - private final List 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 getProperties() { - return properties; - } - - @JsonProperty("propertyBundle") - public List 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 properties; - - public CASEPropertyBundle(String type) { - properties = new LinkedHashMap<>(); - addProperty("@type", type); - } - - @JsonAnyGetter - public Map getProperties() { - return properties; - } - - public void addProperty(String key, Object val) { - properties.put(key, val); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java index d90b4a54c2..2da96ac34f 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java @@ -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,28 +47,36 @@ 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 + * CaseUcoReportModule generates a report in CASE-UCO format. This module will * write all files and data sources to the report. */ 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 SUPPORTED_TYPES = new HashSet() {{ - 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"; + //Supported types of TSK_FS_FILES + private static final Set SUPPORTED_TYPES = new HashSet() { + { + 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"; // Hidden constructor for the report @@ -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,106 @@ 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 dataSources = caseObj.getDataSources().stream() - .filter((dataSource) -> { - if(settings.getSelectedDataSources() == null) { - // Assume all data sources if list is null. - return true; + + 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(); + + //First write the Case to the report file. + CaseUcoExporter exporter = new CaseUcoExporter(caseObj.getSleuthkitCase()); + for (JsonElement element : exporter.exportSleuthkitCase()) { + gson.toJson(element, reportWriter); + } + + // Prune the data sources so that we only report on what was selected. + List dataSources = caseObj.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()); + + 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++) { + 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); + } + + // Write all standard artifacts to the report. + for (ARTIFACT_TYPE artType : caseObj.getSleuthkitCase().getBlackboardArtifactTypesInUse()) { + for (BlackboardArtifact artifact : caseObj.getSleuthkitCase().getBlackboardArtifacts(artType)) { + 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(), + + caseObj.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); } - + /** * Warn the user if ingest is still ongoing. */ @@ -204,28 +252,30 @@ 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 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); } } diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index 8c88f29f53..b871d4bf84 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -20,12 +20,19 @@ package org.sleuthkit.autopsy.report.modules.portablecase; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.stream.JsonWriter; import org.sleuthkit.autopsy.report.ReportModule; import java.util.logging.Level; import java.io.BufferedReader; import java.io.File; +import java.io.FileOutputStream; import java.io.InputStreamReader; 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; @@ -49,7 +56,7 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; import org.sleuthkit.autopsy.report.ReportProgressPanel; -import org.sleuthkit.autopsy.report.modules.caseuco.CaseUcoReportGenerator; +import org.sleuthkit.caseuco.CaseUcoExporter; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; @@ -76,6 +83,7 @@ import org.sleuthkit.datamodel.VolumeSystem; * Creates a portable case from tagged files */ public class PortableCaseReportModule implements ReportModule { + private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName()); private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS @@ -83,35 +91,35 @@ public class PortableCaseReportModule implements ReportModule { private static final String CASE_UCO_FILE_NAME = "portable_CASE_UCO_output"; private static final String CASE_UCO_TMP_DIR = "case_uco_tmp"; private PortableCaseReportModuleSettings settings; - + // These are the types for the exported file subfolders private static final List FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS, FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO); - + private Case currentCase = null; private SleuthkitCase portableSkCase = null; private String caseName = ""; private File caseFolder = null; private File copiedFilesFolder = null; - + // Maps old object ID from current case to new object in portable case private final Map oldIdToNewContent = new HashMap<>(); - + // Maps new object ID to the new object private final Map newIdToContent = new HashMap<>(); - + // Maps old TagName to new TagName private final Map oldTagNameToNewTagName = new HashMap<>(); // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present. private final Map oldArtTypeIdToNewArtTypeId = new HashMap<>(); - + // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present. private final Map oldAttrTypeIdToNewAttrType = new HashMap<>(); - + // Map of old artifact ID to new artifact private final Map oldArtifactIdToNewArtifact = new HashMap<>(); - + public PortableCaseReportModule() { } @@ -141,11 +149,11 @@ public class PortableCaseReportModule implements ReportModule { } return caseName; } - + /** * Convenience method for handling cancellation - * - * @param progressPanel The report progress panel + * + * @param progressPanel The report progress panel */ private void handleCancellation(ReportProgressPanel progressPanel) { logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS @@ -153,16 +161,16 @@ public class PortableCaseReportModule implements ReportModule { progressPanel.complete(ReportProgressPanel.ReportStatus.CANCELED); cleanup(); } - + /** - * Convenience method to avoid code duplication. - * Assumes that if an exception is supplied then the error is SEVERE. Otherwise - * it is logged as a WARNING. - * - * @param logWarning Warning to write to the log - * @param dialogWarning Warning to write to a pop-up window - * @param ex The exception (can be null) - * @param progressPanel The report progress panel + * Convenience method to avoid code duplication. Assumes that if an + * exception is supplied then the error is SEVERE. Otherwise it is logged as + * a WARNING. + * + * @param logWarning Warning to write to the log + * @param dialogWarning Warning to write to a pop-up window + * @param ex The exception (can be null) + * @param progressPanel The report progress panel */ private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) { if (ex == null) { @@ -208,24 +216,24 @@ public class PortableCaseReportModule implements ReportModule { progressPanel.setIndeterminate(true); progressPanel.start(); progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying()); - + // Clear out any old values cleanup(); - + // Validate the input parameters File outputDir = new File(reportPath); - if (! outputDir.exists()) { + if (!outputDir.exists()) { handleError("Output folder " + outputDir.toString() + " does not exist", Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS return; } - - if (! outputDir.isDirectory()) { + + if (!outputDir.isDirectory()) { handleError("Output folder " + outputDir.toString() + " is not a folder", Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS return; } - + // Save the current case object try { currentCase = Case.getCurrentCaseThrows(); @@ -234,41 +242,41 @@ public class PortableCaseReportModule implements ReportModule { handleError("Current case has been closed", Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS return; - } - + } + // Check that there will be something to copy List tagNames; if (options.areAllTagsSelected()) { try { tagNames = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse(); } catch (NoCurrentCaseException | TskCoreException ex) { - handleError("Unable to get all tags", - Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS + handleError("Unable to get all tags", + Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS return; } } else { tagNames = options.getSelectedTagNames(); } - + List setNames; if (options.areAllSetsSelected()) { try { setNames = getAllInterestingItemsSets(); } catch (NoCurrentCaseException | TskCoreException ex) { - handleError("Unable to get all interesting items sets", - Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS + handleError("Unable to get all interesting items sets", + Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS return; } } else { setNames = options.getSelectedSetNames(); } - - if (tagNames.isEmpty() && setNames.isEmpty()) { - handleError("No content to copy", + + if (tagNames.isEmpty() && setNames.isEmpty()) { + handleError("No content to copy", Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS return; } - + // Create the case. // portableSkCase and caseFolder will be set here. progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase()); @@ -277,13 +285,13 @@ public class PortableCaseReportModule implements ReportModule { // The error has already been handled return; } - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); return; } - + // Set up the table for the image tags try { initializeImageTags(progressPanel); @@ -291,11 +299,11 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS return; } - + // Copy the selected tags progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags()); try { - for(TagName tagName:tagNames) { + for (TagName tagName : tagNames) { TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus()); oldTagNameToNewTagName.put(tagName, newTagName); } @@ -303,12 +311,12 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS return; } - + // Set up tracking to support any custom artifact or attribute types - for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) { + for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) { oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID()); } - for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) { + for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) { try { oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel())); } catch (TskCoreException ex) { @@ -316,11 +324,11 @@ public class PortableCaseReportModule implements ReportModule { Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()), ex, progressPanel); // NON-NLS } - } - + } + // Copy the tagged files try { - for(TagName tagName:tagNames) { + for (TagName tagName : tagNames) { // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); @@ -328,7 +336,7 @@ public class PortableCaseReportModule implements ReportModule { } progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName())); addFilesToPortableCase(tagName, progressPanel); - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); @@ -338,11 +346,11 @@ public class PortableCaseReportModule implements ReportModule { } catch (TskCoreException ex) { handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS return; - } - + } + // Copy the tagged artifacts and associated files try { - for(TagName tagName:tagNames) { + for (TagName tagName : tagNames) { // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); @@ -350,7 +358,7 @@ public class PortableCaseReportModule implements ReportModule { } progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName())); addArtifactsToPortableCase(tagName, progressPanel); - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); @@ -361,18 +369,18 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS return; } - + // Copy interesting files and results - if (! setNames.isEmpty()) { + if (!setNames.isEmpty()) { try { List interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); - for (BlackboardArtifact art:interestingFiles) { + for (BlackboardArtifact art : interestingFiles) { // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); return; } - + BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); if (setNames.contains(setAttr.getValueString())) { copyContentToPortableCase(art, progressPanel); @@ -385,7 +393,7 @@ public class PortableCaseReportModule implements ReportModule { try { List interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT); - for (BlackboardArtifact art:interestingResults) { + for (BlackboardArtifact art : interestingResults) { // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); @@ -400,49 +408,49 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS return; } - } - + } + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); return; } - + //Attempt to generate and included the CASE-UCO report. generateCaseUcoReport(tagNames, setNames, progressPanel); // Compress the case (if desired) if (options.shouldCompress()) { progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase()); - + boolean success = compressCase(progressPanel); - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { handleCancellation(progressPanel); return; } - - if (! success) { + + if (!success) { // Errors have been handled already return; } } - + // Close the case connections and clear out the maps cleanup(); - + progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE); - + } - + /** * Generates a CASE-UCO report for all files that have a specified TagName * or TSK_INTERESTING artifacts that are flagged by the specified SET_NAMEs. - * + * * Only one copy of the file will be saved in the report if it is the source * of more than one of the above. - * + * * @param tagNames TagNames to included in the report. * @param setNames SET_NAMEs to include in the report. * @param progressPanel ProgressPanel to relay progress messages. @@ -456,17 +464,24 @@ public class PortableCaseReportModule implements ReportModule { private void generateCaseUcoReport(List tagNames, List setNames, ReportProgressPanel progressPanel) { //Create the 'Reports' directory to include a CASE-UCO report. Path reportsDirectory = Paths.get(caseFolder.toString(), "Reports"); - if(!reportsDirectory.toFile().mkdir()) { + if (!reportsDirectory.toFile().mkdir()) { logger.log(Level.SEVERE, "Could not make the report folder... skipping " + "CASE-UCO report generation for the portable case"); return; } - try { - //Try to generate case uco output. - progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration()); - CaseUcoReportGenerator reportGenerator = new CaseUcoReportGenerator(reportsDirectory, CASE_UCO_FILE_NAME); - //Acquire references for file discovery + Path reportFile = reportsDirectory.resolve(CASE_UCO_FILE_NAME); + + progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration()); + try (OutputStream stream = new FileOutputStream(reportFile.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(); + + //Acquire references to querying files. String caseTempDirectory = currentCase.getTempDirectory(); SleuthkitCase skCase = currentCase.getSleuthkitCase(); TagsManager tagsManager = currentCase.getServices().getTagsManager(); @@ -477,43 +492,45 @@ public class PortableCaseReportModule implements ReportModule { FileUtils.deleteDirectory(tmpDir.toFile()); Files.createDirectory(tmpDir); - reportGenerator.addCase(currentCase); - + //First export the Sleuthkit Case instance. + CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase()); + for (JsonElement element : exporter.exportSleuthkitCase()) { + gson.toJson(element, reportWriter); + } + //Load all interesting BlackboardArtifacts that belong to the selected SET_NAMEs //binned by data source id. Multimap artifactsWithSetName = getInterestingArtifactsBySetName(skCase, setNames); - + //Search each data source looking for content tags and interesting //items that match the selected tag names and set names. - for (Content dataSource : currentCase.getDataSources()) { - /** - * It is currently believed that DataSources in a CASE-UCO report - * should precede all file entities. Therefore, before - * writing a file, add the data source if it - * has yet to be included. - */ + for (DataSource dataSource : currentCase.getSleuthkitCase().getDataSources()) { + // Helper flag to ensure each data source is only written once in + // a report. boolean dataSourceHasBeenIncluded = false; + //Search content tags and artifact tags that match for (TagName tagName : tagNames) { for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) { - dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(), - dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded); + dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(), + dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded); } for (BlackboardArtifactTag bat : tagsManager.getBlackboardArtifactTagsByTagName(tagName, dataSource.getId())) { - dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(), - dataSource, tmpDir, reportGenerator, dataSourceHasBeenIncluded); + dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(), + dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded); } } //Search artifacts that this data source contains - for(BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) { + for (BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) { Content sourceContent = bArt.getParent(); - dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource, - tmpDir, reportGenerator, dataSourceHasBeenIncluded); + dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource, + tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded); } } - - //Create the report. - reportGenerator.generateReport(); + + // Finish the report. + reportWriter.endArray(); + reportWriter.endObject(); progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_successCaseUcoReportGeneration()); } catch (IOException | TskCoreException ex) { progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_errorGeneratingCaseUcoReport()); @@ -522,7 +539,7 @@ public class PortableCaseReportModule implements ReportModule { + "completed without a CASE-UCO report.", ex); } } - + /** * Load all interesting BlackboardArtifacts that belong to the selected * SET_NAME. This operation would be duplicated for every data source, since @@ -530,15 +547,15 @@ public class PortableCaseReportModule implements ReportModule { */ private Multimap getInterestingArtifactsBySetName(SleuthkitCase skCase, List setNames) throws TskCoreException { Multimap artifactsWithSetName = ArrayListMultimap.create(); - if(!setNames.isEmpty()) { + if (!setNames.isEmpty()) { List allArtifacts = skCase.getBlackboardArtifacts( BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); allArtifacts.addAll(skCase.getBlackboardArtifacts( BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT)); - for(BlackboardArtifact bArt : allArtifacts) { + for (BlackboardArtifact bArt : allArtifacts) { BlackboardAttribute setAttr = bArt.getAttribute( - new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); + new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)); if (setNames.contains(setAttr.getValueString())) { artifactsWithSetName.put(bArt.getDataSource().getId(), bArt); } @@ -546,7 +563,7 @@ public class PortableCaseReportModule implements ReportModule { } return artifactsWithSetName; } - + /** * Adds the content if and only if it has not already been seen. * @@ -555,32 +572,36 @@ public class PortableCaseReportModule implements ReportModule { * @param tmpDir Path to the tmpDir to enforce uniqueness * @param reportGenerator Report generator instance to add the content to * @param dataSourceHasBeenIncluded Flag determining if the data source - * should be written before the file. False will cause the data source to be written. + * should be written to the report (false indicates that it should be written). * @throws IOException If an I/O error occurs. * @throws TskCoreException If an internal database error occurs. * - * return True if the data source was written during this operation. + * return True if the file was written during this operation. */ - private boolean addUniqueFile(Content content, Content dataSource, - Path tmpDir, CaseUcoReportGenerator reportGenerator, + private boolean addUniqueFile(Content content, DataSource dataSource, + Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter, boolean dataSourceHasBeenIncluded) throws IOException, TskCoreException { if (content instanceof AbstractFile && !(content instanceof DataSource)) { AbstractFile absFile = (AbstractFile) content; Path filePath = tmpDir.resolve(Long.toString(absFile.getId())); if (!absFile.isDir() && !Files.exists(filePath)) { - if(!dataSourceHasBeenIncluded) { - reportGenerator.addDataSource(dataSource, currentCase); + if (!dataSourceHasBeenIncluded) { + for (JsonElement element : exporter.exportDataSource(dataSource)) { + gson.toJson(element, reportWriter); + } } String subFolder = getExportSubfolder(absFile); String fileName = absFile.getId() + "-" + FileUtil.escapeFileName(absFile.getName()); - reportGenerator.addFile(absFile, dataSource, Paths.get(FILE_FOLDER_NAME, subFolder, fileName)); + for (JsonElement element : exporter.exportAbstractFile(absFile, Paths.get(FILE_FOLDER_NAME, subFolder, fileName).toString())) { + gson.toJson(element, reportWriter); + } Files.createFile(filePath); return true; } } return false; } - + private List getAllInterestingItemsSets() throws NoCurrentCaseException, TskCoreException { // Get the set names in use for the current case. @@ -603,14 +624,13 @@ public class PortableCaseReportModule implements ReportModule { setNames.addAll(setCounts.keySet()); return setNames; } - /** - * Create the case directory and case database. - * portableSkCase will be set if this completes without error. - * - * @param outputDir The parent for the case folder - * @param progressPanel + * Create the case directory and case database. portableSkCase will be set + * if this completes without error. + * + * @param outputDir The parent for the case folder + * @param progressPanel */ @NbBundle.Messages({ "# {0} - case folder", @@ -618,8 +638,7 @@ public class PortableCaseReportModule implements ReportModule { "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case", "# {0} - folder", "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}", - "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs", - }) + "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",}) private void createCase(File outputDir, ReportProgressPanel progressPanel) { // Create the case folder @@ -627,66 +646,66 @@ public class PortableCaseReportModule implements ReportModule { if (caseFolder.exists()) { handleError("Case folder " + caseFolder.toString() + " already exists", - Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS + Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS return; } - + // Create the case try { portableSkCase = currentCase.createPortableCase(caseName, caseFolder); } catch (TskCoreException ex) { handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(), - Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS + Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS return; } - + // Store the highest IDs try { saveHighestIds(); } catch (TskCoreException ex) { handleError("Error storing maximum database IDs", - Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS + Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS return; } - + // Create the base folder for the copied files copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile(); - if (! copiedFilesFolder.mkdir()) { + if (!copiedFilesFolder.mkdir()) { handleError("Error creating folder " + copiedFilesFolder.toString(), Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS return; } - + // Create subfolders for the copied files - for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) { + for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) { File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile(); - if (! subFolder.mkdir()) { + if (!subFolder.mkdir()) { handleError("Error creating folder " + subFolder.toString(), - Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS + Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS return; } } File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile(); - if (! unknownTypeFolder.mkdir()) { + if (!unknownTypeFolder.mkdir()) { handleError("Error creating folder " + unknownTypeFolder.toString(), - Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS + Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS return; } - + } - + /** * Save the current highest IDs to the portable case. - * - * @throws TskCoreException + * + * @throws TskCoreException */ private void saveHighestIds() throws TskCoreException { - + CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager(); - + String tableSchema = "( table_name TEXT PRIMARY KEY, " - + " max_id TEXT)"; // NON-NLS - + + " max_id TEXT)"; // NON-NLS + portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema); currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS @@ -694,51 +713,51 @@ public class PortableCaseReportModule implements ReportModule { currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS } - + /** * Set up the image tag table in the portable case - * - * @param progressPanel - * - * @throws TskCoreException + * + * @param progressPanel + * + * @throws TskCoreException */ private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException { - + // Create the image tags table in the portable case CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager(); - if (! portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) { + if (!portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) { portableDbAccessManager.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE); } } - + /** * Add all files with a given tag to the portable case. - * - * @param oldTagName The TagName object from the current case + * + * @param oldTagName The TagName object from the current case * @param progressPanel The progress panel - * - * @throws TskCoreException + * + * @throws TskCoreException */ private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException { - + // Get all the tags in the current case List tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName); - + // Copy the files into the portable case and tag for (ContentTag tag : tags) { - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { return; } - + Content content = tag.getContent(); if (content instanceof AbstractFile) { - + long newFileId = copyContentToPortableCase(content, progressPanel); - + // Tag the file - if (! oldTagNameToNewTagName.containsKey(tag.getName())) { + if (!oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset()); @@ -746,21 +765,22 @@ public class PortableCaseReportModule implements ReportModule { // Get the image tag data associated with this tag (empty string if there is none) // and save it if present String appData = getImageTagDataForContentTag(tag); - if (! appData.isEmpty()) { + if (!appData.isEmpty()) { addImageTagToPortableCase(newContentTag.getAddedTag(), appData); } } - } - } - + } + } + /** * Gets the image tag data for a given content tag - * + * * @param tag The ContentTag in the current case - * - * @return The app_data string for this content tag or an empty string if there was none - * - * @throws TskCoreException + * + * @return The app_data string for this content tag or an empty string if + * there was none + * + * @throws TskCoreException */ private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException { @@ -769,7 +789,7 @@ public class PortableCaseReportModule implements ReportModule { currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback); return callback.getAppData(); } - + /** * CaseDbAccessManager callback to get the app_data string for the image tag */ @@ -777,7 +797,7 @@ public class PortableCaseReportModule implements ReportModule { private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName()); private String appData = ""; - + @Override public void process(ResultSet rs) { try { @@ -791,106 +811,107 @@ public class PortableCaseReportModule implements ReportModule { } catch (SQLException ex) { logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS } - } - + } + /** * Get the app_data string - * + * * @return the app_data string */ String getAppData() { return appData; } } - + /** * Add an image tag to the portable case. - * + * * @param newContentTag The content tag in the portable case - * @param appData The string to copy into app_data - * - * @throws TskCoreException + * @param appData The string to copy into app_data + * + * @throws TskCoreException */ private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException { String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')"; portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert); } - - + /** * Add all artifacts with a given tag to the portable case. - * - * @param oldTagName The TagName object from the current case + * + * @param oldTagName The TagName object from the current case * @param progressPanel The progress panel - * - * @throws TskCoreException + * + * @throws TskCoreException */ private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException { - + List tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName); - + // Copy the artifacts into the portable case along with their content and tag for (BlackboardArtifactTag tag : tags) { - + // Check for cancellation if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { return; } - + // Copy the source content Content content = tag.getContent(); long newContentId = copyContentToPortableCase(content, progressPanel); - + // Copy the artifact BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact()); - + // Tag the artfiact - if (! oldTagNameToNewTagName.containsKey(tag.getName())) { + if (!oldTagNameToNewTagName.containsKey(tag.getName())) { throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS } portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment()); - } - } - + } + } + /** - * Copy an artifact into the new case. Will also copy any associated artifacts - * - * @param newContentId The content ID (in the portable case) of the source content + * Copy an artifact into the new case. Will also copy any associated + * artifacts + * + * @param newContentId The content ID (in the portable case) of the source + * content * @param artifactToCopy The artifact to copy - * + * * @return The new artifact in the portable case - * - * @throws TskCoreException + * + * @throws TskCoreException */ private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException { - + if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) { return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID()); } - + // First create the associated artifact (if present) BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); List newAttrs = new ArrayList<>(); if (oldAssociatedAttribute != null) { BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong()); BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact); - newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, - String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID())); + newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, + String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID())); } - + // Create the new artifact int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy); BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId); List oldAttrs = artifactToCopy.getAttributes(); - + // Copy over each attribute, making sure the type is in the new case. - for (BlackboardAttribute oldAttr:oldAttrs) { - + for (BlackboardAttribute oldAttr : oldAttrs) { + // The associated artifact has already been handled if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) { continue; } - + BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr); switch (oldAttr.getValueType()) { case BYTE: @@ -905,7 +926,7 @@ public class PortableCaseReportModule implements ReportModule { newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()), oldAttr.getValueInt())); break; - case DATETIME: + case DATETIME: case LONG: newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()), oldAttr.getValueLong())); @@ -919,26 +940,27 @@ public class PortableCaseReportModule implements ReportModule { throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS } } - + newArtifact.addAttributes(newAttrs); - + oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact); return newArtifact; } - + /** - * Get the artifact type ID in the portable case and create new artifact type if needed. - * For built-in artifacts this will be the same as the original. - * + * Get the artifact type ID in the portable case and create new artifact + * type if needed. For built-in artifacts this will be the same as the + * original. + * * @param oldArtifact The artifact in the current case - * + * * @return The corresponding artifact type ID in the portable case */ private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException { if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) { return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID()); } - + BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName()); try { BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName()); @@ -948,13 +970,14 @@ public class PortableCaseReportModule implements ReportModule { throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS } } - + /** - * Get the attribute type ID in the portable case and create new attribute type if needed. - * For built-in attributes this will be the same as the original. - * + * Get the attribute type ID in the portable case and create new attribute + * type if needed. For built-in attributes this will be the same as the + * original. + * * @param oldAttribute The attribute in the current case - * + * * @return The corresponding attribute type in the portable case */ private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException { @@ -962,9 +985,9 @@ public class PortableCaseReportModule implements ReportModule { if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) { return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID()); } - + try { - BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(), + BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(), oldAttrType.getValueType(), oldAttrType.getDisplayName()); oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType); return newCustomType; @@ -975,39 +998,38 @@ public class PortableCaseReportModule implements ReportModule { /** * Top level method to copy a content object to the portable case. - * - * @param content The content object to copy + * + * @param content The content object to copy * @param progressPanel The progress panel - * + * * @return The object ID of the copied content in the portable case - * - * @throws TskCoreException + * + * @throws TskCoreException */ @NbBundle.Messages({ "# {0} - File name", - "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}", - }) + "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",}) private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException { progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath())); return copyContent(content); } - + /** * Returns the object ID for the given content object in the portable case. - * + * * @param content The content object to copy into the portable case - * + * * @return the new object ID for this content - * - * @throws TskCoreException + * + * @throws TskCoreException */ private long copyContent(Content content) throws TskCoreException { - + // Check if we've already copied this content if (oldIdToNewContent.containsKey(content.getId())) { return oldIdToNewContent.get(content.getId()).getId(); } - + // Otherwise: // - Make parent of this object (if applicable) // - Copy this content @@ -1015,42 +1037,42 @@ public class PortableCaseReportModule implements ReportModule { if (content.getParent() != null) { parentId = copyContent(content.getParent()); } - + Content newContent; if (content instanceof BlackboardArtifact) { - BlackboardArtifact artifactToCopy = (BlackboardArtifact)content; + BlackboardArtifact artifactToCopy = (BlackboardArtifact) content; newContent = copyArtifact(parentId, artifactToCopy); } else { CaseDbTransaction trans = portableSkCase.beginTransaction(); try { if (content instanceof Image) { - Image image = (Image)content; - newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(), + Image image = (Image) content; + newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(), new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans); } else if (content instanceof VolumeSystem) { - VolumeSystem vs = (VolumeSystem)content; + VolumeSystem vs = (VolumeSystem) content; newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans); } else if (content instanceof Volume) { - Volume vs = (Volume)content; - newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(), + Volume vs = (Volume) content; + newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(), vs.getDescription(), vs.getFlags(), trans); } else if (content instanceof Pool) { - Pool pool = (Pool)content; + Pool pool = (Pool) content; newContent = portableSkCase.addPool(parentId, pool.getType(), trans); } else if (content instanceof FileSystem) { - FileSystem fs = (FileSystem)content; - newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(), - fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(), + FileSystem fs = (FileSystem) content; + newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(), + fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(), fs.getName(), trans); } else if (content instanceof BlackboardArtifact) { - BlackboardArtifact artifactToCopy = (BlackboardArtifact)content; + BlackboardArtifact artifactToCopy = (BlackboardArtifact) content; newContent = copyArtifact(parentId, artifactToCopy); } else if (content instanceof AbstractFile) { - AbstractFile abstractFile = (AbstractFile)content; - + AbstractFile abstractFile = (AbstractFile) content; + if (abstractFile instanceof LocalFilesDataSource) { - LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile; - newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans); + LocalFilesDataSource localFilesDS = (LocalFilesDataSource) abstractFile; + newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans); } else { if (abstractFile.isDir()) { newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans); @@ -1065,21 +1087,21 @@ public class PortableCaseReportModule implements ReportModule { // Get the new parent object in the portable case database Content oldParent = abstractFile.getParent(); - if (! oldIdToNewContent.containsKey(oldParent.getId())) { + if (!oldIdToNewContent.containsKey(oldParent.getId())) { throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS } Content newParent = oldIdToNewContent.get(oldParent.getId()); // Construct the relative path to the copied file - String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName; + String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName; newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(), abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(), abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(), - true, TskData.EncodingType.NONE, + true, TskData.EncodingType.NONE, newParent, trans); } catch (IOException ex) { - throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID " + throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID " + abstractFile.getId(), ex); // NON-NLS } } @@ -1088,38 +1110,38 @@ public class PortableCaseReportModule implements ReportModule { throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS } trans.commit(); - } catch (TskCoreException ex) { + } catch (TskCoreException ex) { trans.rollback(); - throw(ex); + throw (ex); } } - + // Save the new object oldIdToNewContent.put(content.getId(), newContent); newIdToContent.put(newContent.getId(), newContent); return oldIdToNewContent.get(content.getId()).getId(); } - + /** * Return the subfolder name for this file based on MIME type - * + * * @param abstractFile the file - * - * @return the name of the appropriate subfolder for this file type + * + * @return the name of the appropriate subfolder for this file type */ private String getExportSubfolder(AbstractFile abstractFile) { if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) { return UNKNOWN_FILE_TYPE_FOLDER; } - - for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) { + + for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) { if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) { return cat.getDisplayName(); } } return UNKNOWN_FILE_TYPE_FOLDER; } - + /** * Clear out the maps and other fields and close the database connections. */ @@ -1132,12 +1154,12 @@ public class PortableCaseReportModule implements ReportModule { oldArtifactIdToNewArtifact.clear(); closePortableCaseDatabase(); - + currentCase = null; caseFolder = null; copiedFilesFolder = null; } - + /** * Close the portable case */ @@ -1153,15 +1175,14 @@ public class PortableCaseReportModule implements ReportModule { configPanel = new CreatePortableCasePanel(); return configPanel; } */ - private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback { private final String tableName; - + StoreMaxIdCallback(String tableName) { this.tableName = tableName; } - + @Override public void process(ResultSet rs) { @@ -1177,60 +1198,59 @@ public class PortableCaseReportModule implements ReportModule { } catch (TskCoreException ex) { logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS } - + } } catch (SQLException ex) { logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS } } } - + @NbBundle.Messages({ "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable", "# {0} - Temp folder path", "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}", "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case", - "PortableCaseReportModule.compressCase.canceled=Compression canceled by user", - }) + "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",}) private boolean compressCase(ReportProgressPanel progressPanel) { - + // Close the portable case database (we still need some of the variables that would be cleared by cleanup()) closePortableCaseDatabase(); - + // Make a temporary folder for the compressed case File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS - if (! tempZipFolder.mkdir()) { - handleError("Error creating temporary folder " + tempZipFolder.toString(), + if (!tempZipFolder.mkdir()) { + handleError("Error creating temporary folder " + tempZipFolder.toString(), Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS return false; } - + // Find 7-Zip File sevenZipExe = locate7ZipExecutable(); if (sevenZipExe == null) { handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS return false; } - + // Create the chunk option String chunkOption = ""; if (settings.getChunkSize() != PortableCaseReportModuleSettings.ChunkSize.NONE) { chunkOption = "-v" + settings.getChunkSize().getSevenZipParam(); } - + File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS ProcessBuilder procBuilder = new ProcessBuilder(); procBuilder.command( sevenZipExe.getAbsolutePath(), - "a", // Add to archive + "a", // Add to archive zipFile.getAbsolutePath(), caseFolder.getAbsolutePath(), chunkOption ); - + try { Process process = procBuilder.start(); - + while (process.isAlive()) { if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { process.destroy(); @@ -1248,7 +1268,7 @@ public class PortableCaseReportModule implements ReportModule { sb.append(line).append(System.getProperty("line.separator")); // NON-NLS } } - + handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS return false; } @@ -1256,7 +1276,7 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS return false; } - + // Delete everything in the case folder then copy over the compressed file(s) try { FileUtils.cleanDirectory(caseFolder); @@ -1266,10 +1286,10 @@ public class PortableCaseReportModule implements ReportModule { handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS return false; } - + return true; } - + /** * Locate the 7-Zip executable from the release folder. * @@ -1292,7 +1312,7 @@ public class PortableCaseReportModule implements ReportModule { return exeFile; } - + /** * Processes the result sets from the interesting item set name query. */ @@ -1300,7 +1320,7 @@ public class PortableCaseReportModule implements ReportModule { private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GetInterestingItemSetNamesCallback.class.getName()); private final Map setCounts = new HashMap<>(); - + @Override public void process(ResultSet rs) { try { @@ -1310,7 +1330,7 @@ public class PortableCaseReportModule implements ReportModule { String setName = rs.getString("set_name"); // NON-NLS setCounts.put(setName, setCount); - + } catch (SQLException ex) { logger.log(Level.WARNING, "Unable to get data_source_obj_id or value from result set", ex); // NON-NLS } @@ -1318,11 +1338,11 @@ public class PortableCaseReportModule implements ReportModule { } catch (SQLException ex) { logger.log(Level.WARNING, "Failed to get next result for values by datasource", ex); // NON-NLS } - } - + } + /** * Gets the counts for each interesting items set - * + * * @return A map from each set name to the number of items in it */ public Map getSetCountMap() { From 017f9b3b344c81d21dc402f9e2735c1c0c394925 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 14 Jul 2020 11:35:28 -0400 Subject: [PATCH 2/3] Updated comments and made a bug fix --- .../modules/caseuco/CaseUcoReportModule.java | 75 ++++++++++++------- .../PortableCaseReportModule.java | 2 - 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java index 2da96ac34f..2731515ca9 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java @@ -59,8 +59,8 @@ 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 { @@ -156,7 +156,7 @@ public final class CaseUcoReportModule implements GeneralReportModule { return; } - Case caseObj = Case.getCurrentCaseThrows(); + Case currentCase = Case.getCurrentCaseThrows(); Path caseJsonReportFile = reportDirectory.resolve(REPORT_FILE_NAME + "." + EXTENSION); @@ -168,28 +168,21 @@ public final class CaseUcoReportModule implements GeneralReportModule { reportWriter.name("@graph"); reportWriter.beginArray(); - //First write the Case to the report file. - CaseUcoExporter exporter = new CaseUcoExporter(caseObj.getSleuthkitCase()); + CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase()); for (JsonElement element : exporter.exportSleuthkitCase()) { gson.toJson(element, reportWriter); } - // Prune the data sources so that we only report on what was selected. - List dataSources = caseObj.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()); + // Get a list of selected data sources to process. + List dataSources = getSelectedDataSources(currentCase, settings); progressPanel.setIndeterminate(false); progressPanel.setMaximumProgress(dataSources.size()); progressPanel.start(); - //Then search each data source for file content. + // 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( @@ -204,17 +197,26 @@ public final class CaseUcoReportModule implements GeneralReportModule { progressPanel.setProgress(i + 1); } - // Write all standard artifacts to the report. - for (ARTIFACT_TYPE artType : caseObj.getSleuthkitCase().getBlackboardArtifactTypesInUse()) { - for (BlackboardArtifact artifact : caseObj.getSleuthkitCase().getBlackboardArtifacts(artType)) { - try { - for (JsonElement element : exporter.exportBlackboardArtifact(artifact)) { - gson.toJson(element, reportWriter); + // Second stage of reporting handles artifacts. + Set 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())); } - } 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())); } } } @@ -223,7 +225,7 @@ public final class CaseUcoReportModule implements GeneralReportModule { reportWriter.endObject(); } - caseObj.addReport(caseJsonReportFile.toString(), + currentCase.addReport(caseJsonReportFile.toString(), Bundle.CaseUcoReportModule_srcModuleName(), REPORT_FILE_NAME); progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE); @@ -244,6 +246,21 @@ public final class CaseUcoReportModule implements GeneralReportModule { progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE); } + /** + * Get the selected data sources from the settings instance. + */ + private List 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. */ @@ -268,7 +285,7 @@ public final class CaseUcoReportModule implements GeneralReportModule { 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); } @@ -280,4 +297,4 @@ public final class CaseUcoReportModule implements GeneralReportModule { } } } -} \ No newline at end of file +} diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index b871d4bf84..b02b95aaa6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -481,7 +481,6 @@ public class PortableCaseReportModule implements ReportModule { reportWriter.name("@graph"); reportWriter.beginArray(); - //Acquire references to querying files. String caseTempDirectory = currentCase.getTempDirectory(); SleuthkitCase skCase = currentCase.getSleuthkitCase(); TagsManager tagsManager = currentCase.getServices().getTagsManager(); @@ -492,7 +491,6 @@ public class PortableCaseReportModule implements ReportModule { FileUtils.deleteDirectory(tmpDir.toFile()); Files.createDirectory(tmpDir); - //First export the Sleuthkit Case instance. CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase()); for (JsonElement element : exporter.exportSleuthkitCase()) { gson.toJson(element, reportWriter); From 880f98d8adf1c511d4e00a97beee6bf870244907 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 16 Jul 2020 18:04:10 -0400 Subject: [PATCH 3/3] Updated file extension --- .../autopsy/report/modules/caseuco/CaseUcoReportModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java index 2731515ca9..1523005d92 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/caseuco/CaseUcoReportModule.java @@ -77,7 +77,7 @@ public final class CaseUcoReportModule implements GeneralReportModule { }; private static final String REPORT_FILE_NAME = "CASE_UCO_output"; - private static final String EXTENSION = "json-ld"; + private static final String EXTENSION = "jsonld"; // Hidden constructor for the report private CaseUcoReportModule() {