Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CaseUcoReportGenerator.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2018-2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.report.modules.caseuco;
20 
21 import com.fasterxml.jackson.annotation.JsonAnyGetter;
22 import com.fasterxml.jackson.annotation.JsonInclude;
23 import com.fasterxml.jackson.annotation.JsonProperty;
24 import java.io.IOException;
25 import java.nio.file.Path;
26 import java.util.SimpleTimeZone;
27 import java.util.TimeZone;
31 import org.sleuthkit.datamodel.AbstractFile;
32 import org.sleuthkit.datamodel.SleuthkitCase;
33 import com.fasterxml.jackson.core.JsonEncoding;
34 import com.fasterxml.jackson.core.JsonFactory;
35 import com.fasterxml.jackson.core.JsonGenerator;
36 import com.fasterxml.jackson.core.util.DefaultIndenter;
37 import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
38 import com.fasterxml.jackson.databind.ObjectMapper;
39 import com.google.common.base.Strings;
40 import java.util.ArrayList;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44 import org.sleuthkit.datamodel.Content;
45 import org.sleuthkit.datamodel.Image;
46 import org.sleuthkit.datamodel.TskCoreException;
47 
77 public final class CaseUcoReportGenerator {
78 
79  private static final String EXTENSION = "json-ld";
80 
81  private final TimeZone timeZone;
82  private final Path reportPath;
83  private final JsonGenerator reportGenerator;
84 
98  public CaseUcoReportGenerator(Path directory, String reportName) throws IOException {
99  this.reportPath = directory.resolve(reportName + "." + EXTENSION);
100 
101  JsonFactory jsonGeneratorFactory = new JsonFactory();
102  reportGenerator = jsonGeneratorFactory.createGenerator(reportPath.toFile(), JsonEncoding.UTF8);
103  // Puts a newline between each Key, Value pair for readability.
104  reportGenerator.setPrettyPrinter(new DefaultPrettyPrinter()
105  .withObjectIndenter(new DefaultIndenter(" ", "\n")));
106 
107  ObjectMapper mapper = new ObjectMapper();
108  mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
109  mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
110 
111  reportGenerator.setCodec(mapper);
112 
113  reportGenerator.writeStartObject();
114  reportGenerator.writeFieldName("@graph");
115  reportGenerator.writeStartArray();
116 
117  //Assume GMT+0
118  this.timeZone = new SimpleTimeZone(0, "GMT");
119  }
120 
131  public void addFile(AbstractFile file, Content parentDataSource) throws IOException, TskCoreException {
132  addFile(file, parentDataSource, null);
133  }
134 
148  public void addFile(AbstractFile file, Content parentDataSource, Path localPath) throws IOException, TskCoreException {
149  String fileTraceId = getFileTraceId(file);
150 
151  //Create the Trace CASE node, which will contain attributes about some evidence.
152  //Trace is the standard term for evidence. For us, this means file system files.
153  CASENode fileTrace = new CASENode(fileTraceId, "Trace");
154 
155  //The bits of evidence for each Trace node are contained within Property
156  //Bundles. There are a number of Property Bundles available in the CASE ontology.
157 
158  //Build up the File Property Bundle, as the name implies - properties of
159  //the file itself.
160  CASEPropertyBundle filePropertyBundle = createFileBundle(file);
161  fileTrace.addBundle(filePropertyBundle);
162 
163  //Build up the ContentData Property Bundle, as the name implies - properties of
164  //the File data itself.
165  CASEPropertyBundle contentDataPropertyBundle = createContentDataBundle(file);
166  fileTrace.addBundle(contentDataPropertyBundle);
167 
168  if(localPath != null) {
169  String urlTraceId = getURLTraceId(file);
170  CASENode urlTrace = new CASENode(urlTraceId, "Trace");
171  CASEPropertyBundle urlPropertyBundle = new CASEPropertyBundle("URL");
172  urlPropertyBundle.addProperty("fullValue", localPath.toString());
173  urlTrace.addBundle(urlPropertyBundle);
174 
175  contentDataPropertyBundle.addProperty("dataPayloadReferenceUrl", urlTraceId);
176  reportGenerator.writeObject(urlTrace);
177  }
178 
179  //Create the Relationship CASE node. This defines how the Trace CASE node described above
180  //is related to another CASE node (in this case, the parent data source).
181  String relationshipID = getRelationshipId(file);
182  CASENode relationship = createRelationshipNode(relationshipID,
183  fileTraceId, getDataSourceTraceId(parentDataSource));
184 
185  //Build up the PathRelation bundle for the relationship node,
186  //as the name implies - the Path of the Trace in the data source.
187  CASEPropertyBundle pathRelationPropertyBundle = new CASEPropertyBundle("PathRelation");
188  pathRelationPropertyBundle.addProperty("path", file.getUniquePath());
189  relationship.addBundle(pathRelationPropertyBundle);
190 
191  //This completes the triage, write them to JSON.
192  reportGenerator.writeObject(fileTrace);
193  reportGenerator.writeObject(relationship);
194  }
195 
196  private String getURLTraceId(Content content) {
197  return "url-" + content.getId();
198  }
199 
204  private CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID) {
205  CASENode relationship = new CASENode(relationshipID, "Relationship");
206  relationship.addProperty("source", sourceID);
207  relationship.addProperty("target", targetID);
208  relationship.addProperty("kindOfRelationship", "contained-within");
209  relationship.addProperty("isDirectional", true);
210  return relationship;
211  }
212 
216  private CASEPropertyBundle createFileBundle(AbstractFile file) throws TskCoreException {
217  CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
218  String createdTime = ContentUtils.getStringTimeISO8601(file.getCrtime(), timeZone);
219  String accessedTime = ContentUtils.getStringTimeISO8601(file.getAtime(), timeZone);
220  String modifiedTime = ContentUtils.getStringTimeISO8601(file.getMtime(), timeZone);
221  filePropertyBundle.addProperty("createdTime", createdTime);
222  filePropertyBundle.addProperty("accessedTime", accessedTime);
223  filePropertyBundle.addProperty("modifiedTime", modifiedTime);
224  if (!Strings.isNullOrEmpty(file.getNameExtension())) {
225  filePropertyBundle.addProperty("extension", file.getNameExtension());
226  }
227  filePropertyBundle.addProperty("fileName", file.getName());
228  filePropertyBundle.addProperty("filePath", file.getUniquePath());
229  filePropertyBundle.addProperty("isDirectory", file.isDir());
230  filePropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
231  return filePropertyBundle;
232  }
233 
237  private CASEPropertyBundle createContentDataBundle(AbstractFile file) {
238  CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
239  if (!Strings.isNullOrEmpty(file.getMIMEType())) {
240  contentDataPropertyBundle.addProperty("mimeType", file.getMIMEType());
241  }
242  if (!Strings.isNullOrEmpty(file.getMd5Hash())) {
243  List<CASEPropertyBundle> hashPropertyBundles = new ArrayList<>();
244  CASEPropertyBundle md5HashPropertyBundle = new CASEPropertyBundle("Hash");
245  md5HashPropertyBundle.addProperty("hashMethod", "MD5");
246  md5HashPropertyBundle.addProperty("hashValue", file.getMd5Hash());
247  hashPropertyBundles.add(md5HashPropertyBundle);
248  contentDataPropertyBundle.addProperty("hash", hashPropertyBundles);
249  }
250  contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(file.getSize()));
251  return contentDataPropertyBundle;
252  }
253 
257  private String getFileTraceId(AbstractFile file) {
258  return "file-" + file.getId();
259  }
260 
264  private String getRelationshipId(Content content) {
265  return "relationship-" + content.getId();
266  }
267 
278  public void addDataSource(Content dataSource, Case parentCase) throws IOException, TskCoreException {
279  String dataSourceTraceId = this.getDataSourceTraceId(dataSource);
280 
281  CASENode dataSourceTrace = new CASENode(dataSourceTraceId, "Trace");
282  CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
283 
284  String dataSourcePath = getDataSourcePath(dataSource);
285 
286  filePropertyBundle.addProperty("filePath", dataSourcePath);
287  dataSourceTrace.addBundle(filePropertyBundle);
288 
289  if (dataSource.getSize() > 0) {
290  CASEPropertyBundle contentDataPropertyBundle = new CASEPropertyBundle("ContentData");
291  contentDataPropertyBundle.addProperty("sizeInBytes", Long.toString(dataSource.getSize()));
292  dataSourceTrace.addBundle(contentDataPropertyBundle);
293  }
294 
295  // create a "relationship" entry between the case and the data source
296  String caseTraceId = getCaseTraceId(parentCase);
297  String relationshipTraceId = getRelationshipId(dataSource);
298  CASENode relationship = createRelationshipNode(relationshipTraceId,
299  dataSourceTraceId, caseTraceId);
300 
301  CASEPropertyBundle pathRelationBundle = new CASEPropertyBundle("PathRelation");
302  pathRelationBundle.addProperty("path", dataSourcePath);
303  relationship.addBundle(pathRelationBundle);
304 
305  //This completes the triage, write them to JSON.
306  reportGenerator.writeObject(dataSourceTrace);
307  reportGenerator.writeObject(relationship);
308  }
309 
310  private String getDataSourcePath(Content dataSource) {
311  String dataSourcePath = "";
312  if (dataSource instanceof Image) {
313  String[] paths = ((Image) dataSource).getPaths();
314  if (paths.length > 0) {
315  //Get the first data source in the path, as this will
316  //be reflected in each file's uniquePath.
317  dataSourcePath = paths[0];
318  }
319  } else {
320  dataSourcePath = dataSource.getName();
321  }
322  dataSourcePath = dataSourcePath.replaceAll("\\\\", "/");
323  return dataSourcePath;
324  }
325 
332  private String getDataSourceTraceId(Content dataSource) {
333  return "data-source-" + dataSource.getId();
334  }
335 
343  public void addCase(Case caseObj) throws IOException {
344  SleuthkitCase skCase = caseObj.getSleuthkitCase();
345 
346  String caseDirPath = skCase.getDbDirPath();
347  String caseTraceId = getCaseTraceId(caseObj);
348  CASENode caseTrace = new CASENode(caseTraceId, "Trace");
349  CASEPropertyBundle filePropertyBundle = new CASEPropertyBundle("File");
350 
351  // replace double slashes with single ones
352  caseDirPath = caseDirPath.replaceAll("\\\\", "/");
353 
354  Case.CaseType caseType = caseObj.getCaseType();
355  if (caseType.equals(CaseType.SINGLE_USER_CASE)) {
356  filePropertyBundle.addProperty("filePath", caseDirPath + "/" + skCase.getDatabaseName());
357  filePropertyBundle.addProperty("isDirectory", false);
358  } else {
359  filePropertyBundle.addProperty("filePath", caseDirPath);
360  filePropertyBundle.addProperty("isDirectory", true);
361  }
362 
363  caseTrace.addBundle(filePropertyBundle);
364  reportGenerator.writeObject(caseTrace);
365  }
366 
373  private String getCaseTraceId(Case caseObj) {
374  return "case-" + caseObj.getName();
375  }
376 
387  public Path generateReport() throws IOException {
388  //Finalize the report.
389  reportGenerator.writeEndArray();
390  reportGenerator.writeEndObject();
391  reportGenerator.close();
392 
393  return reportPath;
394  }
395 
400  private final class CASENode {
401 
402  private final String id;
403  private final String type;
404 
405  //Dynamic properties added to this CASENode.
406  private final Map<String, Object> properties;
407  private final List<CASEPropertyBundle> propertyBundle;
408 
409  public CASENode(String id, String type) {
410  this.id = id;
411  this.type = type;
412  properties = new LinkedHashMap<>();
413  propertyBundle = new ArrayList<>();
414  }
415 
416  @JsonProperty("@id")
417  public String getId() {
418  return id;
419  }
420 
421  @JsonProperty("@type")
422  public String getType() {
423  return type;
424  }
425 
426  @JsonAnyGetter
427  public Map<String, Object> getProperties() {
428  return properties;
429  }
430 
431  @JsonProperty("propertyBundle")
433  return propertyBundle;
434  }
435 
436  public void addProperty(String key, Object val) {
437  properties.put(key, val);
438  }
439 
440  public void addBundle(CASEPropertyBundle bundle) {
441  propertyBundle.add(bundle);
442  }
443  }
444 
448  private final class CASEPropertyBundle {
449 
450  private final Map<String, Object> properties;
451 
452  public CASEPropertyBundle(String type) {
453  properties = new LinkedHashMap<>();
454  addProperty("@type", type);
455  }
456 
457  @JsonAnyGetter
458  public Map<String, Object> getProperties() {
459  return properties;
460  }
461 
462  public void addProperty(String key, Object val) {
463  properties.put(key, val);
464  }
465  }
466 }
CASENode createRelationshipNode(String relationshipID, String sourceID, String targetID)
static String getStringTimeISO8601(long epochSeconds, TimeZone tzone)
void addFile(AbstractFile file, Content parentDataSource, Path localPath)

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.